pax_global_header00006660000000000000000000000064147404154030014514gustar00rootroot0000000000000052 comment=b621b5fce739525418f36e0474a46f9e1cab5e64 openal-soft-1.24.2/000077500000000000000000000000001474041540300140315ustar00rootroot00000000000000openal-soft-1.24.2/.clang-tidy000066400000000000000000000223531474041540300160720ustar00rootroot00000000000000--- Checks: '-*, bugprone-argument-comment, bugprone-assert-side-effect, bugprone-assignment-in-if-condition, bugprone-bad-signal-to-kill-thread, bugprone-bool-pointer-implicit-conversion, bugprone-casting-through-void, bugprone-chained-comparison, bugprone-compare-pointer-to-member-virtual-function, bugprone-copy-constructor-init, bugprone-crtp-constructor-accessibility, bugprone-dangling-handle, bugprone-dynamic-static-initializers, bugprone-fold-init-type, bugprone-forward-declaration-namespace, bugprone-forwarding-reference-overload, bugprone-implicit-widening-of-multiplication-result, bugprone-inaccurate-erase, bugprone-incorrect-*, bugprone-infinite-loop, bugprone-integer-division, bugprone-lambda-function-name, bugprone-macro-repeated-side-effects, bugprone-misplaced-*, bugprone-move-forwarding-reference, bugprone-multi-level-implicit-pointer-conversion, bugprone-multiple-*, bugprone-narrowing-conversions, bugprone-no-escape, bugprone-non-zero-enum-to-bool-conversion, bugprone-not-null-terminated-result, bugprone-optional-value-conversion, bugprone-parent-virtual-call, bugprone-pointer-arithmetic-on-polymorphic-object, bugprone-posix-return, bugprone-redundant-branch-condition, bugprone-reserved-identifier, bugprone-return-const-ref-from-parameter, bugprone-shared-ptr-array-mismatch, bugprone-signal-handler, bugprone-signed-char-misuse, bugprone-sizeof-*, bugprone-spuriously-wake-up-functions, bugprone-standalone-empty, bugprone-string-*, bugprone-stringview-nullptr, bugprone-suspicious-*, bugprone-swapped-arguments, bugprone-terminating-continue, bugprone-throw-keyword-missing, bugprone-too-small-loop-variable, bugprone-undefined-memory-manipulation, bugprone-undelegated-constructor, bugprone-unhandled-*, bugprone-unique-ptr-array-mismatch, bugprone-unsafe-functions, bugprone-unused-*, bugprone-use-after-move, bugprone-virtual-near-miss, cert-dcl50-cpp, cert-dcl58-cpp, cert-env33-c, cert-err34-c, cert-err52-cpp, cert-err60-cpp, cert-flp30-c, cert-mem57-cpp, clang-analyzer-apiModeling.*, clang-analyzer-core.*, clang-analyzer-cplusplus.*, clang-analyzer-deadcode.DeadStores, clang-analyzer-fuchsia.HandleChecker, clang-analyzer-nullability.*, clang-analyzer-optin.*, clang-analyzer-osx.*, clang-analyzer-security.FloatLoopCounter, clang-analyzer-security.PutenvStackArray, clang-analyzer-security.SetgidSetuidOrder, clang-analyzer-security.cert.env.InvalidPtr, clang-analyzer-security.insecureAPI.SecuritySyntaxChecker, clang-analyzer-security.insecureAPI.UncheckedReturn, clang-analyzer-security.insecureAPI.bcmp, clang-analyzer-security.insecureAPI.bcopy, clang-analyzer-security.insecureAPI.bzero, clang-analyzer-security.insecureAPI.decodeValueOfObjCType, clang-analyzer-security.insecureAPI.getpw, clang-analyzer-security.insecureAPI.gets, clang-analyzer-security.insecureAPI.mkstemp, clang-analyzer-security.insecureAPI.mktemp, clang-analyzer-security.insecureAPI.rand, clang-analyzer-security.insecureAPI.strcpy, clang-analyzer-security.insecureAPI.vfork, clang-analyzer-unix.*, clang-analyzer-valist.*, clang-analyzer-webkit.*, concurrency-thread-canceltype-asynchronous, cppcoreguidelines-avoid-capturing-lambda-coroutines, cppcoreguidelines-avoid-c-arrays, cppcoreguidelines-avoid-goto, cppcoreguidelines-avoid-reference-coroutine-parameters, cppcoreguidelines-c-copy-assignment-signature, cppcoreguidelines-explicit-virtual-functions, cppcoreguidelines-interfaces-global-init, cppcoreguidelines-narrowing-conversions, cppcoreguidelines-no-malloc, cppcoreguidelines-no-suspend-with-lock, cppcoreguidelines-owning-memory, cppcoreguidelines-prefer-member-initializer, cppcoreguidelines-pro-bounds-array-to-pointer-decay, cppcoreguidelines-pro-bounds-pointer-arithmetic, cppcoreguidelines-pro-type-const-cast, cppcoreguidelines-pro-type-cstyle-cast, cppcoreguidelines-pro-type-member-init, cppcoreguidelines-pro-type-static-cast-downcast, cppcoreguidelines-pro-type-union-access, cppcoreguidelines-pro-type-vararg, cppcoreguidelines-slicing, cppcoreguidelines-virtual-class-destructor, google-build-explicit-make-pair, google-default-arguments, google-explicit-constructor, hicpp-exception-baseclass, misc-confusable-identifiers, misc-coroutine-hostile-raii, misc-misleading-*, misc-non-copyable-objects, misc-throw-by-value-catch-by-reference, misc-uniqueptr-reset-release, modernize-avoid-*, modernize-concat-nested-namespaces, modernize-deprecated-*, modernize-loop-convert, modernize-macro-to-enum, modernize-make-*, modernize-pass-by-value, modernize-raw-string-literal, modernize-redundant-void-arg, modernize-replace-*, modernize-return-braced-init-list, modernize-shrink-to-fit, modernize-unary-static-assert, modernize-use-auto, modernize-use-bool-literals, modernize-use-default-member-init, modernize-use-emplace, modernize-use-equals-*, modernize-use-nodiscard, modernize-use-noexcept, modernize-use-nullptr, modernize-use-override, modernize-use-transparent-functors, modernize-use-uncaught-exceptions, modernize-use-using, performance-faster-string-find, performance-for-range-copy, performance-inefficient-*, performance-move-constructor-init, performance-noexcept-destructor, performance-noexcept-swap, performance-unnecessary-copy-initialization, portability-restrict-system-includes, portability-std-allocator-const, readability-const-return-type, readability-container-contains, readability-container-size-empty, readability-convert-member-functions-to-static, readability-delete-null-pointer, readability-duplicate-include, readability-else-after-return, readability-inconsistent-declaration-parameter-name, readability-make-member-function-const, readability-misleading-indentation, readability-misplaced-array-index, readability-redundant-*, readability-simplify-subscript-expr, readability-static-definition-in-anonymous-namespace, readability-string-compare, readability-uniqueptr-delete-release, readability-use-*' openal-soft-1.24.2/.github/000077500000000000000000000000001474041540300153715ustar00rootroot00000000000000openal-soft-1.24.2/.github/workflows/000077500000000000000000000000001474041540300174265ustar00rootroot00000000000000openal-soft-1.24.2/.github/workflows/ci.yml000066400000000000000000000232331474041540300205470ustar00rootroot00000000000000name: CI on: [push, pull_request] jobs: build: name: ${{matrix.config.name}} runs-on: ${{matrix.config.os}} strategy: fail-fast: false matrix: config: - { name: "Win32-Release", os: windows-latest, cmake_opts: "-A Win32 \ -DALSOFT_TESTS=ON \ -DALSOFT_BUILD_ROUTER=ON \ -DALSOFT_REQUIRE_WINMM=ON \ -DALSOFT_REQUIRE_DSOUND=ON \ -DALSOFT_REQUIRE_WASAPI=ON", build_type: "Release" } - { name: "Win32-Debug", os: windows-latest, cmake_opts: "-A Win32 \ -DALSOFT_TESTS=ON \ -DALSOFT_BUILD_ROUTER=ON \ -DALSOFT_REQUIRE_WINMM=ON \ -DALSOFT_REQUIRE_DSOUND=ON \ -DALSOFT_REQUIRE_WASAPI=ON", build_type: "Debug" } - { name: "Win64-Release", os: windows-latest, cmake_opts: "-A x64 \ -DALSOFT_TESTS=ON \ -DALSOFT_BUILD_ROUTER=ON \ -DALSOFT_REQUIRE_WINMM=ON \ -DALSOFT_REQUIRE_DSOUND=ON \ -DALSOFT_REQUIRE_WASAPI=ON", build_type: "Release" } - { name: "Win64-Debug", os: windows-latest, cmake_opts: "-A x64 \ -DALSOFT_TESTS=ON \ -DALSOFT_BUILD_ROUTER=ON \ -DALSOFT_REQUIRE_WINMM=ON \ -DALSOFT_REQUIRE_DSOUND=ON \ -DALSOFT_REQUIRE_WASAPI=ON", build_type: "Debug" } - { name: "Win64-UWP", os: windows-latest, cmake_opts: "-A x64 \ -DALSOFT_TESTS=OFF \ -DCMAKE_SYSTEM_NAME=WindowsStore \ \"-DCMAKE_SYSTEM_VERSION=10.0\" \ -DALSOFT_BUILD_ROUTER=ON \ -DALSOFT_REQUIRE_WASAPI=ON", build_type: "Release" } - { name: "macOS-Release", os: macos-latest, cmake_opts: "-DALSOFT_REQUIRE_COREAUDIO=ON \ -DALSOFT_TESTS=ON", build_type: "Release" } - { name: "iOS-Release", os: macos-latest, cmake_opts: "-GXcode \ -DCMAKE_SYSTEM_NAME=iOS \ -DALSOFT_REQUIRE_COREAUDIO=ON \ -DALSOFT_UTILS=OFF \ -DALSOFT_EXAMPLES=OFF \ -DALSOFT_TESTS=OFF \ -DALSOFT_INSTALL=OFF \ \"-DCMAKE_OSX_ARCHITECTURES=arm64\"", build_type: "Release" } - { name: "Linux-Release", os: ubuntu-latest, cmake_opts: "-DALSOFT_REQUIRE_RTKIT=ON \ -DALSOFT_REQUIRE_ALSA=ON \ -DALSOFT_REQUIRE_OSS=ON \ -DALSOFT_REQUIRE_PORTAUDIO=ON \ -DALSOFT_REQUIRE_PULSEAUDIO=ON \ -DALSOFT_REQUIRE_JACK=ON \ -DALSOFT_REQUIRE_PIPEWIRE=ON \ -DALSOFT_TESTS=ON", deps_cmdline: "sudo apt update && sudo apt-get install -qq \ libpulse-dev \ portaudio19-dev \ libasound2-dev \ libjack-dev \ libpipewire-0.3-dev \ qtbase5-dev \ libdbus-1-dev", build_type: "Release" } - { name: "Android_armeabi-v7a-Release", os: ubuntu-latest, cmake_opts: "-DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK_HOME/build/cmake/android.toolchain.cmake \ -DALSOFT_EMBED_HRTF_DATA=TRUE \ -DALSOFT_REQUIRE_OPENSL=ON", build_type: "Release" } - { name: "Android_arm64-v8a-Release", os: ubuntu-latest, cmake_opts: "-DANDRIOD_ABI=arm64-v8a \ -DANDROID_PLATFORM=25 \ -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK_HOME/build/cmake/android.toolchain.cmake \ -DALSOFT_EMBED_HRTF_DATA=TRUE \ -DALSOFT_REQUIRE_OPENSL=ON", build_type: "Release" } steps: - uses: actions/checkout@v4 with: fetch-depth: '0' - name: Get current commit tag, short hash, count and date run: | echo "CommitTag=$(git describe --tags --abbrev=0 --match *.*.*)" >> $env:GITHUB_ENV echo "CommitHashShort=$(git rev-parse --short=8 HEAD)" >> $env:GITHUB_ENV echo "CommitCount=$(git rev-list --count $env:GITHUB_REF_NAME)" >> $env:GITHUB_ENV echo "CommitDate=$(git show -s --date=iso-local --format=%cd)" >> $env:GITHUB_ENV - name: Install Dependencies shell: bash run: | if [[ ! -z "${{matrix.config.deps_cmdline}}" ]]; then eval ${{matrix.config.deps_cmdline}} fi - name: Configure shell: bash run: | cmake -B build -DCMAKE_BUILD_TYPE=${{matrix.config.build_type}} ${{matrix.config.cmake_opts}} . - name: Build shell: bash run: | cmake --build build --config ${{matrix.config.build_type}} - name: Test shell: bash run: | cd build ctest - name: Set up Windows artifacts if: ${{ contains(matrix.config.name, 'Win') }} shell: bash run: | cd build mkdir archive mkdir archive/router cp ${{matrix.config.build_type}}/soft_oal.dll archive cp ${{matrix.config.build_type}}/OpenAL32.dll archive/router - name: Set up Android artifacts if: ${{ contains(matrix.config.name, 'Android') }} shell: bash run: | cd build mkdir archive cp ${{github.workspace}}/build/libopenal.so archive/ - name: Upload build as a workflow artifact uses: actions/upload-artifact@v4 if: ${{ contains(matrix.config.name, 'Win') || contains(matrix.config.name, 'Android') }} with: name: soft_oal-${{matrix.config.name}} path: build/archive outputs: CommitTag: ${{env.CommitTag}} CommitHashShort: ${{env.CommitHashShort}} CommitCount: ${{env.CommitCount}} CommitDate: ${{env.CommitDate}} release: if: github.event_name != 'pull_request' needs: build runs-on: ubuntu-latest steps: - name: Download build artifacts uses: actions/download-artifact@v4.1.7 with: path: "build" pattern: "*-Win??-Release" github-token: "${{secrets.GITHUB_TOKEN}}" - name: Set up build folders run: | mkdir -p build/release/OpenALSoft/Documentation mkdir -p build/release/OpenALSoft/Win32 mkdir -p build/release/OpenALSoft/Win64 echo "${{github.repository}}" >> "build/release/OpenALSoft/Documentation/Version.txt" echo "v${{needs.build.outputs.CommitTag}}-${{needs.build.outputs.CommitHashShort}} ${{github.ref_name}}" >> "build/release/OpenALSoft/Documentation/Version.txt" echo "Commit #${{needs.build.outputs.CommitCount}}" >> "build/release/OpenALSoft/Documentation/Version.txt" echo "${{needs.build.outputs.CommitDate}}" >> "build/release/OpenALSoft/Documentation/Version.txt" curl https://raw.githubusercontent.com/${{github.repository}}/${{github.ref_name}}/README.md -o "build/release/OpenALSoft/Documentation/ReadMe.txt" curl https://raw.githubusercontent.com/${{github.repository}}/${{github.ref_name}}/ChangeLog -o "build/release/OpenALSoft/Documentation/ChangeLog.txt" curl https://raw.githubusercontent.com/${{github.repository}}/${{github.ref_name}}/COPYING -o "build/release/OpenALSoft/Documentation/License.txt" curl https://raw.githubusercontent.com/${{github.repository}}/${{github.ref_name}}/BSD-3Clause -o "build/release/OpenALSoft/Documentation/License_BSD-3Clause.txt" curl https://raw.githubusercontent.com/${{github.repository}}/${{github.ref_name}}/alsoftrc.sample -o "build/release/OpenALSoft/Win32/alsoft.ini" curl https://raw.githubusercontent.com/${{github.repository}}/${{github.ref_name}}/alsoftrc.sample -o "build/release/OpenALSoft/Win64/alsoft.ini" cp "build/soft_oal-Win32-Release/soft_oal.dll" "build/release/OpenALSoft/Win32/OpenAL32.dll" cp "build/soft_oal-Win64-Release/soft_oal.dll" "build/release/OpenALSoft/Win64/OpenAL32.dll" cp -r "build/release/OpenALSoft" "build/release/OpenALSoft+HRTF" cp "build/release/OpenALSoft+HRTF/Win32/alsoft.ini" "build/release/OpenALSoft+HRTF/Documentation/alsoft.ini" curl https://raw.githubusercontent.com/${{github.repository}}/${{github.ref_name}}/configs/HRTF/alsoft.ini -o "build/release/OpenALSoft+HRTF/Win32/alsoft.ini" cp "build/release/OpenALSoft+HRTF/Win32/alsoft.ini" "build/release/OpenALSoft+HRTF/Win64/alsoft.ini" - name: Compress artifacts run: | cd build/release 7z a OpenALSoft.zip ./OpenALSoft/* 7z a OpenALSoft+HRTF.zip ./OpenALSoft+HRTF/* - name: GitHub pre-release uses: "Sweeistaken/sweelease@v1.1" with: repo_token: "${{secrets.GITHUB_TOKEN}}" automatic_release_tag: "latest" prerelease: true title: "OpenAL Soft v${{needs.build.outputs.CommitTag}}-${{needs.build.outputs.CommitHashShort}}" files: "build/release/*" openal-soft-1.24.2/.github/workflows/utils.yml000066400000000000000000000061421474041540300213140ustar00rootroot00000000000000name: utils on: push: paths: - 'utils/**' - 'examples/**' - '.github/workflows/utils.yml' workflow_dispatch: env: BUILD_TYPE: Release Branch: ${{github.ref_name}} jobs: Win64: runs-on: windows-latest steps: - name: Clone repo and submodules run: git clone https://github.com/${{github.repository}}.git . --branch ${{env.Branch}} - name: Get current date, commit hash and count run: | echo "CommitDate=$(git show -s --date=format:'%Y-%m-%d' --format=%cd)" >> $env:GITHUB_ENV echo "CommitHashShort=$(git rev-parse --short=7 HEAD)" >> $env:GITHUB_ENV echo "CommitCount=$(git rev-list --count ${{env.Branch}} --)" >> $env:GITHUB_ENV - name: Clone libmysofa run: | git clone https://github.com/hoene/libmysofa.git --branch v1.3.3 - name: Add MSBuild to PATH uses: microsoft/setup-msbuild@v1.1.3 - name: Restore libmysofa NuGet packages working-directory: ${{github.workspace}}/libmysofa run: nuget restore ${{github.workspace}}/libmysofa/windows/libmysofa.sln - name: Build libmysofa working-directory: ${{github.workspace}}/libmysofa run: msbuild /m /p:Configuration=${{env.BUILD_TYPE}} ${{github.workspace}}/libmysofa/windows/libmysofa.sln - name: Configure CMake run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -D "MYSOFA_LIBRARY=${{github.workspace}}/libmysofa/windows/bin/x64/Release/mysofa.lib" -D "MYSOFA_INCLUDE_DIR=${{github.workspace}}/libmysofa/src/hrtf" -D "ZLIB_LIBRARY=${{github.workspace}}/libmysofa/windows/third-party/zlib-1.2.11/lib/zlib.lib" -D "ZLIB_INCLUDE_DIR=${{github.workspace}}/libmysofa/windows/third-party/zlib-1.2.11/include" - name: Build run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} - name: Make Artifacts folder run: | mkdir "Artifacts" mkdir "Release" - name: Collect artifacts run: | copy "build/Release/makemhr.exe" "Artifacts/makemhr.exe" copy "build/Release/sofa-info.exe" "Artifacts/sofa-info.exe" copy "build/Release/alrecord.exe" "Artifacts/alrecord.exe" copy "build/Release/altonegen.exe" "Artifacts/altonegen.exe" copy "build/Release/openal-info.exe" "Artifacts/openal-info.exe" copy "build/Release/allafplay.exe" "Artifacts/allafplay.exe" copy "libmysofa/windows/third-party/zlib-1.2.11/bin/zlib.dll" "Artifacts/zlib.dll" - name: Upload artifacts uses: actions/upload-artifact@v4 with: name: ${{env.CommitDate}}_utils-r${{env.CommitCount}}@${{env.CommitHashShort}} path: "Artifacts/" - name: Compress artifacts uses: papeloto/action-zip@v1 with: files: Artifacts/ dest: "Release/utils.zip" - name: GitHub pre-release uses: "marvinpinto/action-automatic-releases@latest" with: repo_token: "${{secrets.GITHUB_TOKEN}}" automatic_release_tag: "utils" prerelease: true title: "[${{env.CommitDate}}] utils-r${{env.CommitCount}}@${{env.CommitHashShort}}" files: "Release/utils.zip" openal-soft-1.24.2/.gitignore000066400000000000000000000001321474041540300160150ustar00rootroot00000000000000build*/ winbuild win64build .vs/ ## kdevelop *.kdev4 ## qt-creator CMakeLists.txt.user* openal-soft-1.24.2/.travis.yml000066400000000000000000000074721474041540300161540ustar00rootroot00000000000000language: cpp matrix: include: - os: linux dist: xenial - os: linux dist: trusty env: - BUILD_ANDROID=true - os: freebsd compiler: clang - os: osx - os: osx osx_image: xcode11 env: - BUILD_IOS=true sudo: required install: - > if [[ "${TRAVIS_OS_NAME}" == "linux" && -z "${BUILD_ANDROID}" ]]; then # Install pulseaudio, portaudio, ALSA, JACK dependencies for # corresponding backends. # Install Qt5 dependency for alsoft-config. sudo apt-get install -qq \ libpulse-dev \ portaudio19-dev \ libasound2-dev \ libjack-dev \ qtbase5-dev \ libdbus-1-dev fi - > if [[ "${TRAVIS_OS_NAME}" == "linux" && "${BUILD_ANDROID}" == "true" ]]; then curl -o ~/android-ndk.zip https://dl.google.com/android/repository/android-ndk-r21-linux-x86_64.zip unzip -q ~/android-ndk.zip -d ~ \ 'android-ndk-r21/build/cmake/*' \ 'android-ndk-r21/build/core/toolchains/arm-linux-androideabi-*/*' \ 'android-ndk-r21/platforms/android-16/arch-arm/*' \ 'android-ndk-r21/source.properties' \ 'android-ndk-r21/sources/android/support/include/*' \ 'android-ndk-r21/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/*' \ 'android-ndk-r21/sources/cxx-stl/llvm-libc++/include/*' \ 'android-ndk-r21/sysroot/*' \ 'android-ndk-r21/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/*' \ 'android-ndk-r21/toolchains/llvm/prebuilt/linux-x86_64/*' export OBOE_LOC=~/oboe git clone --depth 1 -b 1.3-stable https://github.com/google/oboe "$OBOE_LOC" fi - > if [[ "${TRAVIS_OS_NAME}" == "freebsd" ]]; then # Install Ninja as it's used downstream. # Install dependencies for all supported backends. # Install Qt5 dependency for alsoft-config. # Install ffmpeg for examples. sudo pkg install -y \ alsa-lib \ ffmpeg \ jackit \ libmysofa \ ninja \ portaudio \ pulseaudio \ qt5-buildtools \ qt5-qmake \ qt5-widgets \ sdl2 \ sndio \ $NULL fi script: - cmake --version - > if [[ "${TRAVIS_OS_NAME}" == "linux" && -z "${BUILD_ANDROID}" ]]; then cmake \ -DALSOFT_REQUIRE_ALSA=ON \ -DALSOFT_REQUIRE_OSS=ON \ -DALSOFT_REQUIRE_PORTAUDIO=ON \ -DALSOFT_REQUIRE_PULSEAUDIO=ON \ -DALSOFT_REQUIRE_JACK=ON \ -DALSOFT_EMBED_HRTF_DATA=YES \ . fi - > if [[ "${TRAVIS_OS_NAME}" == "linux" && "${BUILD_ANDROID}" == "true" ]]; then cmake \ -DANDROID_STL=c++_shared \ -DCMAKE_TOOLCHAIN_FILE=~/android-ndk-r21/build/cmake/android.toolchain.cmake \ -DOBOE_SOURCE="$OBOE_LOC" \ -DALSOFT_REQUIRE_OBOE=ON \ -DALSOFT_REQUIRE_OPENSL=ON \ -DALSOFT_EMBED_HRTF_DATA=YES \ . fi - > if [[ "${TRAVIS_OS_NAME}" == "freebsd" ]]; then cmake -GNinja \ -DALSOFT_REQUIRE_ALSA=ON \ -DALSOFT_REQUIRE_JACK=ON \ -DALSOFT_REQUIRE_OSS=ON \ -DALSOFT_REQUIRE_PORTAUDIO=ON \ -DALSOFT_REQUIRE_PULSEAUDIO=ON \ -DALSOFT_REQUIRE_SDL2=ON \ -DALSOFT_REQUIRE_SNDIO=ON \ -DALSOFT_EMBED_HRTF_DATA=YES \ . fi - > if [[ "${TRAVIS_OS_NAME}" == "osx" && -z "${BUILD_IOS}" ]]; then cmake \ -DALSOFT_REQUIRE_COREAUDIO=ON \ -DALSOFT_EMBED_HRTF_DATA=YES \ . fi - > if [[ "${TRAVIS_OS_NAME}" == "osx" && "${BUILD_IOS}" == "true" ]]; then cmake \ -GXcode \ -DCMAKE_SYSTEM_NAME=iOS \ -DALSOFT_OSX_FRAMEWORK=ON \ -DALSOFT_REQUIRE_COREAUDIO=ON \ -DALSOFT_EMBED_HRTF_DATA=YES \ "-DCMAKE_OSX_ARCHITECTURES=armv7;arm64" \ . fi - cmake --build . --clean-first openal-soft-1.24.2/BSD-3Clause000066400000000000000000000031641474041540300156650ustar00rootroot00000000000000Portions of this software are licensed under the BSD 3-Clause license. Copyright (c) 2015, Archontis Politis Copyright (c) 2019, Anis A. Hireche Copyright (c) 2019, Christopher Robinson All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Spherical-Harmonic-Transform nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. openal-soft-1.24.2/CMakeLists.txt000066400000000000000000002100331474041540300165700ustar00rootroot00000000000000# CMake build file list for OpenAL cmake_minimum_required(VERSION 3.13) enable_testing() if(APPLE) # The workaround for try_compile failing with code signing # since cmake-3.18.2, not required set(CMAKE_TRY_COMPILE_PLATFORM_VARIABLES ${CMAKE_TRY_COMPILE_PLATFORM_VARIABLES} "CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED" "CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED") set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED NO) set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED NO) endif() if(CMAKE_SYSTEM_NAME STREQUAL "iOS") # Fix compile failure with armv7 deployment target >= 11.0, xcode clang # will report: # error: invalid iOS deployment version '--target=armv7-apple-ios13.6', # iOS 10 is the maximum deployment target for 32-bit targets # If CMAKE_OSX_DEPLOYMENT_TARGET is not defined, cmake will choose latest # deployment target if("${CMAKE_OSX_ARCHITECTURES}" MATCHES ".*armv7.*") if(NOT DEFINED CMAKE_OSX_DEPLOYMENT_TARGET OR NOT CMAKE_OSX_DEPLOYMENT_TARGET VERSION_LESS "11.0") message(STATUS "Forcing iOS deployment target to 10.0 for armv7") set(CMAKE_OSX_DEPLOYMENT_TARGET "10.0" CACHE STRING "Minimum OS X deployment version" FORCE) endif() endif() elseif(CMAKE_SYSTEM_NAME STREQUAL "WindowsStore") set(ALSOFT_UWP TRUE) endif() if(COMMAND CMAKE_POLICY) cmake_policy(SET CMP0003 NEW) cmake_policy(SET CMP0005 NEW) if(POLICY CMP0020) cmake_policy(SET CMP0020 NEW) endif(POLICY CMP0020) if(POLICY CMP0042) cmake_policy(SET CMP0042 NEW) endif(POLICY CMP0042) if(POLICY CMP0054) cmake_policy(SET CMP0054 NEW) endif(POLICY CMP0054) if(POLICY CMP0058) cmake_policy(SET CMP0058 NEW) endif(POLICY CMP0058) if(POLICY CMP0063) cmake_policy(SET CMP0063 NEW) endif(POLICY CMP0063) if(POLICY CMP0075) cmake_policy(SET CMP0075 NEW) endif(POLICY CMP0075) if(POLICY CMP0092) cmake_policy(SET CMP0092 NEW) endif(POLICY CMP0092) if(POLICY CMP0117) cmake_policy(SET CMP0117 NEW) endif(POLICY CMP0117) endif(COMMAND CMAKE_POLICY) project(OpenAL) if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING "Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel." FORCE) endif() if(NOT CMAKE_DEBUG_POSTFIX) set(CMAKE_DEBUG_POSTFIX "" CACHE STRING "Library postfix for debug builds. Normally left blank." FORCE) endif() set(ALSOFT_STD_VERSION_PROPS # Require C++17. CXX_STANDARD 17 CXX_STANDARD_REQUIRED TRUE # Prefer C17, but support earlier when necessary. C_STANDARD 17) set(CMAKE_MODULE_PATH "${OpenAL_SOURCE_DIR}/cmake") include(CheckFunctionExists) include(CheckLibraryExists) include(CheckIncludeFile) include(CheckIncludeFiles) include(CheckSymbolExists) include(CheckCCompilerFlag) include(CheckCXXCompilerFlag) include(CheckCSourceCompiles) include(CheckCXXSourceCompiles) include(CheckStructHasMember) include(CMakePackageConfigHelpers) include(GNUInstallDirs) find_package(PkgConfig) find_package(SDL3 QUIET) add_subdirectory(fmt-11.1.1 EXCLUDE_FROM_ALL) option(ALSOFT_DLOPEN "Check for the dlopen API for loading optional libs" ON) option(ALSOFT_WERROR "Treat compile warnings as errors" OFF) option(ALSOFT_UTILS "Build utility programs" ON) option(ALSOFT_NO_CONFIG_UTIL "Disable building the alsoft-config utility" OFF) option(ALSOFT_EXAMPLES "Build example programs" ON) option(ALSOFT_TESTS "Build test programs" OFF) option(ALSOFT_INSTALL "Install main library" ON) option(ALSOFT_INSTALL_CONFIG "Install alsoft.conf sample configuration file" ON) option(ALSOFT_INSTALL_HRTF_DATA "Install HRTF data files" ON) option(ALSOFT_INSTALL_AMBDEC_PRESETS "Install AmbDec preset files" ON) option(ALSOFT_INSTALL_EXAMPLES "Install example programs (alplay, alstream, ...)" ON) option(ALSOFT_INSTALL_UTILS "Install utility programs (openal-info, alsoft-config, ...)" ON) option(ALSOFT_UPDATE_BUILD_VERSION "Update git build version info" ON) option(ALSOFT_EAX "Enable legacy EAX extensions" ${WIN32}) option(ALSOFT_SEARCH_INSTALL_DATADIR "Search the installation data directory" OFF) if(ALSOFT_SEARCH_INSTALL_DATADIR) set(ALSOFT_INSTALL_DATADIR ${CMAKE_INSTALL_FULL_DATADIR}) endif() if(DEFINED SHARE_INSTALL_DIR) message(WARNING "SHARE_INSTALL_DIR is deprecated. Use the variables provided by the GNUInstallDirs module instead") set(CMAKE_INSTALL_DATADIR "${SHARE_INSTALL_DIR}") endif() if(DEFINED LIB_SUFFIX) message(WARNING "LIB_SUFFIX is deprecated. Use the variables provided by the GNUInstallDirs module instead") endif() if(DEFINED ALSOFT_CONFIG) message(WARNING "ALSOFT_CONFIG is deprecated. Use ALSOFT_INSTALL_CONFIG instead") endif() if(DEFINED ALSOFT_HRTF_DEFS) message(WARNING "ALSOFT_HRTF_DEFS is deprecated. Use ALSOFT_INSTALL_HRTF_DATA instead") endif() if(DEFINED ALSOFT_AMBDEC_PRESETS) message(WARNING "ALSOFT_AMBDEC_PRESETS is deprecated. Use ALSOFT_INSTALL_AMBDEC_PRESETS instead") endif() set(CPP_DEFS ) # C pre-processor, not C++ set(INC_PATHS ) set(C_FLAGS ) set(LINKER_FLAGS ) set(LINKER_FLAGS_DEBUG ) set(LINKER_FLAGS_RELEASE ) set(EXTRA_LIBS ) if(WIN32) set(CPP_DEFS ${CPP_DEFS} _WIN32 NOMINMAX) if(MINGW) set(CPP_DEFS ${CPP_DEFS} __USE_MINGW_ANSI_STDIO) endif() option(ALSOFT_BUILD_ROUTER "Build the router (EXPERIMENTAL; creates OpenAL32.dll and soft_oal.dll)" OFF) if(MINGW) option(ALSOFT_BUILD_IMPORT_LIB "Build an import .lib using dlltool (requires sed)" ON) endif() elseif(APPLE) option(ALSOFT_OSX_FRAMEWORK "Build as macOS framework" OFF) endif() # QNX's gcc do not uses /usr/include and /usr/lib paths by default if("${CMAKE_C_PLATFORM_ID}" STREQUAL "QNX") set(INC_PATHS ${INC_PATHS} /usr/include) set(LINKER_FLAGS ${LINKER_FLAGS} -L/usr/lib) endif() # When the library is built for static linking, apps should define # AL_LIBTYPE_STATIC when including the AL headers. if(NOT LIBTYPE) set(LIBTYPE SHARED) endif() set(LIB_MAJOR_VERSION "1") set(LIB_MINOR_VERSION "24") set(LIB_REVISION "2") set(LIB_VERSION "${LIB_MAJOR_VERSION}.${LIB_MINOR_VERSION}.${LIB_REVISION}") set(LIB_VERSION_NUM ${LIB_MAJOR_VERSION},${LIB_MINOR_VERSION},${LIB_REVISION},0) set(EXPORT_DECL "") # Some systems erroneously require the __STDC_FORMAT_MACROS macro to be defined # to get the fixed-width integer type formatter macros. check_cxx_source_compiles("#include #include int main() { int64_t i64{}; std::printf(\"%\" PRId64, i64); }" HAVE_STDC_FORMAT_MACROS) if(NOT HAVE_STDC_FORMAT_MACROS) set(CPP_DEFS ${CPP_DEFS} __STDC_FORMAT_MACROS) endif() # Some systems may need libatomic for atomic functions to work set(OLD_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES}) set(CMAKE_REQUIRED_LIBRARIES ${OLD_REQUIRED_LIBRARIES} atomic) check_cxx_source_compiles("#include std::atomic foo{0}; int main() { return foo.fetch_add(2); }" HAVE_LIBATOMIC) if(NOT HAVE_LIBATOMIC) set(CMAKE_REQUIRED_LIBRARIES "${OLD_REQUIRED_LIBRARIES}") else() set(EXTRA_LIBS atomic ${EXTRA_LIBS}) endif() unset(OLD_REQUIRED_LIBRARIES) if(ANDROID) # Include liblog for Android logging check_library_exists(log __android_log_print "" HAVE_LIBLOG) if(HAVE_LIBLOG) set(EXTRA_LIBS log ${EXTRA_LIBS}) set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} log) endif() endif() if(MSVC) # NOTE: _DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR is temporary. When building on # VS 2022 17.10 or newer, but using an older runtime, mutexes can crash # when locked. Ideally the runtime should be updated on the system, but # until the update becomes more widespread, this helps avoid some pain # points. set(CPP_DEFS ${CPP_DEFS} _CRT_SECURE_NO_WARNINGS _DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR) check_cxx_compiler_flag(/permissive- HAVE_PERMISSIVE_SWITCH) if(HAVE_PERMISSIVE_SWITCH) set(C_FLAGS ${C_FLAGS} $<$:/permissive->) endif() set(C_FLAGS ${C_FLAGS} /W4 /w14640 /wd4065 /wd4127 /wd4268 /wd4324 /wd5030 /wd5051 $<$:/EHsc> /utf-8) if(NOT DXSDK_DIR) string(REGEX REPLACE "\\\\" "/" DXSDK_DIR "$ENV{DXSDK_DIR}") else() string(REGEX REPLACE "\\\\" "/" DXSDK_DIR "${DXSDK_DIR}") endif() if(DXSDK_DIR) message(STATUS "Using DirectX SDK directory: ${DXSDK_DIR}") endif() option(FORCE_STATIC_VCRT "Force /MT for static VC runtimes" OFF) if(FORCE_STATIC_VCRT) foreach(flag_var CMAKE_C_FLAGS CMAKE_C_FLAGS_DEBUG CMAKE_C_FLAGS_RELEASE CMAKE_C_FLAGS_MINSIZEREL CMAKE_C_FLAGS_RELWITHDEBINFO CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO) if(${flag_var} MATCHES "/MD") string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}") endif() endforeach(flag_var) endif() else() set(C_FLAGS ${C_FLAGS} -Winline -Wunused -Wall -Wextra -Wshadow -Wconversion -Wcast-align -Wpedantic $<$:-Wold-style-cast -Wnon-virtual-dtor -Woverloaded-virtual>) check_cxx_compiler_flag(-Wno-c++20-attribute-extensions HAVE_WNO_CXX20_ATTR_EXT) if(HAVE_WNO_CXX20_ATTR_EXT) set(C_FLAGS ${C_FLAGS} $<$:-Wno-c++20-attribute-extensions>) else() check_cxx_compiler_flag(-Wno-c++20-extensions HAVE_WNO_CXX20_EXT) if(HAVE_WNO_CXX20_EXT) set(C_FLAGS ${C_FLAGS} $<$:-Wno-c++20-extensions>) endif() endif() check_cxx_compiler_flag(-Wno-interference-size HAVE_WNO_INTERFERENCE_SIZE) if(HAVE_WNO_INTERFERENCE_SIZE) set(C_FLAGS ${C_FLAGS} $<$:-Wno-interference-size>) endif() if(ALSOFT_WERROR) set(C_FLAGS ${C_FLAGS} -Werror) else() set(C_FLAGS ${C_FLAGS} -Werror=undef) endif() # NOTE: This essentially provides the equivalent of the C++26 feature to # initialize all local variables with a non-0 bit pattern. Until C++26 is # adopted, the [[gnu::uninitialized]] attribute will avoid the auto- # initialization where necessary. check_c_compiler_flag(-ftrivial-auto-var-init=pattern HAVE_FTRIVIAL_AUTO_VAR_INIT) if(HAVE_FTRIVIAL_AUTO_VAR_INIT) set(C_FLAGS ${C_FLAGS} -ftrivial-auto-var-init=pattern) endif() check_c_compiler_flag(-fno-math-errno HAVE_FNO_MATH_ERRNO) if(HAVE_FNO_MATH_ERRNO) set(C_FLAGS ${C_FLAGS} -fno-math-errno) endif() option(ALSOFT_STATIC_LIBGCC "Force -static-libgcc for static GCC runtimes" OFF) if(ALSOFT_STATIC_LIBGCC) set(OLD_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES}) set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} -static-libgcc) check_cxx_source_compiles("int main() { }" HAVE_STATIC_LIBGCC_SWITCH) set(CMAKE_REQUIRED_LIBRARIES ${OLD_REQUIRED_LIBRARIES}) unset(OLD_REQUIRED_LIBRARIES) if(NOT HAVE_STATIC_LIBGCC_SWITCH) message(FATAL_ERROR "Cannot static link libgcc") endif() set(LINKER_FLAGS ${LINKER_FLAGS} -static-libgcc) endif() option(ALSOFT_STATIC_STDCXX "Static link libstdc++" OFF) if(ALSOFT_STATIC_STDCXX) set(OLD_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES}) set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} "-static-libstdc++") check_cxx_source_compiles("int main() { }" HAVE_STATIC_LIBSTDCXX_SWITCH) set(CMAKE_REQUIRED_LIBRARIES ${OLD_REQUIRED_LIBRARIES}) unset(OLD_REQUIRED_LIBRARIES) if(NOT HAVE_STATIC_LIBSTDCXX_SWITCH) message(FATAL_ERROR "Cannot static link libstdc++") endif() set(LINKER_FLAGS ${LINKER_FLAGS} "-static-libstdc++") endif() if(WIN32) option(ALSOFT_STATIC_WINPTHREAD "Static link libwinpthread" OFF) if(ALSOFT_STATIC_WINPTHREAD) set(OLD_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES}) set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} "-Wl,--push-state,-Bstatic,-lstdc++,-lwinpthread,--pop-state") check_cxx_source_compiles("int main() { }" HAVE_STATIC_LIBWINPTHREAD_SWITCH) set(CMAKE_REQUIRED_LIBRARIES ${OLD_REQUIRED_LIBRARIES}) unset(OLD_REQUIRED_LIBRARIES) if(NOT HAVE_STATIC_LIBWINPTHREAD_SWITCH) message(FATAL_ERROR "Cannot static link libwinpthread") endif() set(LINKER_FLAGS ${LINKER_FLAGS} "-Wl,--push-state,-Bstatic,-lstdc++,-lwinpthread,--pop-state") endif() endif() endif() # Set visibility/export options if available if(NOT LIBTYPE STREQUAL "STATIC") if(WIN32) set(EXPORT_DECL "__declspec(dllexport)") else() set(OLD_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS}") # Yes GCC, really don't accept visibility modes you don't support set(CMAKE_REQUIRED_FLAGS "${OLD_REQUIRED_FLAGS} -Wattributes -Werror") check_c_source_compiles("int foo() __attribute__((visibility(\"protected\"))); int main() {return 0;}" HAVE_GCC_PROTECTED_VISIBILITY) if(HAVE_GCC_PROTECTED_VISIBILITY) set(EXPORT_DECL "__attribute__((visibility(\"protected\")))") else() check_c_source_compiles("int foo() __attribute__((visibility(\"default\"))); int main() {return 0;}" HAVE_GCC_DEFAULT_VISIBILITY) if(HAVE_GCC_DEFAULT_VISIBILITY) set(EXPORT_DECL "__attribute__((visibility(\"default\")))") endif() endif() if(HAVE_GCC_PROTECTED_VISIBILITY OR HAVE_GCC_DEFAULT_VISIBILITY) set(CMAKE_C_VISIBILITY_PRESET hidden) set(CMAKE_CXX_VISIBILITY_PRESET hidden) endif() set(CMAKE_REQUIRED_FLAGS "${OLD_REQUIRED_FLAGS}") endif() endif() set(SSE2_SWITCH "") if(NOT MSVC) set(OLD_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS}) # Yes GCC, really don't accept command line options you don't support set(CMAKE_REQUIRED_FLAGS "${OLD_REQUIRED_FLAGS} -Werror") check_c_compiler_flag(-msse2 HAVE_MSSE2_SWITCH) if(HAVE_MSSE2_SWITCH) set(SSE2_SWITCH "-msse2") endif() set(CMAKE_REQUIRED_FLAGS ${OLD_REQUIRED_FLAGS}) unset(OLD_REQUIRED_FLAGS) endif() check_include_file(xmmintrin.h HAVE_XMMINTRIN_H) check_include_file(emmintrin.h HAVE_EMMINTRIN_H) check_include_file(pmmintrin.h HAVE_PMMINTRIN_H) check_include_file(smmintrin.h HAVE_SMMINTRIN_H) check_include_file(arm_neon.h HAVE_ARM_NEON_H) set(HAVE_SSE 0) set(HAVE_SSE2 0) set(HAVE_SSE3 0) set(HAVE_SSE4_1 0) set(HAVE_NEON 0) # Check for SSE support option(ALSOFT_CPUEXT_SSE "Enable SSE support" ON) option(ALSOFT_REQUIRE_SSE "Require SSE support" OFF) if(ALSOFT_CPUEXT_SSE AND HAVE_XMMINTRIN_H) set(HAVE_SSE 1) endif() if(ALSOFT_REQUIRE_SSE AND NOT HAVE_SSE) message(FATAL_ERROR "Failed to enable required SSE CPU extensions") endif() option(ALSOFT_CPUEXT_SSE2 "Enable SSE2 support" ON) option(ALSOFT_REQUIRE_SSE2 "Require SSE2 support" OFF) if(ALSOFT_CPUEXT_SSE2 AND HAVE_SSE AND HAVE_EMMINTRIN_H) set(HAVE_SSE2 1) endif() if(ALSOFT_REQUIRE_SSE2 AND NOT HAVE_SSE2) message(FATAL_ERROR "Failed to enable required SSE2 CPU extensions") endif() option(ALSOFT_CPUEXT_SSE3 "Enable SSE3 support" ON) option(ALSOFT_REQUIRE_SSE3 "Require SSE3 support" OFF) if(ALSOFT_CPUEXT_SSE3 AND HAVE_SSE2 AND HAVE_PMMINTRIN_H) set(HAVE_SSE3 1) endif() if(ALSOFT_REQUIRE_SSE3 AND NOT HAVE_SSE3) message(FATAL_ERROR "Failed to enable required SSE3 CPU extensions") endif() option(ALSOFT_CPUEXT_SSE4_1 "Enable SSE4.1 support" ON) option(ALSOFT_REQUIRE_SSE4_1 "Require SSE4.1 support" OFF) if(ALSOFT_CPUEXT_SSE4_1 AND HAVE_SSE3 AND HAVE_SMMINTRIN_H) set(HAVE_SSE4_1 1) endif() if(ALSOFT_REQUIRE_SSE4_1 AND NOT HAVE_SSE4_1) message(FATAL_ERROR "Failed to enable required SSE4.1 CPU extensions") endif() # Check for ARM Neon support option(ALSOFT_CPUEXT_NEON "Enable ARM NEON support" ON) option(ALSOFT_REQUIRE_NEON "Require ARM NEON support" OFF) if(ALSOFT_CPUEXT_NEON AND HAVE_ARM_NEON_H) check_c_source_compiles("#include int main() { int32x4_t ret4 = vdupq_n_s32(0); return vgetq_lane_s32(ret4, 0); }" HAVE_NEON_INTRINSICS) if(HAVE_NEON_INTRINSICS) set(HAVE_NEON 1) endif() endif() if(ALSOFT_REQUIRE_NEON AND NOT HAVE_NEON) message(FATAL_ERROR "Failed to enable required ARM NEON CPU extensions") endif() set(ALSOFT_FORCE_ALIGN ) set(SSE_FLAGS ) set(FPMATH_SET "0") if(CMAKE_SIZEOF_VOID_P MATCHES "4" AND HAVE_SSE2) option(ALSOFT_ENABLE_SSE2_CODEGEN "Enable SSE2 code generation instead of x87 for 32-bit targets." TRUE) if(ALSOFT_ENABLE_SSE2_CODEGEN) if(MSVC) check_c_compiler_flag("/arch:SSE2" HAVE_ARCH_SSE2) if(HAVE_ARCH_SSE2) set(SSE_FLAGS ${SSE_FLAGS} "/arch:SSE2") set(C_FLAGS ${C_FLAGS} ${SSE_FLAGS}) set(FPMATH_SET 2) endif() elseif(SSE2_SWITCH) check_c_compiler_flag("${SSE2_SWITCH} -mfpmath=sse" HAVE_MFPMATH_SSE_2) if(HAVE_MFPMATH_SSE_2) set(SSE_FLAGS ${SSE_FLAGS} ${SSE2_SWITCH} -mfpmath=sse) set(C_FLAGS ${C_FLAGS} ${SSE_FLAGS}) set(FPMATH_SET 2) endif() # SSE depends on a 16-byte aligned stack, and by default, GCC # assumes the stack is suitably aligned. Older Linux code or other # OSs don't guarantee this on 32-bit, so externally-callable # functions need to ensure an aligned stack. set(EXPORT_DECL "${EXPORT_DECL}__attribute__((force_align_arg_pointer))") set(ALSOFT_FORCE_ALIGN "__attribute__((force_align_arg_pointer))") endif() endif() endif() if(HAVE_SSE2) set(OLD_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS}) foreach(flag_var ${SSE_FLAGS}) set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} ${flag_var}") endforeach() check_c_source_compiles("#include int main() {_mm_pause(); return 0;}" HAVE_SSE_INTRINSICS) set(CMAKE_REQUIRED_FLAGS ${OLD_REQUIRED_FLAGS}) endif() check_include_file(cpuid.h HAVE_CPUID_H) check_include_file(intrin.h HAVE_INTRIN_H) check_include_file(guiddef.h HAVE_GUIDDEF_H) # Some systems need libm for some math functions to work set(MATH_LIB ) check_library_exists(m pow "" HAVE_LIBM) if(HAVE_LIBM) set(MATH_LIB ${MATH_LIB} m) set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} m) endif() # Some systems need to link with -lrt for clock_gettime as used by the common # eaxmple functions. set(RT_LIB ) check_library_exists(rt clock_gettime "" HAVE_LIBRT) if(HAVE_LIBRT) set(RT_LIB rt) endif() # Check for the dlopen API (for dynamically loading backend libs) if(ALSOFT_DLOPEN) check_include_file(dlfcn.h HAVE_DLFCN_H) check_library_exists(dl dlopen "" HAVE_LIBDL) if(HAVE_LIBDL) set(EXTRA_LIBS dl ${EXTRA_LIBS}) set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} dl) endif() endif() # Check for a cpuid intrinsic if(HAVE_CPUID_H) check_c_source_compiles("#include int main() { unsigned int eax, ebx, ecx, edx; return __get_cpuid(0, &eax, &ebx, &ecx, &edx); }" HAVE_GCC_GET_CPUID) endif() if(HAVE_INTRIN_H) check_c_source_compiles("#include int main() { int regs[4]; __cpuid(regs, 0); return regs[0]; }" HAVE_CPUID_INTRINSIC) endif() check_symbol_exists(proc_pidpath libproc.h HAVE_PROC_PIDPATH) if(NOT WIN32) # We need pthreads outside of Windows, for semaphores. It's also used to # set the priority and name of threads, when possible. check_include_file(pthread.h HAVE_PTHREAD_H) if(NOT HAVE_PTHREAD_H) message(FATAL_ERROR "PThreads is required for non-Windows builds!") endif() check_c_compiler_flag(-pthread HAVE_PTHREAD) if(HAVE_PTHREAD) set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -pthread") set(C_FLAGS ${C_FLAGS} -pthread) set(LINKER_FLAGS ${LINKER_FLAGS} -pthread) endif() check_symbol_exists(pthread_setschedparam pthread.h HAVE_PTHREAD_SETSCHEDPARAM) # Some systems need pthread_np.h to get pthread_setname_np check_include_files("pthread.h;pthread_np.h" HAVE_PTHREAD_NP_H) if(HAVE_PTHREAD_NP_H) check_symbol_exists(pthread_setname_np "pthread.h;pthread_np.h" HAVE_PTHREAD_SETNAME_NP) if(NOT HAVE_PTHREAD_SETNAME_NP) check_symbol_exists(pthread_set_name_np "pthread.h;pthread_np.h" HAVE_PTHREAD_SET_NAME_NP) endif() else() check_symbol_exists(pthread_setname_np pthread.h HAVE_PTHREAD_SETNAME_NP) if(NOT HAVE_PTHREAD_SETNAME_NP) check_symbol_exists(pthread_set_name_np pthread.h HAVE_PTHREAD_SET_NAME_NP) endif() endif() endif() # Common sources used by both the OpenAL implementation library, the OpenAL # router, and certain tools and examples. set(COMMON_OBJS common/alassert.cpp common/alassert.h common/albit.h common/alcomplex.cpp common/alcomplex.h common/almalloc.h common/alnumbers.h common/alnumeric.h common/alsem.cpp common/alsem.h common/alspan.h common/alstring.cpp common/alstring.h common/althrd_setname.cpp common/althrd_setname.h common/althreads.h common/altraits.h common/atomic.h common/comptr.h common/dynload.cpp common/dynload.h common/filesystem.cpp common/filesystem.h common/flexarray.h common/intrusive_ptr.h common/opthelpers.h common/pffft.cpp common/pffft.h common/phase_shifter.h common/polyphase_resampler.cpp common/polyphase_resampler.h common/pragmadefs.h common/ringbuffer.cpp common/ringbuffer.h common/strutils.cpp common/strutils.h common/vecmat.h common/vector.h) # Core library routines set(CORE_OBJS core/ambdec.cpp core/ambdec.h core/ambidefs.cpp core/ambidefs.h core/async_event.h core/bformatdec.cpp core/bformatdec.h core/bs2b.cpp core/bs2b.h core/bsinc_defs.h core/bsinc_tables.cpp core/bsinc_tables.h core/bufferline.h core/buffer_storage.cpp core/buffer_storage.h core/context.cpp core/context.h core/converter.cpp core/converter.h core/cpu_caps.cpp core/cpu_caps.h core/cubic_defs.h core/cubic_tables.cpp core/cubic_tables.h core/devformat.cpp core/devformat.h core/device.cpp core/device.h core/effects/base.h core/effectslot.cpp core/effectslot.h core/except.cpp core/except.h core/filters/biquad.h core/filters/biquad.cpp core/filters/nfc.cpp core/filters/nfc.h core/filters/splitter.cpp core/filters/splitter.h core/fmt_traits.h core/fpu_ctrl.cpp core/fpu_ctrl.h core/front_stablizer.h core/helpers.cpp core/helpers.h core/hrtf.cpp core/hrtf.h core/logging.cpp core/logging.h core/mastering.cpp core/mastering.h core/mixer.cpp core/mixer.h core/resampler_limits.h core/storage_formats.cpp core/storage_formats.h core/uhjfilter.cpp core/uhjfilter.h core/uiddefs.cpp core/voice.cpp core/voice.h core/voice_change.h) set(HAVE_RTKIT 0) if(NOT WIN32) option(ALSOFT_RTKIT "Enable RTKit support" ON) option(ALSOFT_REQUIRE_RTKIT "Require RTKit/D-Bus support" FALSE) if(ALSOFT_RTKIT) find_package(DBus1 QUIET) if(NOT DBus1_FOUND AND PkgConfig_FOUND) pkg_check_modules(DBUS dbus-1) endif() if(DBus1_FOUND OR DBUS_FOUND) set(HAVE_RTKIT 1) set(CORE_OBJS ${CORE_OBJS} core/dbus_wrap.cpp core/dbus_wrap.h core/rtkit.cpp core/rtkit.h) if(NOT DBus1_FOUND) set(INC_PATHS ${INC_PATHS} ${DBUS_INCLUDE_DIRS}) set(CPP_DEFS ${CPP_DEFS} ${DBUS_CFLAGS_OTHER}) if(NOT HAVE_DLFCN_H) set(EXTRA_LIBS ${EXTRA_LIBS} ${DBUS_LINK_LIBRARIES}) endif() elseif(HAVE_DLFCN_H) set(INC_PATHS ${INC_PATHS} ${DBus1_INCLUDE_DIRS}) set(CPP_DEFS ${CPP_DEFS} ${DBus1_DEFINITIONS}) else() set(EXTRA_LIBS ${EXTRA_LIBS} ${DBus1_LIBRARIES}) endif() else() set(MISSING_VARS "") if(NOT DBus1_INCLUDE_DIRS) set(MISSING_VARS "${MISSING_VARS} DBus1_INCLUDE_DIRS") endif() if(NOT DBus1_LIBRARIES) set(MISSING_VARS "${MISSING_VARS} DBus1_LIBRARIES") endif() message(STATUS "Could NOT find DBus1 (missing:${MISSING_VARS})") unset(MISSING_VARS) endif() endif() endif() if(ALSOFT_REQUIRE_RTKIT AND NOT HAVE_RTKIT) message(FATAL_ERROR "Failed to enable required RTKit support") endif() # Default mixers, always available set(CORE_OBJS ${CORE_OBJS} core/mixer/defs.h core/mixer/hrtfbase.h core/mixer/hrtfdefs.h core/mixer/mixer_c.cpp) # AL and related routines set(OPENAL_OBJS al/auxeffectslot.cpp al/auxeffectslot.h al/buffer.cpp al/buffer.h al/debug.cpp al/debug.h al/direct_defs.h al/effect.cpp al/effect.h al/effects/autowah.cpp al/effects/chorus.cpp al/effects/compressor.cpp al/effects/convolution.cpp al/effects/dedicated.cpp al/effects/distortion.cpp al/effects/echo.cpp al/effects/effects.cpp al/effects/effects.h al/effects/equalizer.cpp al/effects/fshifter.cpp al/effects/modulator.cpp al/effects/null.cpp al/effects/pshifter.cpp al/effects/reverb.cpp al/effects/vmorpher.cpp al/error.cpp al/event.cpp al/event.h al/extension.cpp al/filter.cpp al/filter.h al/listener.cpp al/listener.h al/source.cpp al/source.h al/state.cpp) # ALC and related routines set(ALC_OBJS alc/alc.cpp alc/alu.cpp alc/alu.h alc/alconfig.cpp alc/alconfig.h alc/context.cpp alc/context.h alc/device.cpp alc/device.h alc/effects/base.h alc/effects/autowah.cpp alc/effects/chorus.cpp alc/effects/compressor.cpp alc/effects/convolution.cpp alc/effects/dedicated.cpp alc/effects/distortion.cpp alc/effects/echo.cpp alc/effects/equalizer.cpp alc/effects/fshifter.cpp alc/effects/modulator.cpp alc/effects/null.cpp alc/effects/pshifter.cpp alc/effects/reverb.cpp alc/effects/vmorpher.cpp alc/events.cpp alc/events.h alc/export_list.h alc/inprogext.h alc/panning.cpp) if(ALSOFT_EAX) set(OPENAL_OBJS ${OPENAL_OBJS} al/eax/api.cpp al/eax/api.h al/eax/call.cpp al/eax/call.h al/eax/effect.h al/eax/exception.cpp al/eax/exception.h al/eax/fx_slot_index.cpp al/eax/fx_slot_index.h al/eax/fx_slots.cpp al/eax/fx_slots.h al/eax/globals.h al/eax/utils.cpp al/eax/utils.h al/eax/x_ram.h ) endif() # Include SIMD mixers set(CPU_EXTS "Default") if(HAVE_SSE) set(CORE_OBJS ${CORE_OBJS} core/mixer/mixer_sse.cpp) set(CPU_EXTS "${CPU_EXTS}, SSE") endif() if(HAVE_SSE2) set(CORE_OBJS ${CORE_OBJS} core/mixer/mixer_sse2.cpp) set(CPU_EXTS "${CPU_EXTS}, SSE2") endif() if(HAVE_SSE3) set(CORE_OBJS ${CORE_OBJS} core/mixer/mixer_sse3.cpp) set(CPU_EXTS "${CPU_EXTS}, SSE3") endif() if(HAVE_SSE4_1) set(CORE_OBJS ${CORE_OBJS} core/mixer/mixer_sse41.cpp) set(CPU_EXTS "${CPU_EXTS}, SSE4.1") endif() if(HAVE_NEON) set(CORE_OBJS ${CORE_OBJS} core/mixer/mixer_neon.cpp) set(CPU_EXTS "${CPU_EXTS}, Neon") endif() set(HAVE_ALSA 0) set(HAVE_OSS 0) set(HAVE_PIPEWIRE 0) set(HAVE_SOLARIS 0) set(HAVE_SNDIO 0) set(HAVE_DSOUND 0) set(HAVE_WASAPI 0) set(HAVE_WINMM 0) set(HAVE_PORTAUDIO 0) set(HAVE_PULSEAUDIO 0) set(HAVE_COREAUDIO 0) set(HAVE_OPENSL 0) set(HAVE_OBOE 0) set(HAVE_OTHERIO 0) set(HAVE_WAVE 0) set(HAVE_SDL2 0) set(HAVE_SDL3 0) if(WIN32 OR HAVE_DLFCN_H) set(IS_LINKED "") macro(ADD_BACKEND_LIBS _LIBS) endmacro() else() set(IS_LINKED " (linked)") macro(ADD_BACKEND_LIBS _LIBS) set(EXTRA_LIBS ${_LIBS} ${EXTRA_LIBS}) endmacro() endif() set(BACKENDS "") set(ALC_OBJS ${ALC_OBJS} alc/backends/base.cpp alc/backends/base.h # Default backends, always available alc/backends/loopback.cpp alc/backends/loopback.h alc/backends/null.cpp alc/backends/null.h ) # Check PipeWire backend option(ALSOFT_BACKEND_PIPEWIRE "Enable PipeWire backend" ON) option(ALSOFT_REQUIRE_PIPEWIRE "Require PipeWire backend" OFF) if(ALSOFT_BACKEND_PIPEWIRE AND PkgConfig_FOUND) pkg_check_modules(PIPEWIRE libpipewire-0.3>=0.3.23) if(PIPEWIRE_FOUND) set(HAVE_PIPEWIRE 1) set(BACKENDS "${BACKENDS} PipeWire${IS_LINKED},") set(ALC_OBJS ${ALC_OBJS} alc/backends/pipewire.cpp alc/backends/pipewire.h) add_backend_libs(${PIPEWIRE_LIBRARIES}) set(INC_PATHS ${INC_PATHS} ${PIPEWIRE_INCLUDE_DIRS}) endif() endif() if(ALSOFT_REQUIRE_PIPEWIRE AND NOT HAVE_PIPEWIRE) message(FATAL_ERROR "Failed to enable required PipeWire backend") endif() # Check PulseAudio backend option(ALSOFT_BACKEND_PULSEAUDIO "Enable PulseAudio backend" ON) option(ALSOFT_REQUIRE_PULSEAUDIO "Require PulseAudio backend" OFF) if(ALSOFT_BACKEND_PULSEAUDIO) find_package(PulseAudio) if(PULSEAUDIO_FOUND) set(HAVE_PULSEAUDIO 1) set(BACKENDS "${BACKENDS} PulseAudio${IS_LINKED},") set(ALC_OBJS ${ALC_OBJS} alc/backends/pulseaudio.cpp alc/backends/pulseaudio.h) add_backend_libs(${PULSEAUDIO_LIBRARY}) set(INC_PATHS ${INC_PATHS} ${PULSEAUDIO_INCLUDE_DIR}) endif() endif() if(ALSOFT_REQUIRE_PULSEAUDIO AND NOT HAVE_PULSEAUDIO) message(FATAL_ERROR "Failed to enable required PulseAudio backend") endif() if(NOT WIN32) # Check ALSA backend option(ALSOFT_BACKEND_ALSA "Enable ALSA backend" ON) option(ALSOFT_REQUIRE_ALSA "Require ALSA backend" OFF) if(ALSOFT_BACKEND_ALSA) find_package(ALSA) if(ALSA_FOUND) set(HAVE_ALSA 1) set(BACKENDS "${BACKENDS} ALSA${IS_LINKED},") set(ALC_OBJS ${ALC_OBJS} alc/backends/alsa.cpp alc/backends/alsa.h) add_backend_libs(${ALSA_LIBRARIES}) set(INC_PATHS ${INC_PATHS} ${ALSA_INCLUDE_DIRS}) endif() endif() # Check OSS backend option(ALSOFT_BACKEND_OSS "Enable OSS backend" ON) option(ALSOFT_REQUIRE_OSS "Require OSS backend" OFF) if(ALSOFT_BACKEND_OSS) find_package(OSS) if(OSS_FOUND) set(HAVE_OSS 1) set(BACKENDS "${BACKENDS} OSS,") set(ALC_OBJS ${ALC_OBJS} alc/backends/oss.cpp alc/backends/oss.h) if(OSS_LIBRARIES) set(EXTRA_LIBS ${OSS_LIBRARIES} ${EXTRA_LIBS}) endif() set(INC_PATHS ${INC_PATHS} ${OSS_INCLUDE_DIRS}) endif() endif() # Check Solaris backend option(ALSOFT_BACKEND_SOLARIS "Enable Solaris backend" ON) option(ALSOFT_REQUIRE_SOLARIS "Require Solaris backend" OFF) if(ALSOFT_BACKEND_SOLARIS) find_package(AudioIO) if(AUDIOIO_FOUND) set(HAVE_SOLARIS 1) set(BACKENDS "${BACKENDS} Solaris,") set(ALC_OBJS ${ALC_OBJS} alc/backends/solaris.cpp alc/backends/solaris.h) set(INC_PATHS ${INC_PATHS} ${AUDIOIO_INCLUDE_DIRS}) endif() endif() # Check SndIO backend (disabled by default on non-BSDs) if(BSD) option(ALSOFT_BACKEND_SNDIO "Enable SndIO backend" ON) else() option(ALSOFT_BACKEND_SNDIO "Enable SndIO backend" OFF) endif() option(ALSOFT_REQUIRE_SNDIO "Require SndIO backend" OFF) if(ALSOFT_BACKEND_SNDIO) find_package(SndIO) if(SNDIO_FOUND) set(HAVE_SNDIO 1) set(BACKENDS "${BACKENDS} SndIO (linked),") set(ALC_OBJS ${ALC_OBJS} alc/backends/sndio.cpp alc/backends/sndio.hpp) set(EXTRA_LIBS ${SNDIO_LIBRARIES} ${EXTRA_LIBS}) set(INC_PATHS ${INC_PATHS} ${SNDIO_INCLUDE_DIRS}) endif() endif() endif() if(ALSOFT_REQUIRE_ALSA AND NOT HAVE_ALSA) message(FATAL_ERROR "Failed to enable required ALSA backend") endif() if(ALSOFT_REQUIRE_OSS AND NOT HAVE_OSS) message(FATAL_ERROR "Failed to enable required OSS backend") endif() if(ALSOFT_REQUIRE_SOLARIS AND NOT HAVE_SOLARIS) message(FATAL_ERROR "Failed to enable required Solaris backend") endif() if(ALSOFT_REQUIRE_SNDIO AND NOT HAVE_SNDIO) message(FATAL_ERROR "Failed to enable required SndIO backend") endif() # Check Windows-only backends if(WIN32) if (NOT ALSOFT_UWP) # Check MMSystem backend option(ALSOFT_BACKEND_WINMM "Enable Windows Multimedia backend" ON) option(ALSOFT_REQUIRE_WINMM "Require Windows Multimedia backend" OFF) if(ALSOFT_BACKEND_WINMM) set(HAVE_WINMM 1) set(BACKENDS "${BACKENDS} WinMM,") set(ALC_OBJS ${ALC_OBJS} alc/backends/winmm.cpp alc/backends/winmm.h) # There doesn't seem to be good way to search for winmm.lib for MSVC. # find_library doesn't find it without being told to look in a specific # place in the WindowsSDK, but it links anyway. If there ends up being # Windows targets without this, another means to detect it is needed. set(EXTRA_LIBS winmm ${EXTRA_LIBS}) endif() # Check DSound backend option(ALSOFT_BACKEND_DSOUND "Enable DirectSound backend" ON) option(ALSOFT_REQUIRE_DSOUND "Require DirectSound backend" OFF) if(ALSOFT_BACKEND_DSOUND) check_include_file(dsound.h HAVE_DSOUND_H) if(DXSDK_DIR) find_path(DSOUND_INCLUDE_DIR NAMES "dsound.h" PATHS "${DXSDK_DIR}" PATH_SUFFIXES include DOC "The DirectSound include directory") endif() if(HAVE_DSOUND_H OR DSOUND_INCLUDE_DIR) set(HAVE_DSOUND 1) set(BACKENDS "${BACKENDS} DirectSound,") set(ALC_OBJS ${ALC_OBJS} alc/backends/dsound.cpp alc/backends/dsound.h) if(NOT HAVE_DSOUND_H) set(INC_PATHS ${INC_PATHS} ${DSOUND_INCLUDE_DIR}) endif() endif() endif() endif() # Check for WASAPI backend option(ALSOFT_BACKEND_WASAPI "Enable WASAPI backend" ON) option(ALSOFT_REQUIRE_WASAPI "Require WASAPI backend" OFF) if(ALSOFT_BACKEND_WASAPI) check_include_file(mmdeviceapi.h HAVE_MMDEVICEAPI_H) if(HAVE_MMDEVICEAPI_H) set(HAVE_WASAPI 1) set(BACKENDS "${BACKENDS} WASAPI,") set(ALC_OBJS ${ALC_OBJS} alc/backends/wasapi.cpp alc/backends/wasapi.h) if(NOT ALSOFT_UWP) set(EXTRA_LIBS avrt ${EXTRA_LIBS}) endif() endif() endif() option(ALSOFT_BACKEND_OTHERIO "Enable OtherIO backend" OFF) option(ALSOFT_REQUIRE_OTHERIO "Require OtherIO backend" OFF) if(ALSOFT_BACKEND_OTHERIO) set(HAVE_OTHERIO 1) set(BACKENDS "${BACKENDS} OtherIO,") set(ALC_OBJS ${ALC_OBJS} alc/backends/otherio.cpp alc/backends/otherio.h) endif() endif() if(ALSOFT_REQUIRE_WINMM AND NOT HAVE_WINMM) message(FATAL_ERROR "Failed to enable required WinMM backend") endif() if(ALSOFT_REQUIRE_DSOUND AND NOT HAVE_DSOUND) message(FATAL_ERROR "Failed to enable required DSound backend") endif() if(ALSOFT_REQUIRE_WASAPI AND NOT HAVE_WASAPI) message(FATAL_ERROR "Failed to enable required WASAPI backend") endif() if(ALSOFT_REQUIRE_OTHERIO AND NOT HAVE_OTHERIO) message(FATAL_ERROR "Failed to enable required OtherIO backend") endif() # Check JACK backend option(ALSOFT_BACKEND_JACK "Enable JACK backend" ON) option(ALSOFT_REQUIRE_JACK "Require JACK backend" OFF) if(ALSOFT_BACKEND_JACK) find_package(JACK) if(JACK_FOUND) set(HAVE_JACK 1) set(BACKENDS "${BACKENDS} JACK${IS_LINKED},") set(ALC_OBJS ${ALC_OBJS} alc/backends/jack.cpp alc/backends/jack.h) add_backend_libs(${JACK_LIBRARIES}) set(INC_PATHS ${INC_PATHS} ${JACK_INCLUDE_DIRS}) endif() endif() if(ALSOFT_REQUIRE_JACK AND NOT HAVE_JACK) message(FATAL_ERROR "Failed to enable required JACK backend") endif() # Check CoreAudio backend option(ALSOFT_BACKEND_COREAUDIO "Enable CoreAudio backend" ON) option(ALSOFT_REQUIRE_COREAUDIO "Require CoreAudio backend" OFF) if(ALSOFT_BACKEND_COREAUDIO) find_library(COREAUDIO_FRAMEWORK NAMES CoreAudio) find_path(AUDIOUNIT_INCLUDE_DIR NAMES AudioUnit/AudioUnit.h) if(COREAUDIO_FRAMEWORK AND AUDIOUNIT_INCLUDE_DIR) set(HAVE_COREAUDIO 1) set(ALC_OBJS ${ALC_OBJS} alc/backends/coreaudio.cpp alc/backends/coreaudio.h) set(BACKENDS "${BACKENDS} CoreAudio,") set(EXTRA_LIBS -Wl,-framework,CoreAudio ${EXTRA_LIBS}) if(CMAKE_SYSTEM_NAME MATCHES "^(iOS|tvOS)$") find_library(COREFOUNDATION_FRAMEWORK NAMES CoreFoundation) if(COREFOUNDATION_FRAMEWORK) set(EXTRA_LIBS -Wl,-framework,CoreFoundation ${EXTRA_LIBS}) endif() else() set(EXTRA_LIBS -Wl,-framework,AudioUnit,-framework,ApplicationServices ${EXTRA_LIBS}) endif() # Some versions of OSX may need the AudioToolbox framework. Add it if # it's found. find_library(AUDIOTOOLBOX_LIBRARY NAMES AudioToolbox) if(AUDIOTOOLBOX_LIBRARY) set(EXTRA_LIBS -Wl,-framework,AudioToolbox ${EXTRA_LIBS}) endif() set(INC_PATHS ${INC_PATHS} ${AUDIOUNIT_INCLUDE_DIR}) endif() endif() if(ALSOFT_REQUIRE_COREAUDIO AND NOT HAVE_COREAUDIO) message(FATAL_ERROR "Failed to enable required CoreAudio backend") endif() # Check for Oboe (Android) backend option(ALSOFT_BACKEND_OBOE "Enable Oboe backend" ON) option(ALSOFT_REQUIRE_OBOE "Require Oboe backend" OFF) if(ALSOFT_BACKEND_OBOE) set(OBOE_TARGET ) if(ANDROID) set(OBOE_SOURCE "" CACHE STRING "Source directory for Oboe.") if(OBOE_SOURCE) add_subdirectory(${OBOE_SOURCE} ./oboe EXCLUDE_FROM_ALL) set(OBOE_TARGET oboe) else() find_package(oboe CONFIG) if(NOT TARGET oboe::oboe) find_package(Oboe) endif() if(TARGET oboe::oboe) set(OBOE_TARGET "oboe::oboe") endif() endif() endif() if(OBOE_TARGET) set(HAVE_OBOE 1) set(ALC_OBJS ${ALC_OBJS} alc/backends/oboe.cpp alc/backends/oboe.h) set(BACKENDS "${BACKENDS} Oboe,") set(EXTRA_LIBS ${OBOE_TARGET} ${EXTRA_LIBS}) endif() endif() if(ALSOFT_REQUIRE_OBOE AND NOT HAVE_OBOE) message(FATAL_ERROR "Failed to enable required Oboe backend") endif() # Check for OpenSL (Android) backend option(ALSOFT_BACKEND_OPENSL "Enable OpenSL backend" ON) option(ALSOFT_REQUIRE_OPENSL "Require OpenSL backend" OFF) if(ALSOFT_BACKEND_OPENSL) find_package(OpenSL) if(OPENSL_FOUND) set(HAVE_OPENSL 1) set(ALC_OBJS ${ALC_OBJS} alc/backends/opensl.cpp alc/backends/opensl.h) set(BACKENDS "${BACKENDS} OpenSL${IS_LINKED},") add_backend_libs(${OPENSL_LIBRARIES}) set(INC_PATHS ${INC_PATHS} ${OPENSL_INCLUDE_DIRS}) endif() endif() if(ALSOFT_REQUIRE_OPENSL AND NOT HAVE_OPENSL) message(FATAL_ERROR "Failed to enable required OpenSL backend") endif() # Check PortAudio backend option(ALSOFT_BACKEND_PORTAUDIO "Enable PortAudio backend" ON) option(ALSOFT_REQUIRE_PORTAUDIO "Require PortAudio backend" OFF) if(ALSOFT_BACKEND_PORTAUDIO) find_package(PortAudio) if(PORTAUDIO_FOUND) set(HAVE_PORTAUDIO 1) set(BACKENDS "${BACKENDS} PortAudio${IS_LINKED},") set(ALC_OBJS ${ALC_OBJS} alc/backends/portaudio.cpp alc/backends/portaudio.hpp) add_backend_libs(${PORTAUDIO_LIBRARIES}) set(INC_PATHS ${INC_PATHS} ${PORTAUDIO_INCLUDE_DIRS}) endif() endif() if(ALSOFT_REQUIRE_PORTAUDIO AND NOT HAVE_PORTAUDIO) message(FATAL_ERROR "Failed to enable required PortAudio backend") endif() # Check for SDL2 or SDL3 backend # Off by default, since it adds a runtime dependency. Additionally, both SDL2 # and SDL3 can't be enabled simultaneously. option(ALSOFT_BACKEND_SDL3 "Enable SDL3 backend" OFF) option(ALSOFT_REQUIRE_SDL3 "Require SDL3 backend" OFF) if(ALSOFT_BACKEND_SDL3) if(SDL3_FOUND) set(HAVE_SDL3 1) set(ALC_OBJS ${ALC_OBJS} alc/backends/sdl3.cpp alc/backends/sdl3.h) set(BACKENDS "${BACKENDS} SDL3,") set(EXTRA_LIBS ${EXTRA_LIBS} SDL3::SDL3) else() message(STATUS "Could NOT find SDL3") endif() endif() if(ALSOFT_REQUIRE_SDL3 AND NOT HAVE_SDL3) message(FATAL_ERROR "Failed to enable required SDL3 backend") endif() option(ALSOFT_BACKEND_SDL2 "Enable SDL2 backend" OFF) option(ALSOFT_REQUIRE_SDL2 "Require SDL2 backend" OFF) if(ALSOFT_BACKEND_SDL2 AND NOT HAVE_SDL3) find_package(SDL2) if(SDL2_FOUND) set(HAVE_SDL2 1) set(ALC_OBJS ${ALC_OBJS} alc/backends/sdl2.cpp alc/backends/sdl2.h) set(BACKENDS "${BACKENDS} SDL2,") set(EXTRA_LIBS ${EXTRA_LIBS} SDL2::SDL2) else() message(STATUS "Could NOT find SDL2") endif() endif() if(ALSOFT_REQUIRE_SDL2 AND NOT HAVE_SDL2) message(FATAL_ERROR "Failed to enable required SDL2 backend") endif() # Optionally enable the Wave Writer backend option(ALSOFT_BACKEND_WAVE "Enable Wave Writer backend" ON) if(ALSOFT_BACKEND_WAVE) set(HAVE_WAVE 1) set(ALC_OBJS ${ALC_OBJS} alc/backends/wave.cpp alc/backends/wave.h) set(BACKENDS "${BACKENDS} WaveFile,") endif() # This is always available set(BACKENDS "${BACKENDS} Null") find_package(Git) if(ALSOFT_UPDATE_BUILD_VERSION AND GIT_FOUND AND EXISTS "${OpenAL_SOURCE_DIR}/.git") set(GIT_DIR "${OpenAL_SOURCE_DIR}/.git") # Check if this is a submodule, if it is then find the .git directory if(NOT IS_DIRECTORY "${OpenAL_SOURCE_DIR}/.git") file(READ ${GIT_DIR} submodule) string(REGEX REPLACE "gitdir: (.*)$" "\\1" GIT_DIR_RELATIVE ${submodule}) string(STRIP ${GIT_DIR_RELATIVE} GIT_DIR_RELATIVE) get_filename_component(SUBMODULE_DIR ${GIT_DIR} PATH) get_filename_component(GIT_DIR ${SUBMODULE_DIR}/${GIT_DIR_RELATIVE} ABSOLUTE) endif() # Get the current working branch and its latest abbreviated commit hash add_custom_command(OUTPUT "${OpenAL_BINARY_DIR}/version_witness.txt" BYPRODUCTS "${OpenAL_BINARY_DIR}/version.h" COMMAND ${CMAKE_COMMAND} -D GIT_EXECUTABLE=${GIT_EXECUTABLE} -D LIB_VERSION=${LIB_VERSION} -D LIB_VERSION_NUM=${LIB_VERSION_NUM} -D SRC=${OpenAL_SOURCE_DIR}/version.h.in -D DST=${OpenAL_BINARY_DIR}/version.h -P ${OpenAL_SOURCE_DIR}/version.cmake COMMAND ${CMAKE_COMMAND} -E touch "${OpenAL_BINARY_DIR}/version_witness.txt" WORKING_DIRECTORY "${OpenAL_SOURCE_DIR}" MAIN_DEPENDENCY "${OpenAL_SOURCE_DIR}/version.h.in" DEPENDS "${GIT_DIR}/index" "${OpenAL_SOURCE_DIR}/version.cmake" VERBATIM ) add_custom_target(alsoft.build_version DEPENDS "${OpenAL_BINARY_DIR}/version_witness.txt") else() set(GIT_BRANCH "UNKNOWN") set(GIT_COMMIT_HASH "unknown") configure_file( "${OpenAL_SOURCE_DIR}/version.h.in" "${OpenAL_BINARY_DIR}/version.h") endif() option(ALSOFT_EMBED_HRTF_DATA "Embed the HRTF data files (increases library footprint)" ON) if(ALSOFT_EMBED_HRTF_DATA) macro(make_hrtf_header FILENAME VARNAME) set(infile "${OpenAL_SOURCE_DIR}/hrtf/${FILENAME}") set(outfile "${OpenAL_BINARY_DIR}/${VARNAME}.txt") add_custom_command(OUTPUT "${outfile}" COMMAND ${CMAKE_COMMAND} -D "INPUT_FILE=${infile}" -D "OUTPUT_FILE=${outfile}" -P "${CMAKE_MODULE_PATH}/bin2h.script.cmake" WORKING_DIRECTORY "${OpenAL_SOURCE_DIR}" DEPENDS "${infile}" "${CMAKE_MODULE_PATH}/bin2h.script.cmake" VERBATIM ) set(ALC_OBJS ${ALC_OBJS} "${outfile}") endmacro() make_hrtf_header("Default HRTF.mhr" "default_hrtf") endif() if(ALSOFT_UTILS) find_package(MySOFA) if(NOT ALSOFT_NO_CONFIG_UTIL) find_package(Qt5Widgets QUIET) if(NOT Qt5Widgets_FOUND) message(STATUS "Could NOT find Qt5Widgets") endif() endif() endif() if(ALSOFT_UTILS OR ALSOFT_EXAMPLES) find_package(SndFile) if(SDL3_FOUND) find_package(FFmpeg COMPONENTS AVFORMAT AVCODEC AVUTIL SWSCALE SWRESAMPLE) endif() endif() if(NOT WIN32) set(LIBNAME "openal") else() set(LIBNAME "OpenAL32") endif() # Needed for openal.pc.in set(prefix ${CMAKE_INSTALL_PREFIX}) set(exec_prefix "\${prefix}") set(libdir "${CMAKE_INSTALL_FULL_LIBDIR}") set(bindir "${CMAKE_INSTALL_FULL_BINDIR}") set(includedir "${CMAKE_INSTALL_FULL_INCLUDEDIR}") set(PACKAGE_VERSION "${LIB_VERSION}") set(PKG_CONFIG_CFLAGS ) set(PKG_CONFIG_PRIVATE_LIBS ) if(LIBTYPE STREQUAL "STATIC") set(PKG_CONFIG_CFLAGS -DAL_LIBTYPE_STATIC) foreach(FLAG ${LINKER_FLAGS} ${EXTRA_LIBS} ${MATH_LIB}) # If this is already a linker flag, or is a full path+file, add it # as-is. If it's an SDL2 or SDL3 target, add the link flag for it. # Otherwise, it's a name intended to be dressed as -lname. if(FLAG MATCHES "^-.*" OR EXISTS "${FLAG}") set(PKG_CONFIG_PRIVATE_LIBS "${PKG_CONFIG_PRIVATE_LIBS} ${FLAG}") elseif(FLAG MATCHES "^SDL2::SDL2") set(PKG_CONFIG_PRIVATE_LIBS "${PKG_CONFIG_PRIVATE_LIBS} -lSDL2") elseif(FLAG MATCHES "^SDL3::SDL3") set(PKG_CONFIG_PRIVATE_LIBS "${PKG_CONFIG_PRIVATE_LIBS} -lSDL3") else() set(PKG_CONFIG_PRIVATE_LIBS "${PKG_CONFIG_PRIVATE_LIBS} -l${FLAG}") endif() endforeach() endif() # End configuration configure_file( "${OpenAL_SOURCE_DIR}/config.h.in" "${OpenAL_BINARY_DIR}/config.h") configure_file( "${OpenAL_SOURCE_DIR}/config_backends.h.in" "${OpenAL_BINARY_DIR}/config_backends.h") configure_file( "${OpenAL_SOURCE_DIR}/config_simd.h.in" "${OpenAL_BINARY_DIR}/config_simd.h") configure_file( "${OpenAL_SOURCE_DIR}/openal.pc.in" "${OpenAL_BINARY_DIR}/openal.pc" @ONLY) add_library(alsoft.common STATIC EXCLUDE_FROM_ALL ${COMMON_OBJS}) target_include_directories(alsoft.common PRIVATE ${OpenAL_SOURCE_DIR}/include PUBLIC ${OpenAL_BINARY_DIR} ${OpenAL_SOURCE_DIR}/common) target_compile_definitions(alsoft.common PRIVATE ${CPP_DEFS}) target_compile_options(alsoft.common PRIVATE ${C_FLAGS}) target_link_libraries(alsoft.common PRIVATE alsoft::fmt) set_target_properties(alsoft.common PROPERTIES ${ALSOFT_STD_VERSION_PROPS} POSITION_INDEPENDENT_CODE TRUE) unset(HAS_ROUTER) set(IMPL_TARGET OpenAL) # Either OpenAL or soft_oal. set(NEED_ANALYZE_SOURCE_FILES "") foreach(obj ${CORE_OBJS} ${OPENAL_OBJS} ${ALC_OBJS} ${COMMON_OBJS}) IF (NOT ${obj} MATCHES "${CMAKE_BINARY_DIR}/default_hrtf.txt") list(APPEND NEED_ANALYZE_SOURCE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/${obj}") endif() endforeach() IF (ALSOFT_UTILS) list(APPEND NEED_ANALYZE_SOURCE_FILES "${CMAKE_SOURCE_DIR}/utils/openal-info.c") endif() SET(CLANG_TIDY_EXECUTABLE "clang-tidy") if(DEFINED ENV{CLANG_TIDY_EXECUTABLE}) SET(CLANG_TIDY_EXECUTABLE $ENV{CLANG_TIDY_EXECUTABLE}) endif() add_custom_target(clang-tidy-check ${CLANG_TIDY_EXECUTABLE} -format-style=file -p ${CMAKE_BINARY_DIR}/compile_commands.json ${NEED_ANALYZE_SOURCE_FILES} DEPENDS ${NEED_ANALYZE_SOURCE_FILES}) # Build main library if(LIBTYPE STREQUAL "STATIC") add_library(${IMPL_TARGET} STATIC ${COMMON_OBJS} ${OPENAL_OBJS} ${ALC_OBJS} ${CORE_OBJS}) target_compile_definitions(${IMPL_TARGET} PUBLIC AL_LIBTYPE_STATIC) target_link_libraries(${IMPL_TARGET} PRIVATE ${LINKER_FLAGS} ${EXTRA_LIBS} ${MATH_LIB} $) if(WIN32) # This option is for static linking OpenAL Soft into another project # that already defines the IDs. It is up to that project to ensure all # required IDs are defined. option(ALSOFT_NO_UID_DEFS "Do not define GUIDs, IIDs, CLSIDs, or PropertyKeys" OFF) if(ALSOFT_NO_UID_DEFS) target_compile_definitions(${IMPL_TARGET} PRIVATE AL_NO_UID_DEFS) endif() endif() else() set(RC_CONFIG resources/openal32.rc) if(WIN32 AND ALSOFT_BUILD_ROUTER) add_library(OpenAL SHARED resources/router.rc router/router.cpp router/router.h router/alc.cpp router/al.cpp ) target_compile_definitions(OpenAL PRIVATE AL_BUILD_LIBRARY AL_ALEXT_PROTOTYPES "ALC_API=${EXPORT_DECL}" "AL_API=${EXPORT_DECL}" ${CPP_DEFS}) target_compile_options(OpenAL PRIVATE ${C_FLAGS}) target_link_libraries(OpenAL PRIVATE alsoft.common ${LINKER_FLAGS} alsoft::fmt) target_include_directories(OpenAL PUBLIC $ $ PRIVATE ${OpenAL_SOURCE_DIR}/common ${OpenAL_BINARY_DIR} ) set_target_properties(OpenAL PROPERTIES ${ALSOFT_STD_VERSION_PROPS} PREFIX "" OUTPUT_NAME ${LIBNAME}) if(TARGET alsoft.build_version) add_dependencies(OpenAL alsoft.build_version) endif() set(HAS_ROUTER 1) set(LIBNAME "soft_oal") set(IMPL_TARGET soft_oal) set(RC_CONFIG resources/soft_oal.rc) endif() # !important: for osx framework public header works, the headers must be in # the project set(TARGET_PUBLIC_HEADERS include/AL/al.h include/AL/alc.h include/AL/alext.h include/AL/efx.h include/AL/efx-presets.h) add_library(${IMPL_TARGET} SHARED ${OPENAL_OBJS} ${ALC_OBJS} ${CORE_OBJS} ${RC_CONFIG} ${TARGET_PUBLIC_HEADERS}) if(WIN32) set_target_properties(${IMPL_TARGET} PROPERTIES PREFIX "") endif() target_link_libraries(${IMPL_TARGET} PRIVATE alsoft.common ${LINKER_FLAGS} ${EXTRA_LIBS} ${MATH_LIB} alsoft::fmt) if(ALSOFT_UWP) find_package(cppwinrt CONFIG) if (TARGET Microsoft::CppWinRT) target_link_libraries(${IMPL_TARGET} PRIVATE Microsoft::CppWinRT) else() set(ALSOFT_CPPWINRT_VERSION "2.0.230706.1" CACHE STRING "The soft-oal default cppwinrt version") find_program(NUGET_EXE NAMES nuget) if(NOT NUGET_EXE) message("NUGET.EXE not found.") message(FATAL_ERROR "Please install this executable, and run CMake again.") endif() exec_program(${NUGET_EXE} ARGS install "Microsoft.Windows.CppWinRT" -Version ${ALSOFT_CPPWINRT_VERSION} -ExcludeVersion -OutputDirectory "\"${CMAKE_BINARY_DIR}/packages\"") set_target_properties(${IMPL_TARGET} PROPERTIES VS_PROJECT_IMPORT ${CMAKE_BINARY_DIR}/packages/Microsoft.Windows.CppWinRT/build/native/Microsoft.Windows.CppWinRT.props ) target_link_libraries(${IMPL_TARGET} PRIVATE ${CMAKE_BINARY_DIR}/packages/Microsoft.Windows.CppWinRT/build/native/Microsoft.Windows.CppWinRT.targets) endif() endif() if(NOT WIN32 AND NOT APPLE) # FIXME: This doesn't put a dependency on the version script. Changing # the version script will not cause a relink as it should. target_link_options(${IMPL_TARGET} PRIVATE "-Wl,--version-script=${OpenAL_SOURCE_DIR}/libopenal.version") endif() if(APPLE AND ALSOFT_OSX_FRAMEWORK) # Sets framework name to soft_oal to avoid ambiguity with the system OpenAL.framework set(LIBNAME "soft_oal") if(GIT_FOUND) execute_process(COMMAND ${GIT_EXECUTABLE} rev-list --count HEAD TIMEOUT 5 OUTPUT_VARIABLE BUNDLE_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE) else() set(BUNDLE_VERSION 1) endif() # Build: Fix rpath in iOS shared libraries # If this flag is not set, the final dylib binary ld path will be # /User/xxx/*/soft_oal.framework/soft_oal, which can't be loaded by iOS devices. # See also: https://github.com/libjpeg-turbo/libjpeg-turbo/commit/c80ddef7a4ce21ace9e3ca0fd190d320cc8cdaeb if(NOT CMAKE_SHARED_LIBRARY_RUNTIME_C_FLAG) set(CMAKE_SHARED_LIBRARY_RUNTIME_C_FLAG "-Wl,-rpath,") endif() set_target_properties(${IMPL_TARGET} PROPERTIES FRAMEWORK TRUE FRAMEWORK_VERSION C MACOSX_FRAMEWORK_NAME "${IMPL_TARGET}" MACOSX_FRAMEWORK_IDENTIFIER "org.openal-soft.openal" MACOSX_FRAMEWORK_SHORT_VERSION_STRING "${LIB_VERSION}" MACOSX_FRAMEWORK_BUNDLE_VERSION "${BUNDLE_VERSION}" XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "" XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "NO" XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "NO" PUBLIC_HEADER "${TARGET_PUBLIC_HEADERS}" MACOSX_RPATH TRUE) endif() endif() target_include_directories(${IMPL_TARGET} PUBLIC $ INTERFACE $ $ $ PRIVATE ${INC_PATHS} ${OpenAL_BINARY_DIR} ${OpenAL_SOURCE_DIR} ${OpenAL_SOURCE_DIR}/common ) set_target_properties(${IMPL_TARGET} PROPERTIES ${ALSOFT_STD_VERSION_PROPS} OUTPUT_NAME ${LIBNAME} VERSION ${LIB_VERSION} SOVERSION ${LIB_MAJOR_VERSION} ) target_compile_definitions(${IMPL_TARGET} PRIVATE AL_BUILD_LIBRARY AL_ALEXT_PROTOTYPES "ALC_API=${EXPORT_DECL}" "AL_API=${EXPORT_DECL}" ${CPP_DEFS}) target_compile_options(${IMPL_TARGET} PRIVATE ${C_FLAGS}) if(TARGET alsoft.build_version) add_dependencies(${IMPL_TARGET} alsoft.build_version) endif() if(WIN32 AND MINGW AND ALSOFT_BUILD_IMPORT_LIB AND NOT LIBTYPE STREQUAL "STATIC") find_program(SED_EXECUTABLE NAMES sed DOC "sed executable") if(NOT SED_EXECUTABLE OR NOT CMAKE_DLLTOOL) message(STATUS "") if(NOT SED_EXECUTABLE) message(STATUS "WARNING: Cannot find sed, disabling .def/.lib generation") endif() if(NOT CMAKE_DLLTOOL) message(STATUS "WARNING: Cannot find dlltool, disabling .def/.lib generation") endif() else() target_link_options(OpenAL PRIVATE "-Wl,--output-def,${PROJECT_BINARY_DIR}/OpenAL32.def") add_custom_command(TARGET OpenAL POST_BUILD COMMAND "${SED_EXECUTABLE}" -i -e "s/ @[^ ]*//" OpenAL32.def COMMAND "${CMAKE_DLLTOOL}" -d OpenAL32.def -l OpenAL32.lib -D OpenAL32.dll # Technically OpenAL32.def was created by the build, but cmake # doesn't recognize it due to -Wl,--output-def,OpenAL32.def being # manually specified. But declaring the file here allows it to be # properly cleaned, e.g. during make clean. BYPRODUCTS OpenAL32.def OpenAL32.lib COMMENT "Stripping ordinals from OpenAL32.def and generating OpenAL32.lib..." VERBATIM ) endif() endif() if(HAS_ROUTER) message(STATUS "") message(STATUS "Building DLL router") endif() message(STATUS "") message(STATUS "Building OpenAL with support for the following backends:") message(STATUS " ${BACKENDS}") message(STATUS "") message(STATUS "Building with support for CPU extensions:") message(STATUS " ${CPU_EXTS}") message(STATUS "") if(FPMATH_SET) message(STATUS "Building with SSE${FPMATH_SET} codegen") message(STATUS "") endif() if(ALSOFT_UWP) message(STATUS "Building with UWP support") message(STATUS "") endif() if(ALSOFT_EAX) message(STATUS "Building with legacy EAX extension support") message(STATUS "") endif() if(ALSOFT_EMBED_HRTF_DATA) message(STATUS "Embedding HRTF datasets") message(STATUS "") endif() # An alias for sub-project builds add_library(OpenAL::OpenAL ALIAS OpenAL) # Install main library if(ALSOFT_INSTALL) configure_package_config_file(OpenALConfig.cmake.in OpenALConfig.cmake INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/OpenAL) install(TARGETS OpenAL EXPORT OpenAL RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} FRAMEWORK DESTINATION ${CMAKE_INSTALL_LIBDIR} INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} ${CMAKE_INSTALL_INCLUDEDIR}/AL) export(TARGETS OpenAL NAMESPACE OpenAL:: FILE OpenALTargets.cmake) install(EXPORT OpenAL DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/OpenAL NAMESPACE OpenAL:: FILE OpenALTargets.cmake) install(DIRECTORY include/AL DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) install(FILES "${OpenAL_BINARY_DIR}/openal.pc" DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig") install(FILES "${OpenAL_BINARY_DIR}/OpenALConfig.cmake" DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/OpenAL") if(TARGET soft_oal) install(TARGETS soft_oal RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) endif() message(STATUS "Installing library and headers") else() message(STATUS "NOT installing library and headers") endif() if(ALSOFT_INSTALL_CONFIG) install(FILES alsoftrc.sample DESTINATION ${CMAKE_INSTALL_DATADIR}/openal) message(STATUS "Installing sample configuration") endif() if(ALSOFT_INSTALL_HRTF_DATA) install(DIRECTORY hrtf DESTINATION ${CMAKE_INSTALL_DATADIR}/openal) message(STATUS "Installing HRTF data files") endif() if(ALSOFT_INSTALL_AMBDEC_PRESETS) install(DIRECTORY presets DESTINATION ${CMAKE_INSTALL_DATADIR}/openal) message(STATUS "Installing AmbDec presets") endif() message(STATUS "") set(UNICODE_FLAG ) if(MINGW) set(UNICODE_FLAG ${UNICODE_FLAG} -municode) endif() set(EXTRA_INSTALLS ) if(ALSOFT_UTILS) add_executable(openal-info utils/openal-info.c) target_include_directories(openal-info PRIVATE ${OpenAL_SOURCE_DIR}/common) target_compile_options(openal-info PRIVATE ${C_FLAGS}) target_link_libraries(openal-info PRIVATE ${LINKER_FLAGS} OpenAL ${UNICODE_FLAG}) set_target_properties(openal-info PROPERTIES ${ALSOFT_STD_VERSION_PROPS}) if(ALSOFT_INSTALL_EXAMPLES) set(EXTRA_INSTALLS ${EXTRA_INSTALLS} openal-info) endif() if(SNDFILE_FOUND) add_executable(uhjdecoder utils/uhjdecoder.cpp) target_compile_definitions(uhjdecoder PRIVATE ${CPP_DEFS}) target_include_directories(uhjdecoder PRIVATE ${OpenAL_BINARY_DIR} ${OpenAL_SOURCE_DIR}/common) target_compile_options(uhjdecoder PRIVATE ${C_FLAGS}) target_link_libraries(uhjdecoder PUBLIC alsoft.common PRIVATE ${LINKER_FLAGS} SndFile::SndFile ${UNICODE_FLAG} alsoft::fmt) set_target_properties(uhjdecoder PROPERTIES ${ALSOFT_STD_VERSION_PROPS}) add_executable(uhjencoder utils/uhjencoder.cpp) target_compile_definitions(uhjencoder PRIVATE ${CPP_DEFS}) target_include_directories(uhjencoder PRIVATE ${OpenAL_BINARY_DIR} ${OpenAL_SOURCE_DIR}/common) target_compile_options(uhjencoder PRIVATE ${C_FLAGS}) target_link_libraries(uhjencoder PUBLIC alsoft.common PRIVATE ${LINKER_FLAGS} SndFile::SndFile ${UNICODE_FLAG} alsoft::fmt) set_target_properties(uhjencoder PROPERTIES ${ALSOFT_STD_VERSION_PROPS}) endif() if(MYSOFA_FOUND) set(SOFA_SUPPORT_SRCS utils/sofa-support.cpp utils/sofa-support.h) add_library(alsoft.sofa-support STATIC EXCLUDE_FROM_ALL ${SOFA_SUPPORT_SRCS}) target_compile_definitions(alsoft.sofa-support PRIVATE ${CPP_DEFS}) target_include_directories(alsoft.sofa-support PUBLIC ${OpenAL_SOURCE_DIR}/common) target_compile_options(alsoft.sofa-support PRIVATE ${C_FLAGS}) target_link_libraries(alsoft.sofa-support PUBLIC alsoft.common MySOFA::MySOFA PRIVATE ${LINKER_FLAGS} alsoft::fmt) set_target_properties(alsoft.sofa-support PROPERTIES ${ALSOFT_STD_VERSION_PROPS}) set(MAKEMHR_SRCS utils/makemhr/loaddef.cpp utils/makemhr/loaddef.h utils/makemhr/loadsofa.cpp utils/makemhr/loadsofa.h utils/makemhr/makemhr.cpp utils/makemhr/makemhr.h) add_executable(makemhr ${MAKEMHR_SRCS}) target_compile_definitions(makemhr PRIVATE ${CPP_DEFS}) target_include_directories(makemhr PRIVATE ${OpenAL_BINARY_DIR} ${OpenAL_SOURCE_DIR}/utils) target_compile_options(makemhr PRIVATE ${C_FLAGS}) target_link_libraries(makemhr PRIVATE ${LINKER_FLAGS} alsoft.sofa-support ${UNICODE_FLAG} alsoft::fmt) set_target_properties(makemhr PROPERTIES ${ALSOFT_STD_VERSION_PROPS}) if(ALSOFT_INSTALL_EXAMPLES) set(EXTRA_INSTALLS ${EXTRA_INSTALLS} makemhr) endif() set(SOFAINFO_SRCS utils/sofa-info.cpp) add_executable(sofa-info ${SOFAINFO_SRCS}) target_compile_definitions(sofa-info PRIVATE ${CPP_DEFS}) target_include_directories(sofa-info PRIVATE ${OpenAL_SOURCE_DIR}/utils) target_compile_options(sofa-info PRIVATE ${C_FLAGS}) target_link_libraries(sofa-info PRIVATE ${LINKER_FLAGS} alsoft.sofa-support ${UNICODE_FLAG} alsoft::fmt) set_target_properties(sofa-info PROPERTIES ${ALSOFT_STD_VERSION_PROPS}) endif() message(STATUS "Building utility programs") if(NOT ALSOFT_NO_CONFIG_UTIL) add_subdirectory(utils/alsoft-config) endif() message(STATUS "") endif() # Add a static library with common functions used by multiple example targets add_library(alsoft.excommon STATIC EXCLUDE_FROM_ALL examples/common/alhelpers.c examples/common/alhelpers.h) target_compile_definitions(alsoft.excommon PUBLIC ${CPP_DEFS}) target_include_directories(alsoft.excommon PUBLIC ${OpenAL_BINARY_DIR} ${OpenAL_SOURCE_DIR}/common) target_compile_options(alsoft.excommon PUBLIC ${C_FLAGS}) target_link_libraries(alsoft.excommon PUBLIC OpenAL PRIVATE ${RT_LIB}) set_target_properties(alsoft.excommon PROPERTIES ${ALSOFT_STD_VERSION_PROPS}) if(ALSOFT_EXAMPLES) add_executable(altonegen examples/altonegen.c) target_link_libraries(altonegen PRIVATE ${LINKER_FLAGS} ${MATH_LIB} alsoft.excommon ${UNICODE_FLAG}) set_target_properties(altonegen PROPERTIES ${ALSOFT_STD_VERSION_PROPS}) add_executable(alrecord examples/alrecord.c) target_link_libraries(alrecord PRIVATE ${LINKER_FLAGS} alsoft.excommon ${UNICODE_FLAG}) set_target_properties(alrecord PROPERTIES ${ALSOFT_STD_VERSION_PROPS}) add_executable(aldebug examples/aldebug.cpp) target_link_libraries(aldebug PRIVATE ${LINKER_FLAGS} alsoft.excommon ${UNICODE_FLAG} alsoft::fmt) set_target_properties(aldebug PROPERTIES ${ALSOFT_STD_VERSION_PROPS}) add_executable(allafplay examples/allafplay.cpp) target_link_libraries(allafplay PRIVATE ${LINKER_FLAGS} alsoft.common alsoft.excommon ${UNICODE_FLAG} alsoft::fmt) set_target_properties(allafplay PROPERTIES ${ALSOFT_STD_VERSION_PROPS}) if(ALSOFT_INSTALL_EXAMPLES) set(EXTRA_INSTALLS ${EXTRA_INSTALLS} altonegen alrecord aldebug allafplay) endif() message(STATUS "Building example programs") if(SNDFILE_FOUND) add_executable(alplay examples/alplay.c) target_link_libraries(alplay PRIVATE ${LINKER_FLAGS} SndFile::SndFile alsoft.excommon ${UNICODE_FLAG}) set_target_properties(alplay PROPERTIES ${ALSOFT_STD_VERSION_PROPS}) add_executable(alstream examples/alstream.c) target_link_libraries(alstream PRIVATE ${LINKER_FLAGS} SndFile::SndFile alsoft.excommon ${UNICODE_FLAG}) set_target_properties(alstream PROPERTIES ${ALSOFT_STD_VERSION_PROPS}) add_executable(alreverb examples/alreverb.c) target_link_libraries(alreverb PRIVATE ${LINKER_FLAGS} SndFile::SndFile alsoft.excommon ${UNICODE_FLAG}) set_target_properties(alreverb PROPERTIES ${ALSOFT_STD_VERSION_PROPS}) add_executable(almultireverb examples/almultireverb.c) target_link_libraries(almultireverb PRIVATE ${LINKER_FLAGS} SndFile::SndFile alsoft.excommon ${MATH_LIB} ${UNICODE_FLAG}) set_target_properties(almultireverb PROPERTIES ${ALSOFT_STD_VERSION_PROPS}) add_executable(allatency examples/allatency.c) target_link_libraries(allatency PRIVATE ${LINKER_FLAGS} SndFile::SndFile alsoft.excommon ${UNICODE_FLAG}) set_target_properties(allatency PROPERTIES ${ALSOFT_STD_VERSION_PROPS}) add_executable(alhrtf examples/alhrtf.c) target_link_libraries(alhrtf PRIVATE ${LINKER_FLAGS} SndFile::SndFile alsoft.excommon ${MATH_LIB} ${UNICODE_FLAG}) set_target_properties(alhrtf PROPERTIES ${ALSOFT_STD_VERSION_PROPS}) add_executable(alstreamcb examples/alstreamcb.cpp) target_link_libraries(alstreamcb PRIVATE ${LINKER_FLAGS} SndFile::SndFile alsoft.excommon ${UNICODE_FLAG} alsoft::fmt) set_target_properties(alstreamcb PROPERTIES ${ALSOFT_STD_VERSION_PROPS}) add_executable(aldirect examples/aldirect.cpp) target_link_libraries(aldirect PRIVATE ${LINKER_FLAGS} SndFile::SndFile alsoft.excommon ${UNICODE_FLAG} alsoft::fmt) set_target_properties(aldirect PROPERTIES ${ALSOFT_STD_VERSION_PROPS}) add_executable(alconvolve examples/alconvolve.c) target_link_libraries(alconvolve PRIVATE ${LINKER_FLAGS} alsoft.common SndFile::SndFile alsoft.excommon ${UNICODE_FLAG}) set_target_properties(alconvolve PROPERTIES ${ALSOFT_STD_VERSION_PROPS}) if(ALSOFT_INSTALL_EXAMPLES) set(EXTRA_INSTALLS ${EXTRA_INSTALLS} alplay alstream alreverb almultireverb allatency alhrtf aldirect) endif() message(STATUS "Building SndFile example programs") endif() # Can't safely use SDL3 and SDL2 together if(SDL3_FOUND AND NOT HAVE_SDL2) message(STATUS "Building SDL3 example programs") add_executable(alloopback examples/alloopback.c) target_link_libraries(alloopback PRIVATE ${LINKER_FLAGS} SDL3::SDL3 alsoft.excommon ${MATH_LIB}) set_target_properties(alloopback PROPERTIES ${ALSOFT_STD_VERSION_PROPS}) if(ALSOFT_INSTALL_EXAMPLES) set(EXTRA_INSTALLS ${EXTRA_INSTALLS} alloopback) endif() set(FFVER_OK FALSE) if(FFMPEG_FOUND) set(FFVER_OK TRUE) if(AVFORMAT_VERSION VERSION_LESS "59.27.100") message(STATUS "libavformat is too old! (${AVFORMAT_VERSION}, wanted 59.27.100)") set(FFVER_OK FALSE) endif() if(AVCODEC_VERSION VERSION_LESS "59.37.100") message(STATUS "libavcodec is too old! (${AVCODEC_VERSION}, wanted 59.37.100)") set(FFVER_OK FALSE) endif() if(AVUTIL_VERSION VERSION_LESS "57.28.100") message(STATUS "libavutil is too old! (${AVUTIL_VERSION}, wanted 57.28.100)") set(FFVER_OK FALSE) endif() if(SWSCALE_VERSION VERSION_LESS "6.7.100") message(STATUS "libswscale is too old! (${SWSCALE_VERSION}, wanted 6.7.100)") set(FFVER_OK FALSE) endif() if(SWRESAMPLE_VERSION VERSION_LESS "4.7.100") message(STATUS "libswresample is too old! (${SWRESAMPLE_VERSION}, wanted 4.7.100)") set(FFVER_OK FALSE) endif() endif() if(FFVER_OK) add_executable(alffplay examples/alffplay.cpp) target_include_directories(alffplay PRIVATE ${FFMPEG_INCLUDE_DIRS}) target_link_libraries(alffplay PRIVATE ${LINKER_FLAGS} SDL3::SDL3 ${FFMPEG_LIBRARIES} alsoft.excommon alsoft::fmt) set_target_properties(alffplay PROPERTIES ${ALSOFT_STD_VERSION_PROPS}) if(ALSOFT_INSTALL_EXAMPLES) set(EXTRA_INSTALLS ${EXTRA_INSTALLS} alffplay) endif() message(STATUS "Building SDL3+FFmpeg example programs") endif() endif() message(STATUS "") endif() if(ALSOFT_TESTS) add_subdirectory(tests) endif() if(EXTRA_INSTALLS) install(TARGETS ${EXTRA_INSTALLS} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} BUNDLE DESTINATION ${CMAKE_INSTALL_BINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) endif() openal-soft-1.24.2/COPYING000066400000000000000000000554421474041540300150760ustar00rootroot00000000000000 GNU LIBRARY GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1991 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the library GPL. It is numbered 2 because it goes with version 2 of the ordinary GPL.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Library General Public License, applies to some specially designated Free Software Foundation software, and to any other libraries whose authors decide to use it. You can use it for your libraries, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library, or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link a program with the library, you must provide complete object files to the recipients so that they can relink them with the library, after making changes to the library and recompiling it. And you must show them these terms so they know their rights. Our method of protecting your rights has two steps: (1) copyright the library, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the library. Also, for each distributor's protection, we want to make certain that everyone understands that there is no warranty for this free library. If the library is modified by someone else and passed on, we want its recipients to know that what they have is not the original version, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that companies distributing free software will individually obtain patent licenses, thus in effect transforming the program into proprietary software. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License, which was designed for utility programs. This license, the GNU Library General Public License, applies to certain designated libraries. This license is quite different from the ordinary one; be sure to read it in full, and don't assume that anything in it is the same as in the ordinary license. The reason we have a separate public license for some libraries is that they blur the distinction we usually make between modifying or adding to a program and simply using it. Linking a program with a library, without changing the library, is in some sense simply using the library, and is analogous to running a utility program or application program. However, in a textual and legal sense, the linked executable is a combined work, a derivative of the original library, and the ordinary General Public License treats it as such. Because of this blurred distinction, using the ordinary General Public License for libraries did not effectively promote software sharing, because most developers did not use the libraries. We concluded that weaker conditions might promote sharing better. However, unrestricted linking of non-free programs would deprive the users of those programs of all benefit from the free status of the libraries themselves. This Library General Public License is intended to permit developers of non-free programs to use free libraries, while preserving your freedom as a user of such programs to change the free libraries that are incorporated in them. (We have not seen how to achieve this as regards changes in header files, but we have achieved it as regards changes in the actual functions of the Library.) The hope is that this will lead to faster development of free libraries. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, while the latter only works together with the library. Note that it is possible for a library to be covered by the ordinary General Public License rather than by this special one. GNU LIBRARY GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Library General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also compile or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. c) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. d) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Library General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS openal-soft-1.24.2/ChangeLog000066400000000000000000000651041474041540300156110ustar00rootroot00000000000000openal-soft-1.24.2: Implemented the AL_SOFT_bformat_hoa extension. Implemented default device change events for the PulseAudio backend. Implemented an option for WASAPI exclusive mode playback. Fixed reverb being too quiet for sounds from different directions. Fixed compiling with certain versions of Clang. Fixed compiling for some older macOS versions. Fixed building alffplay on systems without pkg-config. Improved output format detection for CoreAudio. Changed the default resampler back to Cubic Spline. Added an SDL3 playback backend. Disabled by default to avoid a runtime dependency and for compatibility; a single process can't safely use SDL2 and SDL3 together on some OSs, so enable with care. Converted examples from SDL2 to SDL3. Integrated fmtlib into the main library and router for logging and string formatting. openal-soft-1.24.1: Fixed compilation on PowerPC. Fixed compilation on some targets that lack lock-free 64-bit atomics. Fixed a crash when parsing certain option values. Fixed applying noexcept in the public headers with MSVC. Fixed building for UWP with vcpkg. Improved compatibility when compiling as C++20 or later. Integrated fmtlib for some examples and utilities. openal-soft-1.24.0: Updated library codebase to C++17. Implemented the ALC_SOFT_system_events extension. Implemented the AL_EXT_debug extension. Implemented the AL_EXT_direct_context extension. Implemented speaker configuration and headphones detection on CoreAudio. Fixed a potential crash with some extension functions on 32-bit Windows. Fixed a crash that can occur when stopping playback with the Oboe backend. Fixed calculating the reverb room rolloff. Fixed EAX occlusion, obstruction, and exclusion low-pass filter strength. Fixed EAX distance factor calculations. Fixed querying AL_EFFECTSLOT_EFFECT on auxiliary effect slots. Fixed compilation on some macOS systems that lack libdispatch. Fixed compilation as a subproject with MinGW. Changed the context error state to be thread-local. This is technically out of spec, but necessary to avoid race conditions with multi-threaded use. Split the cubic resampler into 4-point spline and gaussian variants. The latter prioritizing the suppression of aliasing distortion and harmonics, the former not reducing high frequencies as much. Improved timing precision of starting delayed sources. Improved ring modulator quality. Improved performance of convolution reverb. Improved WASAPI device enumeration performance. Added UWP support. Added 'noexcept' to functions and function types when compiled as C++. As a C API, OpenAL can't be expected to throw C++ exceptions, nor can it handle them if they leave a callback. Added an experimental config option for using WASAPI spatial audio output. Added enumeration support to the PortAudio backend. Added compatibility options to override the AL_VENDOR, AL_VERSION, and AL_RENDERER strings. Added an example to play LAF files. Disabled real-time mixing by default for PipeWire playback. Disabled the SndIO backend by default on non-BSD targets. openal-soft-1.23.1: Implemented the AL_SOFT_UHJ_ex extension. Implemented the AL_SOFT_buffer_length_query extension. Implemented the AL_SOFT_source_start_delay extension. Implemented the AL_EXT_STATIC_BUFFER extension. Fixed compiling with certain older versions of GCC. Fixed compiling as a submodule. Fixed compiling with newer versions of Oboe. Improved EAX effect version switching. Improved the quality of the reverb modulator. Improved performance of the cubic resampler. Added a compatibility option to restore AL_SOFT_buffer_sub_data. The option disables AL_EXT_SOURCE_RADIUS due to incompatibility. Reduced CPU usage when EAX is initialized and FXSlot0 or FXSlot1 are not used. Reduced memory usage for ADPCM buffer formats. They're no longer converted to 16-bit samples on load. openal-soft-1.23.0: Fixed CoreAudio capture support. Fixed handling per-version EAX properties. Fixed interpolating changes to the Super Stereo width source property. Fixed detection of the update and buffer size from PipeWire. Fixed resuming playback devices with OpenSL. Fixed support for certain OpenAL implementations with the router. Improved reverb environment transitions. Improved performance of convolution reverb. Improved quality and performance of the pitch shifter effect slightly. Improved sub-sample precision for resampled sources. Improved blending spatialized multi-channel sources that use the source radius property. Improved mixing 2D ambisonic sources for higher-order 3D ambisonic mixing. Improved quadraphonic and 7.1 surround sound output slightly. Added config options for UHJ encoding/decoding quality. Including Super Stereo processing. Added a config option for specifying the speaker distance. Added a compatibility config option for specifying the NFC distance scaling. Added a config option for mixing on PipeWire's non-real-time thread. Added support for virtual source nodes with PipeWire capture. Added the ability for the WASAPI backend to use different playback rates. Added support for SOFA files that define per-response delays in makemhr. Changed the default fallback playback sample rate to 48khz. This doesn't affect most backends, which can detect a default rate from the system. Changed the default resampler to cubic. Changed the default HRTF size from 32 to 64 points. openal-soft-1.22.2: Fixed PipeWire version check. Fixed building with PipeWire versions before 0.3.33. openal-soft-1.22.1: Fixed CoreAudio capture. Fixed air absorption strength. Fixed handling 5.1 devices on Windows that use Rear channels instead of Side channels. Fixed some compilation issues on MinGW. Fixed ALSA not being used on some systems without PipeWire and PulseAudio. Fixed OpenSL capturing noise. Fixed Oboe capture failing with some buffer sizes. Added checks for the runtime PipeWire version. The same or newer version than is used for building will be needed at runtime for the backend to work. Separated 3D7.1 into its own speaker configuration. openal-soft-1.22.0: Implemented the ALC_SOFT_reopen_device extension. This allows for moving devices to different outputs without losing object state. Implemented the ALC_SOFT_output_mode extension. Implemented the AL_SOFT_callback_buffer extension. Implemented the AL_SOFT_UHJ extension. This supports native UHJ buffer formats and Super Stereo processing. Implemented the legacy EAX extensions. Enabled by default only on Windows. Improved sound positioning stability when a source is near the listener. Improved the default 5.1 output decoder. Improved the high frequency response for the HRTF second-order ambisonic decoder. Improved SoundIO capture behavior. Fixed UHJ output on NEON-capable CPUs. Fixed redundant effect updates when setting an effect property to the current value. Fixed WASAPI capture using really low sample rates, and sources with very high pitch shifts when using a bsinc resampler. Added a PipeWire backend. Added enumeration for the JACK and CoreAudio backends. Added optional support for RTKit to get real-time priority. Only used as a backup when pthread_setschedparam fails. Added an option for JACK playback to render directly in the real-time processing callback. For lower playback latency, on by default. Added an option for custom JACK devices. Added utilities to encode and decode UHJ audio files. Files are decoded to the .amb format, and are encoded from libsndfile-compatible formats. Added an in-progress extension to hold sources in a playing state when a device disconnects. Allows devices to be reset or reopened and have sources resume from where they left off. Lowered the priority of the JACK backend. To avoid it getting picked when PipeWire is providing JACK compatibility, since the JACK backend is less robust with auto-configuration. openal-soft-1.21.1: Improved alext.h's detection of standard types. Improved slightly the local source position when the listener and source are near each other. Improved click/pop prevention for sounds that stop prematurely. Fixed compilation for Windows ARM targets with MSVC. Fixed ARM NEON detection on Windows. Fixed CoreAudio capture when the requested sample rate doesn't match the system configuration. Fixed OpenSL capture desyncing from the internal capture buffer. Fixed sources missing a batch update when applied after quickly restarting the source. Fixed missing source stop events when stopping a paused source. Added capture support to the experimental Oboe backend. openal-soft-1.21.0: Updated library codebase to C++14. Implemented the AL_SOFT_effect_target extension. Implemented the AL_SOFT_events extension. Implemented the ALC_SOFT_loopback_bformat extension. Improved memory use for mixing voices. Improved detection of NEON capabilities. Improved handling of PulseAudio devices that lack manual start control. Improved mixing performance with PulseAudio. Improved high-frequency scaling quality for the HRTF B-Format decoder. Improved makemhr's HRIR delay calculation. Improved WASAPI capture of mono formats with multichannel input. Reimplemented the modulation stage for reverb. Enabled real-time mixing priority by default, for backends that use the setting. It can still be disabled in the config file. Enabled dual-band processing for the built-in quad and 7.1 output decoders. Fixed a potential crash when deleting an effect slot immediately after the last source using it stops. Fixed building with the static runtime on MSVC. Fixed using source stereo angles outside of -pi...+pi. Fixed the buffer processed event count for sources that start with empty buffers. Fixed trying to open an unopenable WASAPI device causing all devices to stop working. Fixed stale devices when re-enumerating WASAPI devices. Fixed using unicode paths with the log file on Windows. Fixed DirectSound capture reporting bad sample counts or erroring when reading samples. Added an in-progress extension for a callback-driven buffer type. Added an in-progress extension for higher-order B-Format buffers. Added an in-progress extension for convolution reverb. Added an experimental Oboe backend for Android playback. This requires the Oboe sources at build time, so that it's built as a static library included in libopenal. Added an option for auto-connecting JACK ports. Added greater-than-stereo support to the SoundIO backend. Modified the mixer to be fully asynchronous with the external API, and should now be real-time safe. Although alcRenderSamplesSOFT is not due to locking to check the device handle validity. Modified the UHJ encoder to use an all-pass FIR filter that's less harmful to non-filtered signal phase. Converted examples from SDL_sound to libsndfile. To avoid issues when combining SDL2 and SDL_sound. Worked around a 32-bit GCC/MinGW bug with TLS destructors. See: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=83562 Reduced the maximum number of source sends from 16 to 6. Removed the QSA backend. It's been broken for who knows how long. Got rid of the compile-time native-tools targets, using cmake and global initialization instead. This should make cross-compiling less troublesome. openal-soft-1.20.1: Implemented the AL_SOFT_direct_channels_remix extension. This extends AL_DIRECT_CHANNELS_SOFT to optionally remix input channels that don't have a matching output channel. Implemented the AL_SOFT_bformat_ex extension. This extends B-Format buffer support for N3D or SN3D scaling, or ACN channel ordering. Fixed a potential voice leak when a source is started and stopped or restarted in quick succession. Fixed a potential device reset failure with JACK. Improved handling of unsupported channel configurations with WASAPI. Such setups will now try to output at least a stereo mix. Improved clarity a bit for the HRTF second-order ambisonic decoder. Improved detection of compatible layouts for SOFA files in makemhr and sofa-info. Added the ability to resample HRTFs on load. MHR files no longer need to match the device sample rate to be usable. Added an option to limit the HRTF's filter length. openal-soft-1.20.0: Converted the library codebase to C++11. A lot of hacks and custom structures have been replaced with standard or cleaner implementations. Partially implemented the Vocal Morpher effect. Fixed the bsinc SSE resamplers on non-GCC compilers. Fixed OpenSL capture. Fixed support for extended capture formats with OpenSL. Fixed handling of WASAPI not reporting a default device. Fixed performance problems relating to semaphores on macOS. Modified the bsinc12 resampler's transition band to better avoid aliasing noise. Modified alcResetDeviceSOFT to attempt recovery of disconnected devices. Modified the virtual speaker layout for HRTF B-Format decoding. Modified the PulseAudio backend to use a custom processing loop. Renamed the makehrtf utility to makemhr. Improved the efficiency of the bsinc resamplers when up-sampling. Improved the quality of the bsinc resamplers slightly. Improved the efficiency of the HRTF filters. Improved the HRTF B-Format decoder coefficient generation. Improved reverb feedback fading to be more consistent with pan fading. Improved handling of sources that end prematurely, avoiding loud clicks. Improved the performance of some reverb processing loops. Added fast_bsinc12 and 24 resamplers that improve efficiency at the cost of some quality. Notably, down-sampling has less smooth pitch ramping. Added support for SOFA input files with makemhr. Added a build option to use pre-built native tools. For cross-compiling, use with caution and ensure the native tools' binaries are kept up-to-date. Added an adjust-latency config option for the PulseAudio backend. Added basic support for multi-field HRTFs. Added an option for mixing first- or second-order B-Format with HRTF output. This can improve HRTF performance given a number of sources. Added an RC file for proper DLL version information. Disabled some old KDE workarounds by default. Specifically, PulseAudio streams can now be moved (KDE may try to move them after opening). openal-soft-1.19.1: Implemented capture support for the SoundIO backend. Fixed source buffer queues potentially not playing properly when a queue entry completes. Fixed possible unexpected failures when generating auxiliary effect slots. Fixed a crash with certain reverb or device settings. Fixed OpenSL capture. Improved output limiter response, better ensuring the sample amplitude is clamped for output. openal-soft-1.19.0: Implemented the ALC_SOFT_device_clock extension. Implemented the Pitch Shifter, Frequency Shifter, and Autowah effects. Fixed compiling on FreeBSD systems that use freebsd-lib 9.1. Fixed compiling on NetBSD. Fixed the reverb effect's density scale and panning parameters. Fixed use of the WASAPI backend with certain games, which caused odd COM initialization errors. Increased the number of virtual channels for decoding Ambisonics to HRTF output. Changed 32-bit x86 builds to use SSE2 math by default for performance. Build-time options are available to use just SSE1 or x87 instead. Replaced the 4-point Sinc resampler with a more efficient cubic resampler. Renamed the MMDevAPI backend to WASAPI. Added support for 24-bit, dual-ear HRTF data sets. The built-in data set has been updated to 24-bit. Added a 24- to 48-point band-limited Sinc resampler. Added an SDL2 playback backend. Disabled by default to avoid a dependency on SDL2. Improved the performance and quality of the Chorus and Flanger effects. Improved the efficiency of the band-limited Sinc resampler. Improved the Sinc resampler's transition band to avoid over-attenuating higher frequencies. Improved the performance of some filter operations. Improved the efficiency of object ID lookups. Improved the efficienty of internal voice/source synchronization. Improved AL call error logging with contextualized messages. Removed the reverb effect's modulation stage. Due to the lack of reference for its intended behavior and strength. openal-soft-1.18.2: Fixed resetting the FPU rounding mode after certain function calls on Windows. Fixed use of SSE intrinsics when building with Clang on Windows. Fixed a crash with the JACK backend when using JACK1. Fixed use of pthread_setnane_np on NetBSD. Fixed building on FreeBSD with an older freebsd-lib. OSS now links with libossaudio if found at build time (for NetBSD). openal-soft-1.18.1: Fixed an issue where resuming a source might not restart playing it. Fixed PulseAudio playback when the configured stream length is much less than the requested length. Fixed MMDevAPI capture with sample rates not matching the backing device. Fixed int32 output for the Wave Writer. Fixed enumeration of OSS devices that are missing device files. Added correct retrieval of the executable's path on FreeBSD. Added a config option to specify the dithering depth. Added a 5.1 decoder preset that excludes front-center output. openal-soft-1.18.0: Implemented the AL_EXT_STEREO_ANGLES and AL_EXT_SOURCE_RADIUS extensions. Implemented the AL_SOFT_gain_clamp_ex, AL_SOFT_source_resampler, AL_SOFT_source_spatialize, and ALC_SOFT_output_limiter extensions. Implemented 3D processing for some effects. Currently implemented for Reverb, Compressor, Equalizer, and Ring Modulator. Implemented 2-channel UHJ output encoding. This needs to be enabled with a config option to be used. Implemented dual-band processing for high-quality ambisonic decoding. Implemented distance-compensation for surround sound output. Implemented near-field emulation and compensation with ambisonic rendering. Currently only applies when using the high-quality ambisonic decoder or ambisonic output, with appropriate config options. Implemented an output limiter to reduce the amount of distortion from clipping. Implemented dithering for 8-bit and 16-bit output. Implemented a config option to select a preferred HRTF. Implemented a run-time check for NEON extensions using /proc/cpuinfo. Implemented experimental capture support for the OpenSL backend. Fixed building on compilers with NEON support but don't default to having NEON enabled. Fixed support for JACK on Windows. Fixed starting a source while alcSuspendContext is in effect. Fixed detection of headsets as headphones, with MMDevAPI. Added support for AmbDec config files, for custom ambisonic decoder configurations. Version 3 files only. Added backend-specific options to alsoft-config. Added first-, second-, and third-order ambisonic output formats. Currently only works with backends that don't rely on channel labels, like JACK, ALSA, and OSS. Added a build option to embed the default HRTFs into the lib. Added AmbDec presets to enable high-quality ambisonic decoding. Added an AmbDec preset for 3D7.1 speaker setups. Added documentation regarding Ambisonics, 3D7.1, AmbDec config files, and the provided ambdec presets. Added the ability for MMDevAPI to open devices given a Device ID or GUID string. Added an option to the example apps to open a specific device. Increased the maximum auxiliary send limit to 16 (up from 4). Requires requesting them with the ALC_MAX_AUXILIARY_SENDS context creation attribute. Increased the default auxiliary effect slot count to 64 (up from 4). Reduced the default period count to 3 (down from 4). Slightly improved automatic naming for enumerated HRTFs. Improved B-Format decoding with HRTF output. Improved internal property handling for better batching behavior. Improved performance of certain filter uses. Removed support for the AL_SOFT_buffer_samples and AL_SOFT_buffer_sub_data extensions. Due to conflicts with AL_EXT_SOURCE_RADIUS. openal-soft-1.17.2: Implemented device enumeration for OSSv4. Fixed building on OSX. Fixed building on non-Windows systems without POSIX-2008. Fixed Dedicated Dialog and Dedicated LFE effect output. Added a build option to override the share install dir. Added a build option to static-link libgcc for MinGW. openal-soft-1.17.1: Fixed building with JACK and without PulseAudio. Fixed building on FreeBSD. Fixed the ALSA backend's allow-resampler option. Fixed handling of inexact ALSA period counts. Altered device naming scheme on Windows backends to better match other drivers. Updated the CoreAudio backend to use the AudioComponent API. This clears up deprecation warnings for OSX 10.11, although requires OSX 10.6 or newer. openal-soft-1.17.0: Implemented a JACK playback backend. Implemented the AL_EXT_BFORMAT and AL_EXT_MULAW_BFORMAT extensions. Implemented the ALC_SOFT_HRTF extension. Implemented C, SSE3, and SSE4.1 based 4- and 8-point Sinc resamplers. Implemented a C and SSE based band-limited Sinc resampler. This does 12- to 24-point Sinc resampling, and performs anti-aliasing. Implemented B-Format output support for the wave file writer. This creates FuMa-style first-order Ambisonics wave files (AMB format). Implemented a stereo-mode config option for treating stereo modes as either speakers or headphones. Implemented per-device configuration options. Fixed handling of PulseAudio and MMDevAPI devices that have identical descriptions. Fixed a potential lockup when stopping playback of suspended PulseAudio devices. Fixed logging of Unicode characters on Windows. Fixed 5.1 surround sound channels. By default it will now use the side channels for the surround output. A configuration using rear channels is still available. Fixed the QSA backend potentially altering the capture format. Fixed detecting MMDevAPI's default device. Fixed returning the default capture device name. Fixed mixing property calculations when deferring context updates. Altered the behavior of alcSuspendContext and alcProcessContext to better match certain Windows drivers. Altered the panning algorithm, utilizing Ambisonics for better side and back positioning cues with surround sound output. Improved support for certain older Windows apps. Improved the alffplay example to support surround sound streams. Improved support for building as a sub-project. Added an HRTF playback example. Added a tone generator output test. Added a toolchain to help with cross-compiling to Android. openal-soft-1.16.0: Implemented EFX Chorus, Flanger, Distortion, Equalizer, and Compressor effects. Implemented high-pass and band-pass EFX filters. Implemented the high-pass filter for the EAXReverb effect. Implemented SSE2 and SSE4.1 linear resamplers. Implemented Neon-enhanced non-HRTF mixers. Implemented a QSA backend, for QNX. Implemented the ALC_SOFT_pause_device, AL_SOFT_deferred_updates, AL_SOFT_block_alignment, AL_SOFT_MSADPCM, and AL_SOFT_source_length extensions. Fixed resetting mmdevapi backend devices. Fixed clamping when converting 32-bit float samples to integer. Fixed modulation range in the Modulator effect. Several fixes for the OpenSL playback backend. Fixed device specifier names that have Unicode characters on Windows. Added support for filenames and paths with Unicode (UTF-8) characters on Windows. Added support for alsoft.conf config files found in XDG Base Directory Specification locations (XDG_CONFIG_DIRS and XDG_CONFIG_HOME, or their defaults) on non-Windows systems. Added a GUI configuration utility (requires Qt 4.8). Added support for environment variable expansion in config options (not keys or section names). Added an example that uses SDL2 and ffmpeg. Modified examples to use SDL_sound. Modified CMake config option names for better sorting. HRTF data sets specified in the hrtf_tables config option may now be relative or absolute filenames. Made the default HRTF data set an external file, and added a data set for 48khz playback in addition to 44.1khz. Added support for C11 atomic methods. Improved support for some non-GNU build systems. openal-soft-1.15.1: Fixed a regression with retrieving the source's AL_GAIN property. openal-soft-1.15: Fixed device enumeration with the OSS backend. Reorganized internal mixing logic, so unneeded steps can potentially be skipped for better performance. Removed the lookup table for calculating the mixing pans. The panning is now calculated directly for better precision. Improved the panning of stereo source channels when using stereo output. Improved source filter quality on send paths. Added a config option to allow PulseAudio to move streams between devices. The PulseAudio backend will now attempt to spawn a server by default. Added a workaround for a DirectSound bug relating to float32 output. Added SSE-based mixers, for HRTF and non-HRTF mixing. Added support for the new AL_SOFT_source_latency extension. Improved ALSA capture by avoiding an extra buffer when using sizes supported by the underlying device. Improved the makehrtf utility to support new options and input formats. Modified the CFLAGS declared in the pkg-config file so the "AL/" portion of the header includes can optionally be omitted. Added a couple example code programs to show how to apply reverb, and retrieve latency. The configuration sample is now installed into the share/openal/ directory instead of /etc/openal. The configuration sample now gets installed by default. openal-soft-1.24.2/LICENSE-pffft000066400000000000000000000032051474041540300161410ustar00rootroot00000000000000A modified PFFFT is included, with the following license. Copyright (c) 2023 Christopher Robinson Copyright (c) 2013 Julien Pommier ( pommier@modartt.com ) Copyright (c) 2004 the University Corporation for Atmospheric Research ("UCAR"). All rights reserved. Developed by NCAR's Computational and Information Systems Laboratory, UCAR, www.cisl.ucar.edu. Redistribution and use of the Software in source and binary forms, with or without modification, is permitted provided that the following conditions are met: - Neither the names of NCAR's Computational and Information Systems Laboratory, the University Corporation for Atmospheric Research, nor the names of its sponsors or contributors may be used to endorse or promote products derived from this Software without specific prior written permission. - Redistributions of source code must retain the above copyright notices, this list of conditions, and the disclaimer below. - Redistributions in binary form must reproduce the above copyright notice, this list of conditions, and the disclaimer below in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE SOFTWARE. openal-soft-1.24.2/OpenALConfig.cmake.in000066400000000000000000000006101474041540300177010ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.1...3.18) include("${CMAKE_CURRENT_LIST_DIR}/OpenALTargets.cmake") set(OPENAL_FOUND ON) set(OPENAL_INCLUDE_DIR $) set(OPENAL_LIBRARY $) set(OPENAL_DEFINITIONS $) set(OPENAL_VERSION_STRING @PACKAGE_VERSION@) openal-soft-1.24.2/README.md000066400000000000000000000123561474041540300153170ustar00rootroot00000000000000OpenAL Soft =========== `master` branch CI status : [![GitHub Actions Status](https://github.com/kcat/openal-soft/actions/workflows/ci.yml/badge.svg)](https://github.com/kcat/openal-soft/actions) [![Windows Build Status](https://ci.appveyor.com/api/projects/status/github/kcat/openal-soft?branch=master&svg=true)](https://ci.appveyor.com/api/projects/status/github/kcat/openal-soft?branch=master&svg=true) OpenAL Soft is an LGPL-licensed, cross-platform, software implementation of the OpenAL 3D audio API. It's forked from the open-sourced Windows version available originally from openal.org's SVN repository (now defunct). OpenAL provides capabilities for playing audio in a virtual 3D environment. Distance attenuation, doppler shift, and directional sound emitters are among the features handled by the API. More advanced effects, including air absorption, occlusion, and environmental reverb, are available through the EFX extension. It also facilitates streaming audio, multi-channel buffers, and audio capture. More information is available on the [official website](http://openal-soft.org/). Source Install ------------- To install OpenAL Soft, use your favorite shell to go into the build/ directory, and run: ```bash cmake .. ``` Alternatively, you can use any available CMake front-end, like cmake-gui, ccmake, or your preferred IDE's CMake project parser. Assuming configuration went well, you can then build it. The command `cmake --build .` will instruct CMake to build the project with the toolchain chosen during configuration (often GNU Make or NMake, although others are possible). Please Note: Double check that the appropriate backends were detected. Often, complaints of no sound, crashing, and missing devices can be solved by making sure the correct backends are being used. CMake's output will identify which backends were enabled. For most systems, you will likely want to make sure PipeWire, PulseAudio, and ALSA were detected (if your target system uses them). For Windows, make sure WASAPI was detected. Building openal-soft - Using vcpkg ---------------------------------- You can download and install openal-soft using the [vcpkg](https://github.com/Microsoft/vcpkg) dependency manager: git clone https://github.com/Microsoft/vcpkg.git cd vcpkg ./bootstrap-vcpkg.sh ./vcpkg integrate install ./vcpkg install openal-soft The openal-soft port in vcpkg is kept up to date by Microsoft team members and community contributors. If the version is out of date, please [create an issue or pull request](https://github.com/Microsoft/vcpkg) on the vcpkg repository. Utilities --------- The source package comes with an informational utility, openal-info, and is built by default. It prints out information provided by the ALC and AL sub- systems, including discovered devices, version information, and extensions. Configuration ------------- OpenAL Soft can be configured on a per-user and per-system basis. This allows users and sysadmins to control information provided to applications, as well as application-agnostic behavior of the library. See alsoftrc.sample for available settings. Language Bindings ----------------- As a C API, OpenAL Soft can be used directly by any language that can use functions with C linkage. For languages that can't directly use C-style headers, bindings may be developed to allow code written in that language to call into the library. Some bindings for some languages are listed here. C# Bindings: * [OpenTK](https://opentk.net/) includes low-level C# bindings for the OpenAL API, including some extensions. It also includes utility libraries for math and linear algebra, which can be useful for 3D calculations. Java Bindings: * [LWJGL](https://github.com/LWJGL/lwjgl3), the Lightweight Java Game Library, includes Java bindings for the OpenAL API, usable with OpenAL Soft. * [JOAL](https://jogamp.org/joal/www/), part of the JogAmp project, includes Java bindings for the OpenAL API, usable with OpenAL Soft. It also includes a higher level Sound3D Toolkit API and utility functions to make easier use of OpenAL features and capabilities. Kotlin Bindings: * [Multiplatform OpenAL](https://git.karmakrafts.dev/kk/multiplatform-openal), developed for the Kleaver project, includes Kotlin/Native bindings for the OpenAL API, based on OpenAL Soft with support for Windows, Linux, macOS, iOS and Android. Python Bindings: * [PyOpenAL](https://pypi.org/project/PyOpenAL/). Also includes methods to play wave files and, with PyOgg, also Vorbis, Opus, and FLAC. FreePascal/Lazarus Bindings: * [ALSound](https://github.com/Lulu04/ALSound). Also includes a higher level API and libsndfile support to simplify loading and playing sounds. Other bindings for these and other languages also exist. This list will grow as more bindings are found. Acknowledgements ---------------- Special thanks go to: - Creative Labs for the original source code this is based off of. - Christopher Fitzgerald for the current reverb effect implementation, and helping with the low-pass and HRTF filters. - Christian Borss for the 3D panning code previous versions used as a base. - Ben Davis for the idea behind a previous version of the click-removal code. - Richard Furse for helping with my understanding of Ambisonics that is used by the various parts of the library. openal-soft-1.24.2/XCompile-Android.txt000066400000000000000000000013051474041540300176670ustar00rootroot00000000000000# Cross-compiling for Android is handled by the NDK's own provided toolchain, # which as of this writing, should be in # ${ndk_root}/build/cmake/android.toolchain.cmake # # Certain older NDK versions may also need to explicitly pick the libc++ # runtime. So for example: # cmake .. -DANDROID_STL=c++_shared \ # -DCMAKE_TOOLCHAIN_FILE=${ndk_root}/build/cmake/android.toolchain.cmake # # Certain NDK versions may also need to use the lld linker to avoid errors # about missing liblog.so and libOpenSLES.so. That can be done by: # cmake .. -DANDROID_LD=lld \ # -DCMAKE_TOOLCHAIN_FILE=${ndk_root}/build/cmake/android.toolchain.cmake # MESSAGE(FATAL_ERROR "Use the toolchain provided by the Android NDK") openal-soft-1.24.2/XCompile.txt000066400000000000000000000027541474041540300163220ustar00rootroot00000000000000# Cross-compiling requires CMake 2.6 or newer. Example: # cmake .. -DCMAKE_TOOLCHAIN_FILE=../XCompile.txt -DHOST=i686-w64-mingw32 # Where 'i686-w64-mingw32' is the host prefix for your cross-compiler. If you # already have a toolchain file setup, you may use that instead of this file. # the name of the target operating system SET(CMAKE_SYSTEM_NAME Windows) # which compilers to use for C and C++ SET(CMAKE_C_COMPILER "${HOST}-gcc") SET(CMAKE_CXX_COMPILER "${HOST}-g++") SET(CMAKE_RC_COMPILER "${HOST}-windres") # here is the target environment located SET(CMAKE_FIND_ROOT_PATH "/usr/${HOST}") # here is where stuff gets installed to SET(CMAKE_INSTALL_PREFIX "${CMAKE_FIND_ROOT_PATH}" CACHE STRING "Install path prefix, prepended onto install directories." FORCE) # adjust the default behaviour of the FIND_XXX() commands: # search headers and libraries in the target environment, search # programs in the host environment set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) # set env vars so that pkg-config will look in the appropriate directory for # .pc files (as there seems to be no way to force using ${HOST}-pkg-config) set(ENV{PKG_CONFIG_LIBDIR} "${CMAKE_INSTALL_PREFIX}/lib/pkgconfig") set(ENV{PKG_CONFIG_PATH} "") # Qt4 tools SET(QT_QMAKE_EXECUTABLE ${HOST}-qmake) SET(QT_MOC_EXECUTABLE ${HOST}-moc) SET(QT_RCC_EXECUTABLE ${HOST}-rcc) SET(QT_UIC_EXECUTABLE ${HOST}-uic) SET(QT_LRELEASE_EXECUTABLE ${HOST}-lrelease) openal-soft-1.24.2/al/000077500000000000000000000000001474041540300144255ustar00rootroot00000000000000openal-soft-1.24.2/al/auxeffectslot.cpp000066400000000000000000001442141474041540300200130ustar00rootroot00000000000000/** * OpenAL cross platform audio library * Copyright (C) 1999-2007 by authors. * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * Or go to http://www.gnu.org/copyleft/lgpl.html */ #include "config.h" #include "auxeffectslot.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "AL/al.h" #include "AL/alc.h" #include "AL/alext.h" #include "AL/efx.h" #include "albit.h" #include "alc/alu.h" #include "alc/context.h" #include "alc/device.h" #include "alc/effects/base.h" #include "alc/inprogext.h" #include "almalloc.h" #include "alnumeric.h" #include "alspan.h" #include "atomic.h" #include "buffer.h" #include "core/buffer_storage.h" #include "core/device.h" #include "core/except.h" #include "core/fpu_ctrl.h" #include "core/logging.h" #include "direct_defs.h" #include "effect.h" #include "flexarray.h" #include "opthelpers.h" #if ALSOFT_EAX #include "eax/api.h" #include "eax/call.h" #include "eax/effect.h" #include "eax/fx_slot_index.h" #include "eax/utils.h" #endif namespace { using SubListAllocator = al::allocator>; [[nodiscard]] auto getFactoryByType(EffectSlotType type) -> EffectStateFactory* { switch(type) { case EffectSlotType::None: return NullStateFactory_getFactory(); case EffectSlotType::Reverb: return ReverbStateFactory_getFactory(); case EffectSlotType::Chorus: return ChorusStateFactory_getFactory(); case EffectSlotType::Autowah: return AutowahStateFactory_getFactory(); case EffectSlotType::Compressor: return CompressorStateFactory_getFactory(); case EffectSlotType::Convolution: return ConvolutionStateFactory_getFactory(); case EffectSlotType::Dedicated: return DedicatedStateFactory_getFactory(); case EffectSlotType::Distortion: return DistortionStateFactory_getFactory(); case EffectSlotType::Echo: return EchoStateFactory_getFactory(); case EffectSlotType::Equalizer: return EqualizerStateFactory_getFactory(); case EffectSlotType::Flanger: return ChorusStateFactory_getFactory(); case EffectSlotType::FrequencyShifter: return FshifterStateFactory_getFactory(); case EffectSlotType::RingModulator: return ModulatorStateFactory_getFactory(); case EffectSlotType::PitchShifter: return PshifterStateFactory_getFactory(); case EffectSlotType::VocalMorpher: return VmorpherStateFactory_getFactory(); } return nullptr; } [[nodiscard]] auto LookupEffectSlot(ALCcontext *context, ALuint id) noexcept -> ALeffectslot* { const size_t lidx{(id-1) >> 6}; const ALuint slidx{(id-1) & 0x3f}; if(lidx >= context->mEffectSlotList.size()) UNLIKELY return nullptr; EffectSlotSubList &sublist{context->mEffectSlotList[lidx]}; if(sublist.FreeMask & (1_u64 << slidx)) UNLIKELY return nullptr; return al::to_address(sublist.EffectSlots->begin() + slidx); } [[nodiscard]] inline auto LookupEffect(al::Device *device, ALuint id) noexcept -> ALeffect* { const size_t lidx{(id-1) >> 6}; const ALuint slidx{(id-1) & 0x3f}; if(lidx >= device->EffectList.size()) UNLIKELY return nullptr; EffectSubList &sublist = device->EffectList[lidx]; if(sublist.FreeMask & (1_u64 << slidx)) UNLIKELY return nullptr; return al::to_address(sublist.Effects->begin() + slidx); } [[nodiscard]] inline auto LookupBuffer(al::Device *device, ALuint id) noexcept -> ALbuffer* { const size_t lidx{(id-1) >> 6}; const ALuint slidx{(id-1) & 0x3f}; if(lidx >= device->BufferList.size()) UNLIKELY return nullptr; BufferSubList &sublist = device->BufferList[lidx]; if(sublist.FreeMask & (1_u64 << slidx)) UNLIKELY return nullptr; return al::to_address(sublist.Buffers->begin() + slidx); } void AddActiveEffectSlots(const al::span auxslots, ALCcontext *context) { if(auxslots.empty()) return; EffectSlotArray *curarray{context->mActiveAuxSlots.load(std::memory_order_acquire)}; if((curarray->size()>>1) > std::numeric_limits::max()-auxslots.size()) throw std::runtime_error{"Too many active effect slots"}; size_t newcount{(curarray->size()>>1) + auxslots.size()}; if(newcount > std::numeric_limits::max()>>1) throw std::runtime_error{"Too many active effect slots"}; /* Insert the new effect slots into the head of the new array, followed by * the existing ones. */ auto newarray = EffectSlot::CreatePtrArray(newcount<<1); auto new_end = std::transform(auxslots.begin(), auxslots.end(), newarray->begin(), std::mem_fn(&ALeffectslot::mSlot)); new_end = std::copy_n(curarray->begin(), curarray->size()>>1, new_end); /* Remove any duplicates (first instance of each will be kept). */ for(auto start=newarray->begin()+1;;) { new_end = std::remove(start, new_end, *(start-1)); if(start == new_end) break; ++start; } newcount = static_cast(std::distance(newarray->begin(), new_end)); /* Reallocate newarray if the new size ended up smaller from duplicate * removal. */ if(newcount < newarray->size()>>1) UNLIKELY { auto oldarray = std::move(newarray); newarray = EffectSlot::CreatePtrArray(newcount<<1); new_end = std::copy_n(oldarray->begin(), newcount, newarray->begin()); } std::fill(new_end, newarray->end(), nullptr); auto oldarray = context->mActiveAuxSlots.exchange(std::move(newarray), std::memory_order_acq_rel); std::ignore = context->mDevice->waitForMix(); } void RemoveActiveEffectSlots(const al::span auxslots, ALCcontext *context) { if(auxslots.empty()) return; EffectSlotArray *curarray{context->mActiveAuxSlots.load(std::memory_order_acquire)}; /* Don't shrink the allocated array size since we don't know how many (if * any) of the effect slots to remove are in the array. */ auto newarray = EffectSlot::CreatePtrArray(curarray->size()); auto new_end = std::copy_n(curarray->begin(), curarray->size()>>1, newarray->begin()); /* Remove elements from newarray that match any ID in slotids. */ for(const ALeffectslot *auxslot : auxslots) { auto slot_match = [auxslot](EffectSlot *slot) noexcept -> bool { return (slot == auxslot->mSlot); }; new_end = std::remove_if(newarray->begin(), new_end, slot_match); } /* Reallocate with the new size. */ auto newsize = static_cast(std::distance(newarray->begin(), new_end)); if(newsize < newarray->size()>>1) LIKELY { auto oldarray = std::move(newarray); newarray = EffectSlot::CreatePtrArray(newsize<<1); new_end = std::copy_n(oldarray->begin(), newsize, newarray->begin()); } std::fill(new_end, newarray->end(), nullptr); auto oldarray = context->mActiveAuxSlots.exchange(std::move(newarray), std::memory_order_acq_rel); std::ignore = context->mDevice->waitForMix(); } [[nodiscard]] constexpr auto EffectSlotTypeFromEnum(ALenum type) noexcept -> EffectSlotType { switch(type) { case AL_EFFECT_NULL: return EffectSlotType::None; case AL_EFFECT_REVERB: return EffectSlotType::Reverb; case AL_EFFECT_CHORUS: return EffectSlotType::Chorus; case AL_EFFECT_DISTORTION: return EffectSlotType::Distortion; case AL_EFFECT_ECHO: return EffectSlotType::Echo; case AL_EFFECT_FLANGER: return EffectSlotType::Flanger; case AL_EFFECT_FREQUENCY_SHIFTER: return EffectSlotType::FrequencyShifter; case AL_EFFECT_VOCAL_MORPHER: return EffectSlotType::VocalMorpher; case AL_EFFECT_PITCH_SHIFTER: return EffectSlotType::PitchShifter; case AL_EFFECT_RING_MODULATOR: return EffectSlotType::RingModulator; case AL_EFFECT_AUTOWAH: return EffectSlotType::Autowah; case AL_EFFECT_COMPRESSOR: return EffectSlotType::Compressor; case AL_EFFECT_EQUALIZER: return EffectSlotType::Equalizer; case AL_EFFECT_EAXREVERB: return EffectSlotType::Reverb; case AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT: return EffectSlotType::Dedicated; case AL_EFFECT_DEDICATED_DIALOGUE: return EffectSlotType::Dedicated; case AL_EFFECT_CONVOLUTION_SOFT: return EffectSlotType::Convolution; } ERR("Unhandled effect enum: {:#04x}", as_unsigned(type)); return EffectSlotType::None; } [[nodiscard]] auto EnsureEffectSlots(ALCcontext *context, size_t needed) noexcept -> bool try { size_t count{std::accumulate(context->mEffectSlotList.cbegin(), context->mEffectSlotList.cend(), 0_uz, [](size_t cur, const EffectSlotSubList &sublist) noexcept -> size_t { return cur + static_cast(al::popcount(sublist.FreeMask)); })}; while(needed > count) { if(context->mEffectSlotList.size() >= 1<<25) UNLIKELY return false; EffectSlotSubList sublist{}; sublist.FreeMask = ~0_u64; sublist.EffectSlots = SubListAllocator{}.allocate(1); context->mEffectSlotList.emplace_back(std::move(sublist)); count += std::tuple_size_v; } return true; } catch(...) { return false; } [[nodiscard]] auto AllocEffectSlot(ALCcontext *context) -> ALeffectslot* { auto sublist = std::find_if(context->mEffectSlotList.begin(), context->mEffectSlotList.end(), [](const EffectSlotSubList &entry) noexcept -> bool { return entry.FreeMask != 0; }); auto lidx = static_cast(std::distance(context->mEffectSlotList.begin(), sublist)); auto slidx = static_cast(al::countr_zero(sublist->FreeMask)); ASSUME(slidx < 64); ALeffectslot *slot{al::construct_at(al::to_address(sublist->EffectSlots->begin() + slidx), context)}; aluInitEffectPanning(slot->mSlot, context); /* Add 1 to avoid ID 0. */ slot->id = ((lidx<<6) | slidx) + 1; context->mNumEffectSlots += 1; sublist->FreeMask &= ~(1_u64 << slidx); return slot; } void FreeEffectSlot(ALCcontext *context, ALeffectslot *slot) { context->mEffectSlotNames.erase(slot->id); const ALuint id{slot->id - 1}; const size_t lidx{id >> 6}; const ALuint slidx{id & 0x3f}; std::destroy_at(slot); context->mEffectSlotList[lidx].FreeMask |= 1_u64 << slidx; context->mNumEffectSlots--; } inline void UpdateProps(ALeffectslot *slot, ALCcontext *context) { if(!context->mDeferUpdates && slot->mState == SlotState::Playing) { slot->updateProps(context); return; } slot->mPropsDirty = true; } } // namespace AL_API DECL_FUNC2(void, alGenAuxiliaryEffectSlots, ALsizei,n, ALuint*,effectslots) FORCE_ALIGN void AL_APIENTRY alGenAuxiliaryEffectSlotsDirect(ALCcontext *context, ALsizei n, ALuint *effectslots) noexcept try { if(n < 0) context->throw_error(AL_INVALID_VALUE, "Generating {} effect slots", n); if(n <= 0) UNLIKELY return; auto slotlock = std::lock_guard{context->mEffectSlotLock}; auto *device = context->mALDevice.get(); const al::span eids{effectslots, static_cast(n)}; if(context->mNumEffectSlots > device->AuxiliaryEffectSlotMax || eids.size() > device->AuxiliaryEffectSlotMax-context->mNumEffectSlots) context->throw_error(AL_OUT_OF_MEMORY, "Exceeding {} effect slot limit ({} + {})", device->AuxiliaryEffectSlotMax, context->mNumEffectSlots, n); if(!EnsureEffectSlots(context, eids.size())) context->throw_error(AL_OUT_OF_MEMORY, "Failed to allocate {} effectslot{}", n, (n==1) ? "" : "s"); std::vector slots; try { if(eids.size() == 1) { /* Special handling for the easy and normal case. */ eids[0] = AllocEffectSlot(context)->id; } else { slots.reserve(eids.size()); std::generate_n(std::back_inserter(slots), eids.size(), [context]{ return AllocEffectSlot(context); }); std::transform(slots.cbegin(), slots.cend(), eids.begin(), [](ALeffectslot *slot) -> ALuint { return slot->id; }); } } catch(std::exception& e) { ERR("Exception allocating effectslot {} of {}: {}", slots.size()+1, n, e.what()); auto delete_effectslot = [context](ALeffectslot *slot) -> void { FreeEffectSlot(context, slot); }; std::for_each(slots.begin(), slots.end(), delete_effectslot); context->throw_error(AL_INVALID_OPERATION, "Exception allocating {} effectslots: {}", n, e.what()); } } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC2(void, alDeleteAuxiliaryEffectSlots, ALsizei,n, const ALuint*,effectslots) FORCE_ALIGN void AL_APIENTRY alDeleteAuxiliaryEffectSlotsDirect(ALCcontext *context, ALsizei n, const ALuint *effectslots) noexcept try { if(n < 0) UNLIKELY context->throw_error(AL_INVALID_VALUE, "Deleting {} effect slots", n); if(n <= 0) UNLIKELY return; std::lock_guard slotlock{context->mEffectSlotLock}; if(n == 1) { ALeffectslot *slot{LookupEffectSlot(context, *effectslots)}; if(!slot) context->throw_error(AL_INVALID_NAME, "Invalid effect slot ID {}", *effectslots); if(slot->ref.load(std::memory_order_relaxed) != 0) context->throw_error(AL_INVALID_OPERATION, "Deleting in-use effect slot {}", *effectslots); RemoveActiveEffectSlots({&slot, 1u}, context); FreeEffectSlot(context, slot); } else { const al::span eids{effectslots, static_cast(n)}; std::vector slots; slots.reserve(eids.size()); auto lookupslot = [context](const ALuint eid) -> ALeffectslot* { ALeffectslot *slot{LookupEffectSlot(context, eid)}; if(!slot) context->throw_error(AL_INVALID_NAME, "Invalid effect slot ID {}", eid); if(slot->ref.load(std::memory_order_relaxed) != 0) context->throw_error(AL_INVALID_OPERATION, "Deleting in-use effect slot {}", eid); return slot; }; std::transform(eids.cbegin(), eids.cend(), std::back_inserter(slots), lookupslot); /* All effectslots are valid, remove and delete them */ RemoveActiveEffectSlots(slots, context); auto delete_effectslot = [context](const ALuint eid) -> void { if(ALeffectslot *slot{LookupEffectSlot(context, eid)}) FreeEffectSlot(context, slot); }; std::for_each(eids.begin(), eids.end(), delete_effectslot); } } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC1(ALboolean, alIsAuxiliaryEffectSlot, ALuint,effectslot) FORCE_ALIGN ALboolean AL_APIENTRY alIsAuxiliaryEffectSlotDirect(ALCcontext *context, ALuint effectslot) noexcept { std::lock_guard slotlock{context->mEffectSlotLock}; if(LookupEffectSlot(context, effectslot) != nullptr) return AL_TRUE; return AL_FALSE; } AL_API void AL_APIENTRY alAuxiliaryEffectSlotPlaySOFT(ALuint) noexcept { ContextRef context{GetContextRef()}; if(!context) UNLIKELY return; context->setError(AL_INVALID_OPERATION, "alAuxiliaryEffectSlotPlaySOFT not supported"); } AL_API void AL_APIENTRY alAuxiliaryEffectSlotPlayvSOFT(ALsizei, const ALuint*) noexcept { ContextRef context{GetContextRef()}; if(!context) UNLIKELY return; context->setError(AL_INVALID_OPERATION, "alAuxiliaryEffectSlotPlayvSOFT not supported"); } AL_API void AL_APIENTRY alAuxiliaryEffectSlotStopSOFT(ALuint) noexcept { ContextRef context{GetContextRef()}; if(!context) UNLIKELY return; context->setError(AL_INVALID_OPERATION, "alAuxiliaryEffectSlotStopSOFT not supported"); } AL_API void AL_APIENTRY alAuxiliaryEffectSlotStopvSOFT(ALsizei, const ALuint*) noexcept { ContextRef context{GetContextRef()}; if(!context) UNLIKELY return; context->setError(AL_INVALID_OPERATION, "alAuxiliaryEffectSlotStopvSOFT not supported"); } AL_API DECL_FUNC3(void, alAuxiliaryEffectSloti, ALuint,effectslot, ALenum,param, ALint,value) FORCE_ALIGN void AL_APIENTRY alAuxiliaryEffectSlotiDirect(ALCcontext *context, ALuint effectslot, ALenum param, ALint value) noexcept try { std::lock_guard proplock{context->mPropLock}; std::lock_guard slotlock{context->mEffectSlotLock}; ALeffectslot *slot{LookupEffectSlot(context, effectslot)}; if(!slot) UNLIKELY context->throw_error(AL_INVALID_NAME, "Invalid effect slot ID {}", effectslot); ALeffectslot *target{}; ALenum err{}; switch(param) { case AL_EFFECTSLOT_EFFECT: { auto *device = context->mALDevice.get(); auto effectlock = std::lock_guard{device->EffectLock}; auto *effect = value ? LookupEffect(device, static_cast(value)) : nullptr; if(effect) err = slot->initEffect(effect->id, effect->type, effect->Props, context); else { if(value != 0) context->throw_error(AL_INVALID_VALUE, "Invalid effect ID {}", value); err = slot->initEffect(0, AL_EFFECT_NULL, EffectProps{}, context); } } if(err != AL_NO_ERROR) context->throw_error(err, "Effect initialization failed"); if(slot->mState == SlotState::Initial) UNLIKELY { slot->mPropsDirty = false; slot->updateProps(context); AddActiveEffectSlots({&slot, 1}, context); slot->mState = SlotState::Playing; return; } UpdateProps(slot, context); return; case AL_EFFECTSLOT_AUXILIARY_SEND_AUTO: if(!(value == AL_TRUE || value == AL_FALSE)) context->throw_error(AL_INVALID_VALUE, "Effect slot auxiliary send auto out of range"); if(!(slot->AuxSendAuto == !!value)) LIKELY { slot->AuxSendAuto = !!value; UpdateProps(slot, context); } return; case AL_EFFECTSLOT_TARGET_SOFT: target = LookupEffectSlot(context, static_cast(value)); if(value && !target) context->throw_error(AL_INVALID_VALUE, "Invalid effect slot target ID {}", value); if(slot->Target == target) UNLIKELY return; if(target) { ALeffectslot *checker{target}; while(checker && checker != slot) checker = checker->Target; if(checker) context->throw_error(AL_INVALID_OPERATION, "Setting target of effect slot ID {} to {} creates circular chain", slot->id, target->id); } if(ALeffectslot *oldtarget{slot->Target}) { /* We must force an update if there was an existing effect slot * target, in case it's about to be deleted. */ if(target) IncrementRef(target->ref); DecrementRef(oldtarget->ref); slot->Target = target; slot->updateProps(context); return; } if(target) IncrementRef(target->ref); slot->Target = target; UpdateProps(slot, context); return; case AL_BUFFER: if(ALbuffer *buffer{slot->Buffer}) { if(buffer->id == static_cast(value)) return; } else if(value == 0) return; if(slot->mState == SlotState::Playing) { EffectStateFactory *factory{getFactoryByType(slot->Effect.Type)}; assert(factory); al::intrusive_ptr state{factory->create()}; auto *device = context->mALDevice.get(); auto bufferlock = std::unique_lock{device->BufferLock}; ALbuffer *buffer{}; if(value) { buffer = LookupBuffer(device, static_cast(value)); if(!buffer) context->throw_error(AL_INVALID_VALUE, "Invalid buffer ID {}", value); if(buffer->mCallback) context->throw_error(AL_INVALID_OPERATION, "Callback buffer not valid for effects"); IncrementRef(buffer->ref); } /* Stop the effect slot from processing while we switch buffers. */ RemoveActiveEffectSlots({&slot, 1}, context); if(ALbuffer *oldbuffer{slot->Buffer}) DecrementRef(oldbuffer->ref); slot->Buffer = buffer; bufferlock.unlock(); state->mOutTarget = device->Dry.Buffer; { FPUCtl mixer_mode{}; state->deviceUpdate(device, buffer); } slot->Effect.State = std::move(state); slot->mPropsDirty = false; slot->updateProps(context); AddActiveEffectSlots({&slot, 1}, context); } else { auto *device = context->mALDevice.get(); auto bufferlock = std::unique_lock{device->BufferLock}; ALbuffer *buffer{}; if(value) { buffer = LookupBuffer(device, static_cast(value)); if(!buffer) context->throw_error(AL_INVALID_VALUE, "Invalid buffer ID {}", value); if(buffer->mCallback) context->throw_error(AL_INVALID_OPERATION, "Callback buffer not valid for effects"); IncrementRef(buffer->ref); } if(ALbuffer *oldbuffer{slot->Buffer}) DecrementRef(oldbuffer->ref); slot->Buffer = buffer; bufferlock.unlock(); FPUCtl mixer_mode{}; auto *state = slot->Effect.State.get(); state->deviceUpdate(device, buffer); slot->mPropsDirty = true; } return; case AL_EFFECTSLOT_STATE_SOFT: context->throw_error(AL_INVALID_OPERATION, "AL_EFFECTSLOT_STATE_SOFT is read-only"); } context->throw_error(AL_INVALID_ENUM, "Invalid effect slot integer property {:#04x}", as_unsigned(param)); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alAuxiliaryEffectSlotiv, ALuint,effectslot, ALenum,param, const ALint*,values) FORCE_ALIGN void AL_APIENTRY alAuxiliaryEffectSlotivDirect(ALCcontext *context, ALuint effectslot, ALenum param, const ALint *values) noexcept try { switch(param) { case AL_EFFECTSLOT_EFFECT: case AL_EFFECTSLOT_AUXILIARY_SEND_AUTO: case AL_EFFECTSLOT_TARGET_SOFT: case AL_EFFECTSLOT_STATE_SOFT: case AL_BUFFER: alAuxiliaryEffectSlotiDirect(context, effectslot, param, *values); return; } std::lock_guard slotlock{context->mEffectSlotLock}; ALeffectslot *slot{LookupEffectSlot(context, effectslot)}; if(!slot) context->throw_error(AL_INVALID_NAME, "Invalid effect slot ID {}", effectslot); context->throw_error(AL_INVALID_ENUM, "Invalid effect slot integer-vector property {:#04x}", as_unsigned(param)); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alAuxiliaryEffectSlotf, ALuint,effectslot, ALenum,param, ALfloat,value) FORCE_ALIGN void AL_APIENTRY alAuxiliaryEffectSlotfDirect(ALCcontext *context, ALuint effectslot, ALenum param, ALfloat value) noexcept try { std::lock_guard proplock{context->mPropLock}; std::lock_guard slotlock{context->mEffectSlotLock}; ALeffectslot *slot{LookupEffectSlot(context, effectslot)}; if(!slot) context->throw_error(AL_INVALID_NAME, "Invalid effect slot ID {}", effectslot); switch(param) { case AL_EFFECTSLOT_GAIN: if(!(value >= 0.0f && value <= 1.0f)) context->throw_error(AL_INVALID_VALUE, "Effect slot gain {} out of range", value); if(!(slot->Gain == value)) LIKELY { slot->Gain = value; UpdateProps(slot, context); } return; } context->throw_error(AL_INVALID_ENUM, "Invalid effect slot float property {:#04x}", as_unsigned(param)); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alAuxiliaryEffectSlotfv, ALuint,effectslot, ALenum,param, const ALfloat*,values) FORCE_ALIGN void AL_APIENTRY alAuxiliaryEffectSlotfvDirect(ALCcontext *context, ALuint effectslot, ALenum param, const ALfloat *values) noexcept try { switch(param) { case AL_EFFECTSLOT_GAIN: alAuxiliaryEffectSlotfDirect(context, effectslot, param, *values); return; } std::lock_guard slotlock{context->mEffectSlotLock}; ALeffectslot *slot{LookupEffectSlot(context, effectslot)}; if(!slot) context->throw_error(AL_INVALID_NAME, "Invalid effect slot ID {}", effectslot); context->throw_error(AL_INVALID_ENUM, "Invalid effect slot float-vector property {:#04x}", as_unsigned(param)); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alGetAuxiliaryEffectSloti, ALuint,effectslot, ALenum,param, ALint*,value) FORCE_ALIGN void AL_APIENTRY alGetAuxiliaryEffectSlotiDirect(ALCcontext *context, ALuint effectslot, ALenum param, ALint *value) noexcept try { std::lock_guard slotlock{context->mEffectSlotLock}; ALeffectslot *slot{LookupEffectSlot(context, effectslot)}; if(!slot) context->throw_error(AL_INVALID_NAME, "Invalid effect slot ID {}", effectslot); switch(param) { case AL_EFFECTSLOT_EFFECT: *value = static_cast(slot->EffectId); return; case AL_EFFECTSLOT_AUXILIARY_SEND_AUTO: *value = slot->AuxSendAuto ? AL_TRUE : AL_FALSE; return; case AL_EFFECTSLOT_TARGET_SOFT: if(auto *target = slot->Target) *value = static_cast(target->id); else *value = 0; return; case AL_EFFECTSLOT_STATE_SOFT: *value = static_cast(slot->mState); return; case AL_BUFFER: if(auto *buffer = slot->Buffer) *value = static_cast(buffer->id); else *value = 0; return; } context->throw_error(AL_INVALID_ENUM, "Invalid effect slot integer property {:#04x}", as_unsigned(param)); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alGetAuxiliaryEffectSlotiv, ALuint,effectslot, ALenum,param, ALint*,values) FORCE_ALIGN void AL_APIENTRY alGetAuxiliaryEffectSlotivDirect(ALCcontext *context, ALuint effectslot, ALenum param, ALint *values) noexcept try { switch(param) { case AL_EFFECTSLOT_EFFECT: case AL_EFFECTSLOT_AUXILIARY_SEND_AUTO: case AL_EFFECTSLOT_TARGET_SOFT: case AL_EFFECTSLOT_STATE_SOFT: case AL_BUFFER: alGetAuxiliaryEffectSlotiDirect(context, effectslot, param, values); return; } std::lock_guard slotlock{context->mEffectSlotLock}; ALeffectslot *slot = LookupEffectSlot(context, effectslot); if(!slot) context->throw_error(AL_INVALID_NAME, "Invalid effect slot ID {}", effectslot); context->throw_error(AL_INVALID_ENUM, "Invalid effect slot integer-vector property {:#04x}", as_unsigned(param)); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alGetAuxiliaryEffectSlotf, ALuint,effectslot, ALenum,param, ALfloat*,value) FORCE_ALIGN void AL_APIENTRY alGetAuxiliaryEffectSlotfDirect(ALCcontext *context, ALuint effectslot, ALenum param, ALfloat *value) noexcept try { std::lock_guard slotlock{context->mEffectSlotLock}; ALeffectslot *slot{LookupEffectSlot(context, effectslot)}; if(!slot) context->throw_error(AL_INVALID_NAME, "Invalid effect slot ID {}", effectslot); switch(param) { case AL_EFFECTSLOT_GAIN: *value = slot->Gain; return; } context->throw_error(AL_INVALID_ENUM, "Invalid effect slot float property {:#04x}", as_unsigned(param)); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alGetAuxiliaryEffectSlotfv, ALuint,effectslot, ALenum,param, ALfloat*,values) FORCE_ALIGN void AL_APIENTRY alGetAuxiliaryEffectSlotfvDirect(ALCcontext *context, ALuint effectslot, ALenum param, ALfloat *values) noexcept try { switch(param) { case AL_EFFECTSLOT_GAIN: alGetAuxiliaryEffectSlotfDirect(context, effectslot, param, values); return; } std::lock_guard slotlock{context->mEffectSlotLock}; ALeffectslot *slot{LookupEffectSlot(context, effectslot)}; if(!slot) context->throw_error(AL_INVALID_NAME, "Invalid effect slot ID {}", effectslot); context->throw_error(AL_INVALID_ENUM, "Invalid effect slot float-vector property {:#04x}", as_unsigned(param)); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } ALeffectslot::ALeffectslot(ALCcontext *context) { EffectStateFactory *factory{getFactoryByType(EffectSlotType::None)}; if(!factory) throw std::runtime_error{"Failed to get null effect factory"}; al::intrusive_ptr state{factory->create()}; Effect.State = state; mSlot = context->getEffectSlot(); mSlot->InUse = true; mSlot->mEffectState = std::move(state); } ALeffectslot::~ALeffectslot() { if(Target) DecrementRef(Target->ref); Target = nullptr; if(Buffer) DecrementRef(Buffer->ref); Buffer = nullptr; if(auto *slot = mSlot->Update.exchange(nullptr, std::memory_order_relaxed)) slot->State = nullptr; mSlot->mEffectState = nullptr; mSlot->InUse = false; } ALenum ALeffectslot::initEffect(ALuint effectId, ALenum effectType, const EffectProps &effectProps, ALCcontext *context) { EffectSlotType newtype{EffectSlotTypeFromEnum(effectType)}; if(newtype != Effect.Type) { EffectStateFactory *factory{getFactoryByType(newtype)}; if(!factory) { ERR("Failed to find factory for effect slot type {}", int{al::to_underlying(newtype)}); return AL_INVALID_ENUM; } al::intrusive_ptr state{factory->create()}; auto *device = context->mALDevice.get(); state->mOutTarget = device->Dry.Buffer; { FPUCtl mixer_mode{}; state->deviceUpdate(device, Buffer); } Effect.Type = newtype; Effect.Props = effectProps; Effect.State = std::move(state); } else if(newtype != EffectSlotType::None) Effect.Props = effectProps; EffectId = effectId; /* Remove state references from old effect slot property updates. */ EffectSlotProps *props{context->mFreeEffectSlotProps.load()}; while(props) { props->State = nullptr; props = props->next.load(std::memory_order_relaxed); } return AL_NO_ERROR; } void ALeffectslot::updateProps(ALCcontext *context) const { /* Get an unused property container, or allocate a new one as needed. */ EffectSlotProps *props{context->mFreeEffectSlotProps.load(std::memory_order_acquire)}; if(!props) { context->allocEffectSlotProps(); props = context->mFreeEffectSlotProps.load(std::memory_order_acquire); } EffectSlotProps *next; do { next = props->next.load(std::memory_order_relaxed); } while(!context->mFreeEffectSlotProps.compare_exchange_weak(props, next, std::memory_order_acq_rel, std::memory_order_acquire)); /* Copy in current property values. */ props->Gain = Gain; props->AuxSendAuto = AuxSendAuto; props->Target = Target ? Target->mSlot : nullptr; props->Type = Effect.Type; props->Props = Effect.Props; props->State = Effect.State; /* Set the new container for updating internal parameters. */ props = mSlot->Update.exchange(props, std::memory_order_acq_rel); if(props) { /* If there was an unused update container, put it back in the * freelist. */ props->State = nullptr; AtomicReplaceHead(context->mFreeEffectSlotProps, props); } } void ALeffectslot::SetName(ALCcontext* context, ALuint id, std::string_view name) { std::lock_guard slotlock{context->mEffectSlotLock}; auto slot = LookupEffectSlot(context, id); if(!slot) context->throw_error(AL_INVALID_NAME, "Invalid effect slot ID {}", id); context->mEffectSlotNames.insert_or_assign(id, name); } void UpdateAllEffectSlotProps(ALCcontext *context) { std::lock_guard slotlock{context->mEffectSlotLock}; for(auto &sublist : context->mEffectSlotList) { uint64_t usemask{~sublist.FreeMask}; while(usemask) { const auto idx = static_cast(al::countr_zero(usemask)); usemask &= ~(1_u64 << idx); auto &slot = (*sublist.EffectSlots)[idx]; if(std::exchange(slot.mPropsDirty, false)) slot.updateProps(context); } } } EffectSlotSubList::~EffectSlotSubList() { if(!EffectSlots) return; uint64_t usemask{~FreeMask}; while(usemask) { const int idx{al::countr_zero(usemask)}; std::destroy_at(al::to_address(EffectSlots->begin() + idx)); usemask &= ~(1_u64 << idx); } FreeMask = ~usemask; SubListAllocator{}.deallocate(EffectSlots, 1); EffectSlots = nullptr; } #if ALSOFT_EAX void ALeffectslot::eax_initialize(ALCcontext& al_context, EaxFxSlotIndexValue index) { if(index >= EAX_MAX_FXSLOTS) eax_fail("Index out of range."); eax_al_context_ = &al_context; eax_fx_slot_index_ = index; eax_fx_slot_set_defaults(); eax_effect_ = std::make_unique(); if(index == 0) eax_effect_->init(); else if(index == 1) eax_effect_->init(); else eax_effect_->init(); } void ALeffectslot::eax_commit() { if(eax_df_ != EaxDirtyFlags{}) { auto df = EaxDirtyFlags{}; switch(eax_version_) { case 1: case 2: case 3: eax5_fx_slot_commit(eax123_, df); break; case 4: eax4_fx_slot_commit(df); break; case 5: eax5_fx_slot_commit(eax5_, df); break; } eax_df_ = EaxDirtyFlags{}; if((df & eax_volume_dirty_bit) != EaxDirtyFlags{}) eax_fx_slot_set_volume(); if((df & eax_flags_dirty_bit) != EaxDirtyFlags{}) eax_fx_slot_set_flags(); } if(eax_effect_->commit(eax_version_)) eax_set_efx_slot_effect(*eax_effect_); } [[noreturn]] void ALeffectslot::eax_fail(const char* message) { throw Exception{message}; } [[noreturn]] void ALeffectslot::eax_fail_unknown_effect_id() { eax_fail("Unknown effect ID."); } [[noreturn]] void ALeffectslot::eax_fail_unknown_property_id() { eax_fail("Unknown property ID."); } [[noreturn]] void ALeffectslot::eax_fail_unknown_version() { eax_fail("Unknown version."); } void ALeffectslot::eax4_fx_slot_ensure_unlocked() const { if(eax4_fx_slot_is_legacy()) eax_fail("Locked legacy slot."); } ALenum ALeffectslot::eax_get_efx_effect_type(const GUID& guid) { if(guid == EAX_NULL_GUID) return AL_EFFECT_NULL; if(guid == EAX_AUTOWAH_EFFECT) return AL_EFFECT_AUTOWAH; if(guid == EAX_CHORUS_EFFECT) return AL_EFFECT_CHORUS; if(guid == EAX_AGCCOMPRESSOR_EFFECT) return AL_EFFECT_COMPRESSOR; if(guid == EAX_DISTORTION_EFFECT) return AL_EFFECT_DISTORTION; if(guid == EAX_REVERB_EFFECT) return AL_EFFECT_EAXREVERB; if(guid == EAX_ECHO_EFFECT) return AL_EFFECT_ECHO; if(guid == EAX_EQUALIZER_EFFECT) return AL_EFFECT_EQUALIZER; if(guid == EAX_FLANGER_EFFECT) return AL_EFFECT_FLANGER; if(guid == EAX_FREQUENCYSHIFTER_EFFECT) return AL_EFFECT_FREQUENCY_SHIFTER; if(guid == EAX_PITCHSHIFTER_EFFECT) return AL_EFFECT_PITCH_SHIFTER; if(guid == EAX_RINGMODULATOR_EFFECT) return AL_EFFECT_RING_MODULATOR; if(guid == EAX_VOCALMORPHER_EFFECT) return AL_EFFECT_VOCAL_MORPHER; eax_fail_unknown_effect_id(); } const GUID& ALeffectslot::eax_get_eax_default_effect_guid() const noexcept { switch(eax_fx_slot_index_) { case 0: return EAX_REVERB_EFFECT; case 1: return EAX_CHORUS_EFFECT; default: return EAX_NULL_GUID; } } long ALeffectslot::eax_get_eax_default_lock() const noexcept { return eax4_fx_slot_is_legacy() ? EAXFXSLOT_LOCKED : EAXFXSLOT_UNLOCKED; } void ALeffectslot::eax4_fx_slot_set_defaults(Eax4Props& props) noexcept { props.guidLoadEffect = eax_get_eax_default_effect_guid(); props.lVolume = EAXFXSLOT_DEFAULTVOLUME; props.lLock = eax_get_eax_default_lock(); props.ulFlags = EAX40FXSLOT_DEFAULTFLAGS; } void ALeffectslot::eax5_fx_slot_set_defaults(Eax5Props& props) noexcept { props.guidLoadEffect = eax_get_eax_default_effect_guid(); props.lVolume = EAXFXSLOT_DEFAULTVOLUME; props.lLock = EAXFXSLOT_UNLOCKED; props.ulFlags = EAX50FXSLOT_DEFAULTFLAGS; props.lOcclusion = EAXFXSLOT_DEFAULTOCCLUSION; props.flOcclusionLFRatio = EAXFXSLOT_DEFAULTOCCLUSIONLFRATIO; } void ALeffectslot::eax_fx_slot_set_defaults() { eax5_fx_slot_set_defaults(eax123_.i); eax4_fx_slot_set_defaults(eax4_.i); eax5_fx_slot_set_defaults(eax5_.i); eax_ = eax5_.i; eax_df_ = EaxDirtyFlags{}; } void ALeffectslot::eax4_fx_slot_get(const EaxCall& call, const Eax4Props& props) { switch(call.get_property_id()) { case EAXFXSLOT_ALLPARAMETERS: call.set_value(props); break; case EAXFXSLOT_LOADEFFECT: call.set_value(props.guidLoadEffect); break; case EAXFXSLOT_VOLUME: call.set_value(props.lVolume); break; case EAXFXSLOT_LOCK: call.set_value(props.lLock); break; case EAXFXSLOT_FLAGS: call.set_value(props.ulFlags); break; default: eax_fail_unknown_property_id(); } } void ALeffectslot::eax5_fx_slot_get(const EaxCall& call, const Eax5Props& props) { switch(call.get_property_id()) { case EAXFXSLOT_ALLPARAMETERS: call.set_value(props); break; case EAXFXSLOT_LOADEFFECT: call.set_value(props.guidLoadEffect); break; case EAXFXSLOT_VOLUME: call.set_value(props.lVolume); break; case EAXFXSLOT_LOCK: call.set_value(props.lLock); break; case EAXFXSLOT_FLAGS: call.set_value(props.ulFlags); break; case EAXFXSLOT_OCCLUSION: call.set_value(props.lOcclusion); break; case EAXFXSLOT_OCCLUSIONLFRATIO: call.set_value(props.flOcclusionLFRatio); break; default: eax_fail_unknown_property_id(); } } void ALeffectslot::eax_fx_slot_get(const EaxCall& call) const { switch(call.get_version()) { case 4: eax4_fx_slot_get(call, eax4_.i); break; case 5: eax5_fx_slot_get(call, eax5_.i); break; default: eax_fail_unknown_version(); } } bool ALeffectslot::eax_get(const EaxCall& call) { switch(call.get_property_set_id()) { case EaxCallPropertySetId::fx_slot: eax_fx_slot_get(call); break; case EaxCallPropertySetId::fx_slot_effect: eax_effect_->get(call); break; default: eax_fail_unknown_property_id(); } return false; } void ALeffectslot::eax_fx_slot_load_effect(int version, ALenum altype) { if(!IsValidEffectType(altype)) altype = AL_EFFECT_NULL; eax_effect_->set_defaults(version, altype); } void ALeffectslot::eax_fx_slot_set_volume() { const auto volume = std::clamp(eax_.lVolume, EAXFXSLOT_MINVOLUME, EAXFXSLOT_MAXVOLUME); const auto gain = level_mb_to_gain(static_cast(volume)); eax_set_efx_slot_gain(gain); } void ALeffectslot::eax_fx_slot_set_environment_flag() { eax_set_efx_slot_send_auto((eax_.ulFlags & EAXFXSLOTFLAGS_ENVIRONMENT) != 0u); } void ALeffectslot::eax_fx_slot_set_flags() { eax_fx_slot_set_environment_flag(); } void ALeffectslot::eax4_fx_slot_set_all(const EaxCall& call) { eax4_fx_slot_ensure_unlocked(); const auto& src = call.get_value(); Eax4AllValidator{}(src); auto& dst = eax4_.i; eax_df_ |= eax_load_effect_dirty_bit; // Always reset the effect. eax_df_ |= (dst.lVolume != src.lVolume ? eax_volume_dirty_bit : EaxDirtyFlags{}); eax_df_ |= (dst.lLock != src.lLock ? eax_lock_dirty_bit : EaxDirtyFlags{}); eax_df_ |= (dst.ulFlags != src.ulFlags ? eax_flags_dirty_bit : EaxDirtyFlags{}); dst = src; } void ALeffectslot::eax5_fx_slot_set_all(const EaxCall& call) { const auto& src = call.get_value(); Eax5AllValidator{}(src); auto& dst = eax5_.i; eax_df_ |= eax_load_effect_dirty_bit; // Always reset the effect. eax_df_ |= (dst.lVolume != src.lVolume ? eax_volume_dirty_bit : EaxDirtyFlags{}); eax_df_ |= (dst.lLock != src.lLock ? eax_lock_dirty_bit : EaxDirtyFlags{}); eax_df_ |= (dst.ulFlags != src.ulFlags ? eax_flags_dirty_bit : EaxDirtyFlags{}); eax_df_ |= (dst.lOcclusion != src.lOcclusion ? eax_flags_dirty_bit : EaxDirtyFlags{}); eax_df_ |= (dst.flOcclusionLFRatio != src.flOcclusionLFRatio ? eax_flags_dirty_bit : EaxDirtyFlags{}); dst = src; } bool ALeffectslot::eax_fx_slot_should_update_sources() const noexcept { static constexpr auto dirty_bits = eax_occlusion_dirty_bit | eax_occlusion_lf_ratio_dirty_bit | eax_flags_dirty_bit; return (eax_df_ & dirty_bits) != EaxDirtyFlags{}; } // Returns `true` if all sources should be updated, or `false` otherwise. bool ALeffectslot::eax4_fx_slot_set(const EaxCall& call) { auto& dst = eax4_.i; switch(call.get_property_id()) { case EAXFXSLOT_NONE: break; case EAXFXSLOT_ALLPARAMETERS: eax4_fx_slot_set_all(call); if((eax_df_ & eax_load_effect_dirty_bit)) eax_fx_slot_load_effect(4, eax_get_efx_effect_type(dst.guidLoadEffect)); break; case EAXFXSLOT_LOADEFFECT: eax4_fx_slot_ensure_unlocked(); eax_fx_slot_set_dirty(call, dst.guidLoadEffect, eax_df_); if((eax_df_ & eax_load_effect_dirty_bit)) eax_fx_slot_load_effect(4, eax_get_efx_effect_type(dst.guidLoadEffect)); break; case EAXFXSLOT_VOLUME: eax_fx_slot_set(call, dst.lVolume, eax_df_); break; case EAXFXSLOT_LOCK: eax4_fx_slot_ensure_unlocked(); eax_fx_slot_set(call, dst.lLock, eax_df_); break; case EAXFXSLOT_FLAGS: eax_fx_slot_set(call, dst.ulFlags, eax_df_); break; default: eax_fail_unknown_property_id(); } return eax_fx_slot_should_update_sources(); } // Returns `true` if all sources should be updated, or `false` otherwise. bool ALeffectslot::eax5_fx_slot_set(const EaxCall& call) { auto& dst = eax5_.i; switch(call.get_property_id()) { case EAXFXSLOT_NONE: break; case EAXFXSLOT_ALLPARAMETERS: eax5_fx_slot_set_all(call); if((eax_df_ & eax_load_effect_dirty_bit)) eax_fx_slot_load_effect(5, eax_get_efx_effect_type(dst.guidLoadEffect)); break; case EAXFXSLOT_LOADEFFECT: eax_fx_slot_set_dirty(call, dst.guidLoadEffect, eax_df_); if((eax_df_ & eax_load_effect_dirty_bit)) eax_fx_slot_load_effect(5, eax_get_efx_effect_type(dst.guidLoadEffect)); break; case EAXFXSLOT_VOLUME: eax_fx_slot_set(call, dst.lVolume, eax_df_); break; case EAXFXSLOT_LOCK: eax_fx_slot_set(call, dst.lLock, eax_df_); break; case EAXFXSLOT_FLAGS: eax_fx_slot_set(call, dst.ulFlags, eax_df_); break; case EAXFXSLOT_OCCLUSION: eax_fx_slot_set(call, dst.lOcclusion, eax_df_); break; case EAXFXSLOT_OCCLUSIONLFRATIO: eax_fx_slot_set(call, dst.flOcclusionLFRatio, eax_df_); break; default: eax_fail_unknown_property_id(); } return eax_fx_slot_should_update_sources(); } // Returns `true` if all sources should be updated, or `false` otherwise. bool ALeffectslot::eax_fx_slot_set(const EaxCall& call) { switch (call.get_version()) { case 4: return eax4_fx_slot_set(call); case 5: return eax5_fx_slot_set(call); default: eax_fail_unknown_version(); } } // Returns `true` if all sources should be updated, or `false` otherwise. bool ALeffectslot::eax_set(const EaxCall& call) { bool ret{false}; switch(call.get_property_set_id()) { case EaxCallPropertySetId::fx_slot: ret = eax_fx_slot_set(call); break; case EaxCallPropertySetId::fx_slot_effect: eax_effect_->set(call); break; default: eax_fail_unknown_property_id(); } const auto version = call.get_version(); if(eax_version_ != version) eax_df_ = ~EaxDirtyFlags{}; eax_version_ = version; return ret; } void ALeffectslot::eax4_fx_slot_commit(EaxDirtyFlags& dst_df) { eax_fx_slot_commit_property(eax4_, dst_df, &EAX40FXSLOTPROPERTIES::guidLoadEffect); eax_fx_slot_commit_property(eax4_, dst_df, &EAX40FXSLOTPROPERTIES::lVolume); eax_fx_slot_commit_property(eax4_, dst_df, &EAX40FXSLOTPROPERTIES::lLock); eax_fx_slot_commit_property(eax4_, dst_df, &EAX40FXSLOTPROPERTIES::ulFlags); auto& dst_i = eax_; if(dst_i.lOcclusion != EAXFXSLOT_DEFAULTOCCLUSION) { dst_df |= eax_occlusion_dirty_bit; dst_i.lOcclusion = EAXFXSLOT_DEFAULTOCCLUSION; } if(dst_i.flOcclusionLFRatio != EAXFXSLOT_DEFAULTOCCLUSIONLFRATIO) { dst_df |= eax_occlusion_lf_ratio_dirty_bit; dst_i.flOcclusionLFRatio = EAXFXSLOT_DEFAULTOCCLUSIONLFRATIO; } } void ALeffectslot::eax5_fx_slot_commit(Eax5State& state, EaxDirtyFlags& dst_df) { eax_fx_slot_commit_property(state, dst_df, &EAX50FXSLOTPROPERTIES::guidLoadEffect); eax_fx_slot_commit_property(state, dst_df, &EAX50FXSLOTPROPERTIES::lVolume); eax_fx_slot_commit_property(state, dst_df, &EAX50FXSLOTPROPERTIES::lLock); eax_fx_slot_commit_property(state, dst_df, &EAX50FXSLOTPROPERTIES::ulFlags); eax_fx_slot_commit_property(state, dst_df, &EAX50FXSLOTPROPERTIES::lOcclusion); eax_fx_slot_commit_property(state, dst_df, &EAX50FXSLOTPROPERTIES::flOcclusionLFRatio); } void ALeffectslot::eax_set_efx_slot_effect(EaxEffect &effect) { #define EAX_PREFIX "[EAX_SET_EFFECT_SLOT_EFFECT] " const auto error = initEffect(0, effect.al_effect_type_, effect.al_effect_props_, eax_al_context_); if(error != AL_NO_ERROR) { ERR(EAX_PREFIX "Failed to initialize an effect."); return; } if(mState == SlotState::Initial) { mPropsDirty = false; updateProps(eax_al_context_); auto effect_slot_ptr = this; AddActiveEffectSlots({&effect_slot_ptr, 1}, eax_al_context_); mState = SlotState::Playing; return; } mPropsDirty = true; #undef EAX_PREFIX } void ALeffectslot::eax_set_efx_slot_send_auto(bool is_send_auto) { if(AuxSendAuto == is_send_auto) return; AuxSendAuto = is_send_auto; mPropsDirty = true; } void ALeffectslot::eax_set_efx_slot_gain(ALfloat gain) { #define EAX_PREFIX "[EAX_SET_EFFECT_SLOT_GAIN] " if(gain == Gain) return; if(gain < 0.0f || gain > 1.0f) ERR(EAX_PREFIX "Slot gain out of range ({:f})", gain); Gain = std::clamp(gain, 0.0f, 1.0f); mPropsDirty = true; #undef EAX_PREFIX } void ALeffectslot::EaxDeleter::operator()(ALeffectslot* effect_slot) { eax_delete_al_effect_slot(*effect_slot->eax_al_context_, *effect_slot); } EaxAlEffectSlotUPtr eax_create_al_effect_slot(ALCcontext& context) { #define EAX_PREFIX "[EAX_MAKE_EFFECT_SLOT] " std::lock_guard slotlock{context.mEffectSlotLock}; auto& device = *context.mALDevice; if(context.mNumEffectSlots == device.AuxiliaryEffectSlotMax) { ERR(EAX_PREFIX "Out of memory."); return nullptr; } if(!EnsureEffectSlots(&context, 1)) { ERR(EAX_PREFIX "Failed to ensure."); return nullptr; } return EaxAlEffectSlotUPtr{AllocEffectSlot(&context)}; #undef EAX_PREFIX } void eax_delete_al_effect_slot(ALCcontext& context, ALeffectslot& effect_slot) { #define EAX_PREFIX "[EAX_DELETE_EFFECT_SLOT] " std::lock_guard slotlock{context.mEffectSlotLock}; if(effect_slot.ref.load(std::memory_order_relaxed) != 0) { ERR(EAX_PREFIX "Deleting in-use effect slot {}.", effect_slot.id); return; } auto effect_slot_ptr = &effect_slot; RemoveActiveEffectSlots({&effect_slot_ptr, 1}, &context); FreeEffectSlot(&context, &effect_slot); #undef EAX_PREFIX } #endif // ALSOFT_EAX openal-soft-1.24.2/al/auxeffectslot.h000066400000000000000000000303711474041540300174560ustar00rootroot00000000000000#ifndef AL_AUXEFFECTSLOT_H #define AL_AUXEFFECTSLOT_H #include "config.h" #include #include #include #include #include #include "AL/al.h" #include "AL/alc.h" #include "almalloc.h" #include "alnumeric.h" #include "core/effects/base.h" #include "core/effectslot.h" #include "intrusive_ptr.h" #if ALSOFT_EAX #include #include "eax/api.h" #include "eax/call.h" #include "eax/effect.h" #include "eax/exception.h" #include "eax/fx_slot_index.h" #include "eax/utils.h" #endif // ALSOFT_EAX struct ALbuffer; #if ALSOFT_EAX class EaxFxSlotException : public EaxException { public: explicit EaxFxSlotException(const char* message) : EaxException{"EAX_FX_SLOT", message} {} }; #endif // ALSOFT_EAX enum class SlotState : bool { Initial, Playing, }; struct ALeffectslot { ALuint EffectId{}; float Gain{1.0f}; bool AuxSendAuto{true}; ALeffectslot *Target{nullptr}; ALbuffer *Buffer{nullptr}; struct EffectData { EffectSlotType Type{EffectSlotType::None}; EffectProps Props; al::intrusive_ptr State; }; EffectData Effect; bool mPropsDirty{true}; SlotState mState{SlotState::Initial}; std::atomic ref{0u}; EffectSlot *mSlot{nullptr}; /* Self ID */ ALuint id{}; explicit ALeffectslot(ALCcontext *context); ALeffectslot(const ALeffectslot&) = delete; ALeffectslot& operator=(const ALeffectslot&) = delete; ~ALeffectslot(); ALenum initEffect(ALuint effectId, ALenum effectType, const EffectProps &effectProps, ALCcontext *context); void updateProps(ALCcontext *context) const; static void SetName(ALCcontext *context, ALuint id, std::string_view name); #if ALSOFT_EAX public: void eax_initialize(ALCcontext& al_context, EaxFxSlotIndexValue index); [[nodiscard]] auto eax_get_index() const noexcept -> EaxFxSlotIndexValue { return eax_fx_slot_index_; } [[nodiscard]] auto eax_get_eax_fx_slot() const noexcept -> const EAX50FXSLOTPROPERTIES& { return eax_; } // Returns `true` if all sources should be updated, or `false` otherwise. [[nodiscard]] auto eax_dispatch(const EaxCall& call) -> bool { return call.is_get() ? eax_get(call) : eax_set(call); } void eax_commit(); private: static constexpr auto eax_load_effect_dirty_bit = EaxDirtyFlags{1} << 0; static constexpr auto eax_volume_dirty_bit = EaxDirtyFlags{1} << 1; static constexpr auto eax_lock_dirty_bit = EaxDirtyFlags{1} << 2; static constexpr auto eax_flags_dirty_bit = EaxDirtyFlags{1} << 3; static constexpr auto eax_occlusion_dirty_bit = EaxDirtyFlags{1} << 4; static constexpr auto eax_occlusion_lf_ratio_dirty_bit = EaxDirtyFlags{1} << 5; using Exception = EaxFxSlotException; using Eax4Props = EAX40FXSLOTPROPERTIES; struct Eax4State { Eax4Props i; // Immediate. }; using Eax5Props = EAX50FXSLOTPROPERTIES; struct Eax5State { Eax5Props i; // Immediate. }; struct EaxRangeValidator { template void operator()( const char* name, const TValue& value, const TValue& min_value, const TValue& max_value) const { eax_validate_range(name, value, min_value, max_value); } }; struct Eax4GuidLoadEffectValidator { void operator()(const GUID& guidLoadEffect) const { if (guidLoadEffect != EAX_NULL_GUID && guidLoadEffect != EAX_REVERB_EFFECT && guidLoadEffect != EAX_AGCCOMPRESSOR_EFFECT && guidLoadEffect != EAX_AUTOWAH_EFFECT && guidLoadEffect != EAX_CHORUS_EFFECT && guidLoadEffect != EAX_DISTORTION_EFFECT && guidLoadEffect != EAX_ECHO_EFFECT && guidLoadEffect != EAX_EQUALIZER_EFFECT && guidLoadEffect != EAX_FLANGER_EFFECT && guidLoadEffect != EAX_FREQUENCYSHIFTER_EFFECT && guidLoadEffect != EAX_VOCALMORPHER_EFFECT && guidLoadEffect != EAX_PITCHSHIFTER_EFFECT && guidLoadEffect != EAX_RINGMODULATOR_EFFECT) { eax_fail_unknown_effect_id(); } } }; struct Eax4VolumeValidator { void operator()(long lVolume) const { EaxRangeValidator{}( "Volume", lVolume, EAXFXSLOT_MINVOLUME, EAXFXSLOT_MAXVOLUME); } }; struct Eax4LockValidator { void operator()(long lLock) const { EaxRangeValidator{}( "Lock", lLock, EAXFXSLOT_MINLOCK, EAXFXSLOT_MAXLOCK); } }; struct Eax4FlagsValidator { void operator()(unsigned long ulFlags) const { EaxRangeValidator{}( "Flags", ulFlags, 0UL, ~EAX40FXSLOTFLAGS_RESERVED); } }; struct Eax4AllValidator { void operator()(const EAX40FXSLOTPROPERTIES& all) const { Eax4GuidLoadEffectValidator{}(all.guidLoadEffect); Eax4VolumeValidator{}(all.lVolume); Eax4LockValidator{}(all.lLock); Eax4FlagsValidator{}(all.ulFlags); } }; struct Eax5FlagsValidator { void operator()(unsigned long ulFlags) const { EaxRangeValidator{}( "Flags", ulFlags, 0UL, ~EAX50FXSLOTFLAGS_RESERVED); } }; struct Eax5OcclusionValidator { void operator()(long lOcclusion) const { EaxRangeValidator{}( "Occlusion", lOcclusion, EAXFXSLOT_MINOCCLUSION, EAXFXSLOT_MAXOCCLUSION); } }; struct Eax5OcclusionLfRatioValidator { void operator()(float flOcclusionLFRatio) const { EaxRangeValidator{}( "Occlusion LF Ratio", flOcclusionLFRatio, EAXFXSLOT_MINOCCLUSIONLFRATIO, EAXFXSLOT_MAXOCCLUSIONLFRATIO); } }; struct Eax5AllValidator { void operator()(const EAX50FXSLOTPROPERTIES& all) const { Eax4GuidLoadEffectValidator{}(all.guidLoadEffect); Eax4VolumeValidator{}(all.lVolume); Eax4LockValidator{}(all.lLock); Eax5FlagsValidator{}(all.ulFlags); Eax5OcclusionValidator{}(all.lOcclusion); Eax5OcclusionLfRatioValidator{}(all.flOcclusionLFRatio); } }; ALCcontext* eax_al_context_{}; EaxFxSlotIndexValue eax_fx_slot_index_{}; int eax_version_{}; // Current EAX version. EaxDirtyFlags eax_df_{}; // Dirty flags for the current EAX version. EaxEffectUPtr eax_effect_; Eax5State eax123_{}; // EAX1/EAX2/EAX3 state. Eax4State eax4_{}; // EAX4 state. Eax5State eax5_{}; // EAX5 state. Eax5Props eax_{}; // Current EAX state. [[noreturn]] static void eax_fail(const char* message); [[noreturn]] static void eax_fail_unknown_effect_id(); [[noreturn]] static void eax_fail_unknown_property_id(); [[noreturn]] static void eax_fail_unknown_version(); // Gets a new value from EAX call, // validates it, // sets a dirty flag only if the new value differs form the old one, // and assigns the new value. template static void eax_fx_slot_set(const EaxCall& call, TProperties& dst, EaxDirtyFlags& dirty_flags) { const auto& src = call.get_value(); TValidator{}(src); dirty_flags |= (dst != src ? TDirtyBit : EaxDirtyFlags{}); dst = src; } // Gets a new value from EAX call, // validates it, // sets a dirty flag without comparing the values, // and assigns the new value. template static void eax_fx_slot_set_dirty(const EaxCall& call, TProperties& dst, EaxDirtyFlags& dirty_flags) { const auto& src = call.get_value(); TValidator{}(src); dirty_flags |= TDirtyBit; dst = src; } [[nodiscard]] constexpr auto eax4_fx_slot_is_legacy() const noexcept -> bool { return eax_fx_slot_index_ < 2; } void eax4_fx_slot_ensure_unlocked() const; [[nodiscard]] static auto eax_get_efx_effect_type(const GUID& guid) -> ALenum; [[nodiscard]] auto eax_get_eax_default_effect_guid() const noexcept -> const GUID&; [[nodiscard]] auto eax_get_eax_default_lock() const noexcept -> long; void eax4_fx_slot_set_defaults(Eax4Props& props) noexcept; void eax5_fx_slot_set_defaults(Eax5Props& props) noexcept; void eax4_fx_slot_set_current_defaults(const Eax4Props& props) noexcept; void eax5_fx_slot_set_current_defaults(const Eax5Props& props) noexcept; void eax_fx_slot_set_current_defaults(); void eax_fx_slot_set_defaults(); static void eax4_fx_slot_get(const EaxCall& call, const Eax4Props& props); static void eax5_fx_slot_get(const EaxCall& call, const Eax5Props& props); void eax_fx_slot_get(const EaxCall& call) const; // Returns `true` if all sources should be updated, or `false` otherwise. bool eax_get(const EaxCall& call); void eax_fx_slot_load_effect(int version, ALenum altype); void eax_fx_slot_set_volume(); void eax_fx_slot_set_environment_flag(); void eax_fx_slot_set_flags(); void eax4_fx_slot_set_all(const EaxCall& call); void eax5_fx_slot_set_all(const EaxCall& call); [[nodiscard]] auto eax_fx_slot_should_update_sources() const noexcept -> bool; // Returns `true` if all sources should be updated, or `false` otherwise. bool eax4_fx_slot_set(const EaxCall& call); // Returns `true` if all sources should be updated, or `false` otherwise. bool eax5_fx_slot_set(const EaxCall& call); // Returns `true` if all sources should be updated, or `false` otherwise. bool eax_fx_slot_set(const EaxCall& call); // Returns `true` if all sources should be updated, or `false` otherwise. bool eax_set(const EaxCall& call); template< EaxDirtyFlags TDirtyBit, typename TMemberResult, typename TProps, typename TState> void eax_fx_slot_commit_property(TState& state, EaxDirtyFlags& dst_df, TMemberResult TProps::*member) noexcept { auto& src_i = state.i; auto& dst_i = eax_; if((eax_df_ & TDirtyBit) != EaxDirtyFlags{}) { dst_df |= TDirtyBit; dst_i.*member = src_i.*member; } } void eax4_fx_slot_commit(EaxDirtyFlags& dst_df); void eax5_fx_slot_commit(Eax5State& state, EaxDirtyFlags& dst_df); // `alAuxiliaryEffectSloti(effect_slot, AL_EFFECTSLOT_EFFECT, effect)` void eax_set_efx_slot_effect(EaxEffect &effect); // `alAuxiliaryEffectSloti(effect_slot, AL_EFFECTSLOT_AUXILIARY_SEND_AUTO, value)` void eax_set_efx_slot_send_auto(bool is_send_auto); // `alAuxiliaryEffectSlotf(effect_slot, AL_EFFECTSLOT_GAIN, gain)` void eax_set_efx_slot_gain(ALfloat gain); public: class EaxDeleter { public: void operator()(ALeffectslot *effect_slot); }; #endif // ALSOFT_EAX }; void UpdateAllEffectSlotProps(ALCcontext *context); #if ALSOFT_EAX using EaxAlEffectSlotUPtr = std::unique_ptr; EaxAlEffectSlotUPtr eax_create_al_effect_slot(ALCcontext& context); void eax_delete_al_effect_slot(ALCcontext& context, ALeffectslot& effect_slot); #endif // ALSOFT_EAX struct EffectSlotSubList { uint64_t FreeMask{~0_u64}; gsl::owner*> EffectSlots{nullptr}; EffectSlotSubList() noexcept = default; EffectSlotSubList(const EffectSlotSubList&) = delete; EffectSlotSubList(EffectSlotSubList&& rhs) noexcept : FreeMask{rhs.FreeMask}, EffectSlots{rhs.EffectSlots} { rhs.FreeMask = ~0_u64; rhs.EffectSlots = nullptr; } ~EffectSlotSubList(); EffectSlotSubList& operator=(const EffectSlotSubList&) = delete; EffectSlotSubList& operator=(EffectSlotSubList&& rhs) noexcept { std::swap(FreeMask, rhs.FreeMask); std::swap(EffectSlots, rhs.EffectSlots); return *this; } }; #endif openal-soft-1.24.2/al/buffer.cpp000066400000000000000000001757041474041540300164200ustar00rootroot00000000000000/** * OpenAL cross platform audio library * Copyright (C) 1999-2007 by authors. * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * Or go to http://www.gnu.org/copyleft/lgpl.html */ #include "config.h" #include "buffer.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "AL/al.h" #include "AL/alc.h" #include "AL/alext.h" #include "albit.h" #include "alc/context.h" #include "alc/device.h" #include "alc/inprogext.h" #include "almalloc.h" #include "alnumeric.h" #include "alspan.h" #include "core/device.h" #include "core/except.h" #include "core/logging.h" #include "core/resampler_limits.h" #include "core/voice.h" #include "direct_defs.h" #include "intrusive_ptr.h" #include "opthelpers.h" #if ALSOFT_EAX #include #include "eax/globals.h" #include "eax/x_ram.h" #endif // ALSOFT_EAX namespace { using SubListAllocator = al::allocator>; constexpr auto AmbiLayoutFromEnum(ALenum layout) noexcept -> std::optional { switch(layout) { case AL_FUMA_SOFT: return AmbiLayout::FuMa; case AL_ACN_SOFT: return AmbiLayout::ACN; } return std::nullopt; } constexpr auto EnumFromAmbiLayout(AmbiLayout layout) -> ALenum { switch(layout) { case AmbiLayout::FuMa: return AL_FUMA_SOFT; case AmbiLayout::ACN: return AL_ACN_SOFT; } throw std::runtime_error{fmt::format("Invalid AmbiLayout: {}", int{al::to_underlying(layout)})}; } constexpr auto AmbiScalingFromEnum(ALenum scale) noexcept -> std::optional { switch(scale) { case AL_FUMA_SOFT: return AmbiScaling::FuMa; case AL_SN3D_SOFT: return AmbiScaling::SN3D; case AL_N3D_SOFT: return AmbiScaling::N3D; } return std::nullopt; } constexpr auto EnumFromAmbiScaling(AmbiScaling scale) -> ALenum { switch(scale) { case AmbiScaling::FuMa: return AL_FUMA_SOFT; case AmbiScaling::SN3D: return AL_SN3D_SOFT; case AmbiScaling::N3D: return AL_N3D_SOFT; case AmbiScaling::UHJ: break; } throw std::runtime_error{fmt::format("Invalid AmbiScaling: {}", int{al::to_underlying(scale)})}; } #if ALSOFT_EAX constexpr auto EaxStorageFromEnum(ALenum scale) noexcept -> std::optional { switch(scale) { case AL_STORAGE_AUTOMATIC: return EaxStorage::Automatic; case AL_STORAGE_ACCESSIBLE: return EaxStorage::Accessible; case AL_STORAGE_HARDWARE: return EaxStorage::Hardware; } return std::nullopt; } constexpr auto EnumFromEaxStorage(EaxStorage storage) -> ALenum { switch(storage) { case EaxStorage::Automatic: return AL_STORAGE_AUTOMATIC; case EaxStorage::Accessible: return AL_STORAGE_ACCESSIBLE; case EaxStorage::Hardware: return AL_STORAGE_HARDWARE; } throw std::runtime_error{fmt::format("Invalid EaxStorage: {}", int{al::to_underlying(storage)})}; } auto eax_x_ram_check_availability(const al::Device &device, const ALbuffer &buffer, const ALuint newsize) noexcept -> bool { ALuint freemem{device.eax_x_ram_free_size}; /* If the buffer is currently in "hardware", add its memory to the free * pool since it'll be "replaced". */ if(buffer.eax_x_ram_is_hardware) freemem += buffer.OriginalSize; return freemem >= newsize; } void eax_x_ram_apply(al::Device &device, ALbuffer &buffer) noexcept { if(buffer.eax_x_ram_is_hardware) return; if(device.eax_x_ram_free_size >= buffer.OriginalSize) { device.eax_x_ram_free_size -= buffer.OriginalSize; buffer.eax_x_ram_is_hardware = true; } } void eax_x_ram_clear(al::Device& al_device, ALbuffer& al_buffer) noexcept { if(al_buffer.eax_x_ram_is_hardware) al_device.eax_x_ram_free_size += al_buffer.OriginalSize; al_buffer.eax_x_ram_is_hardware = false; } #endif // ALSOFT_EAX constexpr ALbitfieldSOFT INVALID_STORAGE_MASK{~unsigned(AL_MAP_READ_BIT_SOFT | AL_MAP_WRITE_BIT_SOFT | AL_MAP_PERSISTENT_BIT_SOFT | AL_PRESERVE_DATA_BIT_SOFT)}; constexpr ALbitfieldSOFT MAP_READ_WRITE_FLAGS{AL_MAP_READ_BIT_SOFT | AL_MAP_WRITE_BIT_SOFT}; constexpr ALbitfieldSOFT INVALID_MAP_FLAGS{~unsigned(AL_MAP_READ_BIT_SOFT | AL_MAP_WRITE_BIT_SOFT | AL_MAP_PERSISTENT_BIT_SOFT)}; [[nodiscard]] auto EnsureBuffers(al::Device *device, size_t needed) noexcept -> bool try { size_t count{std::accumulate(device->BufferList.cbegin(), device->BufferList.cend(), 0_uz, [](size_t cur, const BufferSubList &sublist) noexcept -> size_t { return cur + static_cast(al::popcount(sublist.FreeMask)); })}; while(needed > count) { if(device->BufferList.size() >= 1<<25) UNLIKELY return false; BufferSubList sublist{}; sublist.FreeMask = ~0_u64; sublist.Buffers = SubListAllocator{}.allocate(1); device->BufferList.emplace_back(std::move(sublist)); count += std::tuple_size_v; } return true; } catch(...) { return false; } [[nodiscard]] auto AllocBuffer(al::Device *device) noexcept -> ALbuffer* { auto sublist = std::find_if(device->BufferList.begin(), device->BufferList.end(), [](const BufferSubList &entry) noexcept -> bool { return entry.FreeMask != 0; }); auto lidx = static_cast(std::distance(device->BufferList.begin(), sublist)); auto slidx = static_cast(al::countr_zero(sublist->FreeMask)); ASSUME(slidx < 64); ALbuffer *buffer{al::construct_at(al::to_address(sublist->Buffers->begin() + slidx))}; /* Add 1 to avoid buffer ID 0. */ buffer->id = ((lidx<<6) | slidx) + 1; sublist->FreeMask &= ~(1_u64 << slidx); return buffer; } void FreeBuffer(al::Device *device, ALbuffer *buffer) { #if ALSOFT_EAX eax_x_ram_clear(*device, *buffer); #endif // ALSOFT_EAX device->mBufferNames.erase(buffer->id); const ALuint id{buffer->id - 1}; const size_t lidx{id >> 6}; const ALuint slidx{id & 0x3f}; std::destroy_at(buffer); device->BufferList[lidx].FreeMask |= 1_u64 << slidx; } [[nodiscard]] auto LookupBuffer(al::Device *device, ALuint id) noexcept -> ALbuffer* { const size_t lidx{(id-1) >> 6}; const ALuint slidx{(id-1) & 0x3f}; if(lidx >= device->BufferList.size()) UNLIKELY return nullptr; BufferSubList &sublist = device->BufferList[lidx]; if(sublist.FreeMask & (1_u64 << slidx)) UNLIKELY return nullptr; return al::to_address(sublist.Buffers->begin() + slidx); } [[nodiscard]] constexpr auto SanitizeAlignment(FmtType type, ALuint align) noexcept -> ALuint { if(align == 0) { if(type == FmtIMA4) { /* Here is where things vary: * nVidia and Apple use 64+1 sample frames per block -> block_size=36 bytes per channel * Most PC sound software uses 2040+1 sample frames per block -> block_size=1024 bytes per channel */ return 65; } if(type == FmtMSADPCM) return 64; return 1; } if(type == FmtIMA4) { /* IMA4 block alignment must be a multiple of 8, plus 1. */ if((align&7) == 1) return align; return 0; } if(type == FmtMSADPCM) { /* MSADPCM block alignment must be a multiple of 2. */ if((align&1) == 0) return align; return 0; } return align; } /** Loads the specified data into the buffer, using the specified format. */ void LoadData(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, ALuint size, const FmtChannels DstChannels, const FmtType DstType, const al::span SrcData, ALbitfieldSOFT access) { if(ALBuf->ref.load(std::memory_order_relaxed) != 0 || ALBuf->MappedAccess != 0) context->throw_error(AL_INVALID_OPERATION, "Modifying storage for in-use buffer {}", ALBuf->id); const ALuint unpackalign{ALBuf->UnpackAlign}; const ALuint align{SanitizeAlignment(DstType, unpackalign)}; if(align < 1) context->throw_error(AL_INVALID_VALUE, "Invalid unpack alignment {} for {} samples", unpackalign, NameFromFormat(DstType)); const ALuint ambiorder{IsBFormat(DstChannels) ? ALBuf->UnpackAmbiOrder : (IsUHJ(DstChannels) ? 1 : 0)}; if(ambiorder > 3) { if(ALBuf->mAmbiLayout == AmbiLayout::FuMa) context->throw_error(AL_INVALID_OPERATION, "Cannot load {}{} order B-Format data with FuMa layout", ALBuf->mAmbiOrder, GetCounterSuffix(ALBuf->mAmbiOrder)); if(ALBuf->mAmbiScaling == AmbiScaling::FuMa) context->throw_error(AL_INVALID_OPERATION, "Cannot load {}{} order B-Format data with FuMa scaling", ALBuf->mAmbiOrder, GetCounterSuffix(ALBuf->mAmbiOrder)); } if((access&AL_PRESERVE_DATA_BIT_SOFT)) { /* Can only preserve data with the same format and alignment. */ if(ALBuf->mChannels != DstChannels || ALBuf->mType != DstType) context->throw_error(AL_INVALID_VALUE, "Preserving data of mismatched format"); if(ALBuf->mBlockAlign != align) context->throw_error(AL_INVALID_VALUE, "Preserving data of mismatched alignment"); if(ALBuf->mAmbiOrder != ambiorder) context->throw_error(AL_INVALID_VALUE, "Preserving data of mismatched order"); } /* Convert the size in bytes to blocks using the unpack block alignment. */ const ALuint NumChannels{ChannelsFromFmt(DstChannels, ambiorder)}; const ALuint BlockSize{NumChannels * ((DstType == FmtIMA4) ? (align-1)/2 + 4 : (DstType == FmtMSADPCM) ? (align-2)/2 + 7 : (align * BytesFromFmt(DstType)))}; if((size%BlockSize) != 0) context->throw_error(AL_INVALID_VALUE, "Data size {} is not a multiple of frame size {} ({} unpack alignment)", size, BlockSize, align); const ALuint blocks{size / BlockSize}; if(blocks > std::numeric_limits::max()/align) context->throw_error(AL_OUT_OF_MEMORY, "Buffer size overflow, {} blocks x {} samples per block", blocks, align); if(blocks > std::numeric_limits::max()/BlockSize) context->throw_error(AL_OUT_OF_MEMORY, "Buffer size overflow, {} frames x {} bytes per frame", blocks, BlockSize); const size_t newsize{static_cast(blocks) * BlockSize}; #if ALSOFT_EAX if(ALBuf->eax_x_ram_mode == EaxStorage::Hardware) { auto &device = *context->mALDevice; if(!eax_x_ram_check_availability(device, *ALBuf, size)) context->throw_error(AL_OUT_OF_MEMORY, "Out of X-RAM memory (avail: {}, needed: {})", device.eax_x_ram_free_size, size); } #endif /* This could reallocate only when increasing the size or the new size is * less than half the current, but then the buffer's AL_SIZE would not be * very reliable for accounting buffer memory usage, and reporting the real * size could cause problems for apps that use AL_SIZE to try to get the * buffer's play length. */ if(newsize != ALBuf->mDataStorage.size()) { auto newdata = decltype(ALBuf->mDataStorage)(newsize, std::byte{}); if((access&AL_PRESERVE_DATA_BIT_SOFT)) { const size_t tocopy{std::min(newdata.size(), ALBuf->mDataStorage.size())}; std::copy_n(ALBuf->mDataStorage.begin(), tocopy, newdata.begin()); } newdata.swap(ALBuf->mDataStorage); } ALBuf->mData = ALBuf->mDataStorage; #if ALSOFT_EAX eax_x_ram_clear(*context->mALDevice, *ALBuf); #endif if(!SrcData.empty() && !ALBuf->mData.empty()) std::copy_n(SrcData.begin(), blocks*BlockSize, ALBuf->mData.begin()); ALBuf->mBlockAlign = (DstType == FmtIMA4 || DstType == FmtMSADPCM) ? align : 1; ALBuf->OriginalSize = size; ALBuf->Access = access; ALBuf->mSampleRate = static_cast(freq); ALBuf->mChannels = DstChannels; ALBuf->mType = DstType; ALBuf->mAmbiOrder = ambiorder; ALBuf->mCallback = nullptr; ALBuf->mUserData = nullptr; ALBuf->mSampleLen = blocks * align; ALBuf->mLoopStart = 0; ALBuf->mLoopEnd = ALBuf->mSampleLen; #if ALSOFT_EAX if(eax_g_is_enabled && ALBuf->eax_x_ram_mode == EaxStorage::Hardware) eax_x_ram_apply(*context->mALDevice, *ALBuf); #endif } /** Prepares the buffer to use the specified callback, using the specified format. */ void PrepareCallback(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, const FmtChannels DstChannels, const FmtType DstType, ALBUFFERCALLBACKTYPESOFT callback, void *userptr) { if(ALBuf->ref.load(std::memory_order_relaxed) != 0 || ALBuf->MappedAccess != 0) context->throw_error(AL_INVALID_OPERATION, "Modifying callback for in-use buffer {}", ALBuf->id); const ALuint ambiorder{IsBFormat(DstChannels) ? ALBuf->UnpackAmbiOrder : (IsUHJ(DstChannels) ? 1 : 0)}; const ALuint unpackalign{ALBuf->UnpackAlign}; const ALuint align{SanitizeAlignment(DstType, unpackalign)}; if(align < 1) context->throw_error(AL_INVALID_VALUE, "Invalid unpack alignment {} for {} samples", unpackalign, NameFromFormat(DstType)); const ALuint BlockSize{ChannelsFromFmt(DstChannels, ambiorder) * ((DstType == FmtIMA4) ? (align-1)/2 + 4 : (DstType == FmtMSADPCM) ? (align-2)/2 + 7 : (align * BytesFromFmt(DstType)))}; /* The maximum number of samples a callback buffer may need to store is a * full mixing line * max pitch * channel count, since it may need to hold * a full line's worth of sample frames before downsampling. An additional * MaxResamplerEdge is needed for "future" samples during resampling (the * voice will hold a history for the past samples). */ static constexpr size_t line_size{DeviceBase::MixerLineSize*MaxPitch + MaxResamplerEdge}; const size_t line_blocks{(line_size + align-1) / align}; using BufferVectorType = decltype(ALBuf->mDataStorage); BufferVectorType(line_blocks*BlockSize).swap(ALBuf->mDataStorage); ALBuf->mData = ALBuf->mDataStorage; #if ALSOFT_EAX eax_x_ram_clear(*context->mALDevice, *ALBuf); #endif ALBuf->mCallback = callback; ALBuf->mUserData = userptr; ALBuf->OriginalSize = 0; ALBuf->Access = 0; ALBuf->mBlockAlign = (DstType == FmtIMA4 || DstType == FmtMSADPCM) ? align : 1; ALBuf->mSampleRate = static_cast(freq); ALBuf->mChannels = DstChannels; ALBuf->mType = DstType; ALBuf->mAmbiOrder = ambiorder; ALBuf->mSampleLen = 0; ALBuf->mLoopStart = 0; ALBuf->mLoopEnd = ALBuf->mSampleLen; } /** Prepares the buffer to use caller-specified storage. */ void PrepareUserPtr(ALCcontext *context [[maybe_unused]], ALbuffer *ALBuf, ALsizei freq, const FmtChannels DstChannels, const FmtType DstType, std::byte *sdata, const ALuint sdatalen) { if(ALBuf->ref.load(std::memory_order_relaxed) != 0 || ALBuf->MappedAccess != 0) context->throw_error(AL_INVALID_OPERATION, "Modifying storage for in-use buffer {}", ALBuf->id); const ALuint unpackalign{ALBuf->UnpackAlign}; const ALuint align{SanitizeAlignment(DstType, unpackalign)}; if(align < 1) context->throw_error(AL_INVALID_VALUE, "Invalid unpack alignment {} for {} samples", unpackalign, NameFromFormat(DstType)); auto get_type_alignment = [](const FmtType type) noexcept -> ALuint { /* NOTE: This only needs to be the required alignment for the CPU to * read/write the given sample type in the mixer. */ switch(type) { case FmtUByte: return alignof(ALubyte); case FmtShort: return alignof(ALshort); case FmtInt: return alignof(ALint); case FmtFloat: return alignof(ALfloat); case FmtDouble: return alignof(ALdouble); case FmtMulaw: return alignof(ALubyte); case FmtAlaw: return alignof(ALubyte); case FmtIMA4: break; case FmtMSADPCM: break; } return 1; }; const auto typealign = get_type_alignment(DstType); if((reinterpret_cast(sdata) & (typealign-1)) != 0) context->throw_error(AL_INVALID_VALUE, "Pointer {} is misaligned for {} samples ({})", static_cast(sdata), NameFromFormat(DstType), typealign); const ALuint ambiorder{IsBFormat(DstChannels) ? ALBuf->UnpackAmbiOrder : (IsUHJ(DstChannels) ? 1 : 0)}; /* Convert the size in bytes to blocks using the unpack block alignment. */ const ALuint NumChannels{ChannelsFromFmt(DstChannels, ambiorder)}; const ALuint BlockSize{NumChannels * ((DstType == FmtIMA4) ? (align-1)/2 + 4 : (DstType == FmtMSADPCM) ? (align-2)/2 + 7 : (align * BytesFromFmt(DstType)))}; if((sdatalen%BlockSize) != 0) context->throw_error(AL_INVALID_VALUE, "Data size {} is not a multiple of frame size {} ({} unpack alignment)", sdatalen, BlockSize, align); const ALuint blocks{sdatalen / BlockSize}; if(blocks > std::numeric_limits::max()/align) context->throw_error(AL_OUT_OF_MEMORY, "Buffer size overflow, {} blocks x {} samples per block", blocks, align); if(blocks > std::numeric_limits::max()/BlockSize) context->throw_error(AL_OUT_OF_MEMORY, "Buffer size overflow, {} frames x {} bytes per frame", blocks, BlockSize); #if ALSOFT_EAX if(ALBuf->eax_x_ram_mode == EaxStorage::Hardware) { auto &device = *context->mALDevice; if(!eax_x_ram_check_availability(device, *ALBuf, sdatalen)) context->throw_error(AL_OUT_OF_MEMORY, "Out of X-RAM memory (avail: {}, needed: {})", device.eax_x_ram_free_size, sdatalen); } #endif decltype(ALBuf->mDataStorage){}.swap(ALBuf->mDataStorage); ALBuf->mData = al::span{sdata, sdatalen}; #if ALSOFT_EAX eax_x_ram_clear(*context->mALDevice, *ALBuf); #endif ALBuf->mCallback = nullptr; ALBuf->mUserData = nullptr; ALBuf->OriginalSize = sdatalen; ALBuf->Access = 0; ALBuf->mBlockAlign = (DstType == FmtIMA4 || DstType == FmtMSADPCM) ? align : 1; ALBuf->mSampleRate = static_cast(freq); ALBuf->mChannels = DstChannels; ALBuf->mType = DstType; ALBuf->mAmbiOrder = ambiorder; ALBuf->mSampleLen = blocks * align; ALBuf->mLoopStart = 0; ALBuf->mLoopEnd = ALBuf->mSampleLen; #if ALSOFT_EAX if(ALBuf->eax_x_ram_mode == EaxStorage::Hardware) eax_x_ram_apply(*context->mALDevice, *ALBuf); #endif } struct DecompResult { FmtChannels channels; FmtType type; }; auto DecomposeUserFormat(ALenum format) noexcept -> std::optional { struct FormatMap { ALenum format; DecompResult result; }; static constexpr std::array UserFmtList{ FormatMap{AL_FORMAT_MONO8, {FmtMono, FmtUByte} }, FormatMap{AL_FORMAT_MONO16, {FmtMono, FmtShort} }, FormatMap{AL_FORMAT_MONO_I32, {FmtMono, FmtInt} }, FormatMap{AL_FORMAT_MONO_FLOAT32, {FmtMono, FmtFloat} }, FormatMap{AL_FORMAT_MONO_DOUBLE_EXT, {FmtMono, FmtDouble} }, FormatMap{AL_FORMAT_MONO_IMA4, {FmtMono, FmtIMA4} }, FormatMap{AL_FORMAT_MONO_MSADPCM_SOFT, {FmtMono, FmtMSADPCM}}, FormatMap{AL_FORMAT_MONO_MULAW, {FmtMono, FmtMulaw} }, FormatMap{AL_FORMAT_MONO_ALAW_EXT, {FmtMono, FmtAlaw} }, FormatMap{AL_FORMAT_STEREO8, {FmtStereo, FmtUByte} }, FormatMap{AL_FORMAT_STEREO16, {FmtStereo, FmtShort} }, FormatMap{AL_FORMAT_STEREO_I32, {FmtStereo, FmtInt} }, FormatMap{AL_FORMAT_STEREO_FLOAT32, {FmtStereo, FmtFloat} }, FormatMap{AL_FORMAT_STEREO_DOUBLE_EXT, {FmtStereo, FmtDouble} }, FormatMap{AL_FORMAT_STEREO_IMA4, {FmtStereo, FmtIMA4} }, FormatMap{AL_FORMAT_STEREO_MSADPCM_SOFT, {FmtStereo, FmtMSADPCM}}, FormatMap{AL_FORMAT_STEREO_MULAW, {FmtStereo, FmtMulaw} }, FormatMap{AL_FORMAT_STEREO_ALAW_EXT, {FmtStereo, FmtAlaw} }, FormatMap{AL_FORMAT_REAR8, {FmtRear, FmtUByte}}, FormatMap{AL_FORMAT_REAR16, {FmtRear, FmtShort}}, FormatMap{AL_FORMAT_REAR32, {FmtRear, FmtFloat}}, FormatMap{AL_FORMAT_REAR_I32, {FmtRear, FmtInt} }, FormatMap{AL_FORMAT_REAR_FLOAT32, {FmtRear, FmtFloat}}, FormatMap{AL_FORMAT_REAR_MULAW, {FmtRear, FmtMulaw}}, FormatMap{AL_FORMAT_QUAD8_LOKI, {FmtQuad, FmtUByte}}, FormatMap{AL_FORMAT_QUAD16_LOKI, {FmtQuad, FmtShort}}, FormatMap{AL_FORMAT_QUAD8, {FmtQuad, FmtUByte}}, FormatMap{AL_FORMAT_QUAD16, {FmtQuad, FmtShort}}, FormatMap{AL_FORMAT_QUAD32, {FmtQuad, FmtFloat}}, FormatMap{AL_FORMAT_QUAD_I32, {FmtQuad, FmtInt} }, FormatMap{AL_FORMAT_QUAD_FLOAT32, {FmtQuad, FmtFloat}}, FormatMap{AL_FORMAT_QUAD_MULAW, {FmtQuad, FmtMulaw}}, FormatMap{AL_FORMAT_51CHN8, {FmtX51, FmtUByte}}, FormatMap{AL_FORMAT_51CHN16, {FmtX51, FmtShort}}, FormatMap{AL_FORMAT_51CHN32, {FmtX51, FmtFloat}}, FormatMap{AL_FORMAT_51CHN_I32, {FmtX51, FmtInt} }, FormatMap{AL_FORMAT_51CHN_FLOAT32, {FmtX51, FmtFloat}}, FormatMap{AL_FORMAT_51CHN_MULAW, {FmtX51, FmtMulaw}}, FormatMap{AL_FORMAT_61CHN8, {FmtX61, FmtUByte}}, FormatMap{AL_FORMAT_61CHN16, {FmtX61, FmtShort}}, FormatMap{AL_FORMAT_61CHN32, {FmtX61, FmtFloat}}, FormatMap{AL_FORMAT_61CHN_I32, {FmtX61, FmtInt} }, FormatMap{AL_FORMAT_61CHN_FLOAT32, {FmtX61, FmtFloat}}, FormatMap{AL_FORMAT_61CHN_MULAW, {FmtX61, FmtMulaw}}, FormatMap{AL_FORMAT_71CHN8, {FmtX71, FmtUByte}}, FormatMap{AL_FORMAT_71CHN16, {FmtX71, FmtShort}}, FormatMap{AL_FORMAT_71CHN32, {FmtX71, FmtFloat}}, FormatMap{AL_FORMAT_71CHN_I32, {FmtX71, FmtInt} }, FormatMap{AL_FORMAT_71CHN_FLOAT32, {FmtX71, FmtFloat}}, FormatMap{AL_FORMAT_71CHN_MULAW, {FmtX71, FmtMulaw}}, FormatMap{AL_FORMAT_BFORMAT2D_8, {FmtBFormat2D, FmtUByte}}, FormatMap{AL_FORMAT_BFORMAT2D_16, {FmtBFormat2D, FmtShort}}, FormatMap{AL_FORMAT_BFORMAT2D_I32, {FmtBFormat2D, FmtInt} }, FormatMap{AL_FORMAT_BFORMAT2D_FLOAT32, {FmtBFormat2D, FmtFloat}}, FormatMap{AL_FORMAT_BFORMAT2D_MULAW, {FmtBFormat2D, FmtMulaw}}, FormatMap{AL_FORMAT_BFORMAT3D_8, {FmtBFormat3D, FmtUByte}}, FormatMap{AL_FORMAT_BFORMAT3D_16, {FmtBFormat3D, FmtShort}}, FormatMap{AL_FORMAT_BFORMAT2D_I32, {FmtBFormat3D, FmtInt} }, FormatMap{AL_FORMAT_BFORMAT3D_FLOAT32, {FmtBFormat3D, FmtFloat}}, FormatMap{AL_FORMAT_BFORMAT3D_MULAW, {FmtBFormat3D, FmtMulaw}}, FormatMap{AL_FORMAT_UHJ2CHN8_SOFT, {FmtUHJ2, FmtUByte} }, FormatMap{AL_FORMAT_UHJ2CHN16_SOFT, {FmtUHJ2, FmtShort} }, FormatMap{AL_FORMAT_UHJ2CHN_I32_SOFT, {FmtUHJ2, FmtInt} }, FormatMap{AL_FORMAT_UHJ2CHN_FLOAT32_SOFT, {FmtUHJ2, FmtFloat} }, FormatMap{AL_FORMAT_UHJ2CHN_MULAW_SOFT, {FmtUHJ2, FmtMulaw} }, FormatMap{AL_FORMAT_UHJ2CHN_ALAW_SOFT, {FmtUHJ2, FmtAlaw} }, FormatMap{AL_FORMAT_UHJ2CHN_IMA4_SOFT, {FmtUHJ2, FmtIMA4} }, FormatMap{AL_FORMAT_UHJ2CHN_MSADPCM_SOFT, {FmtUHJ2, FmtMSADPCM}}, FormatMap{AL_FORMAT_UHJ3CHN8_SOFT, {FmtUHJ3, FmtUByte}}, FormatMap{AL_FORMAT_UHJ3CHN16_SOFT, {FmtUHJ3, FmtShort}}, FormatMap{AL_FORMAT_UHJ3CHN_I32_SOFT, {FmtUHJ3, FmtInt} }, FormatMap{AL_FORMAT_UHJ3CHN_FLOAT32_SOFT, {FmtUHJ3, FmtFloat}}, FormatMap{AL_FORMAT_UHJ3CHN_MULAW_SOFT, {FmtUHJ3, FmtMulaw}}, FormatMap{AL_FORMAT_UHJ3CHN_ALAW_SOFT, {FmtUHJ3, FmtAlaw} }, FormatMap{AL_FORMAT_UHJ4CHN8_SOFT, {FmtUHJ4, FmtUByte}}, FormatMap{AL_FORMAT_UHJ4CHN16_SOFT, {FmtUHJ4, FmtShort}}, FormatMap{AL_FORMAT_UHJ4CHN_I32_SOFT, {FmtUHJ4, FmtInt} }, FormatMap{AL_FORMAT_UHJ4CHN_FLOAT32_SOFT, {FmtUHJ4, FmtFloat}}, FormatMap{AL_FORMAT_UHJ4CHN_MULAW_SOFT, {FmtUHJ4, FmtMulaw}}, FormatMap{AL_FORMAT_UHJ4CHN_ALAW_SOFT, {FmtUHJ4, FmtAlaw} }, }; auto iter = std::find_if(UserFmtList.cbegin(), UserFmtList.cend(), [format](const FormatMap &fmt) noexcept { return fmt.format == format; }); if(iter != UserFmtList.cend()) return iter->result; return std::nullopt; } } // namespace AL_API DECL_FUNC2(void, alGenBuffers, ALsizei,n, ALuint*,buffers) FORCE_ALIGN void AL_APIENTRY alGenBuffersDirect(ALCcontext *context, ALsizei n, ALuint *buffers) noexcept try { if(n < 0) context->throw_error(AL_INVALID_VALUE, "Generating {} buffers", n); if(n <= 0) UNLIKELY return; auto *device = context->mALDevice.get(); auto buflock = std::lock_guard{device->BufferLock}; const al::span bids{buffers, static_cast(n)}; if(!EnsureBuffers(device, bids.size())) context->throw_error(AL_OUT_OF_MEMORY, "Failed to allocate {} buffer{}", n, (n==1) ? "" : "s"); std::generate(bids.begin(), bids.end(), [device]{ return AllocBuffer(device)->id; }); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC2(void, alDeleteBuffers, ALsizei,n, const ALuint*,buffers) FORCE_ALIGN void AL_APIENTRY alDeleteBuffersDirect(ALCcontext *context, ALsizei n, const ALuint *buffers) noexcept try { if(n < 0) context->throw_error(AL_INVALID_VALUE, "Deleting {} buffers", n); if(n <= 0) UNLIKELY return; auto *device = context->mALDevice.get(); auto buflock = std::lock_guard{device->BufferLock}; /* First try to find any buffers that are invalid or in-use. */ auto validate_buffer = [context,device](const ALuint bid) { if(!bid) return; ALbuffer *ALBuf{LookupBuffer(device, bid)}; if(!ALBuf) context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", bid); if(ALBuf->ref.load(std::memory_order_relaxed) != 0) context->throw_error(AL_INVALID_OPERATION, "Deleting in-use buffer {}", bid); }; const al::span bids{buffers, static_cast(n)}; std::for_each(bids.begin(), bids.end(), validate_buffer); /* All good. Delete non-0 buffer IDs. */ auto delete_buffer = [device](const ALuint bid) -> void { if(ALbuffer *buffer{bid ? LookupBuffer(device, bid) : nullptr}) FreeBuffer(device, buffer); }; std::for_each(bids.begin(), bids.end(), delete_buffer); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC1(ALboolean, alIsBuffer, ALuint,buffer) FORCE_ALIGN ALboolean AL_APIENTRY alIsBufferDirect(ALCcontext *context, ALuint buffer) noexcept { auto *device = context->mALDevice.get(); auto buflock = std::lock_guard{device->BufferLock}; if(!buffer || LookupBuffer(device, buffer)) return AL_TRUE; return AL_FALSE; } AL_API void AL_APIENTRY alBufferData(ALuint buffer, ALenum format, const ALvoid *data, ALsizei size, ALsizei freq) noexcept { auto context = GetContextRef(); if(!context) UNLIKELY return; alBufferStorageDirectSOFT(context.get(), buffer, format, data, size, freq, 0); } FORCE_ALIGN void AL_APIENTRY alBufferDataDirect(ALCcontext *context, ALuint buffer, ALenum format, const ALvoid *data, ALsizei size, ALsizei freq) noexcept { alBufferStorageDirectSOFT(context, buffer, format, data, size, freq, 0); } AL_API DECL_FUNCEXT6(void, alBufferStorage,SOFT, ALuint,buffer, ALenum,format, const ALvoid*,data, ALsizei,size, ALsizei,freq, ALbitfieldSOFT,flags) FORCE_ALIGN void AL_APIENTRY alBufferStorageDirectSOFT(ALCcontext *context, ALuint buffer, ALenum format, const ALvoid *data, ALsizei size, ALsizei freq, ALbitfieldSOFT flags) noexcept try { auto *device = context->mALDevice.get(); auto buflock = std::lock_guard{device->BufferLock}; ALbuffer *albuf{LookupBuffer(device, buffer)}; if(!albuf) context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", buffer); if(size < 0) context->throw_error(AL_INVALID_VALUE, "Negative storage size {}", size); if(freq < 1) context->throw_error(AL_INVALID_VALUE, "Invalid sample rate {}", freq); if((flags&INVALID_STORAGE_MASK) != 0) context->throw_error(AL_INVALID_VALUE, "Invalid storage flags {:#x}", flags&INVALID_STORAGE_MASK); if((flags&AL_MAP_PERSISTENT_BIT_SOFT) && !(flags&MAP_READ_WRITE_FLAGS)) context->throw_error(AL_INVALID_VALUE, "Declaring persistently mapped storage without read or write access"); auto usrfmt = DecomposeUserFormat(format); if(!usrfmt) context->throw_error(AL_INVALID_ENUM, "Invalid format {:#04x}", as_unsigned(format)); auto bdata = static_cast(data); LoadData(context, albuf, freq, static_cast(size), usrfmt->channels, usrfmt->type, al::span{bdata, bdata ? static_cast(size) : 0u}, flags); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } FORCE_ALIGN DECL_FUNC5(void, alBufferDataStatic, ALuint,buffer, ALenum,format, ALvoid*,data, ALsizei,size, ALsizei,freq) FORCE_ALIGN void AL_APIENTRY alBufferDataStaticDirect(ALCcontext *context, const ALuint buffer, ALenum format, ALvoid *data, ALsizei size, ALsizei freq) noexcept try { auto *device = context->mALDevice.get(); auto buflock = std::lock_guard{device->BufferLock}; ALbuffer *albuf{LookupBuffer(device, buffer)}; if(!albuf) context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", buffer); if(size < 0) context->throw_error(AL_INVALID_VALUE, "Negative storage size {}", size); if(freq < 1) context->throw_error(AL_INVALID_VALUE, "Invalid sample rate {}", freq); auto usrfmt = DecomposeUserFormat(format); if(!usrfmt) context->throw_error(AL_INVALID_ENUM, "Invalid format {:#04x}", as_unsigned(format)); PrepareUserPtr(context, albuf, freq, usrfmt->channels, usrfmt->type, static_cast(data), static_cast(size)); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNCEXT4(void*, alMapBuffer,SOFT, ALuint,buffer, ALsizei,offset, ALsizei,length, ALbitfieldSOFT,access) FORCE_ALIGN void* AL_APIENTRY alMapBufferDirectSOFT(ALCcontext *context, ALuint buffer, ALsizei offset, ALsizei length, ALbitfieldSOFT access) noexcept try { auto *device = context->mALDevice.get(); auto buflock = std::lock_guard{device->BufferLock}; ALbuffer *albuf{LookupBuffer(device, buffer)}; if(!albuf) context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", buffer); if((access&INVALID_MAP_FLAGS) != 0) context->throw_error(AL_INVALID_VALUE, "Invalid map flags {:#x}", access&INVALID_MAP_FLAGS); if(!(access&MAP_READ_WRITE_FLAGS)) context->throw_error(AL_INVALID_VALUE, "Mapping buffer {} without read or write access", buffer); const ALbitfieldSOFT unavailable{(albuf->Access^access) & access}; if(albuf->ref.load(std::memory_order_relaxed) != 0 && !(access&AL_MAP_PERSISTENT_BIT_SOFT)) context->throw_error(AL_INVALID_OPERATION, "Mapping in-use buffer {} without persistent mapping", buffer); if(albuf->MappedAccess != 0) context->throw_error(AL_INVALID_OPERATION, "Mapping already-mapped buffer {}", buffer); if((unavailable&AL_MAP_READ_BIT_SOFT)) context->throw_error(AL_INVALID_VALUE, "Mapping buffer {} for reading without read access", buffer); if((unavailable&AL_MAP_WRITE_BIT_SOFT)) context->throw_error(AL_INVALID_VALUE, "Mapping buffer {} for writing without write access", buffer); if((unavailable&AL_MAP_PERSISTENT_BIT_SOFT)) context->throw_error(AL_INVALID_VALUE, "Mapping buffer {} persistently without persistent access", buffer); if(offset < 0 || length <= 0 || static_cast(offset) >= albuf->OriginalSize || static_cast(length) > albuf->OriginalSize - static_cast(offset)) context->throw_error(AL_INVALID_VALUE, "Mapping invalid range {}+{} for buffer {}", offset, length, buffer); void *retval{albuf->mData.data() + offset}; albuf->MappedAccess = access; albuf->MappedOffset = offset; albuf->MappedSize = length; return retval; } catch(al::base_exception&) { return nullptr; } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); return nullptr; } AL_API DECL_FUNCEXT1(void, alUnmapBuffer,SOFT, ALuint,buffer) FORCE_ALIGN void AL_APIENTRY alUnmapBufferDirectSOFT(ALCcontext *context, ALuint buffer) noexcept try { auto *device = context->mALDevice.get(); auto buflock = std::lock_guard{device->BufferLock}; ALbuffer *albuf{LookupBuffer(device, buffer)}; if(!albuf) context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", buffer); if(albuf->MappedAccess == 0) context->throw_error(AL_INVALID_OPERATION, "Unmapping unmapped buffer {}", buffer); albuf->MappedAccess = 0; albuf->MappedOffset = 0; albuf->MappedSize = 0; } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNCEXT3(void, alFlushMappedBuffer,SOFT, ALuint,buffer, ALsizei,offset, ALsizei,length) FORCE_ALIGN void AL_APIENTRY alFlushMappedBufferDirectSOFT(ALCcontext *context, ALuint buffer, ALsizei offset, ALsizei length) noexcept try { auto *device = context->mALDevice.get(); auto buflock = std::lock_guard{device->BufferLock}; ALbuffer *albuf{LookupBuffer(device, buffer)}; if(!albuf) context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", buffer); if(!(albuf->MappedAccess&AL_MAP_WRITE_BIT_SOFT)) context->throw_error(AL_INVALID_OPERATION, "Flushing buffer {} while not mapped for writing", buffer); if(offset < albuf->MappedOffset || length <= 0 || offset >= albuf->MappedOffset+albuf->MappedSize || length > albuf->MappedOffset+albuf->MappedSize-offset) context->throw_error(AL_INVALID_VALUE, "Flushing invalid range {}+{} on buffer {}", offset, length, buffer); /* FIXME: Need to use some method of double-buffering for the mixer and app * to hold separate memory, which can be safely transferred asynchronously. * Currently we just say the app shouldn't write where OpenAL's reading, * and hope for the best... */ std::atomic_thread_fence(std::memory_order_seq_cst); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNCEXT5(void, alBufferSubData,SOFT, ALuint,buffer, ALenum,format, const ALvoid*,data, ALsizei,offset, ALsizei,length) FORCE_ALIGN void AL_APIENTRY alBufferSubDataDirectSOFT(ALCcontext *context, ALuint buffer, ALenum format, const ALvoid *data, ALsizei offset, ALsizei length) noexcept try { auto *device = context->mALDevice.get(); auto buflock = std::lock_guard{device->BufferLock}; ALbuffer *albuf{LookupBuffer(device, buffer)}; if(!albuf) context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", buffer); auto usrfmt = DecomposeUserFormat(format); if(!usrfmt) context->throw_error(AL_INVALID_ENUM, "Invalid format {:#04x}", as_unsigned(format)); const ALuint unpack_align{albuf->UnpackAlign}; const ALuint align{SanitizeAlignment(usrfmt->type, unpack_align)}; if(align < 1) context->throw_error(AL_INVALID_VALUE, "Invalid unpack alignment {}", unpack_align); if(usrfmt->channels != albuf->mChannels || usrfmt->type != albuf->mType) context->throw_error(AL_INVALID_ENUM, "Unpacking data with mismatched format"); if(align != albuf->mBlockAlign) context->throw_error(AL_INVALID_VALUE, "Unpacking data with alignment {} does not match original alignment {}", align, albuf->mBlockAlign); if(albuf->isBFormat() && albuf->UnpackAmbiOrder != albuf->mAmbiOrder) context->throw_error(AL_INVALID_VALUE, "Unpacking data with mismatched ambisonic order"); if(albuf->MappedAccess != 0) context->throw_error(AL_INVALID_OPERATION, "Unpacking data into mapped buffer {}", buffer); const ALuint num_chans{albuf->channelsFromFmt()}; const ALuint byte_align{ (albuf->mType == FmtIMA4) ? ((align-1)/2 + 4) * num_chans : (albuf->mType == FmtMSADPCM) ? ((align-2)/2 + 7) * num_chans : (align * albuf->bytesFromFmt() * num_chans)}; if(offset < 0 || length < 0 || static_cast(offset) > albuf->OriginalSize || static_cast(length) > albuf->OriginalSize-static_cast(offset)) context->throw_error(AL_INVALID_VALUE, "Invalid data sub-range {}+{} on buffer {}", offset, length, buffer); if((static_cast(offset)%byte_align) != 0) context->throw_error(AL_INVALID_VALUE, "Sub-range offset {} is not a multiple of frame size {} ({} unpack alignment)", offset, byte_align, align); if((static_cast(length)%byte_align) != 0) context->throw_error(AL_INVALID_VALUE, "Sub-range length {} is not a multiple of frame size {} ({} unpack alignment)", length, byte_align, align); std::memcpy(albuf->mData.data()+offset, data, static_cast(length)); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alBufferf, ALuint,buffer, ALenum,param, ALfloat,value) FORCE_ALIGN void AL_APIENTRY alBufferfDirect(ALCcontext *context, ALuint buffer, ALenum param, ALfloat value [[maybe_unused]]) noexcept try { auto *device = context->mALDevice.get(); auto buflock = std::lock_guard{device->BufferLock}; if(LookupBuffer(device, buffer) == nullptr) context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", buffer); context->throw_error(AL_INVALID_ENUM, "Invalid buffer float property {:#04x}", as_unsigned(param)); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC5(void, alBuffer3f, ALuint,buffer, ALenum,param, ALfloat,value1, ALfloat,value2, ALfloat,value3) FORCE_ALIGN void AL_APIENTRY alBuffer3fDirect(ALCcontext *context, ALuint buffer, ALenum param, ALfloat value1 [[maybe_unused]], ALfloat value2 [[maybe_unused]], ALfloat value3 [[maybe_unused]]) noexcept try { auto *device = context->mALDevice.get(); auto buflock = std::lock_guard{device->BufferLock}; if(LookupBuffer(device, buffer) == nullptr) context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", buffer); context->throw_error(AL_INVALID_ENUM, "Invalid buffer 3-float property {:#04x}", as_unsigned(param)); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alBufferfv, ALuint,buffer, ALenum,param, const ALfloat*,values) FORCE_ALIGN void AL_APIENTRY alBufferfvDirect(ALCcontext *context, ALuint buffer, ALenum param, const ALfloat *values) noexcept try { auto *device = context->mALDevice.get(); auto buflock = std::lock_guard{device->BufferLock}; if(LookupBuffer(device, buffer) == nullptr) context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", buffer); if(!values) context->throw_error(AL_INVALID_VALUE, "NULL pointer"); context->throw_error(AL_INVALID_ENUM, "Invalid buffer float-vector property {:#04x}", as_unsigned(param)); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alBufferi, ALuint,buffer, ALenum,param, ALint,value) FORCE_ALIGN void AL_APIENTRY alBufferiDirect(ALCcontext *context, ALuint buffer, ALenum param, ALint value) noexcept try { auto *device = context->mALDevice.get(); auto buflock = std::lock_guard{device->BufferLock}; ALbuffer *albuf{LookupBuffer(device, buffer)}; if(!albuf) context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", buffer); switch(param) { case AL_UNPACK_BLOCK_ALIGNMENT_SOFT: if(value < 0) context->throw_error(AL_INVALID_VALUE, "Invalid unpack block alignment {}", value); albuf->UnpackAlign = static_cast(value); return; case AL_PACK_BLOCK_ALIGNMENT_SOFT: if(value < 0) context->throw_error(AL_INVALID_VALUE, "Invalid pack block alignment {}", value); albuf->PackAlign = static_cast(value); return; case AL_AMBISONIC_LAYOUT_SOFT: if(albuf->ref.load(std::memory_order_relaxed) != 0) context->throw_error(AL_INVALID_OPERATION, "Modifying in-use buffer {}'s ambisonic layout", buffer); if(const auto layout = AmbiLayoutFromEnum(value)) { if(layout.value() == AmbiLayout::FuMa && albuf->mAmbiOrder > 3) context->throw_error(AL_INVALID_OPERATION, "Cannot set FuMa layout for {}{} order B-Format data", albuf->mAmbiOrder, GetCounterSuffix(albuf->mAmbiOrder)); albuf->mAmbiLayout = layout.value(); return; } context->throw_error(AL_INVALID_VALUE, "Invalid unpack ambisonic layout {:#04x}", as_unsigned(value)); case AL_AMBISONIC_SCALING_SOFT: if(albuf->ref.load(std::memory_order_relaxed) != 0) context->throw_error(AL_INVALID_OPERATION, "Modifying in-use buffer {}'s ambisonic scaling", buffer); if(const auto scaling = AmbiScalingFromEnum(value)) { if(scaling.value() == AmbiScaling::FuMa && albuf->mAmbiOrder > 3) context->throw_error(AL_INVALID_OPERATION, "Cannot set FuMa scaling for {}{} order B-Format data", albuf->mAmbiOrder, GetCounterSuffix(albuf->mAmbiOrder)); albuf->mAmbiScaling = scaling.value(); return; } context->throw_error(AL_INVALID_VALUE, "Invalid unpack ambisonic scaling {:#04x}", as_unsigned(value)); case AL_UNPACK_AMBISONIC_ORDER_SOFT: if(value < 1 || value > 14) context->throw_error(AL_INVALID_VALUE, "Invalid unpack ambisonic order {}", value); albuf->UnpackAmbiOrder = static_cast(value); return; } context->throw_error(AL_INVALID_ENUM, "Invalid buffer integer property {:#04x}", as_unsigned(param)); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC5(void, alBuffer3i, ALuint,buffer, ALenum,param, ALint,value1, ALint,value2, ALint,value3) FORCE_ALIGN void AL_APIENTRY alBuffer3iDirect(ALCcontext *context, ALuint buffer, ALenum param, ALint value1 [[maybe_unused]], ALint value2 [[maybe_unused]], ALint value3 [[maybe_unused]]) noexcept try { auto *device = context->mALDevice.get(); auto buflock = std::lock_guard{device->BufferLock}; if(LookupBuffer(device, buffer) == nullptr) context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", buffer); context->throw_error(AL_INVALID_ENUM, "Invalid buffer 3-integer property {:#04x}", as_unsigned(param)); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alBufferiv, ALuint,buffer, ALenum,param, const ALint*,values) FORCE_ALIGN void AL_APIENTRY alBufferivDirect(ALCcontext *context, ALuint buffer, ALenum param, const ALint *values) noexcept try { if(!values) context->throw_error(AL_INVALID_VALUE, "NULL pointer"); switch(param) { case AL_UNPACK_BLOCK_ALIGNMENT_SOFT: case AL_PACK_BLOCK_ALIGNMENT_SOFT: case AL_AMBISONIC_LAYOUT_SOFT: case AL_AMBISONIC_SCALING_SOFT: case AL_UNPACK_AMBISONIC_ORDER_SOFT: alBufferiDirect(context, buffer, param, *values); return; } auto *device = context->mALDevice.get(); auto buflock = std::lock_guard{device->BufferLock}; ALbuffer *albuf{LookupBuffer(device, buffer)}; if(!albuf) context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", buffer); switch(param) { case AL_LOOP_POINTS_SOFT: auto vals = al::span{values, 2_uz}; if(albuf->ref.load(std::memory_order_relaxed) != 0) context->throw_error(AL_INVALID_OPERATION, "Modifying in-use buffer {}'s loop points", buffer); if(vals[0] < 0 || vals[0] >= vals[1] || static_cast(vals[1]) > albuf->mSampleLen) context->throw_error(AL_INVALID_VALUE, "Invalid loop point range {} -> {} on buffer {}", vals[0], vals[1], buffer); albuf->mLoopStart = static_cast(vals[0]); albuf->mLoopEnd = static_cast(vals[1]); return; } context->throw_error(AL_INVALID_ENUM, "Invalid buffer integer-vector property {:#04x}", as_unsigned(param)); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alGetBufferf, ALuint,buffer, ALenum,param, ALfloat*,value) FORCE_ALIGN void AL_APIENTRY alGetBufferfDirect(ALCcontext *context, ALuint buffer, ALenum param, ALfloat *value) noexcept try { auto *device = context->mALDevice.get(); auto buflock = std::lock_guard{device->BufferLock}; ALbuffer *albuf{LookupBuffer(device, buffer)}; if(!albuf) context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", buffer); if(!value) context->throw_error(AL_INVALID_VALUE, "NULL pointer"); switch(param) { case AL_SEC_LENGTH_SOFT: *value = (albuf->mSampleRate < 1) ? 0.0f : (static_cast(albuf->mSampleLen) / static_cast(albuf->mSampleRate)); return; } context->throw_error(AL_INVALID_ENUM, "Invalid buffer float property {:#04x}", as_unsigned(param)); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC5(void, alGetBuffer3f, ALuint,buffer, ALenum,param, ALfloat*,value1, ALfloat*,value2, ALfloat*,value3) FORCE_ALIGN void AL_APIENTRY alGetBuffer3fDirect(ALCcontext *context, ALuint buffer, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3) noexcept try { auto *device = context->mALDevice.get(); auto buflock = std::lock_guard{device->BufferLock}; if(LookupBuffer(device, buffer) == nullptr) context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", buffer); if(!value1 || !value2 || !value3) context->throw_error(AL_INVALID_VALUE, "NULL pointer"); context->throw_error(AL_INVALID_ENUM, "Invalid buffer 3-float property {:#04x}", as_unsigned(param)); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alGetBufferfv, ALuint,buffer, ALenum,param, ALfloat*,values) FORCE_ALIGN void AL_APIENTRY alGetBufferfvDirect(ALCcontext *context, ALuint buffer, ALenum param, ALfloat *values) noexcept try { switch(param) { case AL_SEC_LENGTH_SOFT: alGetBufferfDirect(context, buffer, param, values); return; } auto *device = context->mALDevice.get(); auto buflock = std::lock_guard{device->BufferLock}; if(LookupBuffer(device, buffer) == nullptr) context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", buffer); if(!values) context->throw_error(AL_INVALID_VALUE, "NULL pointer"); context->throw_error(AL_INVALID_ENUM, "Invalid buffer float-vector property {:#04x}", as_unsigned(param)); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alGetBufferi, ALuint,buffer, ALenum,param, ALint*,value) FORCE_ALIGN void AL_APIENTRY alGetBufferiDirect(ALCcontext *context, ALuint buffer, ALenum param, ALint *value) noexcept try { auto *device = context->mALDevice.get(); auto buflock = std::lock_guard{device->BufferLock}; ALbuffer *albuf{LookupBuffer(device, buffer)}; if(!albuf) context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", buffer); if(!value) context->throw_error(AL_INVALID_VALUE, "NULL pointer"); switch(param) { case AL_FREQUENCY: *value = static_cast(albuf->mSampleRate); return; case AL_BITS: *value = (albuf->mType == FmtIMA4 || albuf->mType == FmtMSADPCM) ? 4 : static_cast(albuf->bytesFromFmt() * 8); return; case AL_CHANNELS: *value = static_cast(albuf->channelsFromFmt()); return; case AL_SIZE: *value = albuf->mCallback ? 0 : static_cast(albuf->mData.size()); return; case AL_BYTE_LENGTH_SOFT: *value = static_cast(albuf->mSampleLen / albuf->mBlockAlign * albuf->blockSizeFromFmt()); return; case AL_SAMPLE_LENGTH_SOFT: *value = static_cast(albuf->mSampleLen); return; case AL_UNPACK_BLOCK_ALIGNMENT_SOFT: *value = static_cast(albuf->UnpackAlign); return; case AL_PACK_BLOCK_ALIGNMENT_SOFT: *value = static_cast(albuf->PackAlign); return; case AL_AMBISONIC_LAYOUT_SOFT: *value = EnumFromAmbiLayout(albuf->mAmbiLayout); return; case AL_AMBISONIC_SCALING_SOFT: *value = EnumFromAmbiScaling(albuf->mAmbiScaling); return; case AL_UNPACK_AMBISONIC_ORDER_SOFT: *value = static_cast(albuf->UnpackAmbiOrder); return; } context->throw_error(AL_INVALID_ENUM, "Invalid buffer integer property {:#04x}", as_unsigned(param)); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC5(void, alGetBuffer3i, ALuint,buffer, ALenum,param, ALint*,value1, ALint*,value2, ALint*,value3) FORCE_ALIGN void AL_APIENTRY alGetBuffer3iDirect(ALCcontext *context, ALuint buffer, ALenum param, ALint *value1, ALint *value2, ALint *value3) noexcept try { auto *device = context->mALDevice.get(); auto buflock = std::lock_guard{device->BufferLock}; if(LookupBuffer(device, buffer) == nullptr) context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", buffer); if(!value1 || !value2 || !value3) context->throw_error(AL_INVALID_VALUE, "NULL pointer"); context->throw_error(AL_INVALID_ENUM, "Invalid buffer 3-integer property {:#04x}", as_unsigned(param)); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alGetBufferiv, ALuint,buffer, ALenum,param, ALint*,values) FORCE_ALIGN void AL_APIENTRY alGetBufferivDirect(ALCcontext *context, ALuint buffer, ALenum param, ALint *values) noexcept try { switch(param) { case AL_FREQUENCY: case AL_BITS: case AL_CHANNELS: case AL_SIZE: case AL_INTERNAL_FORMAT_SOFT: case AL_BYTE_LENGTH_SOFT: case AL_SAMPLE_LENGTH_SOFT: case AL_UNPACK_BLOCK_ALIGNMENT_SOFT: case AL_PACK_BLOCK_ALIGNMENT_SOFT: case AL_AMBISONIC_LAYOUT_SOFT: case AL_AMBISONIC_SCALING_SOFT: case AL_UNPACK_AMBISONIC_ORDER_SOFT: alGetBufferiDirect(context, buffer, param, values); return; } auto *device = context->mALDevice.get(); auto buflock = std::lock_guard{device->BufferLock}; ALbuffer *albuf{LookupBuffer(device, buffer)}; if(!albuf) context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", buffer); if(!values) context->throw_error(AL_INVALID_VALUE, "NULL pointer"); switch(param) { case AL_LOOP_POINTS_SOFT: auto vals = al::span{values, 2_uz}; vals[0] = static_cast(albuf->mLoopStart); vals[1] = static_cast(albuf->mLoopEnd); return; } context->throw_error(AL_INVALID_ENUM, "Invalid buffer integer-vector property {:#04x}", as_unsigned(param)); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNCEXT5(void, alBufferCallback,SOFT, ALuint,buffer, ALenum,format, ALsizei,freq, ALBUFFERCALLBACKTYPESOFT,callback, ALvoid*,userptr) FORCE_ALIGN void AL_APIENTRY alBufferCallbackDirectSOFT(ALCcontext *context, ALuint buffer, ALenum format, ALsizei freq, ALBUFFERCALLBACKTYPESOFT callback, ALvoid *userptr) noexcept try { auto *device = context->mALDevice.get(); auto buflock = std::lock_guard{device->BufferLock}; ALbuffer *albuf{LookupBuffer(device, buffer)}; if(!albuf) context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", buffer); if(freq < 1) context->throw_error(AL_INVALID_VALUE, "Invalid sample rate {}", freq); if(callback == nullptr) context->throw_error(AL_INVALID_VALUE, "NULL callback"); auto usrfmt = DecomposeUserFormat(format); if(!usrfmt) context->throw_error(AL_INVALID_ENUM, "Invalid format {:#04x}", as_unsigned(format)); PrepareCallback(context, albuf, freq, usrfmt->channels, usrfmt->type, callback, userptr); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNCEXT3(void, alGetBufferPtr,SOFT, ALuint,buffer, ALenum,param, ALvoid**,value) FORCE_ALIGN void AL_APIENTRY alGetBufferPtrDirectSOFT(ALCcontext *context, ALuint buffer, ALenum param, ALvoid **value) noexcept try { auto *device = context->mALDevice.get(); auto buflock = std::lock_guard{device->BufferLock}; ALbuffer *albuf{LookupBuffer(device, buffer)}; if(!albuf) context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", buffer); if(!value) context->throw_error(AL_INVALID_VALUE, "NULL pointer"); switch(param) { case AL_BUFFER_CALLBACK_FUNCTION_SOFT: *value = reinterpret_cast(albuf->mCallback); return; case AL_BUFFER_CALLBACK_USER_PARAM_SOFT: *value = albuf->mUserData; return; } context->throw_error(AL_INVALID_ENUM, "Invalid buffer pointer property {:#04x}", as_unsigned(param)); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNCEXT5(void, alGetBuffer3Ptr,SOFT, ALuint,buffer, ALenum,param, ALvoid**,value1, ALvoid**,value2, ALvoid**,value3) FORCE_ALIGN void AL_APIENTRY alGetBuffer3PtrDirectSOFT(ALCcontext *context, ALuint buffer, ALenum param, ALvoid **value1, ALvoid **value2, ALvoid **value3) noexcept try { auto *device = context->mALDevice.get(); auto buflock = std::lock_guard{device->BufferLock}; if(LookupBuffer(device, buffer) == nullptr) context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", buffer); if(!value1 || !value2 || !value3) context->throw_error(AL_INVALID_VALUE, "NULL pointer"); context->throw_error(AL_INVALID_ENUM, "Invalid buffer 3-pointer property {:#04x}", as_unsigned(param)); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNCEXT3(void, alGetBufferPtrv,SOFT, ALuint,buffer, ALenum,param, ALvoid**,values) FORCE_ALIGN void AL_APIENTRY alGetBufferPtrvDirectSOFT(ALCcontext *context, ALuint buffer, ALenum param, ALvoid **values) noexcept try { switch(param) { case AL_BUFFER_CALLBACK_FUNCTION_SOFT: case AL_BUFFER_CALLBACK_USER_PARAM_SOFT: alGetBufferPtrDirectSOFT(context, buffer, param, values); return; } auto *device = context->mALDevice.get(); auto buflock = std::lock_guard{device->BufferLock}; if(LookupBuffer(device, buffer) == nullptr) context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", buffer); if(!values) context->throw_error(AL_INVALID_VALUE, "NULL pointer"); context->throw_error(AL_INVALID_ENUM, "Invalid buffer pointer-vector property {:#04x}", as_unsigned(param)); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API void AL_APIENTRY alBufferSamplesSOFT(ALuint /*buffer*/, ALuint /*samplerate*/, ALenum /*internalformat*/, ALsizei /*samples*/, ALenum /*channels*/, ALenum /*type*/, const ALvoid* /*data*/) noexcept { ContextRef context{GetContextRef()}; if(!context) UNLIKELY return; context->setError(AL_INVALID_OPERATION, "alBufferSamplesSOFT not supported"); } AL_API void AL_APIENTRY alBufferSubSamplesSOFT(ALuint /*buffer*/, ALsizei /*offset*/, ALsizei /*samples*/, ALenum /*channels*/, ALenum /*type*/, const ALvoid* /*data*/) noexcept { ContextRef context{GetContextRef()}; if(!context) UNLIKELY return; context->setError(AL_INVALID_OPERATION, "alBufferSubSamplesSOFT not supported"); } AL_API void AL_APIENTRY alGetBufferSamplesSOFT(ALuint /*buffer*/, ALsizei /*offset*/, ALsizei /*samples*/, ALenum /*channels*/, ALenum /*type*/, ALvoid* /*data*/) noexcept { ContextRef context{GetContextRef()}; if(!context) UNLIKELY return; context->setError(AL_INVALID_OPERATION, "alGetBufferSamplesSOFT not supported"); } AL_API ALboolean AL_APIENTRY alIsBufferFormatSupportedSOFT(ALenum /*format*/) noexcept { ContextRef context{GetContextRef()}; if(!context) UNLIKELY return AL_FALSE; context->setError(AL_INVALID_OPERATION, "alIsBufferFormatSupportedSOFT not supported"); return AL_FALSE; } void ALbuffer::SetName(ALCcontext *context, ALuint id, std::string_view name) { auto *device = context->mALDevice.get(); auto buflock = std::lock_guard{device->BufferLock}; auto buffer = LookupBuffer(device, id); if(!buffer) context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", id); device->mBufferNames.insert_or_assign(id, name); } BufferSubList::~BufferSubList() { if(!Buffers) return; uint64_t usemask{~FreeMask}; while(usemask) { const int idx{al::countr_zero(usemask)}; std::destroy_at(al::to_address(Buffers->begin() + idx)); usemask &= ~(1_u64 << idx); } FreeMask = ~usemask; SubListAllocator{}.deallocate(Buffers, 1); Buffers = nullptr; } #if ALSOFT_EAX FORCE_ALIGN DECL_FUNC3(ALboolean, EAXSetBufferMode, ALsizei,n, const ALuint*,buffers, ALint,value) FORCE_ALIGN ALboolean AL_APIENTRY EAXSetBufferModeDirect(ALCcontext *context, ALsizei n, const ALuint *buffers, ALint value) noexcept try { if(!eax_g_is_enabled) context->throw_error(AL_INVALID_OPERATION, "EAX not enabled"); const auto storage = EaxStorageFromEnum(value); if(!storage) context->throw_error(AL_INVALID_ENUM, "Unsupported X-RAM mode {:#x}", as_unsigned(value)); if(n == 0) return AL_TRUE; if(n < 0) context->throw_error(AL_INVALID_VALUE, "Buffer count {} out of range", n); if(!buffers) context->throw_error(AL_INVALID_VALUE, "Null AL buffers"); auto device = context->mALDevice.get(); std::lock_guard devlock{device->BufferLock}; /* Special-case setting a single buffer, to avoid extraneous allocations. */ if(n == 1) { const auto bufid = *buffers; if(bufid == AL_NONE) return AL_TRUE; const auto buffer = LookupBuffer(device, bufid); if(!buffer) context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", bufid); /* TODO: Is the store location allowed to change for in-use buffers, or * only when not set/queued on a source? */ if(*storage == EaxStorage::Hardware) { if(!buffer->eax_x_ram_is_hardware && buffer->OriginalSize > device->eax_x_ram_free_size) context->throw_error(AL_OUT_OF_MEMORY, "Out of X-RAM memory (need: {}, avail: {})", buffer->OriginalSize, device->eax_x_ram_free_size); eax_x_ram_apply(*device, *buffer); } else eax_x_ram_clear(*device, *buffer); buffer->eax_x_ram_mode = *storage; return AL_TRUE; } /* Validate the buffers. */ std::unordered_set buflist; for(const ALuint bufid : al::span{buffers, static_cast(n)}) { if(bufid == AL_NONE) continue; const auto buffer = LookupBuffer(device, bufid); if(!buffer) context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", bufid); /* TODO: Is the store location allowed to change for in-use buffers, or * only when not set/queued on a source? */ buflist.emplace(buffer); } if(*storage == EaxStorage::Hardware) { size_t total_needed{0}; for(ALbuffer *buffer : buflist) { if(!buffer->eax_x_ram_is_hardware) { if(std::numeric_limits::max() - buffer->OriginalSize < total_needed) context->throw_error(AL_OUT_OF_MEMORY, "Size overflow ({} + {})", buffer->OriginalSize, total_needed); total_needed += buffer->OriginalSize; } } if(total_needed > device->eax_x_ram_free_size) context->throw_error(AL_OUT_OF_MEMORY, "Out of X-RAM memory (need: {}, avail: {})", total_needed, device->eax_x_ram_free_size); } /* Update the mode. */ for(ALbuffer *buffer : buflist) { if(*storage == EaxStorage::Hardware) eax_x_ram_apply(*device, *buffer); else eax_x_ram_clear(*device, *buffer); buffer->eax_x_ram_mode = *storage; } return AL_TRUE; } catch(al::base_exception&) { return AL_FALSE; } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); return AL_FALSE; } FORCE_ALIGN DECL_FUNC2(ALenum, EAXGetBufferMode, ALuint,buffer, ALint*,pReserved) FORCE_ALIGN ALenum AL_APIENTRY EAXGetBufferModeDirect(ALCcontext *context, ALuint buffer, ALint *pReserved) noexcept try { if(!eax_g_is_enabled) context->throw_error(AL_INVALID_OPERATION, "EAX not enabled."); if(pReserved) context->throw_error(AL_INVALID_VALUE, "Non-null reserved parameter"); auto device = context->mALDevice.get(); std::lock_guard devlock{device->BufferLock}; const auto al_buffer = LookupBuffer(device, buffer); if(!al_buffer) context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", buffer); return EnumFromEaxStorage(al_buffer->eax_x_ram_mode); } catch(al::base_exception&) { return AL_NONE; } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); return AL_NONE; } #endif // ALSOFT_EAX openal-soft-1.24.2/al/buffer.h000066400000000000000000000034431474041540300160530ustar00rootroot00000000000000#ifndef AL_BUFFER_H #define AL_BUFFER_H #include "config.h" #include #include #include #include #include #include #include "AL/al.h" #include "AL/alc.h" #include "alc/inprogext.h" #include "almalloc.h" #include "alnumeric.h" #include "core/buffer_storage.h" #include "vector.h" #if ALSOFT_EAX enum class EaxStorage : uint8_t { Automatic, Accessible, Hardware }; #endif // ALSOFT_EAX struct ALbuffer : public BufferStorage { ALbitfieldSOFT Access{0u}; al::vector mDataStorage; ALuint OriginalSize{0}; ALuint UnpackAlign{0}; ALuint PackAlign{0}; ALuint UnpackAmbiOrder{1}; ALbitfieldSOFT MappedAccess{0u}; ALsizei MappedOffset{0}; ALsizei MappedSize{0}; ALuint mLoopStart{0u}; ALuint mLoopEnd{0u}; /* Number of times buffer was attached to a source (deletion can only occur when 0) */ std::atomic ref{0u}; /* Self ID */ ALuint id{0}; static void SetName(ALCcontext *context, ALuint id, std::string_view name); DISABLE_ALLOC #if ALSOFT_EAX EaxStorage eax_x_ram_mode{EaxStorage::Automatic}; bool eax_x_ram_is_hardware{}; #endif // ALSOFT_EAX }; struct BufferSubList { uint64_t FreeMask{~0_u64}; gsl::owner*> Buffers{nullptr}; BufferSubList() noexcept = default; BufferSubList(const BufferSubList&) = delete; BufferSubList(BufferSubList&& rhs) noexcept : FreeMask{rhs.FreeMask}, Buffers{rhs.Buffers} { rhs.FreeMask = ~0_u64; rhs.Buffers = nullptr; } ~BufferSubList(); BufferSubList& operator=(const BufferSubList&) = delete; BufferSubList& operator=(BufferSubList&& rhs) noexcept { std::swap(FreeMask, rhs.FreeMask); std::swap(Buffers, rhs.Buffers); return *this; } }; #endif openal-soft-1.24.2/al/debug.cpp000066400000000000000000000570261474041540300162310ustar00rootroot00000000000000#include "config.h" #include "debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "AL/al.h" #include "AL/alc.h" #include "AL/alext.h" #include "alc/context.h" #include "alc/device.h" #include "alnumeric.h" #include "alspan.h" #include "auxeffectslot.h" #include "buffer.h" #include "core/except.h" #include "core/logging.h" #include "core/voice.h" #include "direct_defs.h" #include "effect.h" #include "filter.h" #include "fmt/core.h" #include "intrusive_ptr.h" #include "opthelpers.h" #include "source.h" /* Declared here to prevent compilers from thinking it should be inlined, which * GCC warns about increasing code size. */ DebugGroup::~DebugGroup() = default; namespace { using namespace std::string_view_literals; static_assert(DebugSeverityBase+DebugSeverityCount <= 32, "Too many debug bits"); template constexpr auto make_array_sequence(std::integer_sequence) { return std::array{Vals...}; } template constexpr auto make_array_sequence() { return make_array_sequence(std::make_integer_sequence{}); } constexpr auto GetDebugSource(ALenum source) noexcept -> std::optional { switch(source) { case AL_DEBUG_SOURCE_API_EXT: return DebugSource::API; case AL_DEBUG_SOURCE_AUDIO_SYSTEM_EXT: return DebugSource::System; case AL_DEBUG_SOURCE_THIRD_PARTY_EXT: return DebugSource::ThirdParty; case AL_DEBUG_SOURCE_APPLICATION_EXT: return DebugSource::Application; case AL_DEBUG_SOURCE_OTHER_EXT: return DebugSource::Other; } return std::nullopt; } constexpr auto GetDebugType(ALenum type) noexcept -> std::optional { switch(type) { case AL_DEBUG_TYPE_ERROR_EXT: return DebugType::Error; case AL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_EXT: return DebugType::DeprecatedBehavior; case AL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_EXT: return DebugType::UndefinedBehavior; case AL_DEBUG_TYPE_PORTABILITY_EXT: return DebugType::Portability; case AL_DEBUG_TYPE_PERFORMANCE_EXT: return DebugType::Performance; case AL_DEBUG_TYPE_MARKER_EXT: return DebugType::Marker; case AL_DEBUG_TYPE_PUSH_GROUP_EXT: return DebugType::PushGroup; case AL_DEBUG_TYPE_POP_GROUP_EXT: return DebugType::PopGroup; case AL_DEBUG_TYPE_OTHER_EXT: return DebugType::Other; } return std::nullopt; } constexpr auto GetDebugSeverity(ALenum severity) noexcept -> std::optional { switch(severity) { case AL_DEBUG_SEVERITY_HIGH_EXT: return DebugSeverity::High; case AL_DEBUG_SEVERITY_MEDIUM_EXT: return DebugSeverity::Medium; case AL_DEBUG_SEVERITY_LOW_EXT: return DebugSeverity::Low; case AL_DEBUG_SEVERITY_NOTIFICATION_EXT: return DebugSeverity::Notification; } return std::nullopt; } constexpr auto GetDebugSourceEnum(DebugSource source) -> ALenum { switch(source) { case DebugSource::API: return AL_DEBUG_SOURCE_API_EXT; case DebugSource::System: return AL_DEBUG_SOURCE_AUDIO_SYSTEM_EXT; case DebugSource::ThirdParty: return AL_DEBUG_SOURCE_THIRD_PARTY_EXT; case DebugSource::Application: return AL_DEBUG_SOURCE_APPLICATION_EXT; case DebugSource::Other: return AL_DEBUG_SOURCE_OTHER_EXT; } throw std::runtime_error{fmt::format("Unexpected debug source value: {}", int{al::to_underlying(source)})}; } constexpr auto GetDebugTypeEnum(DebugType type) -> ALenum { switch(type) { case DebugType::Error: return AL_DEBUG_TYPE_ERROR_EXT; case DebugType::DeprecatedBehavior: return AL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_EXT; case DebugType::UndefinedBehavior: return AL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_EXT; case DebugType::Portability: return AL_DEBUG_TYPE_PORTABILITY_EXT; case DebugType::Performance: return AL_DEBUG_TYPE_PERFORMANCE_EXT; case DebugType::Marker: return AL_DEBUG_TYPE_MARKER_EXT; case DebugType::PushGroup: return AL_DEBUG_TYPE_PUSH_GROUP_EXT; case DebugType::PopGroup: return AL_DEBUG_TYPE_POP_GROUP_EXT; case DebugType::Other: return AL_DEBUG_TYPE_OTHER_EXT; } throw std::runtime_error{fmt::format("Unexpected debug type value: {}", int{al::to_underlying(type)})}; } constexpr auto GetDebugSeverityEnum(DebugSeverity severity) -> ALenum { switch(severity) { case DebugSeverity::High: return AL_DEBUG_SEVERITY_HIGH_EXT; case DebugSeverity::Medium: return AL_DEBUG_SEVERITY_MEDIUM_EXT; case DebugSeverity::Low: return AL_DEBUG_SEVERITY_LOW_EXT; case DebugSeverity::Notification: return AL_DEBUG_SEVERITY_NOTIFICATION_EXT; } throw std::runtime_error{fmt::format("Unexpected debug severity value: {}", int{al::to_underlying(severity)})}; } constexpr auto GetDebugSourceName(DebugSource source) noexcept -> std::string_view { switch(source) { case DebugSource::API: return "API"sv; case DebugSource::System: return "Audio System"sv; case DebugSource::ThirdParty: return "Third Party"sv; case DebugSource::Application: return "Application"sv; case DebugSource::Other: return "Other"sv; } return ""sv; } constexpr auto GetDebugTypeName(DebugType type) noexcept -> std::string_view { switch(type) { case DebugType::Error: return "Error"sv; case DebugType::DeprecatedBehavior: return "Deprecated Behavior"sv; case DebugType::UndefinedBehavior: return "Undefined Behavior"sv; case DebugType::Portability: return "Portability"sv; case DebugType::Performance: return "Performance"sv; case DebugType::Marker: return "Marker"sv; case DebugType::PushGroup: return "Push Group"sv; case DebugType::PopGroup: return "Pop Group"sv; case DebugType::Other: return "Other"sv; } return ""sv; } constexpr auto GetDebugSeverityName(DebugSeverity severity) noexcept -> std::string_view { switch(severity) { case DebugSeverity::High: return "High"sv; case DebugSeverity::Medium: return "Medium"sv; case DebugSeverity::Low: return "Low"sv; case DebugSeverity::Notification: return "Notification"sv; } return ""sv; } } // namespace void ALCcontext::sendDebugMessage(std::unique_lock &debuglock, DebugSource source, DebugType type, ALuint id, DebugSeverity severity, std::string_view message) { if(!mDebugEnabled.load(std::memory_order_relaxed)) UNLIKELY return; if(message.length() >= MaxDebugMessageLength) UNLIKELY { ERR("Debug message too long ({} >= {}):\n-> {}", message.length(), MaxDebugMessageLength, message); return; } DebugGroup &debug = mDebugGroups.back(); const uint64_t idfilter{(1_u64 << (DebugSourceBase+al::to_underlying(source))) | (1_u64 << (DebugTypeBase+al::to_underlying(type))) | (uint64_t{id} << 32)}; auto iditer = std::lower_bound(debug.mIdFilters.cbegin(), debug.mIdFilters.cend(), idfilter); if(iditer != debug.mIdFilters.cend() && *iditer == idfilter) return; const uint filter{(1u << (DebugSourceBase+al::to_underlying(source))) | (1u << (DebugTypeBase+al::to_underlying(type))) | (1u << (DebugSeverityBase+al::to_underlying(severity)))}; auto iter = std::lower_bound(debug.mFilters.cbegin(), debug.mFilters.cend(), filter); if(iter != debug.mFilters.cend() && *iter == filter) return; if(mDebugCb) { auto callback = mDebugCb; auto param = mDebugParam; debuglock.unlock(); callback(GetDebugSourceEnum(source), GetDebugTypeEnum(type), id, GetDebugSeverityEnum(severity), static_cast(message.length()), message.data(), param); } else { if(mDebugLog.size() < MaxDebugLoggedMessages) mDebugLog.emplace_back(source, type, id, severity, message); else UNLIKELY ERR("Debug message log overflow. Lost message:\n" " Source: {}\n" " Type: {}\n" " ID: {}\n" " Severity: {}\n" " Message: \"{}\"", GetDebugSourceName(source), GetDebugTypeName(type), id, GetDebugSeverityName(severity), message); } } FORCE_ALIGN DECL_FUNCEXT2(void, alDebugMessageCallback,EXT, ALDEBUGPROCEXT,callback, void*,userParam) FORCE_ALIGN void AL_APIENTRY alDebugMessageCallbackDirectEXT(ALCcontext *context, ALDEBUGPROCEXT callback, void *userParam) noexcept { std::lock_guard debuglock{context->mDebugCbLock}; context->mDebugCb = callback; context->mDebugParam = userParam; } FORCE_ALIGN DECL_FUNCEXT6(void, alDebugMessageInsert,EXT, ALenum,source, ALenum,type, ALuint,id, ALenum,severity, ALsizei,length, const ALchar*,message) FORCE_ALIGN void AL_APIENTRY alDebugMessageInsertDirectEXT(ALCcontext *context, ALenum source, ALenum type, ALuint id, ALenum severity, ALsizei length, const ALchar *message) noexcept try { if(!context->mContextFlags.test(ContextFlags::DebugBit)) return; if(!message) context->throw_error(AL_INVALID_VALUE, "Null message pointer"); auto msgview = (length < 0) ? std::string_view{message} : std::string_view{message, static_cast(length)}; if(msgview.size() >= MaxDebugMessageLength) context->throw_error(AL_INVALID_VALUE, "Debug message too long ({} >= {})", msgview.size(), MaxDebugMessageLength); auto dsource = GetDebugSource(source); if(!dsource) context->throw_error(AL_INVALID_ENUM, "Invalid debug source {:#04x}", as_unsigned(source)); if(*dsource != DebugSource::ThirdParty && *dsource != DebugSource::Application) context->throw_error(AL_INVALID_ENUM, "Debug source {:#04x} not allowed", as_unsigned(source)); auto dtype = GetDebugType(type); if(!dtype) context->throw_error(AL_INVALID_ENUM, "Invalid debug type {:#04x}", as_unsigned(type)); auto dseverity = GetDebugSeverity(severity); if(!dseverity) context->throw_error(AL_INVALID_ENUM, "Invalid debug severity {:#04x}", as_unsigned(severity)); context->debugMessage(*dsource, *dtype, id, *dseverity, msgview); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } FORCE_ALIGN DECL_FUNCEXT6(void, alDebugMessageControl,EXT, ALenum,source, ALenum,type, ALenum,severity, ALsizei,count, const ALuint*,ids, ALboolean,enable) FORCE_ALIGN void AL_APIENTRY alDebugMessageControlDirectEXT(ALCcontext *context, ALenum source, ALenum type, ALenum severity, ALsizei count, const ALuint *ids, ALboolean enable) noexcept try { if(count > 0) { if(!ids) context->throw_error(AL_INVALID_VALUE, "IDs is null with non-0 count"); if(source == AL_DONT_CARE_EXT) context->throw_error(AL_INVALID_OPERATION, "Debug source cannot be AL_DONT_CARE_EXT with IDs"); if(type == AL_DONT_CARE_EXT) context->throw_error(AL_INVALID_OPERATION, "Debug type cannot be AL_DONT_CARE_EXT with IDs"); if(severity != AL_DONT_CARE_EXT) context->throw_error(AL_INVALID_OPERATION, "Debug severity must be AL_DONT_CARE_EXT with IDs"); } if(enable != AL_TRUE && enable != AL_FALSE) context->throw_error(AL_INVALID_ENUM, "Invalid debug enable {}", enable); static constexpr size_t ElemCount{DebugSourceCount + DebugTypeCount + DebugSeverityCount}; static constexpr auto Values = make_array_sequence(); auto srcIndices = al::span{Values}.subspan(DebugSourceBase,DebugSourceCount); if(source != AL_DONT_CARE_EXT) { auto dsource = GetDebugSource(source); if(!dsource) context->throw_error(AL_INVALID_ENUM, "Invalid debug source {:#04x}", as_unsigned(source)); srcIndices = srcIndices.subspan(al::to_underlying(*dsource), 1); } auto typeIndices = al::span{Values}.subspan(DebugTypeBase,DebugTypeCount); if(type != AL_DONT_CARE_EXT) { auto dtype = GetDebugType(type); if(!dtype) context->throw_error(AL_INVALID_ENUM, "Invalid debug type {:#04x}", as_unsigned(type)); typeIndices = typeIndices.subspan(al::to_underlying(*dtype), 1); } auto svrIndices = al::span{Values}.subspan(DebugSeverityBase,DebugSeverityCount); if(severity != AL_DONT_CARE_EXT) { auto dseverity = GetDebugSeverity(severity); if(!dseverity) context->throw_error(AL_INVALID_ENUM, "Invalid debug severity {:#04x}", as_unsigned(severity)); svrIndices = svrIndices.subspan(al::to_underlying(*dseverity), 1); } std::lock_guard debuglock{context->mDebugCbLock}; DebugGroup &debug = context->mDebugGroups.back(); if(count > 0) { const uint filterbase{(1u<(count)}) { const uint64_t filter{filterbase | (uint64_t{id} << 32)}; auto iter = std::lower_bound(debug.mIdFilters.cbegin(), debug.mIdFilters.cend(), filter); if(!enable && (iter == debug.mIdFilters.cend() || *iter != filter)) debug.mIdFilters.insert(iter, filter); else if(enable && iter != debug.mIdFilters.cend() && *iter == filter) debug.mIdFilters.erase(iter); } } else { auto apply_filter = [enable,&debug](const uint filter) { auto iter = std::lower_bound(debug.mFilters.cbegin(), debug.mFilters.cend(), filter); if(!enable && (iter == debug.mFilters.cend() || *iter != filter)) debug.mFilters.insert(iter, filter); else if(enable && iter != debug.mFilters.cend() && *iter == filter) debug.mFilters.erase(iter); }; auto apply_severity = [apply_filter,svrIndices](const uint filter) { std::for_each(svrIndices.cbegin(), svrIndices.cend(), [apply_filter,filter](const uint idx){ apply_filter(filter | (1<= MaxDebugMessageLength) context->throw_error(AL_INVALID_VALUE, "Debug message too long ({} >= {})", newlen, MaxDebugMessageLength); length = static_cast(newlen); } else if(length >= MaxDebugMessageLength) context->throw_error(AL_INVALID_VALUE, "Debug message too long ({} >= {})", length, MaxDebugMessageLength); auto dsource = GetDebugSource(source); if(!dsource) context->throw_error(AL_INVALID_ENUM, "Invalid debug source {:#04x}", as_unsigned(source)); if(*dsource != DebugSource::ThirdParty && *dsource != DebugSource::Application) context->throw_error(AL_INVALID_ENUM, "Debug source {:#04x} not allowed", as_unsigned(source)); std::unique_lock debuglock{context->mDebugCbLock}; if(context->mDebugGroups.size() >= MaxDebugGroupDepth) context->throw_error(AL_STACK_OVERFLOW_EXT, "Pushing too many debug groups"); context->mDebugGroups.emplace_back(*dsource, id, std::string_view{message, static_cast(length)}); auto &oldback = *(context->mDebugGroups.end()-2); auto &newback = context->mDebugGroups.back(); newback.mFilters = oldback.mFilters; newback.mIdFilters = oldback.mIdFilters; if(context->mContextFlags.test(ContextFlags::DebugBit)) context->sendDebugMessage(debuglock, newback.mSource, DebugType::PushGroup, newback.mId, DebugSeverity::Notification, newback.mMessage); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } FORCE_ALIGN DECL_FUNCEXT(void, alPopDebugGroup,EXT) FORCE_ALIGN void AL_APIENTRY alPopDebugGroupDirectEXT(ALCcontext *context) noexcept try { std::unique_lock debuglock{context->mDebugCbLock}; if(context->mDebugGroups.size() <= 1) context->throw_error(AL_STACK_UNDERFLOW_EXT, "Attempting to pop the default debug group"); DebugGroup &debug = context->mDebugGroups.back(); const auto source = debug.mSource; const auto id = debug.mId; std::string message{std::move(debug.mMessage)}; context->mDebugGroups.pop_back(); if(context->mContextFlags.test(ContextFlags::DebugBit)) context->sendDebugMessage(debuglock, source, DebugType::PopGroup, id, DebugSeverity::Notification, message); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } FORCE_ALIGN DECL_FUNCEXT8(ALuint, alGetDebugMessageLog,EXT, ALuint,count, ALsizei,logBufSize, ALenum*,sources, ALenum*,types, ALuint*,ids, ALenum*,severities, ALsizei*,lengths, ALchar*,logBuf) FORCE_ALIGN ALuint AL_APIENTRY alGetDebugMessageLogDirectEXT(ALCcontext *context, ALuint count, ALsizei logBufSize, ALenum *sources, ALenum *types, ALuint *ids, ALenum *severities, ALsizei *lengths, ALchar *logBuf) noexcept try { if(logBuf && logBufSize < 0) context->throw_error(AL_INVALID_VALUE, "Negative debug log buffer size"); const auto sourcesSpan = al::span{sources, sources ? count : 0u}; const auto typesSpan = al::span{types, types ? count : 0u}; const auto idsSpan = al::span{ids, ids ? count : 0u}; const auto severitiesSpan = al::span{severities, severities ? count : 0u}; const auto lengthsSpan = al::span{lengths, lengths ? count : 0u}; const auto logSpan = al::span{logBuf, logBuf ? static_cast(logBufSize) : 0u}; auto sourceiter = sourcesSpan.begin(); auto typeiter = typesSpan.begin(); auto iditer = idsSpan.begin(); auto severityiter = severitiesSpan.begin(); auto lengthiter = lengthsSpan.begin(); auto logiter = logSpan.begin(); auto debuglock = std::lock_guard{context->mDebugCbLock}; for(ALuint i{0};i < count;++i) { if(context->mDebugLog.empty()) return i; auto &entry = context->mDebugLog.front(); const auto tocopy = size_t{entry.mMessage.size() + 1}; if(al::to_address(logiter) != nullptr) { if(static_cast(std::distance(logiter, logSpan.end())) < tocopy) return i; logiter = std::copy(entry.mMessage.cbegin(), entry.mMessage.cend(), logiter); *(logiter++) = '\0'; } if(al::to_address(sourceiter) != nullptr) *(sourceiter++) = GetDebugSourceEnum(entry.mSource); if(al::to_address(typeiter) != nullptr) *(typeiter++) = GetDebugTypeEnum(entry.mType); if(al::to_address(iditer) != nullptr) *(iditer++) = entry.mId; if(al::to_address(severityiter) != nullptr) *(severityiter++) = GetDebugSeverityEnum(entry.mSeverity); if(al::to_address(lengthiter) != nullptr) *(lengthiter++) = static_cast(tocopy); context->mDebugLog.pop_front(); } return count; } catch(al::base_exception&) { return 0; } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); return 0; } FORCE_ALIGN DECL_FUNCEXT4(void, alObjectLabel,EXT, ALenum,identifier, ALuint,name, ALsizei,length, const ALchar*,label) FORCE_ALIGN void AL_APIENTRY alObjectLabelDirectEXT(ALCcontext *context, ALenum identifier, ALuint name, ALsizei length, const ALchar *label) noexcept try { if(!label && length != 0) context->throw_error(AL_INVALID_VALUE, "Null label pointer"); auto objname = (length < 0) ? std::string_view{label} : std::string_view{label, static_cast(length)}; if(objname.size() >= MaxObjectLabelLength) context->throw_error(AL_INVALID_VALUE, "Object label length too long ({} >= {})", objname.size(), MaxObjectLabelLength); switch(identifier) { case AL_SOURCE_EXT: ALsource::SetName(context, name, objname); return; case AL_BUFFER: ALbuffer::SetName(context, name, objname); return; case AL_FILTER_EXT: ALfilter::SetName(context, name, objname); return; case AL_EFFECT_EXT: ALeffect::SetName(context, name, objname); return; case AL_AUXILIARY_EFFECT_SLOT_EXT: ALeffectslot::SetName(context, name, objname); return; } context->throw_error(AL_INVALID_ENUM, "Invalid name identifier {:#04x}", as_unsigned(identifier)); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } FORCE_ALIGN DECL_FUNCEXT5(void, alGetObjectLabel,EXT, ALenum,identifier, ALuint,name, ALsizei,bufSize, ALsizei*,length, ALchar*,label) FORCE_ALIGN void AL_APIENTRY alGetObjectLabelDirectEXT(ALCcontext *context, ALenum identifier, ALuint name, ALsizei bufSize, ALsizei *length, ALchar *label) noexcept try { if(bufSize < 0) context->throw_error(AL_INVALID_VALUE, "Negative label bufSize"); if(!label && !length) context->throw_error(AL_INVALID_VALUE, "Null length and label"); if(label && bufSize == 0) context->throw_error(AL_INVALID_VALUE, "Zero label bufSize"); const auto labelOut = al::span{label, label ? static_cast(bufSize) : 0u}; auto copy_name = [name,length,labelOut](std::unordered_map &names) { std::string_view objname; auto iter = names.find(name); if(iter != names.end()) objname = iter->second; if(labelOut.empty()) *length = static_cast(objname.size()); else { const size_t tocopy{std::min(objname.size(), labelOut.size()-1)}; auto oiter = std::copy_n(objname.cbegin(), tocopy, labelOut.begin()); *oiter = '\0'; if(length) *length = static_cast(tocopy); } }; if(identifier == AL_SOURCE_EXT) { std::lock_guard srclock{context->mSourceLock}; copy_name(context->mSourceNames); } else if(identifier == AL_BUFFER) { auto *device = context->mALDevice.get(); auto buflock = std::lock_guard{device->BufferLock}; copy_name(device->mBufferNames); } else if(identifier == AL_FILTER_EXT) { auto *device = context->mALDevice.get(); auto buflock = std::lock_guard{device->FilterLock}; copy_name(device->mFilterNames); } else if(identifier == AL_EFFECT_EXT) { auto *device = context->mALDevice.get(); auto buflock = std::lock_guard{device->EffectLock}; copy_name(device->mEffectNames); } else if(identifier == AL_AUXILIARY_EFFECT_SLOT_EXT) { std::lock_guard slotlock{context->mEffectSlotLock}; copy_name(context->mEffectSlotNames); } else context->throw_error(AL_INVALID_ENUM, "Invalid name identifier {:#04x}", as_unsigned(identifier)); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } openal-soft-1.24.2/al/debug.h000066400000000000000000000032121474041540300156620ustar00rootroot00000000000000#ifndef AL_DEBUG_H #define AL_DEBUG_H #include #include #include #include using uint = unsigned int; /* Somewhat arbitrary. Avoid letting it get out of control if the app enables * logging but never reads it. */ inline constexpr std::uint8_t MaxDebugLoggedMessages{64}; inline constexpr std::uint16_t MaxDebugMessageLength{1024}; inline constexpr std::uint8_t MaxDebugGroupDepth{64}; inline constexpr std::uint16_t MaxObjectLabelLength{1024}; inline constexpr uint DebugSourceBase{0}; enum class DebugSource : std::uint8_t { API = 0, System, ThirdParty, Application, Other, }; inline constexpr uint DebugSourceCount{5}; inline constexpr uint DebugTypeBase{DebugSourceBase + DebugSourceCount}; enum class DebugType : std::uint8_t { Error = 0, DeprecatedBehavior, UndefinedBehavior, Portability, Performance, Marker, PushGroup, PopGroup, Other, }; inline constexpr uint DebugTypeCount{9}; inline constexpr uint DebugSeverityBase{DebugTypeBase + DebugTypeCount}; enum class DebugSeverity : std::uint8_t { High = 0, Medium, Low, Notification, }; inline constexpr uint DebugSeverityCount{4}; struct DebugGroup { const uint mId; const DebugSource mSource; std::string mMessage; std::vector mFilters; std::vector mIdFilters; template DebugGroup(DebugSource source, uint id, T&& message) : mId{id}, mSource{source}, mMessage{std::forward(message)} { } DebugGroup(const DebugGroup&) = default; DebugGroup(DebugGroup&&) = default; ~DebugGroup(); }; #endif /* AL_DEBUG_H */ openal-soft-1.24.2/al/direct_defs.h000066400000000000000000000156131474041540300170570ustar00rootroot00000000000000#ifndef AL_DIRECT_DEFS_H #define AL_DIRECT_DEFS_H namespace detail_ { template constexpr T DefaultVal() noexcept { return T{}; } template<> constexpr void DefaultVal() noexcept { } } // namespace detail_ #define DECL_FUNC(R, Name) \ auto AL_APIENTRY Name() noexcept -> R \ { \ auto context = GetContextRef(); \ if(!context) UNLIKELY return detail_::DefaultVal(); \ return Name##Direct(context.get()); \ } #define DECL_FUNC1(R, Name, T1,n1) \ auto AL_APIENTRY Name(T1 n1) noexcept -> R \ { \ auto context = GetContextRef(); \ if(!context) UNLIKELY return detail_::DefaultVal(); \ return Name##Direct(context.get(), n1); \ } #define DECL_FUNC2(R, Name, T1,n1, T2,n2) \ auto AL_APIENTRY Name(T1 n1, T2 n2) noexcept -> R \ { \ auto context = GetContextRef(); \ if(!context) UNLIKELY return detail_::DefaultVal(); \ return Name##Direct(context.get(), n1, n2); \ } #define DECL_FUNC3(R, Name, T1,n1, T2,n2, T3,n3) \ auto AL_APIENTRY Name(T1 n1, T2 n2, T3 n3) noexcept -> R \ { \ auto context = GetContextRef(); \ if(!context) UNLIKELY return detail_::DefaultVal(); \ return Name##Direct(context.get(), n1, n2, n3); \ } #define DECL_FUNC4(R, Name, T1,n1, T2,n2, T3,n3, T4,n4) \ auto AL_APIENTRY Name(T1 n1, T2 n2, T3 n3, T4 n4) noexcept -> R \ { \ auto context = GetContextRef(); \ if(!context) UNLIKELY return detail_::DefaultVal(); \ return Name##Direct(context.get(), n1, n2, n3, n4); \ } #define DECL_FUNC5(R, Name, T1,n1, T2,n2, T3,n3, T4,n4, T5,n5) \ auto AL_APIENTRY Name(T1 n1, T2 n2, T3 n3, T4 n4, T5 n5) noexcept -> R \ { \ auto context = GetContextRef(); \ if(!context) UNLIKELY return detail_::DefaultVal(); \ return Name##Direct(context.get(), n1, n2, n3, n4, n5); \ } #define DECL_FUNCEXT(R, Name,Ext) \ auto AL_APIENTRY Name##Ext() noexcept -> R \ { \ auto context = GetContextRef(); \ if(!context) UNLIKELY return detail_::DefaultVal(); \ return Name##Direct##Ext(context.get()); \ } #define DECL_FUNCEXT1(R, Name,Ext, T1,n1) \ auto AL_APIENTRY Name##Ext(T1 n1) noexcept -> R \ { \ auto context = GetContextRef(); \ if(!context) UNLIKELY return detail_::DefaultVal(); \ return Name##Direct##Ext(context.get(), n1); \ } #define DECL_FUNCEXT2(R, Name,Ext, T1,n1, T2,n2) \ auto AL_APIENTRY Name##Ext(T1 n1, T2 n2) noexcept -> R \ { \ auto context = GetContextRef(); \ if(!context) UNLIKELY return detail_::DefaultVal(); \ return Name##Direct##Ext(context.get(), n1, n2); \ } #define DECL_FUNCEXT3(R, Name,Ext, T1,n1, T2,n2, T3,n3) \ auto AL_APIENTRY Name##Ext(T1 n1, T2 n2, T3 n3) noexcept -> R \ { \ auto context = GetContextRef(); \ if(!context) UNLIKELY return detail_::DefaultVal(); \ return Name##Direct##Ext(context.get(), n1, n2, n3); \ } #define DECL_FUNCEXT4(R, Name,Ext, T1,n1, T2,n2, T3,n3, T4,n4) \ auto AL_APIENTRY Name##Ext(T1 n1, T2 n2, T3 n3, T4 n4) noexcept -> R \ { \ auto context = GetContextRef(); \ if(!context) UNLIKELY return detail_::DefaultVal(); \ return Name##Direct##Ext(context.get(), n1, n2, n3, n4); \ } #define DECL_FUNCEXT5(R, Name,Ext, T1,n1, T2,n2, T3,n3, T4,n4, T5,n5) \ auto AL_APIENTRY Name##Ext(T1 n1, T2 n2, T3 n3, T4 n4, T5 n5) noexcept -> R \ { \ auto context = GetContextRef(); \ if(!context) UNLIKELY return detail_::DefaultVal(); \ return Name##Direct##Ext(context.get(), n1, n2, n3, n4, n5); \ } #define DECL_FUNCEXT6(R, Name,Ext, T1,n1, T2,n2, T3,n3, T4,n4, T5,n5, T6,n6) \ auto AL_APIENTRY Name##Ext(T1 n1, T2 n2, T3 n3, T4 n4, T5 n5, T6 n6) noexcept -> R \ { \ auto context = GetContextRef(); \ if(!context) UNLIKELY return detail_::DefaultVal(); \ return Name##Direct##Ext(context.get(), n1, n2, n3, n4, n5, n6); \ } #define DECL_FUNCEXT8(R, Name,Ext, T1,n1, T2,n2, T3,n3, T4,n4, T5,n5, T6,n6, T7,n7, T8,n8) \ auto AL_APIENTRY Name##Ext(T1 n1, T2 n2, T3 n3, T4 n4, T5 n5, T6 n6, T7 n7, T8 n8) noexcept -> R \ { \ auto context = GetContextRef(); \ if(!context) UNLIKELY return detail_::DefaultVal(); \ return Name##Direct##Ext(context.get(), n1, n2, n3, n4, n5, n6, n7, n8); \ } #endif /* AL_DIRECT_DEFS_H */ openal-soft-1.24.2/al/eax/000077500000000000000000000000001474041540300152025ustar00rootroot00000000000000openal-soft-1.24.2/al/eax/api.cpp000066400000000000000000000711701474041540300164650ustar00rootroot00000000000000// // EAX API. // // Based on headers `eax[2-5].h` included in Doom 3 source code: // https://github.com/id-Software/DOOM-3/tree/master/neo/openal/include // #include "config.h" #include #include "api.h" const GUID DSPROPSETID_EAX_ReverbProperties = { 0x4A4E6FC1, 0xC341, 0x11D1, {0xB7, 0x3A, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00} }; const GUID DSPROPSETID_EAXBUFFER_ReverbProperties = { 0x4A4E6FC0, 0xC341, 0x11D1, {0xB7, 0x3A, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00} }; const GUID DSPROPSETID_EAX20_ListenerProperties = { 0x306A6A8, 0xB224, 0x11D2, {0x99, 0xE5, 0x00, 0x00, 0xE8, 0xD8, 0xC7, 0x22} }; const GUID DSPROPSETID_EAX20_BufferProperties = { 0x306A6A7, 0xB224, 0x11D2, {0x99, 0xE5, 0x00, 0x00, 0xE8, 0xD8, 0xC7, 0x22} }; const GUID DSPROPSETID_EAX30_ListenerProperties = { 0xA8FA6882, 0xB476, 0x11D3, {0xBD, 0xB9, 0x00, 0xC0, 0xF0, 0x2D, 0xDF, 0x87} }; const GUID DSPROPSETID_EAX30_BufferProperties = { 0xA8FA6881, 0xB476, 0x11D3, {0xBD, 0xB9, 0x00, 0xC0, 0xF0, 0x2D, 0xDF, 0x87} }; const GUID EAX_NULL_GUID = { 0x00000000, 0x0000, 0x0000, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} }; const GUID EAX_PrimaryFXSlotID = { 0xF317866D, 0x924C, 0x450C, {0x86, 0x1B, 0xE6, 0xDA, 0xA2, 0x5E, 0x7C, 0x20} }; const GUID EAXPROPERTYID_EAX40_Context = { 0x1D4870AD, 0xDEF, 0x43C0, {0xA4, 0xC, 0x52, 0x36, 0x32, 0x29, 0x63, 0x42} }; const GUID EAXPROPERTYID_EAX50_Context = { 0x57E13437, 0xB932, 0x4AB2, {0xB8, 0xBD, 0x52, 0x66, 0xC1, 0xA8, 0x87, 0xEE} }; const GUID EAXPROPERTYID_EAX40_FXSlot0 = { 0xC4D79F1E, 0xF1AC, 0x436B, {0xA8, 0x1D, 0xA7, 0x38, 0xE7, 0x04, 0x54, 0x69} }; const GUID EAXPROPERTYID_EAX50_FXSlot0 = { 0x91F9590F, 0xC388, 0x407A, {0x84, 0xB0, 0x1B, 0xAE, 0xE, 0xF7, 0x1A, 0xBC} }; const GUID EAXPROPERTYID_EAX40_FXSlot1 = { 0x8C00E96, 0x74BE, 0x4491, {0x93, 0xAA, 0xE8, 0xAD, 0x35, 0xA4, 0x91, 0x17} }; const GUID EAXPROPERTYID_EAX50_FXSlot1 = { 0x8F5F7ACA, 0x9608, 0x4965, {0x81, 0x37, 0x82, 0x13, 0xC7, 0xB9, 0xD9, 0xDE} }; const GUID EAXPROPERTYID_EAX40_FXSlot2 = { 0x1D433B88, 0xF0F6, 0x4637, {0x91, 0x9F, 0x60, 0xE7, 0xE0, 0x6B, 0x5E, 0xDD} }; const GUID EAXPROPERTYID_EAX50_FXSlot2 = { 0x3C0F5252, 0x9834, 0x46F0, {0xA1, 0xD8, 0x5B, 0x95, 0xC4, 0xA0, 0xA, 0x30} }; const GUID EAXPROPERTYID_EAX40_FXSlot3 = { 0xEFFF08EA, 0xC7D8, 0x44AB, {0x93, 0xAD, 0x6D, 0xBD, 0x5F, 0x91, 0x00, 0x64} }; const GUID EAXPROPERTYID_EAX50_FXSlot3 = { 0xE2EB0EAA, 0xE806, 0x45E7, {0x9F, 0x86, 0x06, 0xC1, 0x57, 0x1A, 0x6F, 0xA3} }; const GUID EAXPROPERTYID_EAX40_Source = { 0x1B86B823, 0x22DF, 0x4EAE, {0x8B, 0x3C, 0x12, 0x78, 0xCE, 0x54, 0x42, 0x27} }; const GUID EAXPROPERTYID_EAX50_Source = { 0x5EDF82F0, 0x24A7, 0x4F38, {0x8E, 0x64, 0x2F, 0x09, 0xCA, 0x05, 0xDE, 0xE1} }; const GUID EAX_REVERB_EFFECT = { 0xCF95C8F, 0xA3CC, 0x4849, {0xB0, 0xB6, 0x83, 0x2E, 0xCC, 0x18, 0x22, 0xDF} }; const GUID EAX_AGCCOMPRESSOR_EFFECT = { 0xBFB7A01E, 0x7825, 0x4039, {0x92, 0x7F, 0x03, 0xAA, 0xBD, 0xA0, 0xC5, 0x60} }; const GUID EAX_AUTOWAH_EFFECT = { 0xEC3130C0, 0xAC7A, 0x11D2, {0x88, 0xDD, 0x00, 0xA0, 0x24, 0xD1, 0x3C, 0xE1} }; const GUID EAX_CHORUS_EFFECT = { 0xDE6D6FE0, 0xAC79, 0x11D2, {0x88, 0xDD, 0x00, 0xA0, 0x24, 0xD1, 0x3C, 0xE1} }; const GUID EAX_DISTORTION_EFFECT = { 0x975A4CE0, 0xAC7E, 0x11D2, {0x88, 0xDD, 0x00, 0xA0, 0x24, 0xD1, 0x3C, 0xE1} }; const GUID EAX_ECHO_EFFECT = { 0xE9F1BC0, 0xAC82, 0x11D2, {0x88, 0xDD, 0x00, 0xA0, 0x24, 0xD1, 0x3C, 0xE1} }; const GUID EAX_EQUALIZER_EFFECT = { 0x65F94CE0, 0x9793, 0x11D3, {0x93, 0x9D, 0x00, 0xC0, 0xF0, 0x2D, 0xD6, 0xF0} }; const GUID EAX_FLANGER_EFFECT = { 0xA70007C0, 0x7D2, 0x11D3, {0x9B, 0x1E, 0x00, 0xA0, 0x24, 0xD1, 0x3C, 0xE1} }; const GUID EAX_FREQUENCYSHIFTER_EFFECT = { 0xDC3E1880, 0x9212, 0x11D3, {0x93, 0x9D, 0x00, 0xC0, 0xF0, 0x2D, 0xD6, 0xF0} }; const GUID EAX_VOCALMORPHER_EFFECT = { 0xE41CF10C, 0x3383, 0x11D2, {0x88, 0xDD, 0x00, 0xA0, 0x24, 0xD1, 0x3C, 0xE1} }; const GUID EAX_PITCHSHIFTER_EFFECT = { 0xE7905100, 0xAFB2, 0x11D2, {0x88, 0xDD, 0x00, 0xA0, 0x24, 0xD1, 0x3C, 0xE1} }; const GUID EAX_RINGMODULATOR_EFFECT = { 0xB89FE60, 0xAFB5, 0x11D2, {0x88, 0xDD, 0x00, 0xA0, 0x24, 0xD1, 0x3C, 0xE1} }; const GUID EAX40CONTEXT_DEFAULTPRIMARYFXSLOTID = EAXPROPERTYID_EAX40_FXSlot0; const GUID EAX50CONTEXT_DEFAULTPRIMARYFXSLOTID = EAXPROPERTYID_EAX50_FXSlot0; const EAX40ACTIVEFXSLOTS EAX40SOURCE_DEFAULTACTIVEFXSLOTID = EAX40ACTIVEFXSLOTS {{ EAX_NULL_GUID, EAXPROPERTYID_EAX40_FXSlot0, }}; const EAX50ACTIVEFXSLOTS EAX50SOURCE_3DDEFAULTACTIVEFXSLOTID = EAX50ACTIVEFXSLOTS {{ EAX_NULL_GUID, EAX_PrimaryFXSlotID, EAX_NULL_GUID, EAX_NULL_GUID, }}; const EAX50ACTIVEFXSLOTS EAX50SOURCE_2DDEFAULTACTIVEFXSLOTID = EAX50ACTIVEFXSLOTS {{ EAX_NULL_GUID, EAX_NULL_GUID, EAX_NULL_GUID, EAX_NULL_GUID, }}; // EAX1 ===================================================================== namespace { constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_GENERIC = {EAX_ENVIRONMENT_GENERIC, 0.5F, 1.493F, 0.5F}; constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_PADDEDCELL = {EAX_ENVIRONMENT_PADDEDCELL, 0.25F, 0.1F, 0.0F}; constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_ROOM = {EAX_ENVIRONMENT_ROOM, 0.417F, 0.4F, 0.666F}; constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_BATHROOM = {EAX_ENVIRONMENT_BATHROOM, 0.653F, 1.499F, 0.166F}; constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_LIVINGROOM = {EAX_ENVIRONMENT_LIVINGROOM, 0.208F, 0.478F, 0.0F}; constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_STONEROOM = {EAX_ENVIRONMENT_STONEROOM, 0.5F, 2.309F, 0.888F}; constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_AUDITORIUM = {EAX_ENVIRONMENT_AUDITORIUM, 0.403F, 4.279F, 0.5F}; constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_CONCERTHALL = {EAX_ENVIRONMENT_CONCERTHALL, 0.5F, 3.961F, 0.5F}; constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_CAVE = {EAX_ENVIRONMENT_CAVE, 0.5F, 2.886F, 1.304F}; constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_ARENA = {EAX_ENVIRONMENT_ARENA, 0.361F, 7.284F, 0.332F}; constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_HANGAR = {EAX_ENVIRONMENT_HANGAR, 0.5F, 10.0F, 0.3F}; constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_CARPETTEDHALLWAY = {EAX_ENVIRONMENT_CARPETEDHALLWAY, 0.153F, 0.259F, 2.0F}; constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_HALLWAY = {EAX_ENVIRONMENT_HALLWAY, 0.361F, 1.493F, 0.0F}; constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_STONECORRIDOR = {EAX_ENVIRONMENT_STONECORRIDOR, 0.444F, 2.697F, 0.638F}; constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_ALLEY = {EAX_ENVIRONMENT_ALLEY, 0.25F, 1.752F, 0.776F}; constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_FOREST = {EAX_ENVIRONMENT_FOREST, 0.111F, 3.145F, 0.472F}; constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_CITY = {EAX_ENVIRONMENT_CITY, 0.111F, 2.767F, 0.224F}; constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_MOUNTAINS = {EAX_ENVIRONMENT_MOUNTAINS, 0.194F, 7.841F, 0.472F}; constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_QUARRY = {EAX_ENVIRONMENT_QUARRY, 1.0F, 1.499F, 0.5F}; constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_PLAIN = {EAX_ENVIRONMENT_PLAIN, 0.097F, 2.767F, 0.224F}; constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_PARKINGLOT = {EAX_ENVIRONMENT_PARKINGLOT, 0.208F, 1.652F, 1.5F}; constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_SEWERPIPE = {EAX_ENVIRONMENT_SEWERPIPE, 0.652F, 2.886F, 0.25F}; constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_UNDERWATER = {EAX_ENVIRONMENT_UNDERWATER, 1.0F, 1.499F, 0.0F}; constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_DRUGGED = {EAX_ENVIRONMENT_DRUGGED, 0.875F, 8.392F, 1.388F}; constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_DIZZY = {EAX_ENVIRONMENT_DIZZY, 0.139F, 17.234F, 0.666F}; constexpr EAX_REVERBPROPERTIES EAX1REVERB_PRESET_PSYCHOTIC = {EAX_ENVIRONMENT_PSYCHOTIC, 0.486F, 7.563F, 0.806F}; } // namespace const Eax1ReverbPresets EAX1REVERB_PRESETS{{ EAX1REVERB_PRESET_GENERIC, EAX1REVERB_PRESET_PADDEDCELL, EAX1REVERB_PRESET_ROOM, EAX1REVERB_PRESET_BATHROOM, EAX1REVERB_PRESET_LIVINGROOM, EAX1REVERB_PRESET_STONEROOM, EAX1REVERB_PRESET_AUDITORIUM, EAX1REVERB_PRESET_CONCERTHALL, EAX1REVERB_PRESET_CAVE, EAX1REVERB_PRESET_ARENA, EAX1REVERB_PRESET_HANGAR, EAX1REVERB_PRESET_CARPETTEDHALLWAY, EAX1REVERB_PRESET_HALLWAY, EAX1REVERB_PRESET_STONECORRIDOR, EAX1REVERB_PRESET_ALLEY, EAX1REVERB_PRESET_FOREST, EAX1REVERB_PRESET_CITY, EAX1REVERB_PRESET_MOUNTAINS, EAX1REVERB_PRESET_QUARRY, EAX1REVERB_PRESET_PLAIN, EAX1REVERB_PRESET_PARKINGLOT, EAX1REVERB_PRESET_SEWERPIPE, EAX1REVERB_PRESET_UNDERWATER, EAX1REVERB_PRESET_DRUGGED, EAX1REVERB_PRESET_DIZZY, EAX1REVERB_PRESET_PSYCHOTIC, }}; // EAX2 ===================================================================== namespace { constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_GENERIC{ EAX2LISTENER_DEFAULTROOM, EAX2LISTENER_DEFAULTROOMHF, EAX2LISTENER_DEFAULTROOMROLLOFFFACTOR, EAX2LISTENER_DEFAULTDECAYTIME, EAX2LISTENER_DEFAULTDECAYHFRATIO, EAX2LISTENER_DEFAULTREFLECTIONS, EAX2LISTENER_DEFAULTREFLECTIONSDELAY, EAX2LISTENER_DEFAULTREVERB, EAX2LISTENER_DEFAULTREVERBDELAY, EAX2LISTENER_DEFAULTENVIRONMENT, EAX2LISTENER_DEFAULTENVIRONMENTSIZE, EAX2LISTENER_DEFAULTENVIRONMENTDIFFUSION, EAX2LISTENER_DEFAULTAIRABSORPTIONHF, EAX2LISTENER_DEFAULTFLAGS, }; constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_PADDEDCELL{ -1'000L, -6'000L, 0.0F, 0.17F, 0.1F, -1'204L, 0.001F, 207L, 0.002F, EAX2_ENVIRONMENT_PADDEDCELL, 1.4F, 1.0F, -5.0F, EAX2LISTENER_DEFAULTFLAGS, }; constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_ROOM{ -1'000L, -454L, 0.0F, 0.4F, 0.83F, -1'646L, 0.002F, 53L, 0.003F, EAX2_ENVIRONMENT_ROOM, 1.9F, 1.0F, -5.0F, EAX2LISTENER_DEFAULTFLAGS, }; constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_BATHROOM{ -1'000L, -1'200L, 0.0F, 1.49F, 0.54F, -370L, 0.007F, 1'030L, 0.011F, EAX2_ENVIRONMENT_BATHROOM, 1.4F, 1.0F, -5.0F, EAX2LISTENER_DEFAULTFLAGS, }; constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_LIVINGROOM{ -1'000L, -6'000L, 0.0F, 0.5F, 0.1F, -1'376L, 0.003F, -1'104L, 0.004F, EAX2_ENVIRONMENT_LIVINGROOM, 2.5F, 1.0F, -5.0F, EAX2LISTENER_DEFAULTFLAGS, }; constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_STONEROOM{ -1'000L, -300L, 0.0F, 2.31F, 0.64F, -711L, 0.012F, 83L, 0.017F, EAX2_ENVIRONMENT_STONEROOM, 11.6F, 1.0F, -5.0F, EAX2LISTENER_DEFAULTFLAGS, }; constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_AUDITORIUM{ -1'000L, -476L, 0.0F, 4.32F, 0.59F, -789L, 0.02F, -289L, 0.03F, EAX2_ENVIRONMENT_AUDITORIUM, 21.6F, 1.0F, -5.0F, EAX2LISTENER_DEFAULTFLAGS, }; constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_CONCERTHALL{ -1'000L, -500L, 0.0F, 3.92F, 0.7F, -1'230L, 0.02F, -2L, 0.029F, EAX2_ENVIRONMENT_CONCERTHALL, 19.6F, 1.0F, -5.0F, EAX2LISTENER_DEFAULTFLAGS, }; constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_CAVE{ -1'000L, 0L, 0.0F, 2.91F, 1.3F, -602L, 0.015F, -302L, 0.022F, EAX2_ENVIRONMENT_CAVE, 14.6F, 1.0F, -5.0F, EAX2LISTENERFLAGS_DECAYTIMESCALE | EAX2LISTENERFLAGS_REFLECTIONSSCALE | EAX2LISTENERFLAGS_REFLECTIONSDELAYSCALE | EAX2LISTENERFLAGS_REVERBSCALE | EAX2LISTENERFLAGS_REVERBDELAYSCALE, }; constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_ARENA{ -1'000L, -698L, 0.0F, 7.24F, 0.33F, -1'166L, 0.02F, 16L, 0.03F, EAX2_ENVIRONMENT_ARENA, 36.2F, 1.0F, -5.0F, EAX2LISTENER_DEFAULTFLAGS, }; constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_HANGAR{ -1'000L, -1'000L, 0.0F, 10.05F, 0.23F, -602L, 0.02F, 198L, 0.03F, EAX2_ENVIRONMENT_HANGAR, 50.3F, 1.0F, -5.0F, EAX2LISTENER_DEFAULTFLAGS, }; constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_CARPETTEDHALLWAY{ -1'000L, -4'000L, 0.0F, 0.3F, 0.1F, -1'831L, 0.002F, -1'630L, 0.03F, EAX2_ENVIRONMENT_CARPETEDHALLWAY, 1.9F, 1.0F, -5.0F, EAX2LISTENER_DEFAULTFLAGS, }; constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_HALLWAY{ -1'000L, -300L, 0.0F, 1.49F, 0.59F, -1'219L, 0.007F, 441L, 0.011F, EAX2_ENVIRONMENT_HALLWAY, 1.8F, 1.0F, -5.0F, EAX2LISTENER_DEFAULTFLAGS, }; constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_STONECORRIDOR{ -1'000L, -237L, 0.0F, 2.7F, 0.79F, -1'214L, 0.013F, 395L, 0.02F, EAX2_ENVIRONMENT_STONECORRIDOR, 13.5F, 1.0F, -5.0F, EAX2LISTENER_DEFAULTFLAGS, }; constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_ALLEY{ -1'000L, -270L, 0.0F, 1.49F, 0.86F, -1'204L, 0.007F, -4L, 0.011F, EAX2_ENVIRONMENT_ALLEY, 7.5F, 0.3F, -5.0F, EAX2LISTENER_DEFAULTFLAGS, }; constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_FOREST{ -1'000L, -3'300L, 0.0F, 1.49F, 0.54F, -2'560L, 0.162F, -229L, 0.088F, EAX2_ENVIRONMENT_FOREST, 38.0F, 0.3F, -5.0F, EAX2LISTENER_DEFAULTFLAGS, }; constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_CITY{ -1'000L, -800L, 0.0F, 1.49F, 0.67F, -2'273L, 0.007F, -1'691L, 0.011F, EAX2_ENVIRONMENT_CITY, 7.5F, 0.5F, -5.0F, EAX2LISTENER_DEFAULTFLAGS, }; constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_MOUNTAINS{ -1'000L, -2'500L, 0.0F, 1.49F, 0.21F, -2'780L, 0.3F, -1'434L, 0.1F, EAX2_ENVIRONMENT_MOUNTAINS, 100.0F, 0.27F, -5.0F, EAX2LISTENERFLAGS_DECAYTIMESCALE | EAX2LISTENERFLAGS_REFLECTIONSSCALE | EAX2LISTENERFLAGS_REFLECTIONSDELAYSCALE | EAX2LISTENERFLAGS_REVERBSCALE | EAX2LISTENERFLAGS_REVERBDELAYSCALE, }; constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_QUARRY{ -1'000L, -1'000L, 0.0F, 1.49F, 0.83F, -10'000L, 0.061F, 500L, 0.025F, EAX2_ENVIRONMENT_QUARRY, 17.5F, 1.0F, -5.0F, EAX2LISTENER_DEFAULTFLAGS, }; constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_PLAIN{ -1'000L, -2'000L, 0.0F, 1.49F, 0.5F, -2'466L, 0.179F, -1'926L, 0.1F, EAX2_ENVIRONMENT_PLAIN, 42.5F, 0.21F, -5.0F, EAX2LISTENER_DEFAULTFLAGS, }; constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_PARKINGLOT{ -1'000L, 0L, 0.0F, 1.65F, 1.5F, -1'363L, 0.008F, -1'153L, 0.012F, EAX2_ENVIRONMENT_PARKINGLOT, 8.3F, 1.0F, -5.0F, EAX2LISTENERFLAGS_DECAYTIMESCALE | EAX2LISTENERFLAGS_REFLECTIONSSCALE | EAX2LISTENERFLAGS_REFLECTIONSDELAYSCALE | EAX2LISTENERFLAGS_REVERBSCALE | EAX2LISTENERFLAGS_REVERBDELAYSCALE, }; constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_SEWERPIPE{ -1'000L, -1'000L, 0.0F, 2.81F, 0.14F, 429L, 0.014F, 1'023L, 0.021F, EAX2_ENVIRONMENT_SEWERPIPE, 1.7F, 0.8F, -5.0F, EAX2LISTENER_DEFAULTFLAGS, }; constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_UNDERWATER{ -1'000L, -4'000L, 0.0F, 1.49F, 0.1F, -449L, 0.007F, 1'700L, 0.011F, EAX2_ENVIRONMENT_UNDERWATER, 1.8F, 1.0F, -5.0F, EAX2LISTENER_DEFAULTFLAGS, }; constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_DRUGGED{ -1'000L, 0L, 0.0F, 8.39F, 1.39F, -115L, 0.002F, 985L, 0.03F, EAX2_ENVIRONMENT_DRUGGED, 1.9F, 0.5F, -5.0F, EAX2LISTENERFLAGS_DECAYTIMESCALE | EAX2LISTENERFLAGS_REFLECTIONSSCALE | EAX2LISTENERFLAGS_REFLECTIONSDELAYSCALE | EAX2LISTENERFLAGS_REVERBSCALE | EAX2LISTENERFLAGS_REVERBDELAYSCALE, }; constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_DIZZY{ -1'000L, -400L, 0.0F, 17.23F, 0.56F, -1'713L, 0.02F, -613L, 0.03F, EAX2_ENVIRONMENT_DIZZY, 1.8F, 0.6F, -5.0F, EAX2LISTENERFLAGS_DECAYTIMESCALE | EAX2LISTENERFLAGS_REFLECTIONSSCALE | EAX2LISTENERFLAGS_REFLECTIONSDELAYSCALE | EAX2LISTENERFLAGS_REVERBSCALE | EAX2LISTENERFLAGS_REVERBDELAYSCALE, }; constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_PSYCHOTIC{ -1'000L, -151L, 0.0F, 7.56F, 0.91F, -626L, 0.02F, 774L, 0.03F, EAX2_ENVIRONMENT_PSYCHOTIC, 1.0F, 0.5F, -5.0F, EAX2LISTENERFLAGS_DECAYTIMESCALE | EAX2LISTENERFLAGS_REFLECTIONSSCALE | EAX2LISTENERFLAGS_REFLECTIONSDELAYSCALE | EAX2LISTENERFLAGS_REVERBSCALE | EAX2LISTENERFLAGS_REVERBDELAYSCALE, }; } // namespace const Eax2ReverbPresets EAX2REVERB_PRESETS{ EAX2REVERB_PRESET_GENERIC, EAX2REVERB_PRESET_PADDEDCELL, EAX2REVERB_PRESET_ROOM, EAX2REVERB_PRESET_BATHROOM, EAX2REVERB_PRESET_LIVINGROOM, EAX2REVERB_PRESET_STONEROOM, EAX2REVERB_PRESET_AUDITORIUM, EAX2REVERB_PRESET_CONCERTHALL, EAX2REVERB_PRESET_CAVE, EAX2REVERB_PRESET_ARENA, EAX2REVERB_PRESET_HANGAR, EAX2REVERB_PRESET_CARPETTEDHALLWAY, EAX2REVERB_PRESET_HALLWAY, EAX2REVERB_PRESET_STONECORRIDOR, EAX2REVERB_PRESET_ALLEY, EAX2REVERB_PRESET_FOREST, EAX2REVERB_PRESET_CITY, EAX2REVERB_PRESET_MOUNTAINS, EAX2REVERB_PRESET_QUARRY, EAX2REVERB_PRESET_PLAIN, EAX2REVERB_PRESET_PARKINGLOT, EAX2REVERB_PRESET_SEWERPIPE, EAX2REVERB_PRESET_UNDERWATER, EAX2REVERB_PRESET_DRUGGED, EAX2REVERB_PRESET_DIZZY, EAX2REVERB_PRESET_PSYCHOTIC, }; // EAX3+ ==================================================================== namespace { constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_GENERIC = { EAXREVERB_DEFAULTENVIRONMENT, EAXREVERB_DEFAULTENVIRONMENTSIZE, EAXREVERB_DEFAULTENVIRONMENTDIFFUSION, EAXREVERB_DEFAULTROOM, EAXREVERB_DEFAULTROOMHF, EAXREVERB_DEFAULTROOMLF, EAXREVERB_DEFAULTDECAYTIME, EAXREVERB_DEFAULTDECAYHFRATIO, EAXREVERB_DEFAULTDECAYLFRATIO, EAXREVERB_DEFAULTREFLECTIONS, EAXREVERB_DEFAULTREFLECTIONSDELAY, EAXREVERB_DEFAULTREFLECTIONSPAN, EAXREVERB_DEFAULTREVERB, EAXREVERB_DEFAULTREVERBDELAY, EAXREVERB_DEFAULTREVERBPAN, EAXREVERB_DEFAULTECHOTIME, EAXREVERB_DEFAULTECHODEPTH, EAXREVERB_DEFAULTMODULATIONTIME, EAXREVERB_DEFAULTMODULATIONDEPTH, EAXREVERB_DEFAULTAIRABSORPTIONHF, EAXREVERB_DEFAULTHFREFERENCE, EAXREVERB_DEFAULTLFREFERENCE, EAXREVERB_DEFAULTROOMROLLOFFFACTOR, EAXREVERB_DEFAULTFLAGS, }; constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_PADDEDCELL = { EAX_ENVIRONMENT_PADDEDCELL, 1.4F, 1.0F, -1'000L, -6'000L, 0L, 0.17F, 0.10F, 1.0F, -1'204L, 0.001F, EAXVECTOR{}, 207L, 0.002F, EAXVECTOR{}, 0.250F, 0.0F, 0.250F, 0.0F, -5.0F, 5'000.0F, 250.0F, 0.0F, 0x3FUL, }; constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_ROOM = { EAX_ENVIRONMENT_ROOM, 1.9F, 1.0F, -1'000L, -454L, 0L, 0.40F, 0.83F, 1.0F, -1'646L, 0.002F, EAXVECTOR{}, 53L, 0.003F, EAXVECTOR{}, 0.250F, 0.0F, 0.250F, 0.0F, -5.0F, 5'000.0F, 250.0F, 0.0F, 0x3FUL, }; constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_BATHROOM = { EAX_ENVIRONMENT_BATHROOM, 1.4F, 1.0F, -1'000L, -1'200L, 0L, 1.49F, 0.54F, 1.0F, -370L, 0.007F, EAXVECTOR{}, 1'030L, 0.011F, EAXVECTOR{}, 0.250F, 0.0F, 0.250F, 0.0F, -5.0F, 5'000.0F, 250.0F, 0.0F, 0x3FUL, }; constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_LIVINGROOM = { EAX_ENVIRONMENT_LIVINGROOM, 2.5F, 1.0F, -1'000L, -6'000L, 0L, 0.50F, 0.10F, 1.0F, -1'376, 0.003F, EAXVECTOR{}, -1'104L, 0.004F, EAXVECTOR{}, 0.250F, 0.0F, 0.250F, 0.0F, -5.0F, 5'000.0F, 250.0F, 0.0F, 0x3FUL, }; constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_STONEROOM = { EAX_ENVIRONMENT_STONEROOM, 11.6F, 1.0F, -1'000L, -300L, 0L, 2.31F, 0.64F, 1.0F, -711L, 0.012F, EAXVECTOR{}, 83L, 0.017F, EAXVECTOR{}, 0.250F, 0.0F, 0.250F, 0.0F, -5.0F, 5'000.0F, 250.0F, 0.0F, 0x3FUL, }; constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_AUDITORIUM = { EAX_ENVIRONMENT_AUDITORIUM, 21.6F, 1.0F, -1'000L, -476L, 0L, 4.32F, 0.59F, 1.0F, -789L, 0.020F, EAXVECTOR{}, -289L, 0.030F, EAXVECTOR{}, 0.250F, 0.0F, 0.250F, 0.0F, -5.0F, 5'000.0F, 250.0F, 0.0F, 0x3FUL, }; constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_CONCERTHALL = { EAX_ENVIRONMENT_CONCERTHALL, 19.6F, 1.0F, -1'000L, -500L, 0L, 3.92F, 0.70F, 1.0F, -1'230L, 0.020F, EAXVECTOR{}, -2L, 0.029F, EAXVECTOR{}, 0.250F, 0.0F, 0.250F, 0.0F, -5.0F, 5'000.0F, 250.0F, 0.0F, 0x3FUL, }; constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_CAVE = { EAX_ENVIRONMENT_CAVE, 14.6F, 1.0F, -1'000L, 0L, 0L, 2.91F, 1.30F, 1.0F, -602L, 0.015F, EAXVECTOR{}, -302L, 0.022F, EAXVECTOR{}, 0.250F, 0.0F, 0.250F, 0.0F, -5.0F, 5'000.0F, 250.0F, 0.0F, 0x1FUL, }; constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_ARENA = { EAX_ENVIRONMENT_ARENA, 36.2F, 1.0F, -1'000L, -698L, 0L, 7.24F, 0.33F, 1.0F, -1'166L, 0.020F, EAXVECTOR{}, 16L, 0.030F, EAXVECTOR{}, 0.250F, 0.0F, 0.250F, 0.0F, -5.0F, 5'000.0F, 250.0F, 0.0F, 0x3FUL, }; constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_HANGAR = { EAX_ENVIRONMENT_HANGAR, 50.3F, 1.0F, -1'000L, -1'000L, 0L, 10.05F, 0.23F, 1.0F, -602L, 0.020F, EAXVECTOR{}, 198L, 0.030F, EAXVECTOR{}, 0.250F, 0.0F, 0.250F, 0.0F, -5.0F, 5'000.0F, 250.0F, 0.0F, 0x3FUL, }; constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_CARPETTEDHALLWAY = { EAX_ENVIRONMENT_CARPETEDHALLWAY, 1.9F, 1.0F, -1'000L, -4'000L, 0L, 0.30F, 0.10F, 1.0F, -1'831L, 0.002F, EAXVECTOR{}, -1'630L, 0.030F, EAXVECTOR{}, 0.250F, 0.0F, 0.250F, 0.0F, -5.0F, 5'000.0F, 250.0F, 0.0F, 0x3FUL, }; constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_HALLWAY = { EAX_ENVIRONMENT_HALLWAY, 1.8F, 1.0F, -1'000L, -300L, 0L, 1.49F, 0.59F, 1.0F, -1'219L, 0.007F, EAXVECTOR{}, 441L, 0.011F, EAXVECTOR{}, 0.250F, 0.0F, 0.250F, 0.0F, -5.0F, 5'000.0F, 250.0F, 0.0F, 0x3FUL, }; constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_STONECORRIDOR = { EAX_ENVIRONMENT_STONECORRIDOR, 13.5F, 1.0F, -1'000L, -237L, 0L, 2.70F, 0.79F, 1.0F, -1'214L, 0.013F, EAXVECTOR{}, 395L, 0.020F, EAXVECTOR{}, 0.250F, 0.0F, 0.250F, 0.0F, -5.0F, 5'000.0F, 250.0F, 0.0F, 0x3FUL, }; constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_ALLEY = { EAX_ENVIRONMENT_ALLEY, 7.5F, 0.300F, -1'000L, -270L, 0L, 1.49F, 0.86F, 1.0F, -1'204L, 0.007F, EAXVECTOR{}, -4L, 0.011F, EAXVECTOR{}, 0.125F, 0.950F, 0.250F, 0.0F, -5.0F, 5'000.0F, 250.0F, 0.0F, 0x3FUL, }; constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_FOREST = { EAX_ENVIRONMENT_FOREST, 38.0F, 0.300F, -1'000L, -3'300L, 0L, 1.49F, 0.54F, 1.0F, -2'560L, 0.162F, EAXVECTOR{}, -229L, 0.088F, EAXVECTOR{}, 0.125F, 1.0F, 0.250F, 0.0F, -5.0F, 5'000.0F, 250.0F, 0.0F, 0x3FUL, }; constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_CITY = { EAX_ENVIRONMENT_CITY, 7.5F, 0.500F, -1'000L, -800L, 0L, 1.49F, 0.67F, 1.0F, -2'273L, 0.007F, EAXVECTOR{}, -1'691L, 0.011F, EAXVECTOR{}, 0.250F, 0.0F, 0.250F, 0.0F, -5.0F, 5'000.0F, 250.0F, 0.0F, 0x3FUL, }; constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_MOUNTAINS = { EAX_ENVIRONMENT_MOUNTAINS, 100.0F, 0.270F, -1'000L, -2'500L, 0L, 1.49F, 0.21F, 1.0F, -2'780L, 0.300F, EAXVECTOR{}, -1'434L, 0.100F, EAXVECTOR{}, 0.250F, 1.0F, 0.250F, 0.0F, -5.0F, 5'000.0F, 250.0F, 0.0F, 0x1FUL, }; constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_QUARRY = { EAX_ENVIRONMENT_QUARRY, 17.5F, 1.0F, -1'000L, -1'000L, 0L, 1.49F, 0.83F, 1.0F, -10'000L, 0.061F, EAXVECTOR{}, 500L, 0.025F, EAXVECTOR{}, 0.125F, 0.700F, 0.250F, 0.0F, -5.0F, 5'000.0F, 250.0F, 0.0F, 0x3FUL, }; constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_PLAIN = { EAX_ENVIRONMENT_PLAIN, 42.5F, 0.210F, -1'000L, -2'000L, 0L, 1.49F, 0.50F, 1.0F, -2'466L, 0.179F, EAXVECTOR{}, -1'926L, 0.100F, EAXVECTOR{}, 0.250F, 1.0F, 0.250F, 0.0F, -5.0F, 5'000.0F, 250.0F, 0.0F, 0x3FUL, }; constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_PARKINGLOT = { EAX_ENVIRONMENT_PARKINGLOT, 8.3F, 1.0F, -1'000L, 0L, 0L, 1.65F, 1.50F, 1.0F, -1'363L, 0.008F, EAXVECTOR{}, -1'153L, 0.012F, EAXVECTOR{}, 0.250F, 0.0F, 0.250F, 0.0F, -5.0F, 5'000.0F, 250.0F, 0.0F, 0x1FUL, }; constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_SEWERPIPE = { EAX_ENVIRONMENT_SEWERPIPE, 1.7F, 0.800F, -1'000L, -1'000L, 0L, 2.81F, 0.14F, 1.0F, 429L, 0.014F, EAXVECTOR{}, 1'023L, 0.021F, EAXVECTOR{}, 0.250F, 0.0F, 0.250F, 0.0F, -5.0F, 5'000.0F, 250.0F, 0.0F, 0x3FUL, }; constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_UNDERWATER = { EAX_ENVIRONMENT_UNDERWATER, 1.8F, 1.0F, -1'000L, -4'000L, 0L, 1.49F, 0.10F, 1.0F, -449L, 0.007F, EAXVECTOR{}, 1'700L, 0.011F, EAXVECTOR{}, 0.250F, 0.0F, 1.180F, 0.348F, -5.0F, 5'000.0F, 250.0F, 0.0F, 0x3FUL, }; constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_DRUGGED = { EAX_ENVIRONMENT_DRUGGED, 1.9F, 0.500F, -1'000L, 0L, 0L, 8.39F, 1.39F, 1.0F, -115L, 0.002F, EAXVECTOR{}, 985L, 0.030F, EAXVECTOR{}, 0.250F, 0.0F, 0.250F, 1.0F, -5.0F, 5'000.0F, 250.0F, 0.0F, 0x1FUL, }; constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_DIZZY = { EAX_ENVIRONMENT_DIZZY, 1.8F, 0.600F, -1'000L, -400L, 0L, 17.23F, 0.56F, 1.0F, -1'713L, 0.020F, EAXVECTOR{}, -613L, 0.030F, EAXVECTOR{}, 0.250F, 1.0F, 0.810F, 0.310F, -5.0F, 5'000.0F, 250.0F, 0.0F, 0x1FUL, }; constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_PSYCHOTIC = { EAX_ENVIRONMENT_PSYCHOTIC, 1.0F, 0.500F, -1'000L, -151L, 0L, 7.56F, 0.91F, 1.0F, -626L, 0.020F, EAXVECTOR{}, 774L, 0.030F, EAXVECTOR{}, 0.250F, 0.0F, 4.0F, 1.0F, -5.0F, 5'000.0F, 250.0F, 0.0F, 0x1FUL, }; } // namespace const EaxReverbPresets EAXREVERB_PRESETS{{ EAXREVERB_PRESET_GENERIC, EAXREVERB_PRESET_PADDEDCELL, EAXREVERB_PRESET_ROOM, EAXREVERB_PRESET_BATHROOM, EAXREVERB_PRESET_LIVINGROOM, EAXREVERB_PRESET_STONEROOM, EAXREVERB_PRESET_AUDITORIUM, EAXREVERB_PRESET_CONCERTHALL, EAXREVERB_PRESET_CAVE, EAXREVERB_PRESET_ARENA, EAXREVERB_PRESET_HANGAR, EAXREVERB_PRESET_CARPETTEDHALLWAY, EAXREVERB_PRESET_HALLWAY, EAXREVERB_PRESET_STONECORRIDOR, EAXREVERB_PRESET_ALLEY, EAXREVERB_PRESET_FOREST, EAXREVERB_PRESET_CITY, EAXREVERB_PRESET_MOUNTAINS, EAXREVERB_PRESET_QUARRY, EAXREVERB_PRESET_PLAIN, EAXREVERB_PRESET_PARKINGLOT, EAXREVERB_PRESET_SEWERPIPE, EAXREVERB_PRESET_UNDERWATER, EAXREVERB_PRESET_DRUGGED, EAXREVERB_PRESET_DIZZY, EAXREVERB_PRESET_PSYCHOTIC, }}; openal-soft-1.24.2/al/eax/api.h000066400000000000000000001450471474041540300161370ustar00rootroot00000000000000#ifndef EAX_API_INCLUDED #define EAX_API_INCLUDED // // EAX API. // // Based on headers `eax[2-5].h` included in Doom 3 source code: // https://github.com/id-Software/DOOM-3/tree/master/neo/openal/include // #include #include #include #include #include #ifdef _WIN32 #include #endif #include "AL/al.h" #include "opthelpers.h" #ifndef _WIN32 using GUID = struct _GUID { /* NOLINT(*-reserved-identifier) */ std::uint32_t Data1; std::uint16_t Data2; std::uint16_t Data3; std::array Data4; }; inline bool operator==(const GUID& lhs, const GUID& rhs) noexcept { return std::memcmp(&lhs, &rhs, sizeof(GUID)) == 0; } inline bool operator!=(const GUID& lhs, const GUID& rhs) noexcept { return !(lhs == rhs); } #endif // _WIN32 /* TODO: This seems to create very inefficient comparisons. C++20 should allow * creating default comparison operators, avoiding the need for this. */ #define DECL_EQOP(T, ...) \ [[nodiscard]] auto get_members() const noexcept { return std::forward_as_tuple(__VA_ARGS__); } \ [[nodiscard]] friend bool operator==(const T &lhs, const T &rhs) noexcept \ { return lhs.get_members() == rhs.get_members(); } \ [[nodiscard]] friend bool operator!=(const T &lhs, const T &rhs) noexcept { return !(lhs == rhs); } DECL_HIDDEN extern const GUID DSPROPSETID_EAX_ReverbProperties; enum DSPROPERTY_EAX_REVERBPROPERTY : unsigned int { DSPROPERTY_EAX_ALL, DSPROPERTY_EAX_ENVIRONMENT, DSPROPERTY_EAX_VOLUME, DSPROPERTY_EAX_DECAYTIME, DSPROPERTY_EAX_DAMPING, }; // DSPROPERTY_EAX_REVERBPROPERTY struct EAX_REVERBPROPERTIES { unsigned long environment; float fVolume; float fDecayTime_sec; float fDamping; }; // EAX_REVERBPROPERTIES DECL_HIDDEN extern const GUID DSPROPSETID_EAXBUFFER_ReverbProperties; enum DSPROPERTY_EAXBUFFER_REVERBPROPERTY : unsigned int { DSPROPERTY_EAXBUFFER_ALL, DSPROPERTY_EAXBUFFER_REVERBMIX, }; // DSPROPERTY_EAXBUFFER_REVERBPROPERTY struct EAXBUFFER_REVERBPROPERTIES { float fMix; }; constexpr auto EAX_BUFFER_MINREVERBMIX = 0.0F; constexpr auto EAX_BUFFER_MAXREVERBMIX = 1.0F; constexpr auto EAX_REVERBMIX_USEDISTANCE = -1.0F; DECL_HIDDEN extern const GUID DSPROPSETID_EAX20_ListenerProperties; enum DSPROPERTY_EAX20_LISTENERPROPERTY : unsigned int { DSPROPERTY_EAX20LISTENER_NONE, DSPROPERTY_EAX20LISTENER_ALLPARAMETERS, DSPROPERTY_EAX20LISTENER_ROOM, DSPROPERTY_EAX20LISTENER_ROOMHF, DSPROPERTY_EAX20LISTENER_ROOMROLLOFFFACTOR, DSPROPERTY_EAX20LISTENER_DECAYTIME, DSPROPERTY_EAX20LISTENER_DECAYHFRATIO, DSPROPERTY_EAX20LISTENER_REFLECTIONS, DSPROPERTY_EAX20LISTENER_REFLECTIONSDELAY, DSPROPERTY_EAX20LISTENER_REVERB, DSPROPERTY_EAX20LISTENER_REVERBDELAY, DSPROPERTY_EAX20LISTENER_ENVIRONMENT, DSPROPERTY_EAX20LISTENER_ENVIRONMENTSIZE, DSPROPERTY_EAX20LISTENER_ENVIRONMENTDIFFUSION, DSPROPERTY_EAX20LISTENER_AIRABSORPTIONHF, DSPROPERTY_EAX20LISTENER_FLAGS }; // DSPROPERTY_EAX20_LISTENERPROPERTY struct EAX20LISTENERPROPERTIES { long lRoom; // room effect level at low frequencies long lRoomHF; // room effect high-frequency level re. low frequency level float flRoomRolloffFactor; // like DS3D flRolloffFactor but for room effect float flDecayTime; // reverberation decay time at low frequencies float flDecayHFRatio; // high-frequency to low-frequency decay time ratio long lReflections; // early reflections level relative to room effect float flReflectionsDelay; // initial reflection delay time long lReverb; // late reverberation level relative to room effect float flReverbDelay; // late reverberation delay time relative to initial reflection unsigned long dwEnvironment; // sets all listener properties float flEnvironmentSize; // environment size in meters float flEnvironmentDiffusion; // environment diffusion float flAirAbsorptionHF; // change in level per meter at 5 kHz unsigned long dwFlags; // modifies the behavior of properties }; // EAX20LISTENERPROPERTIES enum : unsigned long { EAX2_ENVIRONMENT_GENERIC, EAX2_ENVIRONMENT_PADDEDCELL, EAX2_ENVIRONMENT_ROOM, EAX2_ENVIRONMENT_BATHROOM, EAX2_ENVIRONMENT_LIVINGROOM, EAX2_ENVIRONMENT_STONEROOM, EAX2_ENVIRONMENT_AUDITORIUM, EAX2_ENVIRONMENT_CONCERTHALL, EAX2_ENVIRONMENT_CAVE, EAX2_ENVIRONMENT_ARENA, EAX2_ENVIRONMENT_HANGAR, EAX2_ENVIRONMENT_CARPETEDHALLWAY, EAX2_ENVIRONMENT_HALLWAY, EAX2_ENVIRONMENT_STONECORRIDOR, EAX2_ENVIRONMENT_ALLEY, EAX2_ENVIRONMENT_FOREST, EAX2_ENVIRONMENT_CITY, EAX2_ENVIRONMENT_MOUNTAINS, EAX2_ENVIRONMENT_QUARRY, EAX2_ENVIRONMENT_PLAIN, EAX2_ENVIRONMENT_PARKINGLOT, EAX2_ENVIRONMENT_SEWERPIPE, EAX2_ENVIRONMENT_UNDERWATER, EAX2_ENVIRONMENT_DRUGGED, EAX2_ENVIRONMENT_DIZZY, EAX2_ENVIRONMENT_PSYCHOTIC, EAX2_ENVIRONMENT_COUNT, }; constexpr auto EAX2LISTENERFLAGS_DECAYTIMESCALE = 0x00000001UL; constexpr auto EAX2LISTENERFLAGS_REFLECTIONSSCALE = 0x00000002UL; constexpr auto EAX2LISTENERFLAGS_REFLECTIONSDELAYSCALE = 0x00000004UL; constexpr auto EAX2LISTENERFLAGS_REVERBSCALE = 0x00000008UL; constexpr auto EAX2LISTENERFLAGS_REVERBDELAYSCALE = 0x00000010UL; constexpr auto EAX2LISTENERFLAGS_DECAYHFLIMIT = 0x00000020UL; constexpr auto EAX2LISTENERFLAGS_RESERVED = 0xFFFFFFC0UL; constexpr auto EAX2LISTENER_MINROOM = -10'000L; constexpr auto EAX2LISTENER_MAXROOM = 0L; constexpr auto EAX2LISTENER_DEFAULTROOM = -1'000L; constexpr auto EAX2LISTENER_MINROOMHF = -10'000L; constexpr auto EAX2LISTENER_MAXROOMHF = 0L; constexpr auto EAX2LISTENER_DEFAULTROOMHF = -100L; constexpr auto EAX2LISTENER_MINROOMROLLOFFFACTOR = 0.0F; constexpr auto EAX2LISTENER_MAXROOMROLLOFFFACTOR = 10.0F; constexpr auto EAX2LISTENER_DEFAULTROOMROLLOFFFACTOR = 0.0F; constexpr auto EAX2LISTENER_MINDECAYTIME = 0.1F; constexpr auto EAX2LISTENER_MAXDECAYTIME = 20.0F; constexpr auto EAX2LISTENER_DEFAULTDECAYTIME = 1.49F; constexpr auto EAX2LISTENER_MINDECAYHFRATIO = 0.1F; constexpr auto EAX2LISTENER_MAXDECAYHFRATIO = 2.0F; constexpr auto EAX2LISTENER_DEFAULTDECAYHFRATIO = 0.83F; constexpr auto EAX2LISTENER_MINREFLECTIONS = -10'000L; constexpr auto EAX2LISTENER_MAXREFLECTIONS = 1'000L; constexpr auto EAX2LISTENER_DEFAULTREFLECTIONS = -2'602L; constexpr auto EAX2LISTENER_MINREFLECTIONSDELAY = 0.0F; constexpr auto EAX2LISTENER_MAXREFLECTIONSDELAY = 0.3F; constexpr auto EAX2LISTENER_DEFAULTREFLECTIONSDELAY = 0.007F; constexpr auto EAX2LISTENER_MINREVERB = -10'000L; constexpr auto EAX2LISTENER_MAXREVERB = 2'000L; constexpr auto EAX2LISTENER_DEFAULTREVERB = 200L; constexpr auto EAX2LISTENER_MINREVERBDELAY = 0.0F; constexpr auto EAX2LISTENER_MAXREVERBDELAY = 0.1F; constexpr auto EAX2LISTENER_DEFAULTREVERBDELAY = 0.011F; constexpr auto EAX2LISTENER_MINENVIRONMENT = 0UL; constexpr auto EAX2LISTENER_MAXENVIRONMENT = EAX2_ENVIRONMENT_COUNT - 1; constexpr auto EAX2LISTENER_DEFAULTENVIRONMENT = EAX2_ENVIRONMENT_GENERIC; constexpr auto EAX2LISTENER_MINENVIRONMENTSIZE = 1.0F; constexpr auto EAX2LISTENER_MAXENVIRONMENTSIZE = 100.0F; constexpr auto EAX2LISTENER_DEFAULTENVIRONMENTSIZE = 7.5F; constexpr auto EAX2LISTENER_MINENVIRONMENTDIFFUSION = 0.0F; constexpr auto EAX2LISTENER_MAXENVIRONMENTDIFFUSION = 1.0F; constexpr auto EAX2LISTENER_DEFAULTENVIRONMENTDIFFUSION = 1.0F; constexpr auto EAX2LISTENER_MINAIRABSORPTIONHF = -100.0F; constexpr auto EAX2LISTENER_MAXAIRABSORPTIONHF = 0.0F; constexpr auto EAX2LISTENER_DEFAULTAIRABSORPTIONHF = -5.0F; constexpr auto EAX2LISTENER_DEFAULTFLAGS = EAX2LISTENERFLAGS_DECAYTIMESCALE | EAX2LISTENERFLAGS_REFLECTIONSSCALE | EAX2LISTENERFLAGS_REFLECTIONSDELAYSCALE | EAX2LISTENERFLAGS_REVERBSCALE | EAX2LISTENERFLAGS_REVERBDELAYSCALE | EAX2LISTENERFLAGS_DECAYHFLIMIT; DECL_HIDDEN extern const GUID DSPROPSETID_EAX20_BufferProperties; enum DSPROPERTY_EAX20_BUFFERPROPERTY : unsigned int { DSPROPERTY_EAX20BUFFER_NONE, DSPROPERTY_EAX20BUFFER_ALLPARAMETERS, DSPROPERTY_EAX20BUFFER_DIRECT, DSPROPERTY_EAX20BUFFER_DIRECTHF, DSPROPERTY_EAX20BUFFER_ROOM, DSPROPERTY_EAX20BUFFER_ROOMHF, DSPROPERTY_EAX20BUFFER_ROOMROLLOFFFACTOR, DSPROPERTY_EAX20BUFFER_OBSTRUCTION, DSPROPERTY_EAX20BUFFER_OBSTRUCTIONLFRATIO, DSPROPERTY_EAX20BUFFER_OCCLUSION, DSPROPERTY_EAX20BUFFER_OCCLUSIONLFRATIO, DSPROPERTY_EAX20BUFFER_OCCLUSIONROOMRATIO, DSPROPERTY_EAX20BUFFER_OUTSIDEVOLUMEHF, DSPROPERTY_EAX20BUFFER_AIRABSORPTIONFACTOR, DSPROPERTY_EAX20BUFFER_FLAGS }; // DSPROPERTY_EAX20_BUFFERPROPERTY struct EAX20BUFFERPROPERTIES { long lDirect; // direct path level long lDirectHF; // direct path level at high frequencies long lRoom; // room effect level long lRoomHF; // room effect level at high frequencies float flRoomRolloffFactor; // like DS3D flRolloffFactor but for room effect long lObstruction; // main obstruction control (attenuation at high frequencies) float flObstructionLFRatio; // obstruction low-frequency level re. main control long lOcclusion; // main occlusion control (attenuation at high frequencies) float flOcclusionLFRatio; // occlusion low-frequency level re. main control float flOcclusionRoomRatio; // occlusion room effect level re. main control long lOutsideVolumeHF; // outside sound cone level at high frequencies float flAirAbsorptionFactor; // multiplies DSPROPERTY_EAXLISTENER_AIRABSORPTIONHF unsigned long dwFlags; // modifies the behavior of properties }; // EAX20BUFFERPROPERTIES DECL_HIDDEN extern const GUID DSPROPSETID_EAX30_ListenerProperties; DECL_HIDDEN extern const GUID DSPROPSETID_EAX30_BufferProperties; constexpr auto EAX_MAX_FXSLOTS = 4; constexpr auto EAX40_MAX_ACTIVE_FXSLOTS = 2; constexpr auto EAX50_MAX_ACTIVE_FXSLOTS = 4; constexpr auto EAX_OK = 0L; constexpr auto EAXERR_INVALID_OPERATION = -1L; constexpr auto EAXERR_INVALID_VALUE = -2L; constexpr auto EAXERR_NO_EFFECT_LOADED = -3L; constexpr auto EAXERR_UNKNOWN_EFFECT = -4L; constexpr auto EAXERR_INCOMPATIBLE_SOURCE_TYPE = -5L; constexpr auto EAXERR_INCOMPATIBLE_EAX_VERSION = -6L; DECL_HIDDEN extern const GUID EAX_NULL_GUID; DECL_HIDDEN extern const GUID EAX_PrimaryFXSlotID; struct EAXVECTOR { float x; float y; float z; }; [[nodiscard]] inline bool operator==(const EAXVECTOR& lhs, const EAXVECTOR& rhs) noexcept { return lhs.x == rhs.x && lhs.y == rhs.y && lhs.z == rhs.z; } [[nodiscard]] inline bool operator!=(const EAXVECTOR& lhs, const EAXVECTOR& rhs) noexcept { return !(lhs == rhs); } DECL_HIDDEN extern const GUID EAXPROPERTYID_EAX40_Context; DECL_HIDDEN extern const GUID EAXPROPERTYID_EAX50_Context; // EAX50 constexpr auto HEADPHONES = 0UL; constexpr auto SPEAKERS_2 = 1UL; constexpr auto SPEAKERS_4 = 2UL; constexpr auto SPEAKERS_5 = 3UL; // 5.1 speakers constexpr auto SPEAKERS_6 = 4UL; // 6.1 speakers constexpr auto SPEAKERS_7 = 5UL; // 7.1 speakers constexpr auto EAXCONTEXT_MINSPEAKERCONFIG = HEADPHONES; constexpr auto EAXCONTEXT_MAXSPEAKERCONFIG = SPEAKERS_7; // EAX50 constexpr auto EAX_40 = 5UL; // EAX 4.0 constexpr auto EAX_50 = 6UL; // EAX 5.0 constexpr auto EAXCONTEXT_MINEAXSESSION = EAX_40; constexpr auto EAXCONTEXT_MAXEAXSESSION = EAX_50; constexpr auto EAXCONTEXT_DEFAULTEAXSESSION = EAX_40; constexpr auto EAXCONTEXT_MINMAXACTIVESENDS = 2UL; constexpr auto EAXCONTEXT_MAXMAXACTIVESENDS = 4UL; constexpr auto EAXCONTEXT_DEFAULTMAXACTIVESENDS = 2UL; // EAX50 struct EAXSESSIONPROPERTIES { unsigned long ulEAXVersion; unsigned long ulMaxActiveSends; }; // EAXSESSIONPROPERTIES enum EAXCONTEXT_PROPERTY : unsigned int { EAXCONTEXT_NONE = 0, EAXCONTEXT_ALLPARAMETERS, EAXCONTEXT_PRIMARYFXSLOTID, EAXCONTEXT_DISTANCEFACTOR, EAXCONTEXT_AIRABSORPTIONHF, EAXCONTEXT_HFREFERENCE, EAXCONTEXT_LASTERROR, // EAX50 EAXCONTEXT_SPEAKERCONFIG, EAXCONTEXT_EAXSESSION, EAXCONTEXT_MACROFXFACTOR, }; // EAXCONTEXT_PROPERTY struct EAX40CONTEXTPROPERTIES { GUID guidPrimaryFXSlotID; float flDistanceFactor; float flAirAbsorptionHF; float flHFReference; }; // EAX40CONTEXTPROPERTIES struct EAX50CONTEXTPROPERTIES : public EAX40CONTEXTPROPERTIES { float flMacroFXFactor; }; // EAX50CONTEXTPROPERTIES constexpr auto EAXCONTEXT_MINDISTANCEFACTOR = FLT_MIN; constexpr auto EAXCONTEXT_MAXDISTANCEFACTOR = FLT_MAX; constexpr auto EAXCONTEXT_DEFAULTDISTANCEFACTOR = 1.0F; constexpr auto EAXCONTEXT_MINAIRABSORPTIONHF = -100.0F; constexpr auto EAXCONTEXT_MAXAIRABSORPTIONHF = 0.0F; constexpr auto EAXCONTEXT_DEFAULTAIRABSORPTIONHF = -5.0F; constexpr auto EAXCONTEXT_MINHFREFERENCE = 1000.0F; constexpr auto EAXCONTEXT_MAXHFREFERENCE = 20000.0F; constexpr auto EAXCONTEXT_DEFAULTHFREFERENCE = 5000.0F; constexpr auto EAXCONTEXT_MINMACROFXFACTOR = 0.0F; constexpr auto EAXCONTEXT_MAXMACROFXFACTOR = 1.0F; constexpr auto EAXCONTEXT_DEFAULTMACROFXFACTOR = 0.0F; constexpr auto EAXCONTEXT_DEFAULTLASTERROR = EAX_OK; DECL_HIDDEN extern const GUID EAXPROPERTYID_EAX40_FXSlot0; DECL_HIDDEN extern const GUID EAXPROPERTYID_EAX50_FXSlot0; DECL_HIDDEN extern const GUID EAXPROPERTYID_EAX40_FXSlot1; DECL_HIDDEN extern const GUID EAXPROPERTYID_EAX50_FXSlot1; DECL_HIDDEN extern const GUID EAXPROPERTYID_EAX40_FXSlot2; DECL_HIDDEN extern const GUID EAXPROPERTYID_EAX50_FXSlot2; DECL_HIDDEN extern const GUID EAXPROPERTYID_EAX40_FXSlot3; DECL_HIDDEN extern const GUID EAXPROPERTYID_EAX50_FXSlot3; DECL_HIDDEN extern const GUID EAX40CONTEXT_DEFAULTPRIMARYFXSLOTID; DECL_HIDDEN extern const GUID EAX50CONTEXT_DEFAULTPRIMARYFXSLOTID; enum EAXFXSLOT_PROPERTY : unsigned int { EAXFXSLOT_PARAMETER = 0, EAXFXSLOT_NONE = 0x10000, EAXFXSLOT_ALLPARAMETERS, EAXFXSLOT_LOADEFFECT, EAXFXSLOT_VOLUME, EAXFXSLOT_LOCK, EAXFXSLOT_FLAGS, // EAX50 EAXFXSLOT_OCCLUSION, EAXFXSLOT_OCCLUSIONLFRATIO, }; // EAXFXSLOT_PROPERTY constexpr auto EAXFXSLOTFLAGS_ENVIRONMENT = 0x00000001UL; // EAX50 constexpr auto EAXFXSLOTFLAGS_UPMIX = 0x00000002UL; constexpr auto EAX40FXSLOTFLAGS_RESERVED = 0xFFFFFFFEUL; // reserved future use constexpr auto EAX50FXSLOTFLAGS_RESERVED = 0xFFFFFFFCUL; // reserved future use constexpr auto EAXFXSLOT_MINVOLUME = -10'000L; constexpr auto EAXFXSLOT_MAXVOLUME = 0L; constexpr auto EAXFXSLOT_DEFAULTVOLUME = 0L; constexpr auto EAXFXSLOT_MINLOCK = 0L; constexpr auto EAXFXSLOT_MAXLOCK = 1L; enum : long { EAXFXSLOT_UNLOCKED = 0, EAXFXSLOT_LOCKED = 1 }; constexpr auto EAXFXSLOT_MINOCCLUSION = -10'000L; constexpr auto EAXFXSLOT_MAXOCCLUSION = 0L; constexpr auto EAXFXSLOT_DEFAULTOCCLUSION = 0L; constexpr auto EAXFXSLOT_MINOCCLUSIONLFRATIO = 0.0F; constexpr auto EAXFXSLOT_MAXOCCLUSIONLFRATIO = 1.0F; constexpr auto EAXFXSLOT_DEFAULTOCCLUSIONLFRATIO = 0.25F; constexpr auto EAX40FXSLOT_DEFAULTFLAGS = EAXFXSLOTFLAGS_ENVIRONMENT; constexpr auto EAX50FXSLOT_DEFAULTFLAGS = EAXFXSLOTFLAGS_ENVIRONMENT | EAXFXSLOTFLAGS_UPMIX; // ignored for reverb; struct EAX40FXSLOTPROPERTIES { GUID guidLoadEffect; long lVolume; long lLock; unsigned long ulFlags; }; // EAX40FXSLOTPROPERTIES struct EAX50FXSLOTPROPERTIES : public EAX40FXSLOTPROPERTIES { long lOcclusion; float flOcclusionLFRatio; }; // EAX50FXSLOTPROPERTIES DECL_HIDDEN extern const GUID EAXPROPERTYID_EAX40_Source; DECL_HIDDEN extern const GUID EAXPROPERTYID_EAX50_Source; // Source object properties enum EAXSOURCE_PROPERTY : unsigned int { // EAX30 EAXSOURCE_NONE, EAXSOURCE_ALLPARAMETERS, EAXSOURCE_OBSTRUCTIONPARAMETERS, EAXSOURCE_OCCLUSIONPARAMETERS, EAXSOURCE_EXCLUSIONPARAMETERS, EAXSOURCE_DIRECT, EAXSOURCE_DIRECTHF, EAXSOURCE_ROOM, EAXSOURCE_ROOMHF, EAXSOURCE_OBSTRUCTION, EAXSOURCE_OBSTRUCTIONLFRATIO, EAXSOURCE_OCCLUSION, EAXSOURCE_OCCLUSIONLFRATIO, EAXSOURCE_OCCLUSIONROOMRATIO, EAXSOURCE_OCCLUSIONDIRECTRATIO, EAXSOURCE_EXCLUSION, EAXSOURCE_EXCLUSIONLFRATIO, EAXSOURCE_OUTSIDEVOLUMEHF, EAXSOURCE_DOPPLERFACTOR, EAXSOURCE_ROLLOFFFACTOR, EAXSOURCE_ROOMROLLOFFFACTOR, EAXSOURCE_AIRABSORPTIONFACTOR, EAXSOURCE_FLAGS, // EAX40 EAXSOURCE_SENDPARAMETERS, EAXSOURCE_ALLSENDPARAMETERS, EAXSOURCE_OCCLUSIONSENDPARAMETERS, EAXSOURCE_EXCLUSIONSENDPARAMETERS, EAXSOURCE_ACTIVEFXSLOTID, // EAX50 EAXSOURCE_MACROFXFACTOR, EAXSOURCE_SPEAKERLEVELS, EAXSOURCE_ALL2DPARAMETERS, }; // EAXSOURCE_PROPERTY constexpr auto EAXSOURCEFLAGS_DIRECTHFAUTO = 0x00000001UL; // relates to EAXSOURCE_DIRECTHF constexpr auto EAXSOURCEFLAGS_ROOMAUTO = 0x00000002UL; // relates to EAXSOURCE_ROOM constexpr auto EAXSOURCEFLAGS_ROOMHFAUTO = 0x00000004UL; // relates to EAXSOURCE_ROOMHF // EAX50 constexpr auto EAXSOURCEFLAGS_3DELEVATIONFILTER = 0x00000008UL; constexpr auto EAXSOURCEFLAGS_UPMIX = 0x00000010UL; constexpr auto EAXSOURCEFLAGS_APPLYSPEAKERLEVELS = 0x00000020UL; constexpr auto EAX20SOURCEFLAGS_RESERVED = 0xFFFFFFF8UL; // reserved future use constexpr auto EAX50SOURCEFLAGS_RESERVED = 0xFFFFFFC0UL; // reserved future use constexpr auto EAXSOURCE_MINSEND = -10'000L; constexpr auto EAXSOURCE_MAXSEND = 0L; constexpr auto EAXSOURCE_DEFAULTSEND = 0L; constexpr auto EAXSOURCE_MINSENDHF = -10'000L; constexpr auto EAXSOURCE_MAXSENDHF = 0L; constexpr auto EAXSOURCE_DEFAULTSENDHF = 0L; constexpr auto EAXSOURCE_MINDIRECT = -10'000L; constexpr auto EAXSOURCE_MAXDIRECT = 1'000L; constexpr auto EAXSOURCE_DEFAULTDIRECT = 0L; constexpr auto EAXSOURCE_MINDIRECTHF = -10'000L; constexpr auto EAXSOURCE_MAXDIRECTHF = 0L; constexpr auto EAXSOURCE_DEFAULTDIRECTHF = 0L; constexpr auto EAXSOURCE_MINROOM = -10'000L; constexpr auto EAXSOURCE_MAXROOM = 1'000L; constexpr auto EAXSOURCE_DEFAULTROOM = 0L; constexpr auto EAXSOURCE_MINROOMHF = -10'000L; constexpr auto EAXSOURCE_MAXROOMHF = 0L; constexpr auto EAXSOURCE_DEFAULTROOMHF = 0L; constexpr auto EAXSOURCE_MINOBSTRUCTION = -10'000L; constexpr auto EAXSOURCE_MAXOBSTRUCTION = 0L; constexpr auto EAXSOURCE_DEFAULTOBSTRUCTION = 0L; constexpr auto EAXSOURCE_MINOBSTRUCTIONLFRATIO = 0.0F; constexpr auto EAXSOURCE_MAXOBSTRUCTIONLFRATIO = 1.0F; constexpr auto EAXSOURCE_DEFAULTOBSTRUCTIONLFRATIO = 0.0F; constexpr auto EAXSOURCE_MINOCCLUSION = -10'000L; constexpr auto EAXSOURCE_MAXOCCLUSION = 0L; constexpr auto EAXSOURCE_DEFAULTOCCLUSION = 0L; constexpr auto EAXSOURCE_MINOCCLUSIONLFRATIO = 0.0F; constexpr auto EAXSOURCE_MAXOCCLUSIONLFRATIO = 1.0F; constexpr auto EAXSOURCE_DEFAULTOCCLUSIONLFRATIO = 0.25F; constexpr auto EAXSOURCE_MINOCCLUSIONROOMRATIO = 0.0F; constexpr auto EAXSOURCE_MAXOCCLUSIONROOMRATIO = 10.0F; constexpr auto EAXSOURCE_DEFAULTOCCLUSIONROOMRATIO = 1.5F; constexpr auto EAXSOURCE_MINOCCLUSIONDIRECTRATIO = 0.0F; constexpr auto EAXSOURCE_MAXOCCLUSIONDIRECTRATIO = 10.0F; constexpr auto EAXSOURCE_DEFAULTOCCLUSIONDIRECTRATIO = 1.0F; constexpr auto EAXSOURCE_MINEXCLUSION = -10'000L; constexpr auto EAXSOURCE_MAXEXCLUSION = 0L; constexpr auto EAXSOURCE_DEFAULTEXCLUSION = 0L; constexpr auto EAXSOURCE_MINEXCLUSIONLFRATIO = 0.0F; constexpr auto EAXSOURCE_MAXEXCLUSIONLFRATIO = 1.0F; constexpr auto EAXSOURCE_DEFAULTEXCLUSIONLFRATIO = 1.0F; constexpr auto EAXSOURCE_MINOUTSIDEVOLUMEHF = -10'000L; constexpr auto EAXSOURCE_MAXOUTSIDEVOLUMEHF = 0L; constexpr auto EAXSOURCE_DEFAULTOUTSIDEVOLUMEHF = 0L; constexpr auto EAXSOURCE_MINDOPPLERFACTOR = 0.0F; constexpr auto EAXSOURCE_MAXDOPPLERFACTOR = 10.0F; constexpr auto EAXSOURCE_DEFAULTDOPPLERFACTOR = 1.0F; constexpr auto EAXSOURCE_MINROLLOFFFACTOR = 0.0F; constexpr auto EAXSOURCE_MAXROLLOFFFACTOR = 10.0F; constexpr auto EAXSOURCE_DEFAULTROLLOFFFACTOR = 0.0F; constexpr auto EAXSOURCE_MINROOMROLLOFFFACTOR = 0.0F; constexpr auto EAXSOURCE_MAXROOMROLLOFFFACTOR = 10.0F; constexpr auto EAXSOURCE_DEFAULTROOMROLLOFFFACTOR = 0.0F; constexpr auto EAXSOURCE_MINAIRABSORPTIONFACTOR = 0.0F; constexpr auto EAXSOURCE_MAXAIRABSORPTIONFACTOR = 10.0F; constexpr auto EAXSOURCE_DEFAULTAIRABSORPTIONFACTOR = 0.0F; // EAX50 constexpr auto EAXSOURCE_MINMACROFXFACTOR = 0.0F; constexpr auto EAXSOURCE_MAXMACROFXFACTOR = 1.0F; constexpr auto EAXSOURCE_DEFAULTMACROFXFACTOR = 1.0F; constexpr auto EAXSOURCE_MINSPEAKERLEVEL = -10'000L; constexpr auto EAXSOURCE_MAXSPEAKERLEVEL = 0L; constexpr auto EAXSOURCE_DEFAULTSPEAKERLEVEL = -10'000L; constexpr auto EAXSOURCE_DEFAULTFLAGS = EAXSOURCEFLAGS_DIRECTHFAUTO | EAXSOURCEFLAGS_ROOMAUTO | EAXSOURCEFLAGS_ROOMHFAUTO; enum : long { EAXSPEAKER_FRONT_LEFT = 1, EAXSPEAKER_FRONT_CENTER = 2, EAXSPEAKER_FRONT_RIGHT = 3, EAXSPEAKER_SIDE_RIGHT = 4, EAXSPEAKER_REAR_RIGHT = 5, EAXSPEAKER_REAR_CENTER = 6, EAXSPEAKER_REAR_LEFT = 7, EAXSPEAKER_SIDE_LEFT = 8, EAXSPEAKER_LOW_FREQUENCY = 9 }; // EAXSOURCEFLAGS_DIRECTHFAUTO, EAXSOURCEFLAGS_ROOMAUTO and EAXSOURCEFLAGS_ROOMHFAUTO are ignored for 2D sources // EAXSOURCEFLAGS_UPMIX is ignored for 3D sources constexpr auto EAX50SOURCE_DEFAULTFLAGS = EAXSOURCEFLAGS_DIRECTHFAUTO | EAXSOURCEFLAGS_ROOMAUTO | EAXSOURCEFLAGS_ROOMHFAUTO | EAXSOURCEFLAGS_UPMIX; struct EAX30SOURCEPROPERTIES { long lDirect; // direct path level (at low and mid frequencies) long lDirectHF; // relative direct path level at high frequencies long lRoom; // room effect level (at low and mid frequencies) long lRoomHF; // relative room effect level at high frequencies long lObstruction; // main obstruction control (attenuation at high frequencies) float flObstructionLFRatio; // obstruction low-frequency level re. main control long lOcclusion; // main occlusion control (attenuation at high frequencies) float flOcclusionLFRatio; // occlusion low-frequency level re. main control float flOcclusionRoomRatio; // relative occlusion control for room effect float flOcclusionDirectRatio; // relative occlusion control for direct path long lExclusion; // main exclusion control (attenuation at high frequencies) float flExclusionLFRatio; // exclusion low-frequency level re. main control long lOutsideVolumeHF; // outside sound cone level at high frequencies float flDopplerFactor; // like DS3D flDopplerFactor but per source float flRolloffFactor; // like DS3D flRolloffFactor but per source float flRoomRolloffFactor; // like DS3D flRolloffFactor but for room effect float flAirAbsorptionFactor; // multiplies EAXREVERB_AIRABSORPTIONHF unsigned long ulFlags; // modifies the behavior of properties }; // EAX30SOURCEPROPERTIES struct EAX50SOURCEPROPERTIES : public EAX30SOURCEPROPERTIES { float flMacroFXFactor; }; // EAX50SOURCEPROPERTIES struct EAXSOURCEALLSENDPROPERTIES { GUID guidReceivingFXSlotID; long lSend; // send level (at low and mid frequencies) long lSendHF; // relative send level at high frequencies long lOcclusion; float flOcclusionLFRatio; float flOcclusionRoomRatio; float flOcclusionDirectRatio; long lExclusion; float flExclusionLFRatio; }; // EAXSOURCEALLSENDPROPERTIES struct EAXSOURCE2DPROPERTIES { long lDirect; // direct path level (at low and mid frequencies) long lDirectHF; // relative direct path level at high frequencies long lRoom; // room effect level (at low and mid frequencies) long lRoomHF; // relative room effect level at high frequencies unsigned long ulFlags; // modifies the behavior of properties }; // EAXSOURCE2DPROPERTIES struct EAXSPEAKERLEVELPROPERTIES { long lSpeakerID; long lLevel; }; // EAXSPEAKERLEVELPROPERTIES struct EAX40ACTIVEFXSLOTS { std::array guidActiveFXSlots; }; // EAX40ACTIVEFXSLOTS struct EAX50ACTIVEFXSLOTS { std::array guidActiveFXSlots; }; // EAX50ACTIVEFXSLOTS // Use this structure for EAXSOURCE_OBSTRUCTIONPARAMETERS property. struct EAXOBSTRUCTIONPROPERTIES { long lObstruction; float flObstructionLFRatio; }; // EAXOBSTRUCTIONPROPERTIES // Use this structure for EAXSOURCE_OCCLUSIONPARAMETERS property. struct EAXOCCLUSIONPROPERTIES { long lOcclusion; float flOcclusionLFRatio; float flOcclusionRoomRatio; float flOcclusionDirectRatio; }; // EAXOCCLUSIONPROPERTIES // Use this structure for EAXSOURCE_EXCLUSIONPARAMETERS property. struct EAXEXCLUSIONPROPERTIES { long lExclusion; float flExclusionLFRatio; }; // EAXEXCLUSIONPROPERTIES // Use this structure for EAXSOURCE_SENDPARAMETERS properties. struct EAXSOURCESENDPROPERTIES { GUID guidReceivingFXSlotID; long lSend; long lSendHF; }; // EAXSOURCESENDPROPERTIES // Use this structure for EAXSOURCE_OCCLUSIONSENDPARAMETERS struct EAXSOURCEOCCLUSIONSENDPROPERTIES { GUID guidReceivingFXSlotID; long lOcclusion; float flOcclusionLFRatio; float flOcclusionRoomRatio; float flOcclusionDirectRatio; }; // EAXSOURCEOCCLUSIONSENDPROPERTIES // Use this structure for EAXSOURCE_EXCLUSIONSENDPARAMETERS struct EAXSOURCEEXCLUSIONSENDPROPERTIES { GUID guidReceivingFXSlotID; long lExclusion; float flExclusionLFRatio; }; // EAXSOURCEEXCLUSIONSENDPROPERTIES DECL_HIDDEN extern const EAX40ACTIVEFXSLOTS EAX40SOURCE_DEFAULTACTIVEFXSLOTID; DECL_HIDDEN extern const EAX50ACTIVEFXSLOTS EAX50SOURCE_3DDEFAULTACTIVEFXSLOTID; DECL_HIDDEN extern const EAX50ACTIVEFXSLOTS EAX50SOURCE_2DDEFAULTACTIVEFXSLOTID; // EAX Reverb Effect DECL_HIDDEN extern const GUID EAX_REVERB_EFFECT; // Reverb effect properties enum EAXREVERB_PROPERTY : unsigned int { EAXREVERB_NONE, EAXREVERB_ALLPARAMETERS, EAXREVERB_ENVIRONMENT, EAXREVERB_ENVIRONMENTSIZE, EAXREVERB_ENVIRONMENTDIFFUSION, EAXREVERB_ROOM, EAXREVERB_ROOMHF, EAXREVERB_ROOMLF, EAXREVERB_DECAYTIME, EAXREVERB_DECAYHFRATIO, EAXREVERB_DECAYLFRATIO, EAXREVERB_REFLECTIONS, EAXREVERB_REFLECTIONSDELAY, EAXREVERB_REFLECTIONSPAN, EAXREVERB_REVERB, EAXREVERB_REVERBDELAY, EAXREVERB_REVERBPAN, EAXREVERB_ECHOTIME, EAXREVERB_ECHODEPTH, EAXREVERB_MODULATIONTIME, EAXREVERB_MODULATIONDEPTH, EAXREVERB_AIRABSORPTIONHF, EAXREVERB_HFREFERENCE, EAXREVERB_LFREFERENCE, EAXREVERB_ROOMROLLOFFFACTOR, EAXREVERB_FLAGS, }; // EAXREVERB_PROPERTY // used by EAXREVERB_ENVIRONMENT enum : unsigned long { EAX_ENVIRONMENT_GENERIC, EAX_ENVIRONMENT_PADDEDCELL, EAX_ENVIRONMENT_ROOM, EAX_ENVIRONMENT_BATHROOM, EAX_ENVIRONMENT_LIVINGROOM, EAX_ENVIRONMENT_STONEROOM, EAX_ENVIRONMENT_AUDITORIUM, EAX_ENVIRONMENT_CONCERTHALL, EAX_ENVIRONMENT_CAVE, EAX_ENVIRONMENT_ARENA, EAX_ENVIRONMENT_HANGAR, EAX_ENVIRONMENT_CARPETEDHALLWAY, EAX_ENVIRONMENT_HALLWAY, EAX_ENVIRONMENT_STONECORRIDOR, EAX_ENVIRONMENT_ALLEY, EAX_ENVIRONMENT_FOREST, EAX_ENVIRONMENT_CITY, EAX_ENVIRONMENT_MOUNTAINS, EAX_ENVIRONMENT_QUARRY, EAX_ENVIRONMENT_PLAIN, EAX_ENVIRONMENT_PARKINGLOT, EAX_ENVIRONMENT_SEWERPIPE, EAX_ENVIRONMENT_UNDERWATER, EAX_ENVIRONMENT_DRUGGED, EAX_ENVIRONMENT_DIZZY, EAX_ENVIRONMENT_PSYCHOTIC, EAX1_ENVIRONMENT_COUNT, // EAX30 EAX_ENVIRONMENT_UNDEFINED = EAX1_ENVIRONMENT_COUNT, EAX3_ENVIRONMENT_COUNT, }; // reverberation decay time constexpr auto EAXREVERBFLAGS_DECAYTIMESCALE = 0x00000001UL; // reflection level constexpr auto EAXREVERBFLAGS_REFLECTIONSSCALE = 0x00000002UL; // initial reflection delay time constexpr auto EAXREVERBFLAGS_REFLECTIONSDELAYSCALE = 0x00000004UL; // reflections level constexpr auto EAXREVERBFLAGS_REVERBSCALE = 0x00000008UL; // late reverberation delay time constexpr auto EAXREVERBFLAGS_REVERBDELAYSCALE = 0x00000010UL; // echo time // EAX30+ constexpr auto EAXREVERBFLAGS_ECHOTIMESCALE = 0x00000040UL; // modulation time // EAX30+ constexpr auto EAXREVERBFLAGS_MODULATIONTIMESCALE = 0x00000080UL; // This flag limits high-frequency decay time according to air absorption. constexpr auto EAXREVERBFLAGS_DECAYHFLIMIT = 0x00000020UL; constexpr auto EAXREVERBFLAGS_RESERVED = 0xFFFFFF00UL; // reserved future use struct EAXREVERBPROPERTIES { unsigned long ulEnvironment; // sets all reverb properties float flEnvironmentSize; // environment size in meters float flEnvironmentDiffusion; // environment diffusion long lRoom; // room effect level (at mid frequencies) long lRoomHF; // relative room effect level at high frequencies long lRoomLF; // relative room effect level at low frequencies float flDecayTime; // reverberation decay time at mid frequencies float flDecayHFRatio; // high-frequency to mid-frequency decay time ratio float flDecayLFRatio; // low-frequency to mid-frequency decay time ratio long lReflections; // early reflections level relative to room effect float flReflectionsDelay; // initial reflection delay time EAXVECTOR vReflectionsPan; // early reflections panning vector long lReverb; // late reverberation level relative to room effect float flReverbDelay; // late reverberation delay time relative to initial reflection EAXVECTOR vReverbPan; // late reverberation panning vector float flEchoTime; // echo time float flEchoDepth; // echo depth float flModulationTime; // modulation time float flModulationDepth; // modulation depth float flAirAbsorptionHF; // change in level per meter at high frequencies float flHFReference; // reference high frequency float flLFReference; // reference low frequency float flRoomRolloffFactor; // like DS3D flRolloffFactor but for room effect unsigned long ulFlags; // modifies the behavior of properties DECL_EQOP(EAXREVERBPROPERTIES, ulEnvironment, flEnvironmentSize, flEnvironmentDiffusion, lRoom, lRoomHF, lRoomLF, flDecayTime, flDecayHFRatio, flDecayLFRatio, lReflections, flReflectionsDelay, vReflectionsPan, lReverb, flReverbDelay, vReverbPan, flEchoTime, flEchoDepth, flModulationTime, flModulationDepth, flAirAbsorptionHF, flHFReference, flLFReference, flRoomRolloffFactor, ulFlags) }; // EAXREVERBPROPERTIES constexpr auto EAXREVERB_MINENVIRONMENT = static_cast(EAX_ENVIRONMENT_GENERIC); constexpr auto EAX1REVERB_MAXENVIRONMENT = static_cast(EAX_ENVIRONMENT_PSYCHOTIC); constexpr auto EAX30REVERB_MAXENVIRONMENT = static_cast(EAX_ENVIRONMENT_UNDEFINED); constexpr auto EAXREVERB_DEFAULTENVIRONMENT = static_cast(EAX_ENVIRONMENT_GENERIC); constexpr auto EAXREVERB_MINENVIRONMENTSIZE = 1.0F; constexpr auto EAXREVERB_MAXENVIRONMENTSIZE = 100.0F; constexpr auto EAXREVERB_DEFAULTENVIRONMENTSIZE = 7.5F; constexpr auto EAXREVERB_MINENVIRONMENTDIFFUSION = 0.0F; constexpr auto EAXREVERB_MAXENVIRONMENTDIFFUSION = 1.0F; constexpr auto EAXREVERB_DEFAULTENVIRONMENTDIFFUSION = 1.0F; constexpr auto EAXREVERB_MINROOM = -10'000L; constexpr auto EAXREVERB_MAXROOM = 0L; constexpr auto EAXREVERB_DEFAULTROOM = -1'000L; constexpr auto EAXREVERB_MINROOMHF = -10'000L; constexpr auto EAXREVERB_MAXROOMHF = 0L; constexpr auto EAXREVERB_DEFAULTROOMHF = -100L; constexpr auto EAXREVERB_MINROOMLF = -10'000L; constexpr auto EAXREVERB_MAXROOMLF = 0L; constexpr auto EAXREVERB_DEFAULTROOMLF = 0L; constexpr auto EAXREVERB_MINDECAYTIME = 0.1F; constexpr auto EAXREVERB_MAXDECAYTIME = 20.0F; constexpr auto EAXREVERB_DEFAULTDECAYTIME = 1.49F; constexpr auto EAXREVERB_MINDECAYHFRATIO = 0.1F; constexpr auto EAXREVERB_MAXDECAYHFRATIO = 2.0F; constexpr auto EAXREVERB_DEFAULTDECAYHFRATIO = 0.83F; constexpr auto EAXREVERB_MINDECAYLFRATIO = 0.1F; constexpr auto EAXREVERB_MAXDECAYLFRATIO = 2.0F; constexpr auto EAXREVERB_DEFAULTDECAYLFRATIO = 1.0F; constexpr auto EAXREVERB_MINREFLECTIONS = -10'000L; constexpr auto EAXREVERB_MAXREFLECTIONS = 1'000L; constexpr auto EAXREVERB_DEFAULTREFLECTIONS = -2'602L; constexpr auto EAXREVERB_MINREFLECTIONSDELAY = 0.0F; constexpr auto EAXREVERB_MAXREFLECTIONSDELAY = 0.3F; constexpr auto EAXREVERB_DEFAULTREFLECTIONSDELAY = 0.007F; constexpr auto EAXREVERB_DEFAULTREFLECTIONSPAN = EAXVECTOR{0.0F, 0.0F, 0.0F}; constexpr auto EAXREVERB_MINREVERB = -10'000L; constexpr auto EAXREVERB_MAXREVERB = 2'000L; constexpr auto EAXREVERB_DEFAULTREVERB = 200L; constexpr auto EAXREVERB_MINREVERBDELAY = 0.0F; constexpr auto EAXREVERB_MAXREVERBDELAY = 0.1F; constexpr auto EAXREVERB_DEFAULTREVERBDELAY = 0.011F; constexpr auto EAXREVERB_DEFAULTREVERBPAN = EAXVECTOR{0.0F, 0.0F, 0.0F}; constexpr auto EAXREVERB_MINECHOTIME = 0.075F; constexpr auto EAXREVERB_MAXECHOTIME = 0.25F; constexpr auto EAXREVERB_DEFAULTECHOTIME = 0.25F; constexpr auto EAXREVERB_MINECHODEPTH = 0.0F; constexpr auto EAXREVERB_MAXECHODEPTH = 1.0F; constexpr auto EAXREVERB_DEFAULTECHODEPTH = 0.0F; constexpr auto EAXREVERB_MINMODULATIONTIME = 0.04F; constexpr auto EAXREVERB_MAXMODULATIONTIME = 4.0F; constexpr auto EAXREVERB_DEFAULTMODULATIONTIME = 0.25F; constexpr auto EAXREVERB_MINMODULATIONDEPTH = 0.0F; constexpr auto EAXREVERB_MAXMODULATIONDEPTH = 1.0F; constexpr auto EAXREVERB_DEFAULTMODULATIONDEPTH = 0.0F; constexpr auto EAXREVERB_MINAIRABSORPTIONHF = -100.0F; constexpr auto EAXREVERB_MAXAIRABSORPTIONHF = 0.0F; constexpr auto EAXREVERB_DEFAULTAIRABSORPTIONHF = -5.0F; constexpr auto EAXREVERB_MINHFREFERENCE = 1'000.0F; constexpr auto EAXREVERB_MAXHFREFERENCE = 20'000.0F; constexpr auto EAXREVERB_DEFAULTHFREFERENCE = 5'000.0F; constexpr auto EAXREVERB_MINLFREFERENCE = 20.0F; constexpr auto EAXREVERB_MAXLFREFERENCE = 1'000.0F; constexpr auto EAXREVERB_DEFAULTLFREFERENCE = 250.0F; constexpr auto EAXREVERB_MINROOMROLLOFFFACTOR = 0.0F; constexpr auto EAXREVERB_MAXROOMROLLOFFFACTOR = 10.0F; constexpr auto EAXREVERB_DEFAULTROOMROLLOFFFACTOR = 0.0F; constexpr auto EAX1REVERB_MINVOLUME = 0.0F; constexpr auto EAX1REVERB_MAXVOLUME = 1.0F; constexpr auto EAX1REVERB_MINDAMPING = 0.0F; constexpr auto EAX1REVERB_MAXDAMPING = 2.0F; constexpr auto EAXREVERB_DEFAULTFLAGS = EAXREVERBFLAGS_DECAYTIMESCALE | EAXREVERBFLAGS_REFLECTIONSSCALE | EAXREVERBFLAGS_REFLECTIONSDELAYSCALE | EAXREVERBFLAGS_REVERBSCALE | EAXREVERBFLAGS_REVERBDELAYSCALE | EAXREVERBFLAGS_DECAYHFLIMIT; using Eax1ReverbPresets = std::array; DECL_HIDDEN extern const Eax1ReverbPresets EAX1REVERB_PRESETS; using Eax2ReverbPresets = std::array; DECL_HIDDEN extern const Eax2ReverbPresets EAX2REVERB_PRESETS; using EaxReverbPresets = std::array; DECL_HIDDEN extern const EaxReverbPresets EAXREVERB_PRESETS; // AGC Compressor Effect DECL_HIDDEN extern const GUID EAX_AGCCOMPRESSOR_EFFECT; enum EAXAGCCOMPRESSOR_PROPERTY : unsigned int { EAXAGCCOMPRESSOR_NONE, EAXAGCCOMPRESSOR_ALLPARAMETERS, EAXAGCCOMPRESSOR_ONOFF, }; // EAXAGCCOMPRESSOR_PROPERTY struct EAXAGCCOMPRESSORPROPERTIES { unsigned long ulOnOff; // Switch Compressor on or off DECL_EQOP(EAXAGCCOMPRESSORPROPERTIES, ulOnOff) }; // EAXAGCCOMPRESSORPROPERTIES constexpr auto EAXAGCCOMPRESSOR_MINONOFF = 0UL; constexpr auto EAXAGCCOMPRESSOR_MAXONOFF = 1UL; constexpr auto EAXAGCCOMPRESSOR_DEFAULTONOFF = EAXAGCCOMPRESSOR_MAXONOFF; // Autowah Effect DECL_HIDDEN extern const GUID EAX_AUTOWAH_EFFECT; enum EAXAUTOWAH_PROPERTY : unsigned int { EAXAUTOWAH_NONE, EAXAUTOWAH_ALLPARAMETERS, EAXAUTOWAH_ATTACKTIME, EAXAUTOWAH_RELEASETIME, EAXAUTOWAH_RESONANCE, EAXAUTOWAH_PEAKLEVEL, }; // EAXAUTOWAH_PROPERTY struct EAXAUTOWAHPROPERTIES { float flAttackTime; // Attack time (seconds) float flReleaseTime; // Release time (seconds) long lResonance; // Resonance (mB) long lPeakLevel; // Peak level (mB) DECL_EQOP(EAXAUTOWAHPROPERTIES, flAttackTime, flReleaseTime, lResonance, lPeakLevel) }; // EAXAUTOWAHPROPERTIES constexpr auto EAXAUTOWAH_MINATTACKTIME = 0.0001F; constexpr auto EAXAUTOWAH_MAXATTACKTIME = 1.0F; constexpr auto EAXAUTOWAH_DEFAULTATTACKTIME = 0.06F; constexpr auto EAXAUTOWAH_MINRELEASETIME = 0.0001F; constexpr auto EAXAUTOWAH_MAXRELEASETIME = 1.0F; constexpr auto EAXAUTOWAH_DEFAULTRELEASETIME = 0.06F; constexpr auto EAXAUTOWAH_MINRESONANCE = 600L; constexpr auto EAXAUTOWAH_MAXRESONANCE = 6000L; constexpr auto EAXAUTOWAH_DEFAULTRESONANCE = 6000L; constexpr auto EAXAUTOWAH_MINPEAKLEVEL = -9000L; constexpr auto EAXAUTOWAH_MAXPEAKLEVEL = 9000L; constexpr auto EAXAUTOWAH_DEFAULTPEAKLEVEL = 2100L; // Chorus Effect DECL_HIDDEN extern const GUID EAX_CHORUS_EFFECT; enum EAXCHORUS_PROPERTY : unsigned int { EAXCHORUS_NONE, EAXCHORUS_ALLPARAMETERS, EAXCHORUS_WAVEFORM, EAXCHORUS_PHASE, EAXCHORUS_RATE, EAXCHORUS_DEPTH, EAXCHORUS_FEEDBACK, EAXCHORUS_DELAY, }; // EAXCHORUS_PROPERTY enum : unsigned long { EAX_CHORUS_SINUSOID, EAX_CHORUS_TRIANGLE, }; struct EAXCHORUSPROPERTIES { unsigned long ulWaveform; // Waveform selector - see enum above long lPhase; // Phase (Degrees) float flRate; // Rate (Hz) float flDepth; // Depth (0 to 1) float flFeedback; // Feedback (-1 to 1) float flDelay; // Delay (seconds) DECL_EQOP(EAXCHORUSPROPERTIES, ulWaveform, lPhase, flRate, flDepth, flFeedback, flDelay) }; // EAXCHORUSPROPERTIES constexpr auto EAXCHORUS_MINWAVEFORM = 0UL; constexpr auto EAXCHORUS_MAXWAVEFORM = 1UL; constexpr auto EAXCHORUS_DEFAULTWAVEFORM = 1UL; constexpr auto EAXCHORUS_MINPHASE = -180L; constexpr auto EAXCHORUS_MAXPHASE = 180L; constexpr auto EAXCHORUS_DEFAULTPHASE = 90L; constexpr auto EAXCHORUS_MINRATE = 0.0F; constexpr auto EAXCHORUS_MAXRATE = 10.0F; constexpr auto EAXCHORUS_DEFAULTRATE = 1.1F; constexpr auto EAXCHORUS_MINDEPTH = 0.0F; constexpr auto EAXCHORUS_MAXDEPTH = 1.0F; constexpr auto EAXCHORUS_DEFAULTDEPTH = 0.1F; constexpr auto EAXCHORUS_MINFEEDBACK = -1.0F; constexpr auto EAXCHORUS_MAXFEEDBACK = 1.0F; constexpr auto EAXCHORUS_DEFAULTFEEDBACK = 0.25F; constexpr auto EAXCHORUS_MINDELAY = 0.0002F; constexpr auto EAXCHORUS_MAXDELAY = 0.016F; constexpr auto EAXCHORUS_DEFAULTDELAY = 0.016F; // Distortion Effect DECL_HIDDEN extern const GUID EAX_DISTORTION_EFFECT; enum EAXDISTORTION_PROPERTY : unsigned int { EAXDISTORTION_NONE, EAXDISTORTION_ALLPARAMETERS, EAXDISTORTION_EDGE, EAXDISTORTION_GAIN, EAXDISTORTION_LOWPASSCUTOFF, EAXDISTORTION_EQCENTER, EAXDISTORTION_EQBANDWIDTH, }; // EAXDISTORTION_PROPERTY struct EAXDISTORTIONPROPERTIES { float flEdge; // Controls the shape of the distortion (0 to 1) long lGain; // Controls the post distortion gain (mB) float flLowPassCutOff; // Controls the cut-off of the filter pre-distortion (Hz) float flEQCenter; // Controls the center frequency of the EQ post-distortion (Hz) float flEQBandwidth; // Controls the bandwidth of the EQ post-distortion (Hz) DECL_EQOP(EAXDISTORTIONPROPERTIES, flEdge, lGain, flLowPassCutOff, flEQCenter, flEQBandwidth) }; // EAXDISTORTIONPROPERTIES constexpr auto EAXDISTORTION_MINEDGE = 0.0F; constexpr auto EAXDISTORTION_MAXEDGE = 1.0F; constexpr auto EAXDISTORTION_DEFAULTEDGE = 0.2F; constexpr auto EAXDISTORTION_MINGAIN = -6000L; constexpr auto EAXDISTORTION_MAXGAIN = 0L; constexpr auto EAXDISTORTION_DEFAULTGAIN = -2600L; constexpr auto EAXDISTORTION_MINLOWPASSCUTOFF = 80.0F; constexpr auto EAXDISTORTION_MAXLOWPASSCUTOFF = 24000.0F; constexpr auto EAXDISTORTION_DEFAULTLOWPASSCUTOFF = 8000.0F; constexpr auto EAXDISTORTION_MINEQCENTER = 80.0F; constexpr auto EAXDISTORTION_MAXEQCENTER = 24000.0F; constexpr auto EAXDISTORTION_DEFAULTEQCENTER = 3600.0F; constexpr auto EAXDISTORTION_MINEQBANDWIDTH = 80.0F; constexpr auto EAXDISTORTION_MAXEQBANDWIDTH = 24000.0F; constexpr auto EAXDISTORTION_DEFAULTEQBANDWIDTH = 3600.0F; // Echo Effect DECL_HIDDEN extern const GUID EAX_ECHO_EFFECT; enum EAXECHO_PROPERTY : unsigned int { EAXECHO_NONE, EAXECHO_ALLPARAMETERS, EAXECHO_DELAY, EAXECHO_LRDELAY, EAXECHO_DAMPING, EAXECHO_FEEDBACK, EAXECHO_SPREAD, }; // EAXECHO_PROPERTY struct EAXECHOPROPERTIES { float flDelay; // Controls the initial delay time (seconds) float flLRDelay; // Controls the delay time between the first and second taps (seconds) float flDamping; // Controls a low-pass filter that dampens the echoes (0 to 1) float flFeedback; // Controls the duration of echo repetition (0 to 1) float flSpread; // Controls the left-right spread of the echoes DECL_EQOP(EAXECHOPROPERTIES, flDelay, flLRDelay, flDamping, flFeedback, flSpread) }; // EAXECHOPROPERTIES constexpr auto EAXECHO_MINDAMPING = 0.0F; constexpr auto EAXECHO_MAXDAMPING = 0.99F; constexpr auto EAXECHO_DEFAULTDAMPING = 0.5F; constexpr auto EAXECHO_MINDELAY = 0.002F; constexpr auto EAXECHO_MAXDELAY = 0.207F; constexpr auto EAXECHO_DEFAULTDELAY = 0.1F; constexpr auto EAXECHO_MINLRDELAY = 0.0F; constexpr auto EAXECHO_MAXLRDELAY = 0.404F; constexpr auto EAXECHO_DEFAULTLRDELAY = 0.1F; constexpr auto EAXECHO_MINFEEDBACK = 0.0F; constexpr auto EAXECHO_MAXFEEDBACK = 1.0F; constexpr auto EAXECHO_DEFAULTFEEDBACK = 0.5F; constexpr auto EAXECHO_MINSPREAD = -1.0F; constexpr auto EAXECHO_MAXSPREAD = 1.0F; constexpr auto EAXECHO_DEFAULTSPREAD = -1.0F; // Equalizer Effect DECL_HIDDEN extern const GUID EAX_EQUALIZER_EFFECT; enum EAXEQUALIZER_PROPERTY : unsigned int { EAXEQUALIZER_NONE, EAXEQUALIZER_ALLPARAMETERS, EAXEQUALIZER_LOWGAIN, EAXEQUALIZER_LOWCUTOFF, EAXEQUALIZER_MID1GAIN, EAXEQUALIZER_MID1CENTER, EAXEQUALIZER_MID1WIDTH, EAXEQUALIZER_MID2GAIN, EAXEQUALIZER_MID2CENTER, EAXEQUALIZER_MID2WIDTH, EAXEQUALIZER_HIGHGAIN, EAXEQUALIZER_HIGHCUTOFF, }; // EAXEQUALIZER_PROPERTY struct EAXEQUALIZERPROPERTIES { long lLowGain; // (mB) float flLowCutOff; // (Hz) long lMid1Gain; // (mB) float flMid1Center; // (Hz) float flMid1Width; // (octaves) long lMid2Gain; // (mB) float flMid2Center; // (Hz) float flMid2Width; // (octaves) long lHighGain; // (mB) float flHighCutOff; // (Hz) DECL_EQOP(EAXEQUALIZERPROPERTIES, lLowGain, flLowCutOff, lMid1Gain, flMid1Center, flMid1Width, lMid2Gain, flMid2Center, flMid2Width, lHighGain, flHighCutOff) }; // EAXEQUALIZERPROPERTIES constexpr auto EAXEQUALIZER_MINLOWGAIN = -1800L; constexpr auto EAXEQUALIZER_MAXLOWGAIN = 1800L; constexpr auto EAXEQUALIZER_DEFAULTLOWGAIN = 0L; constexpr auto EAXEQUALIZER_MINLOWCUTOFF = 50.0F; constexpr auto EAXEQUALIZER_MAXLOWCUTOFF = 800.0F; constexpr auto EAXEQUALIZER_DEFAULTLOWCUTOFF = 200.0F; constexpr auto EAXEQUALIZER_MINMID1GAIN = -1800L; constexpr auto EAXEQUALIZER_MAXMID1GAIN = 1800L; constexpr auto EAXEQUALIZER_DEFAULTMID1GAIN = 0L; constexpr auto EAXEQUALIZER_MINMID1CENTER = 200.0F; constexpr auto EAXEQUALIZER_MAXMID1CENTER = 3000.0F; constexpr auto EAXEQUALIZER_DEFAULTMID1CENTER = 500.0F; constexpr auto EAXEQUALIZER_MINMID1WIDTH = 0.01F; constexpr auto EAXEQUALIZER_MAXMID1WIDTH = 1.0F; constexpr auto EAXEQUALIZER_DEFAULTMID1WIDTH = 1.0F; constexpr auto EAXEQUALIZER_MINMID2GAIN = -1800L; constexpr auto EAXEQUALIZER_MAXMID2GAIN = 1800L; constexpr auto EAXEQUALIZER_DEFAULTMID2GAIN = 0L; constexpr auto EAXEQUALIZER_MINMID2CENTER = 1000.0F; constexpr auto EAXEQUALIZER_MAXMID2CENTER = 8000.0F; constexpr auto EAXEQUALIZER_DEFAULTMID2CENTER = 3000.0F; constexpr auto EAXEQUALIZER_MINMID2WIDTH = 0.01F; constexpr auto EAXEQUALIZER_MAXMID2WIDTH = 1.0F; constexpr auto EAXEQUALIZER_DEFAULTMID2WIDTH = 1.0F; constexpr auto EAXEQUALIZER_MINHIGHGAIN = -1800L; constexpr auto EAXEQUALIZER_MAXHIGHGAIN = 1800L; constexpr auto EAXEQUALIZER_DEFAULTHIGHGAIN = 0L; constexpr auto EAXEQUALIZER_MINHIGHCUTOFF = 4000.0F; constexpr auto EAXEQUALIZER_MAXHIGHCUTOFF = 16000.0F; constexpr auto EAXEQUALIZER_DEFAULTHIGHCUTOFF = 6000.0F; // Flanger Effect DECL_HIDDEN extern const GUID EAX_FLANGER_EFFECT; enum EAXFLANGER_PROPERTY : unsigned int { EAXFLANGER_NONE, EAXFLANGER_ALLPARAMETERS, EAXFLANGER_WAVEFORM, EAXFLANGER_PHASE, EAXFLANGER_RATE, EAXFLANGER_DEPTH, EAXFLANGER_FEEDBACK, EAXFLANGER_DELAY, }; // EAXFLANGER_PROPERTY enum : unsigned long { EAX_FLANGER_SINUSOID, EAX_FLANGER_TRIANGLE, }; struct EAXFLANGERPROPERTIES { unsigned long ulWaveform; // Waveform selector - see enum above long lPhase; // Phase (Degrees) float flRate; // Rate (Hz) float flDepth; // Depth (0 to 1) float flFeedback; // Feedback (0 to 1) float flDelay; // Delay (seconds) DECL_EQOP(EAXFLANGERPROPERTIES, ulWaveform, lPhase, flRate, flDepth, flFeedback, flDelay) }; // EAXFLANGERPROPERTIES constexpr auto EAXFLANGER_MINWAVEFORM = 0UL; constexpr auto EAXFLANGER_MAXWAVEFORM = 1UL; constexpr auto EAXFLANGER_DEFAULTWAVEFORM = 1UL; constexpr auto EAXFLANGER_MINPHASE = -180L; constexpr auto EAXFLANGER_MAXPHASE = 180L; constexpr auto EAXFLANGER_DEFAULTPHASE = 0L; constexpr auto EAXFLANGER_MINRATE = 0.0F; constexpr auto EAXFLANGER_MAXRATE = 10.0F; constexpr auto EAXFLANGER_DEFAULTRATE = 0.27F; constexpr auto EAXFLANGER_MINDEPTH = 0.0F; constexpr auto EAXFLANGER_MAXDEPTH = 1.0F; constexpr auto EAXFLANGER_DEFAULTDEPTH = 1.0F; constexpr auto EAXFLANGER_MINFEEDBACK = -1.0F; constexpr auto EAXFLANGER_MAXFEEDBACK = 1.0F; constexpr auto EAXFLANGER_DEFAULTFEEDBACK = -0.5F; constexpr auto EAXFLANGER_MINDELAY = 0.0002F; constexpr auto EAXFLANGER_MAXDELAY = 0.004F; constexpr auto EAXFLANGER_DEFAULTDELAY = 0.002F; // Frequency Shifter Effect DECL_HIDDEN extern const GUID EAX_FREQUENCYSHIFTER_EFFECT; enum EAXFREQUENCYSHIFTER_PROPERTY : unsigned int { EAXFREQUENCYSHIFTER_NONE, EAXFREQUENCYSHIFTER_ALLPARAMETERS, EAXFREQUENCYSHIFTER_FREQUENCY, EAXFREQUENCYSHIFTER_LEFTDIRECTION, EAXFREQUENCYSHIFTER_RIGHTDIRECTION, }; // EAXFREQUENCYSHIFTER_PROPERTY enum : unsigned long { EAX_FREQUENCYSHIFTER_DOWN, EAX_FREQUENCYSHIFTER_UP, EAX_FREQUENCYSHIFTER_OFF }; struct EAXFREQUENCYSHIFTERPROPERTIES { float flFrequency; // (Hz) unsigned long ulLeftDirection; // see enum above unsigned long ulRightDirection; // see enum above DECL_EQOP(EAXFREQUENCYSHIFTERPROPERTIES, flFrequency, ulLeftDirection, ulRightDirection) }; // EAXFREQUENCYSHIFTERPROPERTIES constexpr auto EAXFREQUENCYSHIFTER_MINFREQUENCY = 0.0F; constexpr auto EAXFREQUENCYSHIFTER_MAXFREQUENCY = 24000.0F; constexpr auto EAXFREQUENCYSHIFTER_DEFAULTFREQUENCY = EAXFREQUENCYSHIFTER_MINFREQUENCY; constexpr auto EAXFREQUENCYSHIFTER_MINLEFTDIRECTION = 0UL; constexpr auto EAXFREQUENCYSHIFTER_MAXLEFTDIRECTION = 2UL; constexpr auto EAXFREQUENCYSHIFTER_DEFAULTLEFTDIRECTION = EAXFREQUENCYSHIFTER_MINLEFTDIRECTION; constexpr auto EAXFREQUENCYSHIFTER_MINRIGHTDIRECTION = 0UL; constexpr auto EAXFREQUENCYSHIFTER_MAXRIGHTDIRECTION = 2UL; constexpr auto EAXFREQUENCYSHIFTER_DEFAULTRIGHTDIRECTION = EAXFREQUENCYSHIFTER_MINRIGHTDIRECTION; // Vocal Morpher Effect DECL_HIDDEN extern const GUID EAX_VOCALMORPHER_EFFECT; enum EAXVOCALMORPHER_PROPERTY : unsigned int { EAXVOCALMORPHER_NONE, EAXVOCALMORPHER_ALLPARAMETERS, EAXVOCALMORPHER_PHONEMEA, EAXVOCALMORPHER_PHONEMEACOARSETUNING, EAXVOCALMORPHER_PHONEMEB, EAXVOCALMORPHER_PHONEMEBCOARSETUNING, EAXVOCALMORPHER_WAVEFORM, EAXVOCALMORPHER_RATE, }; // EAXVOCALMORPHER_PROPERTY enum : unsigned long { A, E, I, O, U, AA, AE, AH, AO, EH, ER, IH, IY, UH, UW, B, D, F, G, J, K, L, M, N, P, R, S, T, V, Z, }; enum : unsigned long { EAX_VOCALMORPHER_SINUSOID, EAX_VOCALMORPHER_TRIANGLE, EAX_VOCALMORPHER_SAWTOOTH }; // Use this structure for EAXVOCALMORPHER_ALLPARAMETERS struct EAXVOCALMORPHERPROPERTIES { unsigned long ulPhonemeA; // see enum above long lPhonemeACoarseTuning; // (semitones) unsigned long ulPhonemeB; // see enum above long lPhonemeBCoarseTuning; // (semitones) unsigned long ulWaveform; // Waveform selector - see enum above float flRate; // (Hz) DECL_EQOP(EAXVOCALMORPHERPROPERTIES, ulPhonemeA, lPhonemeACoarseTuning, ulPhonemeB, lPhonemeBCoarseTuning, ulWaveform, flRate) }; // EAXVOCALMORPHERPROPERTIES constexpr auto EAXVOCALMORPHER_MINPHONEMEA = 0UL; constexpr auto EAXVOCALMORPHER_MAXPHONEMEA = 29UL; constexpr auto EAXVOCALMORPHER_DEFAULTPHONEMEA = EAXVOCALMORPHER_MINPHONEMEA; constexpr auto EAXVOCALMORPHER_MINPHONEMEACOARSETUNING = -24L; constexpr auto EAXVOCALMORPHER_MAXPHONEMEACOARSETUNING = 24L; constexpr auto EAXVOCALMORPHER_DEFAULTPHONEMEACOARSETUNING = 0L; constexpr auto EAXVOCALMORPHER_MINPHONEMEB = 0UL; constexpr auto EAXVOCALMORPHER_MAXPHONEMEB = 29UL; constexpr auto EAXVOCALMORPHER_DEFAULTPHONEMEB = 10UL; constexpr auto EAXVOCALMORPHER_MINPHONEMEBCOARSETUNING = -24L; constexpr auto EAXVOCALMORPHER_MAXPHONEMEBCOARSETUNING = 24L; constexpr auto EAXVOCALMORPHER_DEFAULTPHONEMEBCOARSETUNING = 0L; constexpr auto EAXVOCALMORPHER_MINWAVEFORM = 0UL; constexpr auto EAXVOCALMORPHER_MAXWAVEFORM = 2UL; constexpr auto EAXVOCALMORPHER_DEFAULTWAVEFORM = EAXVOCALMORPHER_MINWAVEFORM; constexpr auto EAXVOCALMORPHER_MINRATE = 0.0F; constexpr auto EAXVOCALMORPHER_MAXRATE = 10.0F; constexpr auto EAXVOCALMORPHER_DEFAULTRATE = 1.41F; // Pitch Shifter Effect DECL_HIDDEN extern const GUID EAX_PITCHSHIFTER_EFFECT; enum EAXPITCHSHIFTER_PROPERTY : unsigned int { EAXPITCHSHIFTER_NONE, EAXPITCHSHIFTER_ALLPARAMETERS, EAXPITCHSHIFTER_COARSETUNE, EAXPITCHSHIFTER_FINETUNE, }; // EAXPITCHSHIFTER_PROPERTY struct EAXPITCHSHIFTERPROPERTIES { long lCoarseTune; // Amount of pitch shift (semitones) long lFineTune; // Amount of pitch shift (cents) DECL_EQOP(EAXPITCHSHIFTERPROPERTIES, lCoarseTune, lFineTune) }; // EAXPITCHSHIFTERPROPERTIES constexpr auto EAXPITCHSHIFTER_MINCOARSETUNE = -12L; constexpr auto EAXPITCHSHIFTER_MAXCOARSETUNE = 12L; constexpr auto EAXPITCHSHIFTER_DEFAULTCOARSETUNE = 12L; constexpr auto EAXPITCHSHIFTER_MINFINETUNE = -50L; constexpr auto EAXPITCHSHIFTER_MAXFINETUNE = 50L; constexpr auto EAXPITCHSHIFTER_DEFAULTFINETUNE = 0L; // Ring Modulator Effect DECL_HIDDEN extern const GUID EAX_RINGMODULATOR_EFFECT; enum EAXRINGMODULATOR_PROPERTY : unsigned int { EAXRINGMODULATOR_NONE, EAXRINGMODULATOR_ALLPARAMETERS, EAXRINGMODULATOR_FREQUENCY, EAXRINGMODULATOR_HIGHPASSCUTOFF, EAXRINGMODULATOR_WAVEFORM, }; // EAXRINGMODULATOR_PROPERTY enum : unsigned long { EAX_RINGMODULATOR_SINUSOID, EAX_RINGMODULATOR_SAWTOOTH, EAX_RINGMODULATOR_SQUARE, }; // Use this structure for EAXRINGMODULATOR_ALLPARAMETERS struct EAXRINGMODULATORPROPERTIES { float flFrequency; // Frequency of modulation (Hz) float flHighPassCutOff; // Cut-off frequency of high-pass filter (Hz) unsigned long ulWaveform; // Waveform selector - see enum above DECL_EQOP(EAXRINGMODULATORPROPERTIES, flFrequency, flHighPassCutOff, ulWaveform) }; // EAXRINGMODULATORPROPERTIES constexpr auto EAXRINGMODULATOR_MINFREQUENCY = 0.0F; constexpr auto EAXRINGMODULATOR_MAXFREQUENCY = 8000.0F; constexpr auto EAXRINGMODULATOR_DEFAULTFREQUENCY = 440.0F; constexpr auto EAXRINGMODULATOR_MINHIGHPASSCUTOFF = 0.0F; constexpr auto EAXRINGMODULATOR_MAXHIGHPASSCUTOFF = 24000.0F; constexpr auto EAXRINGMODULATOR_DEFAULTHIGHPASSCUTOFF = 800.0F; constexpr auto EAXRINGMODULATOR_MINWAVEFORM = 0UL; constexpr auto EAXRINGMODULATOR_MAXWAVEFORM = 2UL; constexpr auto EAXRINGMODULATOR_DEFAULTWAVEFORM = EAXRINGMODULATOR_MINWAVEFORM; using LPEAXSET = ALenum(AL_APIENTRY*)( const GUID* property_set_id, ALuint property_id, ALuint property_source_id, ALvoid* property_buffer, ALuint property_size); using LPEAXGET = ALenum(AL_APIENTRY*)( const GUID* property_set_id, ALuint property_id, ALuint property_source_id, ALvoid* property_buffer, ALuint property_size); #undef DECL_EQOP #endif // !EAX_API_INCLUDED openal-soft-1.24.2/al/eax/call.cpp000066400000000000000000000141041474041540300166210ustar00rootroot00000000000000#include "config.h" #include "call.h" #include "exception.h" namespace { constexpr auto deferred_flag = 0x80000000U; class EaxCallException : public EaxException { public: explicit EaxCallException(const char* message) : EaxException{"EAX_CALL", message} {} }; // EaxCallException } // namespace EaxCall::EaxCall(EaxCallType type, const GUID &property_set_guid, ALuint property_id, ALuint property_source_id, ALvoid *property_buffer, ALuint property_size) : mCallType{type}, mIsDeferred{(property_id & deferred_flag) != 0} , mPropertyId{property_id & ~deferred_flag}, mPropertySourceId{property_source_id} , mPropertyBuffer{property_buffer}, mPropertyBufferSize{property_size} { switch(mCallType) { case EaxCallType::get: case EaxCallType::set: break; default: fail("Invalid type."); } if (false) { } else if (property_set_guid == EAXPROPERTYID_EAX40_Context) { mVersion = 4; mPropertySetId = EaxCallPropertySetId::context; } else if (property_set_guid == EAXPROPERTYID_EAX50_Context) { mVersion = 5; mPropertySetId = EaxCallPropertySetId::context; } else if (property_set_guid == DSPROPSETID_EAX20_ListenerProperties) { mVersion = 2; mFxSlotIndex = 0u; mPropertySetId = EaxCallPropertySetId::fx_slot_effect; } else if (property_set_guid == DSPROPSETID_EAX30_ListenerProperties) { mVersion = 3; mFxSlotIndex = 0u; mPropertySetId = EaxCallPropertySetId::fx_slot_effect; } else if (property_set_guid == EAXPROPERTYID_EAX40_FXSlot0) { mVersion = 4; mFxSlotIndex = 0u; mPropertySetId = EaxCallPropertySetId::fx_slot; } else if (property_set_guid == EAXPROPERTYID_EAX50_FXSlot0) { mVersion = 5; mFxSlotIndex = 0u; mPropertySetId = EaxCallPropertySetId::fx_slot; } else if (property_set_guid == EAXPROPERTYID_EAX40_FXSlot1) { mVersion = 4; mFxSlotIndex = 1u; mPropertySetId = EaxCallPropertySetId::fx_slot; } else if (property_set_guid == EAXPROPERTYID_EAX50_FXSlot1) { mVersion = 5; mFxSlotIndex = 1u; mPropertySetId = EaxCallPropertySetId::fx_slot; } else if (property_set_guid == EAXPROPERTYID_EAX40_FXSlot2) { mVersion = 4; mFxSlotIndex = 2u; mPropertySetId = EaxCallPropertySetId::fx_slot; } else if (property_set_guid == EAXPROPERTYID_EAX50_FXSlot2) { mVersion = 5; mFxSlotIndex = 2u; mPropertySetId = EaxCallPropertySetId::fx_slot; } else if (property_set_guid == EAXPROPERTYID_EAX40_FXSlot3) { mVersion = 4; mFxSlotIndex = 3u; mPropertySetId = EaxCallPropertySetId::fx_slot; } else if (property_set_guid == EAXPROPERTYID_EAX50_FXSlot3) { mVersion = 5; mFxSlotIndex = 3u; mPropertySetId = EaxCallPropertySetId::fx_slot; } else if (property_set_guid == DSPROPSETID_EAX20_BufferProperties) { mVersion = 2; mPropertySetId = EaxCallPropertySetId::source; } else if (property_set_guid == DSPROPSETID_EAX30_BufferProperties) { mVersion = 3; mPropertySetId = EaxCallPropertySetId::source; } else if (property_set_guid == EAXPROPERTYID_EAX40_Source) { mVersion = 4; mPropertySetId = EaxCallPropertySetId::source; } else if (property_set_guid == EAXPROPERTYID_EAX50_Source) { mVersion = 5; mPropertySetId = EaxCallPropertySetId::source; } else if (property_set_guid == DSPROPSETID_EAX_ReverbProperties) { mVersion = 1; mFxSlotIndex = 0u; mPropertySetId = EaxCallPropertySetId::fx_slot_effect; } else if (property_set_guid == DSPROPSETID_EAXBUFFER_ReverbProperties) { mVersion = 1; mPropertySetId = EaxCallPropertySetId::source; } else { fail("Unsupported property set id."); } if(mPropertySetId == EaxCallPropertySetId::context) { switch(mPropertyId) { case EAXCONTEXT_LASTERROR: case EAXCONTEXT_SPEAKERCONFIG: case EAXCONTEXT_EAXSESSION: // EAX allow to set "defer" flag on immediate-only properties. // If we don't clear our flag then "applyAllUpdates" in EAX context won't be called. mIsDeferred = false; break; } } else if(mPropertySetId == EaxCallPropertySetId::fx_slot) { switch(mPropertyId) { case EAXFXSLOT_NONE: case EAXFXSLOT_ALLPARAMETERS: case EAXFXSLOT_LOADEFFECT: case EAXFXSLOT_VOLUME: case EAXFXSLOT_LOCK: case EAXFXSLOT_FLAGS: case EAXFXSLOT_OCCLUSION: case EAXFXSLOT_OCCLUSIONLFRATIO: mIsDeferred = false; break; } } if(!mIsDeferred) { if(mPropertySetId != EaxCallPropertySetId::fx_slot && mPropertyId != 0) { if(mPropertyBuffer == nullptr) fail("Null property buffer."); if(mPropertyBufferSize == 0) fail("Empty property."); } } if(mPropertySetId == EaxCallPropertySetId::source && mPropertySourceId == 0) fail("Null AL source id."); if(mPropertySetId == EaxCallPropertySetId::fx_slot) { if(mPropertyId < EAXFXSLOT_NONE) mPropertySetId = EaxCallPropertySetId::fx_slot_effect; } } [[noreturn]] void EaxCall::fail(const char* message) { throw EaxCallException{message}; } [[noreturn]] void EaxCall::fail_too_small() { fail("Property buffer too small."); } EaxCall create_eax_call( EaxCallType type, const GUID* property_set_id, ALuint property_id, ALuint property_source_id, ALvoid* property_buffer, ALuint property_size) { if(!property_set_id) throw EaxCallException{"Null property set ID."}; return EaxCall{ type, *property_set_id, property_id, property_source_id, property_buffer, property_size }; } openal-soft-1.24.2/al/eax/call.h000066400000000000000000000054631474041540300162760ustar00rootroot00000000000000#ifndef EAX_EAX_CALL_INCLUDED #define EAX_EAX_CALL_INCLUDED #include "AL/al.h" #include "alnumeric.h" #include "alspan.h" #include "api.h" #include "fx_slot_index.h" enum class EaxCallType { none, get, set, }; // EaxCallType enum class EaxCallPropertySetId { none, context, fx_slot, source, fx_slot_effect, }; // EaxCallPropertySetId class EaxCall { public: EaxCall( EaxCallType type, const GUID& property_set_guid, ALuint property_id, ALuint property_source_id, ALvoid* property_buffer, ALuint property_size); [[nodiscard]] auto is_get() const noexcept -> bool { return mCallType == EaxCallType::get; } [[nodiscard]] auto is_deferred() const noexcept -> bool { return mIsDeferred; } [[nodiscard]] auto get_version() const noexcept -> int { return mVersion; } [[nodiscard]] auto get_property_set_id() const noexcept -> EaxCallPropertySetId { return mPropertySetId; } [[nodiscard]] auto get_property_id() const noexcept -> ALuint { return mPropertyId; } [[nodiscard]] auto get_property_al_name() const noexcept -> ALuint { return mPropertySourceId; } [[nodiscard]] auto get_fx_slot_index() const noexcept -> EaxFxSlotIndex { return mFxSlotIndex; } template [[nodiscard]] auto get_value() const -> TValue& { if(mPropertyBufferSize < sizeof(TValue)) fail_too_small(); return *static_cast(mPropertyBuffer); } template [[nodiscard]] auto get_values(size_t max_count) const -> al::span { if(max_count == 0 || mPropertyBufferSize < sizeof(TValue)) fail_too_small(); const auto count = std::min(mPropertyBufferSize/sizeof(TValue), max_count); return {static_cast(mPropertyBuffer), count}; } template [[nodiscard]] auto get_values() const -> al::span { return get_values(~0_uz); } template auto set_value(const TValue& value) const -> void { get_value() = value; } private: const EaxCallType mCallType; int mVersion{}; EaxFxSlotIndex mFxSlotIndex{}; EaxCallPropertySetId mPropertySetId{EaxCallPropertySetId::none}; bool mIsDeferred; const ALuint mPropertyId; const ALuint mPropertySourceId; ALvoid*const mPropertyBuffer; const ALuint mPropertyBufferSize; [[noreturn]] static void fail(const char* message); [[noreturn]] static void fail_too_small(); }; // EaxCall EaxCall create_eax_call( EaxCallType type, const GUID* property_set_id, ALuint property_id, ALuint property_source_id, ALvoid* property_buffer, ALuint property_size); #endif // !EAX_EAX_CALL_INCLUDED openal-soft-1.24.2/al/eax/effect.h000066400000000000000000000432641474041540300166200ustar00rootroot00000000000000#ifndef EAX_EFFECT_INCLUDED #define EAX_EFFECT_INCLUDED #include #include #include #include "AL/al.h" #include "AL/alext.h" #include "core/effects/base.h" #include "call.h" struct EaxEffectErrorMessages { static constexpr auto unknown_property_id() noexcept { return "Unknown property id."; } static constexpr auto unknown_version() noexcept { return "Unknown version."; } }; // EaxEffectErrorMessages using EaxEffectProps = std::variant; template struct overloaded : Ts... { using Ts::operator()...; }; template overloaded(Ts...) -> overloaded; constexpr ALenum EnumFromEaxEffectType(const EaxEffectProps &props) { return std::visit(overloaded{ [](const std::monostate&) noexcept { return AL_EFFECT_NULL; }, [](const EAXREVERBPROPERTIES&) noexcept { return AL_EFFECT_EAXREVERB; }, [](const EAXCHORUSPROPERTIES&) noexcept { return AL_EFFECT_CHORUS; }, [](const EAXAUTOWAHPROPERTIES&) noexcept { return AL_EFFECT_AUTOWAH; }, [](const EAXAGCCOMPRESSORPROPERTIES&) noexcept { return AL_EFFECT_COMPRESSOR; }, [](const EAXDISTORTIONPROPERTIES&) noexcept { return AL_EFFECT_DISTORTION; }, [](const EAXECHOPROPERTIES&) noexcept { return AL_EFFECT_ECHO; }, [](const EAXEQUALIZERPROPERTIES&) noexcept { return AL_EFFECT_EQUALIZER; }, [](const EAXFLANGERPROPERTIES&) noexcept { return AL_EFFECT_FLANGER; }, [](const EAXFREQUENCYSHIFTERPROPERTIES&) noexcept { return AL_EFFECT_FREQUENCY_SHIFTER; }, [](const EAXRINGMODULATORPROPERTIES&) noexcept { return AL_EFFECT_RING_MODULATOR; }, [](const EAXPITCHSHIFTERPROPERTIES&) noexcept { return AL_EFFECT_PITCH_SHIFTER; }, [](const EAXVOCALMORPHERPROPERTIES&) noexcept { return AL_EFFECT_VOCAL_MORPHER; } }, props); } struct EaxReverbCommitter { struct Exception; EaxReverbCommitter(EaxEffectProps &eaxprops, EffectProps &alprops) : mEaxProps{eaxprops}, mAlProps{alprops} { } EaxEffectProps &mEaxProps; EffectProps &mAlProps; [[noreturn]] static void fail(const char* message); [[noreturn]] static void fail_unknown_property_id() { fail(EaxEffectErrorMessages::unknown_property_id()); } template static void defer(const EaxCall& call, TProperty& property) { const auto& value = call.get_value(); TValidator{}(value); property = value; } template static void defer(const EaxCall& call, TProperties& properties, TProperty&) { const auto& value = call.get_value(); TValidator{}(value); TDeferrer{}(properties, value); } template static void defer3(const EaxCall& call, EAXREVERBPROPERTIES& properties, TProperty& property) { const auto& value = call.get_value(); TValidator{}(value); if (value == property) return; property = value; properties.ulEnvironment = EAX_ENVIRONMENT_UNDEFINED; } bool commit(const EAX_REVERBPROPERTIES &props); bool commit(const EAX20LISTENERPROPERTIES &props); bool commit(const EAXREVERBPROPERTIES &props); static void SetDefaults(EAX_REVERBPROPERTIES &props); static void SetDefaults(EAX20LISTENERPROPERTIES &props); static void SetDefaults(EAXREVERBPROPERTIES &props); static void SetDefaults(EaxEffectProps &props); static void Get(const EaxCall &call, const EAX_REVERBPROPERTIES &props); static void Get(const EaxCall &call, const EAX20LISTENERPROPERTIES &props); static void Get(const EaxCall &call, const EAXREVERBPROPERTIES &props); static void Set(const EaxCall &call, EAX_REVERBPROPERTIES &props); static void Set(const EaxCall &call, EAX20LISTENERPROPERTIES &props); static void Set(const EaxCall &call, EAXREVERBPROPERTIES &props); static void translate(const EAX_REVERBPROPERTIES& src, EAXREVERBPROPERTIES& dst) noexcept; static void translate(const EAX20LISTENERPROPERTIES& src, EAXREVERBPROPERTIES& dst) noexcept; }; template struct EaxCommitter { struct Exception; EaxEffectProps &mEaxProps; EffectProps &mAlProps; template static void defer(const EaxCall& call, TProperty& property) { const auto& value = call.get_value(); TValidator{}(value); property = value; } [[noreturn]] static void fail(const char *message); [[noreturn]] static void fail_unknown_property_id() { fail(EaxEffectErrorMessages::unknown_property_id()); } private: EaxCommitter(EaxEffectProps &eaxprops, EffectProps &alprops) : mEaxProps{eaxprops}, mAlProps{alprops} { } friend T; }; struct EaxAutowahCommitter : public EaxCommitter { template explicit EaxAutowahCommitter(Args&& ...args) : EaxCommitter{std::forward(args)...} { } bool commit(const EAXAUTOWAHPROPERTIES &props); static void SetDefaults(EaxEffectProps &props); static void Get(const EaxCall &call, const EAXAUTOWAHPROPERTIES &props); static void Set(const EaxCall &call, EAXAUTOWAHPROPERTIES &props); }; struct EaxChorusCommitter : public EaxCommitter { template explicit EaxChorusCommitter(Args&& ...args) : EaxCommitter{std::forward(args)...} { } bool commit(const EAXCHORUSPROPERTIES &props); static void SetDefaults(EaxEffectProps &props); static void Get(const EaxCall &call, const EAXCHORUSPROPERTIES &props); static void Set(const EaxCall &call, EAXCHORUSPROPERTIES &props); }; struct EaxCompressorCommitter : public EaxCommitter { template explicit EaxCompressorCommitter(Args&& ...args) : EaxCommitter{std::forward(args)...} { } bool commit(const EAXAGCCOMPRESSORPROPERTIES &props); static void SetDefaults(EaxEffectProps &props); static void Get(const EaxCall &call, const EAXAGCCOMPRESSORPROPERTIES &props); static void Set(const EaxCall &call, EAXAGCCOMPRESSORPROPERTIES &props); }; struct EaxDistortionCommitter : public EaxCommitter { template explicit EaxDistortionCommitter(Args&& ...args) : EaxCommitter{std::forward(args)...} { } bool commit(const EAXDISTORTIONPROPERTIES &props); static void SetDefaults(EaxEffectProps &props); static void Get(const EaxCall &call, const EAXDISTORTIONPROPERTIES &props); static void Set(const EaxCall &call, EAXDISTORTIONPROPERTIES &props); }; struct EaxEchoCommitter : public EaxCommitter { template explicit EaxEchoCommitter(Args&& ...args) : EaxCommitter{std::forward(args)...} { } bool commit(const EAXECHOPROPERTIES &props); static void SetDefaults(EaxEffectProps &props); static void Get(const EaxCall &call, const EAXECHOPROPERTIES &props); static void Set(const EaxCall &call, EAXECHOPROPERTIES &props); }; struct EaxEqualizerCommitter : public EaxCommitter { template explicit EaxEqualizerCommitter(Args&& ...args) : EaxCommitter{std::forward(args)...} { } bool commit(const EAXEQUALIZERPROPERTIES &props); static void SetDefaults(EaxEffectProps &props); static void Get(const EaxCall &call, const EAXEQUALIZERPROPERTIES &props); static void Set(const EaxCall &call, EAXEQUALIZERPROPERTIES &props); }; struct EaxFlangerCommitter : public EaxCommitter { template explicit EaxFlangerCommitter(Args&& ...args) : EaxCommitter{std::forward(args)...} { } bool commit(const EAXFLANGERPROPERTIES &props); static void SetDefaults(EaxEffectProps &props); static void Get(const EaxCall &call, const EAXFLANGERPROPERTIES &props); static void Set(const EaxCall &call, EAXFLANGERPROPERTIES &props); }; struct EaxFrequencyShifterCommitter : public EaxCommitter { template explicit EaxFrequencyShifterCommitter(Args&& ...args) : EaxCommitter{std::forward(args)...} { } bool commit(const EAXFREQUENCYSHIFTERPROPERTIES &props); static void SetDefaults(EaxEffectProps &props); static void Get(const EaxCall &call, const EAXFREQUENCYSHIFTERPROPERTIES &props); static void Set(const EaxCall &call, EAXFREQUENCYSHIFTERPROPERTIES &props); }; struct EaxModulatorCommitter : public EaxCommitter { template explicit EaxModulatorCommitter(Args&& ...args) : EaxCommitter{std::forward(args)...} { } bool commit(const EAXRINGMODULATORPROPERTIES &props); static void SetDefaults(EaxEffectProps &props); static void Get(const EaxCall &call, const EAXRINGMODULATORPROPERTIES &props); static void Set(const EaxCall &call, EAXRINGMODULATORPROPERTIES &props); }; struct EaxPitchShifterCommitter : public EaxCommitter { template explicit EaxPitchShifterCommitter(Args&& ...args) : EaxCommitter{std::forward(args)...} { } bool commit(const EAXPITCHSHIFTERPROPERTIES &props); static void SetDefaults(EaxEffectProps &props); static void Get(const EaxCall &call, const EAXPITCHSHIFTERPROPERTIES &props); static void Set(const EaxCall &call, EAXPITCHSHIFTERPROPERTIES &props); }; struct EaxVocalMorpherCommitter : public EaxCommitter { template explicit EaxVocalMorpherCommitter(Args&& ...args) : EaxCommitter{std::forward(args)...} { } bool commit(const EAXVOCALMORPHERPROPERTIES &props); static void SetDefaults(EaxEffectProps &props); static void Get(const EaxCall &call, const EAXVOCALMORPHERPROPERTIES &props); static void Set(const EaxCall &call, EAXVOCALMORPHERPROPERTIES &props); }; struct EaxNullCommitter : public EaxCommitter { template explicit EaxNullCommitter(Args&& ...args) : EaxCommitter{std::forward(args)...} { } bool commit(const std::monostate &props); static void SetDefaults(EaxEffectProps &props); static void Get(const EaxCall &call, const std::monostate &props); static void Set(const EaxCall &call, std::monostate &props); }; template struct CommitterFromProps { }; template<> struct CommitterFromProps { using type = EaxNullCommitter; }; template<> struct CommitterFromProps { using type = EaxReverbCommitter; }; template<> struct CommitterFromProps { using type = EaxChorusCommitter; }; template<> struct CommitterFromProps { using type = EaxCompressorCommitter; }; template<> struct CommitterFromProps { using type = EaxAutowahCommitter; }; template<> struct CommitterFromProps { using type = EaxDistortionCommitter; }; template<> struct CommitterFromProps { using type = EaxEchoCommitter; }; template<> struct CommitterFromProps { using type = EaxEqualizerCommitter; }; template<> struct CommitterFromProps { using type = EaxFlangerCommitter; }; template<> struct CommitterFromProps { using type = EaxFrequencyShifterCommitter; }; template<> struct CommitterFromProps { using type = EaxModulatorCommitter; }; template<> struct CommitterFromProps { using type = EaxPitchShifterCommitter; }; template<> struct CommitterFromProps { using type = EaxVocalMorpherCommitter; }; template using CommitterFor = typename CommitterFromProps>>::type; class EaxEffect { public: EaxEffect() noexcept = default; ~EaxEffect() = default; ALenum al_effect_type_{AL_EFFECT_NULL}; EffectProps al_effect_props_; using Props1 = EAX_REVERBPROPERTIES; using Props2 = EAX20LISTENERPROPERTIES; using Props3 = EAXREVERBPROPERTIES; using Props4 = EaxEffectProps; struct State1 { Props1 i; // Immediate. Props1 d; // Deferred. }; struct State2 { Props2 i; // Immediate. Props2 d; // Deferred. }; struct State3 { Props3 i; // Immediate. Props3 d; // Deferred. }; struct State4 { Props4 i; // Immediate. Props4 d; // Deferred. }; int version_{}; bool changed_{}; Props4 props_; State1 state1_{}; State2 state2_{}; State3 state3_{}; State4 state4_{}; State4 state5_{}; static void call_set_defaults(const ALenum altype, EaxEffectProps &props) { switch(altype) { case AL_EFFECT_EAXREVERB: return EaxReverbCommitter::SetDefaults(props); case AL_EFFECT_CHORUS: return EaxChorusCommitter::SetDefaults(props); case AL_EFFECT_AUTOWAH: return EaxAutowahCommitter::SetDefaults(props); case AL_EFFECT_COMPRESSOR: return EaxCompressorCommitter::SetDefaults(props); case AL_EFFECT_DISTORTION: return EaxDistortionCommitter::SetDefaults(props); case AL_EFFECT_ECHO: return EaxEchoCommitter::SetDefaults(props); case AL_EFFECT_EQUALIZER: return EaxEqualizerCommitter::SetDefaults(props); case AL_EFFECT_FLANGER: return EaxFlangerCommitter::SetDefaults(props); case AL_EFFECT_FREQUENCY_SHIFTER: return EaxFrequencyShifterCommitter::SetDefaults(props); case AL_EFFECT_RING_MODULATOR: return EaxModulatorCommitter::SetDefaults(props); case AL_EFFECT_PITCH_SHIFTER: return EaxPitchShifterCommitter::SetDefaults(props); case AL_EFFECT_VOCAL_MORPHER: return EaxVocalMorpherCommitter::SetDefaults(props); case AL_EFFECT_NULL: break; } return EaxNullCommitter::SetDefaults(props); } template void init() { EaxReverbCommitter::SetDefaults(state1_.d); state1_.i = state1_.d; EaxReverbCommitter::SetDefaults(state2_.d); state2_.i = state2_.d; EaxReverbCommitter::SetDefaults(state3_.d); state3_.i = state3_.d; T::SetDefaults(state4_.d); state4_.i = state4_.d; T::SetDefaults(state5_.d); state5_.i = state5_.d; } void set_defaults(int eax_version, ALenum altype) { switch(eax_version) { case 1: EaxReverbCommitter::SetDefaults(state1_.d); break; case 2: EaxReverbCommitter::SetDefaults(state2_.d); break; case 3: EaxReverbCommitter::SetDefaults(state3_.d); break; case 4: call_set_defaults(altype, state4_.d); break; case 5: call_set_defaults(altype, state5_.d); break; } changed_ = true; } static void call_set(const EaxCall &call, EaxEffectProps &props) { return std::visit([&](auto &arg) { return CommitterFor::Set(call, arg); }, props); } void set(const EaxCall &call) { switch(call.get_version()) { case 1: EaxReverbCommitter::Set(call, state1_.d); break; case 2: EaxReverbCommitter::Set(call, state2_.d); break; case 3: EaxReverbCommitter::Set(call, state3_.d); break; case 4: call_set(call, state4_.d); break; case 5: call_set(call, state5_.d); break; } changed_ = true; } static void call_get(const EaxCall &call, const EaxEffectProps &props) { return std::visit([&](auto &arg) { return CommitterFor::Get(call, arg); }, props); } void get(const EaxCall &call) const { switch(call.get_version()) { case 1: EaxReverbCommitter::Get(call, state1_.d); break; case 2: EaxReverbCommitter::Get(call, state2_.d); break; case 3: EaxReverbCommitter::Get(call, state3_.d); break; case 4: call_get(call, state4_.d); break; case 5: call_get(call, state5_.d); break; } } bool call_commit(const EaxEffectProps &props) { return std::visit([&](auto &arg) { return CommitterFor{props_, al_effect_props_}.commit(arg); }, props); } bool commit(int eax_version) { changed_ |= version_ != eax_version; if(!changed_) return false; bool ret{version_ != eax_version}; version_ = eax_version; changed_ = false; switch(eax_version) { case 1: state1_.i = state1_.d; ret |= EaxReverbCommitter{props_, al_effect_props_}.commit(state1_.d); break; case 2: state2_.i = state2_.d; ret |= EaxReverbCommitter{props_, al_effect_props_}.commit(state2_.d); break; case 3: state3_.i = state3_.d; ret |= EaxReverbCommitter{props_, al_effect_props_}.commit(state3_.d); break; case 4: state4_.i = state4_.d; ret |= call_commit(state4_.d); break; case 5: state5_.i = state5_.d; ret |= call_commit(state5_.d); break; } al_effect_type_ = EnumFromEaxEffectType(props_); return ret; } #undef EAXCALL }; // EaxEffect using EaxEffectUPtr = std::unique_ptr; #endif // !EAX_EFFECT_INCLUDED openal-soft-1.24.2/al/eax/exception.cpp000066400000000000000000000012601474041540300177030ustar00rootroot00000000000000#include "config.h" #include "exception.h" #include #include EaxException::EaxException(std::string_view context, std::string_view message) : std::runtime_error{make_message(context, message)} { } EaxException::~EaxException() = default; std::string EaxException::make_message(std::string_view context, std::string_view message) { auto what = std::string{}; if(context.empty() && message.empty()) return what; what.reserve((!context.empty() ? context.size() + 3 : 0) + message.length() + 1); if(!context.empty()) { what += "["; what += context; what += "] "; } what += message; return what; } openal-soft-1.24.2/al/eax/exception.h000066400000000000000000000012341474041540300173510ustar00rootroot00000000000000#ifndef EAX_EXCEPTION_INCLUDED #define EAX_EXCEPTION_INCLUDED #include #include #include class EaxException : public std::runtime_error { static std::string make_message(std::string_view context, std::string_view message); public: EaxException() = delete; EaxException(const EaxException&) = default; EaxException(EaxException&&) = default; EaxException(std::string_view context, std::string_view message); ~EaxException() override; auto operator=(const EaxException&) -> EaxException& = default; auto operator=(EaxException&&) -> EaxException& = default; }; #endif /* EAX_EXCEPTION_INCLUDED */ openal-soft-1.24.2/al/eax/fx_slot_index.cpp000066400000000000000000000024301474041540300205520ustar00rootroot00000000000000#include "config.h" #include "fx_slot_index.h" #include "exception.h" namespace { class EaxFxSlotIndexException : public EaxException { public: explicit EaxFxSlotIndexException( const char* message) : EaxException{"EAX_FX_SLOT_INDEX", message} { } }; // EaxFxSlotIndexException } // namespace void EaxFxSlotIndex::set(EaxFxSlotIndexValue index) { if(index >= EaxFxSlotIndexValue{EAX_MAX_FXSLOTS}) fail("Index out of range."); emplace(index); } void EaxFxSlotIndex::set(const GUID &guid) { if (false) { } else if (guid == EAX_NULL_GUID) { reset(); } else if (guid == EAXPROPERTYID_EAX40_FXSlot0 || guid == EAXPROPERTYID_EAX50_FXSlot0) { emplace(0u); } else if (guid == EAXPROPERTYID_EAX40_FXSlot1 || guid == EAXPROPERTYID_EAX50_FXSlot1) { emplace(1u); } else if (guid == EAXPROPERTYID_EAX40_FXSlot2 || guid == EAXPROPERTYID_EAX50_FXSlot2) { emplace(2u); } else if (guid == EAXPROPERTYID_EAX40_FXSlot3 || guid == EAXPROPERTYID_EAX50_FXSlot3) { emplace(3u); } else { fail("Unsupported GUID."); } } [[noreturn]] void EaxFxSlotIndex::fail(const char* message) { throw EaxFxSlotIndexException{message}; } openal-soft-1.24.2/al/eax/fx_slot_index.h000066400000000000000000000017731474041540300202300ustar00rootroot00000000000000#ifndef EAX_FX_SLOT_INDEX_INCLUDED #define EAX_FX_SLOT_INDEX_INCLUDED #include #include #include "api.h" using EaxFxSlotIndexValue = std::size_t; class EaxFxSlotIndex : public std::optional { public: using std::optional::optional; EaxFxSlotIndex& operator=(const EaxFxSlotIndexValue &value) { set(value); return *this; } EaxFxSlotIndex& operator=(const GUID &guid) { set(guid); return *this; } void set(EaxFxSlotIndexValue index); void set(const GUID& guid); private: [[noreturn]] static void fail(const char *message); }; // EaxFxSlotIndex inline bool operator==(const EaxFxSlotIndex& lhs, const EaxFxSlotIndex& rhs) noexcept { if(lhs.has_value() != rhs.has_value()) return false; if(lhs.has_value()) return *lhs == *rhs; return true; } inline bool operator!=(const EaxFxSlotIndex& lhs, const EaxFxSlotIndex& rhs) noexcept { return !(lhs == rhs); } #endif // !EAX_FX_SLOT_INDEX_INCLUDED openal-soft-1.24.2/al/eax/fx_slots.cpp000066400000000000000000000024241474041540300175510ustar00rootroot00000000000000#include "config.h" #include "fx_slots.h" #include #include "api.h" #include "exception.h" namespace { class EaxFxSlotsException : public EaxException { public: explicit EaxFxSlotsException( const char* message) : EaxException{"EAX_FX_SLOTS", message} { } }; // EaxFxSlotsException } // namespace void EaxFxSlots::initialize(ALCcontext& al_context) { initialize_fx_slots(al_context); } void EaxFxSlots::uninitialize() noexcept { for (auto& fx_slot : fx_slots_) { fx_slot = nullptr; } } const ALeffectslot& EaxFxSlots::get(EaxFxSlotIndex index) const { if(!index.has_value()) fail("Empty index."); return *fx_slots_[index.value()]; } ALeffectslot& EaxFxSlots::get(EaxFxSlotIndex index) { if(!index.has_value()) fail("Empty index."); return *fx_slots_[index.value()]; } [[noreturn]] void EaxFxSlots::fail( const char* message) { throw EaxFxSlotsException{message}; } void EaxFxSlots::initialize_fx_slots(ALCcontext& al_context) { auto fx_slot_index = EaxFxSlotIndexValue{}; for (auto& fx_slot : fx_slots_) { fx_slot = eax_create_al_effect_slot(al_context); fx_slot->eax_initialize(al_context, fx_slot_index); fx_slot_index += 1; } } openal-soft-1.24.2/al/eax/fx_slots.h000066400000000000000000000014051474041540300172140ustar00rootroot00000000000000#ifndef EAX_FX_SLOTS_INCLUDED #define EAX_FX_SLOTS_INCLUDED #include #include "al/auxeffectslot.h" #include "fx_slot_index.h" class EaxFxSlots { public: void initialize(ALCcontext& al_context); void uninitialize() noexcept; void commit() { for(auto& fx_slot : fx_slots_) fx_slot->eax_commit(); } [[nodiscard]] auto get(EaxFxSlotIndex index) const -> const ALeffectslot&; [[nodiscard]] auto get(EaxFxSlotIndex index) -> ALeffectslot&; private: using Items = std::array; Items fx_slots_{}; [[noreturn]] static void fail(const char* message); void initialize_fx_slots(ALCcontext& al_context); }; // EaxFxSlots #endif // !EAX_FX_SLOTS_INCLUDED openal-soft-1.24.2/al/eax/globals.h000066400000000000000000000002021474041540300167700ustar00rootroot00000000000000#ifndef EAX_GLOBALS_INCLUDED #define EAX_GLOBALS_INCLUDED inline bool eax_g_is_enabled{true}; #endif /* EAX_GLOBALS_INCLUDED */ openal-soft-1.24.2/al/eax/utils.cpp000066400000000000000000000007421474041540300170510ustar00rootroot00000000000000#include "config.h" #include "utils.h" #include #include #include "core/logging.h" void eax_log_exception(std::string_view message) noexcept { const auto exception_ptr = std::current_exception(); assert(exception_ptr); try { std::rethrow_exception(exception_ptr); } catch(const std::exception& ex) { ERR("{} {}", message, ex.what()); } catch(...) { ERR("{} {}", message, "Generic exception."); } } openal-soft-1.24.2/al/eax/utils.h000066400000000000000000000045201474041540300165140ustar00rootroot00000000000000#ifndef EAX_UTILS_INCLUDED #define EAX_UTILS_INCLUDED #include #include #include #include #include #include "opthelpers.h" using EaxDirtyFlags = unsigned int; struct EaxAlLowPassParam { float gain; float gain_hf; }; void eax_log_exception(std::string_view message) noexcept; template void eax_validate_range(std::string_view value_name, const TValue& value, const TValue& min_value, const TValue& max_value) { if(value >= min_value && value <= max_value) LIKELY return; const auto message = std::string{value_name} + " out of range (value: " + std::to_string(value) + "; min: " + std::to_string(min_value) + "; max: " + std::to_string(max_value) + ")."; throw TException{message.c_str()}; } namespace detail { template struct EaxIsBitFieldStruct { private: using yes = std::true_type; using no = std::false_type; template static auto test(int) -> decltype(std::declval(), yes{}); template static no test(...); public: static constexpr auto value = std::is_same(0)), yes>::value; }; template inline bool eax_bit_fields_are_equal(const T& lhs, const T& rhs) noexcept { static_assert(sizeof(T) == sizeof(TValue), "Invalid type size."); return reinterpret_cast(lhs) == reinterpret_cast(rhs); } } // namespace detail template< typename T, std::enable_if_t::value, int> = 0 > inline bool operator==(const T& lhs, const T& rhs) noexcept { using Value = std::conditional_t< sizeof(T) == 1, std::uint8_t, std::conditional_t< sizeof(T) == 2, std::uint16_t, std::conditional_t< sizeof(T) == 4, std::uint32_t, void>>>; static_assert(!std::is_same::value, "Unsupported type."); return detail::eax_bit_fields_are_equal(lhs, rhs); } template< typename T, std::enable_if_t::value, int> = 0 > inline bool operator!=(const T& lhs, const T& rhs) noexcept { return !(lhs == rhs); } #endif // !EAX_UTILS_INCLUDED openal-soft-1.24.2/al/eax/x_ram.h000066400000000000000000000017331474041540300164650ustar00rootroot00000000000000#ifndef EAX_X_RAM_INCLUDED #define EAX_X_RAM_INCLUDED #include "AL/al.h" constexpr auto eax_x_ram_min_size = ALsizei{}; constexpr auto eax_x_ram_max_size = ALsizei{64 * 1'024 * 1'024}; constexpr auto AL_EAX_RAM_SIZE = ALenum{0x202201}; constexpr auto AL_EAX_RAM_FREE = ALenum{0x202202}; constexpr auto AL_STORAGE_AUTOMATIC = ALenum{0x202203}; constexpr auto AL_STORAGE_HARDWARE = ALenum{0x202204}; constexpr auto AL_STORAGE_ACCESSIBLE = ALenum{0x202205}; constexpr auto AL_EAX_RAM_SIZE_NAME = "AL_EAX_RAM_SIZE"; constexpr auto AL_EAX_RAM_FREE_NAME = "AL_EAX_RAM_FREE"; constexpr auto AL_STORAGE_AUTOMATIC_NAME = "AL_STORAGE_AUTOMATIC"; constexpr auto AL_STORAGE_HARDWARE_NAME = "AL_STORAGE_HARDWARE"; constexpr auto AL_STORAGE_ACCESSIBLE_NAME = "AL_STORAGE_ACCESSIBLE"; ALboolean AL_APIENTRY EAXSetBufferMode(ALsizei n, const ALuint *buffers, ALint value) noexcept; ALenum AL_APIENTRY EAXGetBufferMode(ALuint buffer, ALint *pReserved) noexcept; #endif // !EAX_X_RAM_INCLUDED openal-soft-1.24.2/al/effect.cpp000066400000000000000000000620571474041540300163770ustar00rootroot00000000000000/** * OpenAL cross platform audio library * Copyright (C) 1999-2007 by authors. * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * Or go to http://www.gnu.org/copyleft/lgpl.html */ #include "config.h" #include "effect.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "AL/al.h" #include "AL/alc.h" #include "AL/alext.h" #include "AL/efx-presets.h" #include "AL/efx.h" #include "al/effects/effects.h" #include "albit.h" #include "alc/context.h" #include "alc/device.h" #include "alc/inprogext.h" #include "almalloc.h" #include "alnumeric.h" #include "alspan.h" #include "alstring.h" #include "core/except.h" #include "core/logging.h" #include "direct_defs.h" #include "intrusive_ptr.h" #include "opthelpers.h" const std::array gEffectList{{ { "eaxreverb", EAXREVERB_EFFECT, AL_EFFECT_EAXREVERB }, { "reverb", REVERB_EFFECT, AL_EFFECT_REVERB }, { "autowah", AUTOWAH_EFFECT, AL_EFFECT_AUTOWAH }, { "chorus", CHORUS_EFFECT, AL_EFFECT_CHORUS }, { "compressor", COMPRESSOR_EFFECT, AL_EFFECT_COMPRESSOR }, { "distortion", DISTORTION_EFFECT, AL_EFFECT_DISTORTION }, { "echo", ECHO_EFFECT, AL_EFFECT_ECHO }, { "equalizer", EQUALIZER_EFFECT, AL_EFFECT_EQUALIZER }, { "flanger", FLANGER_EFFECT, AL_EFFECT_FLANGER }, { "fshifter", FSHIFTER_EFFECT, AL_EFFECT_FREQUENCY_SHIFTER }, { "modulator", MODULATOR_EFFECT, AL_EFFECT_RING_MODULATOR }, { "pshifter", PSHIFTER_EFFECT, AL_EFFECT_PITCH_SHIFTER }, { "vmorpher", VMORPHER_EFFECT, AL_EFFECT_VOCAL_MORPHER }, { "dedicated", DEDICATED_EFFECT, AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT }, { "dedicated", DEDICATED_EFFECT, AL_EFFECT_DEDICATED_DIALOGUE }, { "convolution", CONVOLUTION_EFFECT, AL_EFFECT_CONVOLUTION_SOFT }, }}; namespace { using SubListAllocator = al::allocator>; constexpr auto GetDefaultProps(ALenum type) noexcept -> const EffectProps& { switch(type) { case AL_EFFECT_NULL: return NullEffectProps; case AL_EFFECT_EAXREVERB: return ReverbEffectProps; case AL_EFFECT_REVERB: return StdReverbEffectProps; case AL_EFFECT_AUTOWAH: return AutowahEffectProps; case AL_EFFECT_CHORUS: return ChorusEffectProps; case AL_EFFECT_COMPRESSOR: return CompressorEffectProps; case AL_EFFECT_DISTORTION: return DistortionEffectProps; case AL_EFFECT_ECHO: return EchoEffectProps; case AL_EFFECT_EQUALIZER: return EqualizerEffectProps; case AL_EFFECT_FLANGER: return FlangerEffectProps; case AL_EFFECT_FREQUENCY_SHIFTER: return FshifterEffectProps; case AL_EFFECT_RING_MODULATOR: return ModulatorEffectProps; case AL_EFFECT_PITCH_SHIFTER: return PshifterEffectProps; case AL_EFFECT_VOCAL_MORPHER: return VmorpherEffectProps; case AL_EFFECT_DEDICATED_DIALOGUE: return DedicatedDialogEffectProps; case AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT: return DedicatedLfeEffectProps; case AL_EFFECT_CONVOLUTION_SOFT: return ConvolutionEffectProps; } return NullEffectProps; } void InitEffectParams(ALeffect *effect, ALenum type) noexcept { switch(type) { case AL_EFFECT_NULL: effect->PropsVariant.emplace(); break; case AL_EFFECT_EAXREVERB: effect->PropsVariant.emplace(); break; case AL_EFFECT_REVERB: effect->PropsVariant.emplace(); break; case AL_EFFECT_AUTOWAH: effect->PropsVariant.emplace(); break; case AL_EFFECT_CHORUS: effect->PropsVariant.emplace(); break; case AL_EFFECT_COMPRESSOR: effect->PropsVariant.emplace(); break; case AL_EFFECT_DISTORTION: effect->PropsVariant.emplace(); break; case AL_EFFECT_ECHO: effect->PropsVariant.emplace(); break; case AL_EFFECT_EQUALIZER: effect->PropsVariant.emplace(); break; case AL_EFFECT_FLANGER: effect->PropsVariant.emplace(); break; case AL_EFFECT_FREQUENCY_SHIFTER: effect->PropsVariant.emplace(); break; case AL_EFFECT_RING_MODULATOR: effect->PropsVariant.emplace(); break; case AL_EFFECT_PITCH_SHIFTER: effect->PropsVariant.emplace(); break; case AL_EFFECT_VOCAL_MORPHER: effect->PropsVariant.emplace(); break; case AL_EFFECT_DEDICATED_DIALOGUE: effect->PropsVariant.emplace(); break; case AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT: effect->PropsVariant.emplace(); break; case AL_EFFECT_CONVOLUTION_SOFT: effect->PropsVariant.emplace(); break; } effect->Props = GetDefaultProps(type); effect->type = type; } [[nodiscard]] auto EnsureEffects(al::Device *device, size_t needed) noexcept -> bool try { size_t count{std::accumulate(device->EffectList.cbegin(), device->EffectList.cend(), 0_uz, [](size_t cur, const EffectSubList &sublist) noexcept -> size_t { return cur + static_cast(al::popcount(sublist.FreeMask)); })}; while(needed > count) { if(device->EffectList.size() >= 1<<25) UNLIKELY return false; EffectSubList sublist{}; sublist.FreeMask = ~0_u64; sublist.Effects = SubListAllocator{}.allocate(1); device->EffectList.emplace_back(std::move(sublist)); count += std::tuple_size_v; } return true; } catch(...) { return false; } [[nodiscard]] auto AllocEffect(al::Device *device) noexcept -> ALeffect* { auto sublist = std::find_if(device->EffectList.begin(), device->EffectList.end(), [](const EffectSubList &entry) noexcept -> bool { return entry.FreeMask != 0; }); auto lidx = static_cast(std::distance(device->EffectList.begin(), sublist)); auto slidx = static_cast(al::countr_zero(sublist->FreeMask)); ASSUME(slidx < 64); ALeffect *effect{al::construct_at(al::to_address(sublist->Effects->begin() + slidx))}; InitEffectParams(effect, AL_EFFECT_NULL); /* Add 1 to avoid effect ID 0. */ effect->id = ((lidx<<6) | slidx) + 1; sublist->FreeMask &= ~(1_u64 << slidx); return effect; } void FreeEffect(al::Device *device, ALeffect *effect) { device->mEffectNames.erase(effect->id); const ALuint id{effect->id - 1}; const size_t lidx{id >> 6}; const ALuint slidx{id & 0x3f}; std::destroy_at(effect); device->EffectList[lidx].FreeMask |= 1_u64 << slidx; } [[nodiscard]] inline auto LookupEffect(al::Device *device, ALuint id) noexcept -> ALeffect* { const size_t lidx{(id-1) >> 6}; const ALuint slidx{(id-1) & 0x3f}; if(lidx >= device->EffectList.size()) UNLIKELY return nullptr; EffectSubList &sublist = device->EffectList[lidx]; if(sublist.FreeMask & (1_u64 << slidx)) UNLIKELY return nullptr; return al::to_address(sublist.Effects->begin() + slidx); } } // namespace AL_API DECL_FUNC2(void, alGenEffects, ALsizei,n, ALuint*,effects) FORCE_ALIGN void AL_APIENTRY alGenEffectsDirect(ALCcontext *context, ALsizei n, ALuint *effects) noexcept try { if(n < 0) context->throw_error(AL_INVALID_VALUE, "Generating {} effects", n); if(n <= 0) UNLIKELY return; auto *device = context->mALDevice.get(); auto effectlock = std::lock_guard{device->EffectLock}; const al::span eids{effects, static_cast(n)}; if(!EnsureEffects(device, eids.size())) context->throw_error(AL_OUT_OF_MEMORY, "Failed to allocate {} effect{}", n, (n==1) ? "" : "s"); std::generate(eids.begin(), eids.end(), [device]{ return AllocEffect(device)->id; }); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC2(void, alDeleteEffects, ALsizei,n, const ALuint*,effects) FORCE_ALIGN void AL_APIENTRY alDeleteEffectsDirect(ALCcontext *context, ALsizei n, const ALuint *effects) noexcept try { if(n < 0) context->throw_error(AL_INVALID_VALUE, "Deleting {} effects", n); if(n <= 0) UNLIKELY return; auto *device = context->mALDevice.get(); auto effectlock = std::lock_guard{device->EffectLock}; /* First try to find any effects that are invalid. */ auto validate_effect = [device](const ALuint eid) -> bool { return !eid || LookupEffect(device, eid) != nullptr; }; const al::span eids{effects, static_cast(n)}; auto inveffect = std::find_if_not(eids.begin(), eids.end(), validate_effect); if(inveffect != eids.end()) context->throw_error(AL_INVALID_NAME, "Invalid effect ID {}", *inveffect); /* All good. Delete non-0 effect IDs. */ auto delete_effect = [device](ALuint eid) -> void { if(ALeffect *effect{eid ? LookupEffect(device, eid) : nullptr}) FreeEffect(device, effect); }; std::for_each(eids.begin(), eids.end(), delete_effect); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC1(ALboolean, alIsEffect, ALuint,effect) FORCE_ALIGN ALboolean AL_APIENTRY alIsEffectDirect(ALCcontext *context, ALuint effect) noexcept { auto *device = context->mALDevice.get(); auto effectlock = std::lock_guard{device->EffectLock}; if(!effect || LookupEffect(device, effect)) return AL_TRUE; return AL_FALSE; } AL_API DECL_FUNC3(void, alEffecti, ALuint,effect, ALenum,param, ALint,value) FORCE_ALIGN void AL_APIENTRY alEffectiDirect(ALCcontext *context, ALuint effect, ALenum param, ALint value) noexcept try { auto *device = context->mALDevice.get(); auto effectlock = std::lock_guard{device->EffectLock}; ALeffect *aleffect{LookupEffect(device, effect)}; if(!aleffect) context->throw_error(AL_INVALID_NAME, "Invalid effect ID {}", effect); switch(param) { case AL_EFFECT_TYPE: if(value != AL_EFFECT_NULL) { auto check_effect = [value](const EffectList &item) -> bool { return value == item.val && !DisabledEffects.test(item.type); }; if(!std::any_of(gEffectList.cbegin(), gEffectList.cend(), check_effect)) context->throw_error(AL_INVALID_VALUE, "Effect type {:#04x} not supported", as_unsigned(value)); } InitEffectParams(aleffect, value); return; } /* Call the appropriate handler */ std::visit([context,aleffect,param,value](auto &arg) { using Type = std::remove_cv_t>; using PropType = typename Type::prop_type; return arg.SetParami(context, std::get(aleffect->Props), param, value); }, aleffect->PropsVariant); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alEffectiv, ALuint,effect, ALenum,param, const ALint*,values) FORCE_ALIGN void AL_APIENTRY alEffectivDirect(ALCcontext *context, ALuint effect, ALenum param, const ALint *values) noexcept try { switch(param) { case AL_EFFECT_TYPE: alEffectiDirect(context, effect, param, *values); return; } auto *device = context->mALDevice.get(); auto effectlock = std::lock_guard{device->EffectLock}; ALeffect *aleffect{LookupEffect(device, effect)}; if(!aleffect) context->throw_error(AL_INVALID_NAME, "Invalid effect ID {}", effect); /* Call the appropriate handler */ std::visit([context,aleffect,param,values](auto &arg) { using Type = std::remove_cv_t>; using PropType = typename Type::prop_type; return arg.SetParamiv(context, std::get(aleffect->Props), param, values); }, aleffect->PropsVariant); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alEffectf, ALuint,effect, ALenum,param, ALfloat,value) FORCE_ALIGN void AL_APIENTRY alEffectfDirect(ALCcontext *context, ALuint effect, ALenum param, ALfloat value) noexcept try { auto *device = context->mALDevice.get(); auto effectlock = std::lock_guard{device->EffectLock}; ALeffect *aleffect{LookupEffect(device, effect)}; if(!aleffect) context->throw_error(AL_INVALID_NAME, "Invalid effect ID {}", effect); /* Call the appropriate handler */ std::visit([context,aleffect,param,value](auto &arg) { using Type = std::remove_cv_t>; using PropType = typename Type::prop_type; return arg.SetParamf(context, std::get(aleffect->Props), param, value); }, aleffect->PropsVariant); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alEffectfv, ALuint,effect, ALenum,param, const ALfloat*,values) FORCE_ALIGN void AL_APIENTRY alEffectfvDirect(ALCcontext *context, ALuint effect, ALenum param, const ALfloat *values) noexcept try { auto *device = context->mALDevice.get(); auto effectlock = std::lock_guard{device->EffectLock}; ALeffect *aleffect{LookupEffect(device, effect)}; if(!aleffect) context->throw_error(AL_INVALID_NAME, "Invalid effect ID {}", effect); /* Call the appropriate handler */ std::visit([context,aleffect,param,values](auto &arg) { using Type = std::remove_cv_t>; using PropType = typename Type::prop_type; return arg.SetParamfv(context, std::get(aleffect->Props), param, values); }, aleffect->PropsVariant); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alGetEffecti, ALuint,effect, ALenum,param, ALint*,value) FORCE_ALIGN void AL_APIENTRY alGetEffectiDirect(ALCcontext *context, ALuint effect, ALenum param, ALint *value) noexcept try { auto *device = context->mALDevice.get(); auto effectlock = std::lock_guard{device->EffectLock}; const ALeffect *aleffect{LookupEffect(device, effect)}; if(!aleffect) context->throw_error(AL_INVALID_NAME, "Invalid effect ID {}", effect); switch(param) { case AL_EFFECT_TYPE: *value = aleffect->type; return; } /* Call the appropriate handler */ std::visit([context,aleffect,param,value](auto &arg) { using Type = std::remove_cv_t>; using PropType = typename Type::prop_type; return arg.GetParami(context, std::get(aleffect->Props), param, value); }, aleffect->PropsVariant); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alGetEffectiv, ALuint,effect, ALenum,param, ALint*,values) FORCE_ALIGN void AL_APIENTRY alGetEffectivDirect(ALCcontext *context, ALuint effect, ALenum param, ALint *values) noexcept try { switch(param) { case AL_EFFECT_TYPE: alGetEffectiDirect(context, effect, param, values); return; } auto *device = context->mALDevice.get(); auto effectlock = std::lock_guard{device->EffectLock}; const ALeffect *aleffect{LookupEffect(device, effect)}; if(!aleffect) context->throw_error(AL_INVALID_NAME, "Invalid effect ID {}", effect); /* Call the appropriate handler */ std::visit([context,aleffect,param,values](auto &arg) { using Type = std::remove_cv_t>; using PropType = typename Type::prop_type; return arg.GetParamiv(context, std::get(aleffect->Props), param, values); }, aleffect->PropsVariant); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alGetEffectf, ALuint,effect, ALenum,param, ALfloat*,value) FORCE_ALIGN void AL_APIENTRY alGetEffectfDirect(ALCcontext *context, ALuint effect, ALenum param, ALfloat *value) noexcept try { auto *device = context->mALDevice.get(); auto effectlock = std::lock_guard{device->EffectLock}; const ALeffect *aleffect{LookupEffect(device, effect)}; if(!aleffect) context->throw_error(AL_INVALID_NAME, "Invalid effect ID {}", effect); /* Call the appropriate handler */ std::visit([context,aleffect,param,value](auto &arg) { using Type = std::remove_cv_t>; using PropType = typename Type::prop_type; return arg.GetParamf(context, std::get(aleffect->Props), param, value); }, aleffect->PropsVariant); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alGetEffectfv, ALuint,effect, ALenum,param, ALfloat*,values) FORCE_ALIGN void AL_APIENTRY alGetEffectfvDirect(ALCcontext *context, ALuint effect, ALenum param, ALfloat *values) noexcept try { auto *device = context->mALDevice.get(); auto effectlock = std::lock_guard{device->EffectLock}; const ALeffect *aleffect{LookupEffect(device, effect)}; if(!aleffect) context->throw_error(AL_INVALID_NAME, "Invalid effect ID {}", effect); /* Call the appropriate handler */ std::visit([context,aleffect,param,values](auto &arg) { using Type = std::remove_cv_t>; using PropType = typename Type::prop_type; return arg.GetParamfv(context, std::get(aleffect->Props), param, values); }, aleffect->PropsVariant); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } void InitEffect(ALeffect *effect) { InitEffectParams(effect, AL_EFFECT_NULL); } void ALeffect::SetName(ALCcontext* context, ALuint id, std::string_view name) { auto *device = context->mALDevice.get(); auto effectlock = std::lock_guard{device->EffectLock}; auto effect = LookupEffect(device, id); if(!effect) context->throw_error(AL_INVALID_NAME, "Invalid effect ID {}", id); device->mEffectNames.insert_or_assign(id, name); } EffectSubList::~EffectSubList() { if(!Effects) return; uint64_t usemask{~FreeMask}; while(usemask) { const int idx{al::countr_zero(usemask)}; std::destroy_at(al::to_address(Effects->begin()+idx)); usemask &= ~(1_u64 << idx); } FreeMask = ~usemask; SubListAllocator{}.deallocate(Effects, 1); Effects = nullptr; } struct EffectPreset { const char name[32]; /* NOLINT(*-avoid-c-arrays) */ EFXEAXREVERBPROPERTIES props; }; #define DECL(x) EffectPreset{#x, EFX_REVERB_PRESET_##x} static constexpr std::array reverblist{ DECL(GENERIC), DECL(PADDEDCELL), DECL(ROOM), DECL(BATHROOM), DECL(LIVINGROOM), DECL(STONEROOM), DECL(AUDITORIUM), DECL(CONCERTHALL), DECL(CAVE), DECL(ARENA), DECL(HANGAR), DECL(CARPETEDHALLWAY), DECL(HALLWAY), DECL(STONECORRIDOR), DECL(ALLEY), DECL(FOREST), DECL(CITY), DECL(MOUNTAINS), DECL(QUARRY), DECL(PLAIN), DECL(PARKINGLOT), DECL(SEWERPIPE), DECL(UNDERWATER), DECL(DRUGGED), DECL(DIZZY), DECL(PSYCHOTIC), DECL(CASTLE_SMALLROOM), DECL(CASTLE_SHORTPASSAGE), DECL(CASTLE_MEDIUMROOM), DECL(CASTLE_LARGEROOM), DECL(CASTLE_LONGPASSAGE), DECL(CASTLE_HALL), DECL(CASTLE_CUPBOARD), DECL(CASTLE_COURTYARD), DECL(CASTLE_ALCOVE), DECL(FACTORY_SMALLROOM), DECL(FACTORY_SHORTPASSAGE), DECL(FACTORY_MEDIUMROOM), DECL(FACTORY_LARGEROOM), DECL(FACTORY_LONGPASSAGE), DECL(FACTORY_HALL), DECL(FACTORY_CUPBOARD), DECL(FACTORY_COURTYARD), DECL(FACTORY_ALCOVE), DECL(ICEPALACE_SMALLROOM), DECL(ICEPALACE_SHORTPASSAGE), DECL(ICEPALACE_MEDIUMROOM), DECL(ICEPALACE_LARGEROOM), DECL(ICEPALACE_LONGPASSAGE), DECL(ICEPALACE_HALL), DECL(ICEPALACE_CUPBOARD), DECL(ICEPALACE_COURTYARD), DECL(ICEPALACE_ALCOVE), DECL(SPACESTATION_SMALLROOM), DECL(SPACESTATION_SHORTPASSAGE), DECL(SPACESTATION_MEDIUMROOM), DECL(SPACESTATION_LARGEROOM), DECL(SPACESTATION_LONGPASSAGE), DECL(SPACESTATION_HALL), DECL(SPACESTATION_CUPBOARD), DECL(SPACESTATION_ALCOVE), DECL(WOODEN_SMALLROOM), DECL(WOODEN_SHORTPASSAGE), DECL(WOODEN_MEDIUMROOM), DECL(WOODEN_LARGEROOM), DECL(WOODEN_LONGPASSAGE), DECL(WOODEN_HALL), DECL(WOODEN_CUPBOARD), DECL(WOODEN_COURTYARD), DECL(WOODEN_ALCOVE), DECL(SPORT_EMPTYSTADIUM), DECL(SPORT_SQUASHCOURT), DECL(SPORT_SMALLSWIMMINGPOOL), DECL(SPORT_LARGESWIMMINGPOOL), DECL(SPORT_GYMNASIUM), DECL(SPORT_FULLSTADIUM), DECL(SPORT_STADIUMTANNOY), DECL(PREFAB_WORKSHOP), DECL(PREFAB_SCHOOLROOM), DECL(PREFAB_PRACTISEROOM), DECL(PREFAB_OUTHOUSE), DECL(PREFAB_CARAVAN), DECL(DOME_TOMB), DECL(PIPE_SMALL), DECL(DOME_SAINTPAULS), DECL(PIPE_LONGTHIN), DECL(PIPE_LARGE), DECL(PIPE_RESONANT), DECL(OUTDOORS_BACKYARD), DECL(OUTDOORS_ROLLINGPLAINS), DECL(OUTDOORS_DEEPCANYON), DECL(OUTDOORS_CREEK), DECL(OUTDOORS_VALLEY), DECL(MOOD_HEAVEN), DECL(MOOD_HELL), DECL(MOOD_MEMORY), DECL(DRIVING_COMMENTATOR), DECL(DRIVING_PITGARAGE), DECL(DRIVING_INCAR_RACER), DECL(DRIVING_INCAR_SPORTS), DECL(DRIVING_INCAR_LUXURY), DECL(DRIVING_FULLGRANDSTAND), DECL(DRIVING_EMPTYGRANDSTAND), DECL(DRIVING_TUNNEL), DECL(CITY_STREETS), DECL(CITY_SUBWAY), DECL(CITY_MUSEUM), DECL(CITY_LIBRARY), DECL(CITY_UNDERPASS), DECL(CITY_ABANDONED), DECL(DUSTYROOM), DECL(CHAPEL), DECL(SMALLWATERROOM), }; #undef DECL void LoadReverbPreset(const std::string_view name, ALeffect *effect) { using namespace std::string_view_literals; if(al::case_compare(name, "NONE"sv) == 0) { InitEffectParams(effect, AL_EFFECT_NULL); TRACE("Loading reverb '{}'", "NONE"); return; } if(!DisabledEffects.test(EAXREVERB_EFFECT)) InitEffectParams(effect, AL_EFFECT_EAXREVERB); else if(!DisabledEffects.test(REVERB_EFFECT)) InitEffectParams(effect, AL_EFFECT_REVERB); else InitEffectParams(effect, AL_EFFECT_NULL); for(const auto &reverbitem : reverblist) { if(al::case_compare(name, std::data(reverbitem.name)) != 0) continue; TRACE("Loading reverb '{}'", std::data(reverbitem.name)); const auto &props = reverbitem.props; auto &dst = std::get(effect->Props); dst.Density = props.flDensity; dst.Diffusion = props.flDiffusion; dst.Gain = props.flGain; dst.GainHF = props.flGainHF; dst.GainLF = props.flGainLF; dst.DecayTime = props.flDecayTime; dst.DecayHFRatio = props.flDecayHFRatio; dst.DecayLFRatio = props.flDecayLFRatio; dst.ReflectionsGain = props.flReflectionsGain; dst.ReflectionsDelay = props.flReflectionsDelay; dst.ReflectionsPan[0] = props.flReflectionsPan[0]; dst.ReflectionsPan[1] = props.flReflectionsPan[1]; dst.ReflectionsPan[2] = props.flReflectionsPan[2]; dst.LateReverbGain = props.flLateReverbGain; dst.LateReverbDelay = props.flLateReverbDelay; dst.LateReverbPan[0] = props.flLateReverbPan[0]; dst.LateReverbPan[1] = props.flLateReverbPan[1]; dst.LateReverbPan[2] = props.flLateReverbPan[2]; dst.EchoTime = props.flEchoTime; dst.EchoDepth = props.flEchoDepth; dst.ModulationTime = props.flModulationTime; dst.ModulationDepth = props.flModulationDepth; dst.AirAbsorptionGainHF = props.flAirAbsorptionGainHF; dst.HFReference = props.flHFReference; dst.LFReference = props.flLFReference; dst.RoomRolloffFactor = props.flRoomRolloffFactor; dst.DecayHFLimit = props.iDecayHFLimit ? AL_TRUE : AL_FALSE; return; } WARN("Reverb preset '{}' not found", name); } bool IsValidEffectType(ALenum type) noexcept { if(type == AL_EFFECT_NULL) return true; auto check_effect = [type](const EffectList &item) noexcept -> bool { return type == item.val && !DisabledEffects.test(item.type); }; return std::any_of(gEffectList.cbegin(), gEffectList.cend(), check_effect); } openal-soft-1.24.2/al/effect.h000066400000000000000000000045411474041540300160360ustar00rootroot00000000000000#ifndef AL_EFFECT_H #define AL_EFFECT_H #include #include #include #include #include #include #include "AL/al.h" #include "AL/alc.h" #include "AL/efx.h" #include "almalloc.h" #include "alnumeric.h" #include "core/effects/base.h" #include "effects/effects.h" enum { EAXREVERB_EFFECT = 0, REVERB_EFFECT, AUTOWAH_EFFECT, CHORUS_EFFECT, COMPRESSOR_EFFECT, DISTORTION_EFFECT, ECHO_EFFECT, EQUALIZER_EFFECT, FLANGER_EFFECT, FSHIFTER_EFFECT, MODULATOR_EFFECT, PSHIFTER_EFFECT, VMORPHER_EFFECT, DEDICATED_EFFECT, CONVOLUTION_EFFECT, MAX_EFFECTS }; inline std::bitset DisabledEffects; struct EffectList { const char name[16]; /* NOLINT(*-avoid-c-arrays) */ ALuint type; ALenum val; }; DECL_HIDDEN extern const std::array gEffectList; using EffectHandlerVariant = std::variant; struct ALeffect { // Effect type (AL_EFFECT_NULL, ...) ALenum type{AL_EFFECT_NULL}; EffectHandlerVariant PropsVariant; EffectProps Props; /* Self ID */ ALuint id{0u}; static void SetName(ALCcontext *context, ALuint id, std::string_view name); DISABLE_ALLOC }; void InitEffect(ALeffect *effect); void LoadReverbPreset(const std::string_view name, ALeffect *effect); bool IsValidEffectType(ALenum type) noexcept; struct EffectSubList { uint64_t FreeMask{~0_u64}; gsl::owner*> Effects{nullptr}; /* 64 */ EffectSubList() noexcept = default; EffectSubList(const EffectSubList&) = delete; EffectSubList(EffectSubList&& rhs) noexcept : FreeMask{rhs.FreeMask}, Effects{rhs.Effects} { rhs.FreeMask = ~0_u64; rhs.Effects = nullptr; } ~EffectSubList(); EffectSubList& operator=(const EffectSubList&) = delete; EffectSubList& operator=(EffectSubList&& rhs) noexcept { std::swap(FreeMask, rhs.FreeMask); std::swap(Effects, rhs.Effects); return *this; } }; #endif openal-soft-1.24.2/al/effects/000077500000000000000000000000001474041540300160445ustar00rootroot00000000000000openal-soft-1.24.2/al/effects/autowah.cpp000066400000000000000000000171341474041540300202260ustar00rootroot00000000000000 #include "config.h" #include #include #include "AL/efx.h" #include "alc/context.h" #include "alnumeric.h" #include "effects.h" #if ALSOFT_EAX #include "al/eax/effect.h" #include "al/eax/exception.h" #include "al/eax/utils.h" #endif // ALSOFT_EAX namespace { constexpr EffectProps genDefaultProps() noexcept { AutowahProps props{}; props.AttackTime = AL_AUTOWAH_DEFAULT_ATTACK_TIME; props.ReleaseTime = AL_AUTOWAH_DEFAULT_RELEASE_TIME; props.Resonance = AL_AUTOWAH_DEFAULT_RESONANCE; props.PeakGain = AL_AUTOWAH_DEFAULT_PEAK_GAIN; return props; } } // namespace const EffectProps AutowahEffectProps{genDefaultProps()}; void AutowahEffectHandler::SetParami(ALCcontext *context, AutowahProps&, ALenum param, int) { context->throw_error(AL_INVALID_ENUM, "Invalid autowah integer property {:#04x}", as_unsigned(param)); } void AutowahEffectHandler::SetParamiv(ALCcontext *context, AutowahProps&, ALenum param, const int*) { context->throw_error(AL_INVALID_ENUM, "Invalid autowah integer vector property {:#04x}", as_unsigned(param)); } void AutowahEffectHandler::SetParamf(ALCcontext *context, AutowahProps &props, ALenum param, float val) { switch(param) { case AL_AUTOWAH_ATTACK_TIME: if(!(val >= AL_AUTOWAH_MIN_ATTACK_TIME && val <= AL_AUTOWAH_MAX_ATTACK_TIME)) context->throw_error(AL_INVALID_VALUE, "Autowah attack time out of range"); props.AttackTime = val; return; case AL_AUTOWAH_RELEASE_TIME: if(!(val >= AL_AUTOWAH_MIN_RELEASE_TIME && val <= AL_AUTOWAH_MAX_RELEASE_TIME)) context->throw_error(AL_INVALID_VALUE, "Autowah release time out of range"); props.ReleaseTime = val; return; case AL_AUTOWAH_RESONANCE: if(!(val >= AL_AUTOWAH_MIN_RESONANCE && val <= AL_AUTOWAH_MAX_RESONANCE)) context->throw_error(AL_INVALID_VALUE, "Autowah resonance out of range"); props.Resonance = val; return; case AL_AUTOWAH_PEAK_GAIN: if(!(val >= AL_AUTOWAH_MIN_PEAK_GAIN && val <= AL_AUTOWAH_MAX_PEAK_GAIN)) context->throw_error(AL_INVALID_VALUE, "Autowah peak gain out of range"); props.PeakGain = val; return; } context->throw_error(AL_INVALID_ENUM, "Invalid autowah float property {:#04x}", as_unsigned(param)); } void AutowahEffectHandler::SetParamfv(ALCcontext *context, AutowahProps &props, ALenum param, const float *vals) { SetParamf(context, props, param, *vals); } void AutowahEffectHandler::GetParami(ALCcontext *context, const AutowahProps&, ALenum param, int*) { context->throw_error(AL_INVALID_ENUM, "Invalid autowah integer property {:#04x}", as_unsigned(param)); } void AutowahEffectHandler::GetParamiv(ALCcontext *context, const AutowahProps&, ALenum param, int*) { context->throw_error(AL_INVALID_ENUM, "Invalid autowah integer vector property {:#04x}", as_unsigned(param)); } void AutowahEffectHandler::GetParamf(ALCcontext *context, const AutowahProps &props, ALenum param, float *val) { switch(param) { case AL_AUTOWAH_ATTACK_TIME: *val = props.AttackTime; return; case AL_AUTOWAH_RELEASE_TIME: *val = props.ReleaseTime; return; case AL_AUTOWAH_RESONANCE: *val = props.Resonance; return; case AL_AUTOWAH_PEAK_GAIN: *val = props.PeakGain; return; } context->throw_error(AL_INVALID_ENUM, "Invalid autowah float property {:#04x}", as_unsigned(param)); } void AutowahEffectHandler::GetParamfv(ALCcontext *context, const AutowahProps &props, ALenum param, float *vals) { GetParamf(context, props, param, vals); } #if ALSOFT_EAX namespace { using AutowahCommitter = EaxCommitter; struct AttackTimeValidator { void operator()(float flAttackTime) const { eax_validate_range( "Attack Time", flAttackTime, EAXAUTOWAH_MINATTACKTIME, EAXAUTOWAH_MAXATTACKTIME); } }; // AttackTimeValidator struct ReleaseTimeValidator { void operator()(float flReleaseTime) const { eax_validate_range( "Release Time", flReleaseTime, EAXAUTOWAH_MINRELEASETIME, EAXAUTOWAH_MAXRELEASETIME); } }; // ReleaseTimeValidator struct ResonanceValidator { void operator()(long lResonance) const { eax_validate_range( "Resonance", lResonance, EAXAUTOWAH_MINRESONANCE, EAXAUTOWAH_MAXRESONANCE); } }; // ResonanceValidator struct PeakLevelValidator { void operator()(long lPeakLevel) const { eax_validate_range( "Peak Level", lPeakLevel, EAXAUTOWAH_MINPEAKLEVEL, EAXAUTOWAH_MAXPEAKLEVEL); } }; // PeakLevelValidator struct AllValidator { void operator()(const EAXAUTOWAHPROPERTIES& all) const { AttackTimeValidator{}(all.flAttackTime); ReleaseTimeValidator{}(all.flReleaseTime); ResonanceValidator{}(all.lResonance); PeakLevelValidator{}(all.lPeakLevel); } }; // AllValidator } // namespace template<> struct AutowahCommitter::Exception : public EaxException { explicit Exception(const char *message) : EaxException{"EAX_AUTOWAH_EFFECT", message} { } }; template<> [[noreturn]] void AutowahCommitter::fail(const char *message) { throw Exception{message}; } bool EaxAutowahCommitter::commit(const EAXAUTOWAHPROPERTIES &props) { if(auto *cur = std::get_if(&mEaxProps); cur && *cur == props) return false; mEaxProps = props; mAlProps = [&]{ AutowahProps ret{}; ret.AttackTime = props.flAttackTime; ret.ReleaseTime = props.flReleaseTime; ret.Resonance = level_mb_to_gain(static_cast(props.lResonance)); ret.PeakGain = level_mb_to_gain(static_cast(props.lPeakLevel)); return ret; }(); return true; } void EaxAutowahCommitter::SetDefaults(EaxEffectProps &props) { static constexpr EAXAUTOWAHPROPERTIES defprops{[] { EAXAUTOWAHPROPERTIES ret{}; ret.flAttackTime = EAXAUTOWAH_DEFAULTATTACKTIME; ret.flReleaseTime = EAXAUTOWAH_DEFAULTRELEASETIME; ret.lResonance = EAXAUTOWAH_DEFAULTRESONANCE; ret.lPeakLevel = EAXAUTOWAH_DEFAULTPEAKLEVEL; return ret; }()}; props = defprops; } void EaxAutowahCommitter::Get(const EaxCall &call, const EAXAUTOWAHPROPERTIES &props) { switch(call.get_property_id()) { case EAXAUTOWAH_NONE: break; case EAXAUTOWAH_ALLPARAMETERS: call.set_value(props); break; case EAXAUTOWAH_ATTACKTIME: call.set_value(props.flAttackTime); break; case EAXAUTOWAH_RELEASETIME: call.set_value(props.flReleaseTime); break; case EAXAUTOWAH_RESONANCE: call.set_value(props.lResonance); break; case EAXAUTOWAH_PEAKLEVEL: call.set_value(props.lPeakLevel); break; default: fail_unknown_property_id(); } } void EaxAutowahCommitter::Set(const EaxCall &call, EAXAUTOWAHPROPERTIES &props) { switch(call.get_property_id()) { case EAXAUTOWAH_NONE: break; case EAXAUTOWAH_ALLPARAMETERS: defer(call, props); break; case EAXAUTOWAH_ATTACKTIME: defer(call, props.flAttackTime); break; case EAXAUTOWAH_RELEASETIME: defer(call, props.flReleaseTime); break; case EAXAUTOWAH_RESONANCE: defer(call, props.lResonance); break; case EAXAUTOWAH_PEAKLEVEL: defer(call, props.lPeakLevel); break; default: fail_unknown_property_id(); } } #endif // ALSOFT_EAX openal-soft-1.24.2/al/effects/chorus.cpp000066400000000000000000000607371474041540300200700ustar00rootroot00000000000000 #include "config.h" #include #include #include "AL/al.h" #include "AL/efx.h" #include "alc/context.h" #include "alnumeric.h" #include "effects.h" #if ALSOFT_EAX #include #include "al/eax/effect.h" #include "al/eax/exception.h" #include "al/eax/utils.h" #endif // ALSOFT_EAX namespace { static_assert(ChorusMaxDelay >= AL_CHORUS_MAX_DELAY, "Chorus max delay too small"); static_assert(FlangerMaxDelay >= AL_FLANGER_MAX_DELAY, "Flanger max delay too small"); static_assert(AL_CHORUS_WAVEFORM_SINUSOID == AL_FLANGER_WAVEFORM_SINUSOID, "Chorus/Flanger waveform value mismatch"); static_assert(AL_CHORUS_WAVEFORM_TRIANGLE == AL_FLANGER_WAVEFORM_TRIANGLE, "Chorus/Flanger waveform value mismatch"); constexpr std::optional WaveformFromEnum(ALenum type) noexcept { switch(type) { case AL_CHORUS_WAVEFORM_SINUSOID: return ChorusWaveform::Sinusoid; case AL_CHORUS_WAVEFORM_TRIANGLE: return ChorusWaveform::Triangle; } return std::nullopt; } constexpr ALenum EnumFromWaveform(ChorusWaveform type) { switch(type) { case ChorusWaveform::Sinusoid: return AL_CHORUS_WAVEFORM_SINUSOID; case ChorusWaveform::Triangle: return AL_CHORUS_WAVEFORM_TRIANGLE; } throw std::runtime_error{fmt::format("Invalid chorus waveform: {}", int{al::to_underlying(type)})}; } constexpr EffectProps genDefaultChorusProps() noexcept { ChorusProps props{}; props.Waveform = WaveformFromEnum(AL_CHORUS_DEFAULT_WAVEFORM).value(); props.Phase = AL_CHORUS_DEFAULT_PHASE; props.Rate = AL_CHORUS_DEFAULT_RATE; props.Depth = AL_CHORUS_DEFAULT_DEPTH; props.Feedback = AL_CHORUS_DEFAULT_FEEDBACK; props.Delay = AL_CHORUS_DEFAULT_DELAY; return props; } constexpr EffectProps genDefaultFlangerProps() noexcept { ChorusProps props{}; props.Waveform = WaveformFromEnum(AL_FLANGER_DEFAULT_WAVEFORM).value(); props.Phase = AL_FLANGER_DEFAULT_PHASE; props.Rate = AL_FLANGER_DEFAULT_RATE; props.Depth = AL_FLANGER_DEFAULT_DEPTH; props.Feedback = AL_FLANGER_DEFAULT_FEEDBACK; props.Delay = AL_FLANGER_DEFAULT_DELAY; return props; } } // namespace const EffectProps ChorusEffectProps{genDefaultChorusProps()}; void ChorusEffectHandler::SetParami(ALCcontext *context, ChorusProps &props, ALenum param, int val) { switch(param) { case AL_CHORUS_WAVEFORM: if(auto formopt = WaveformFromEnum(val)) props.Waveform = *formopt; else context->throw_error(AL_INVALID_VALUE, "Invalid chorus waveform: {:#04x}", as_unsigned(val)); return; case AL_CHORUS_PHASE: if(!(val >= AL_CHORUS_MIN_PHASE && val <= AL_CHORUS_MAX_PHASE)) context->throw_error(AL_INVALID_VALUE, "Chorus phase out of range: {}", val); props.Phase = val; return; } context->throw_error(AL_INVALID_ENUM, "Invalid chorus integer property {:#04x}", as_unsigned(param)); } void ChorusEffectHandler::SetParamiv(ALCcontext *context, ChorusProps &props, ALenum param, const int *vals) { SetParami(context, props, param, *vals); } void ChorusEffectHandler::SetParamf(ALCcontext *context, ChorusProps &props, ALenum param, float val) { switch(param) { case AL_CHORUS_RATE: if(!(val >= AL_CHORUS_MIN_RATE && val <= AL_CHORUS_MAX_RATE)) context->throw_error(AL_INVALID_VALUE, "Chorus rate out of range: {:f}", val); props.Rate = val; return; case AL_CHORUS_DEPTH: if(!(val >= AL_CHORUS_MIN_DEPTH && val <= AL_CHORUS_MAX_DEPTH)) context->throw_error(AL_INVALID_VALUE, "Chorus depth out of range: {:f}", val); props.Depth = val; return; case AL_CHORUS_FEEDBACK: if(!(val >= AL_CHORUS_MIN_FEEDBACK && val <= AL_CHORUS_MAX_FEEDBACK)) context->throw_error(AL_INVALID_VALUE, "Chorus feedback out of range: {:f}", val); props.Feedback = val; return; case AL_CHORUS_DELAY: if(!(val >= AL_CHORUS_MIN_DELAY && val <= AL_CHORUS_MAX_DELAY)) context->throw_error(AL_INVALID_VALUE, "Chorus delay out of range: {:f}", val); props.Delay = val; return; } context->throw_error(AL_INVALID_ENUM, "Invalid chorus float property {:#04x}", as_unsigned(param)); } void ChorusEffectHandler::SetParamfv(ALCcontext *context, ChorusProps &props, ALenum param, const float *vals) { SetParamf(context, props, param, *vals); } void ChorusEffectHandler::GetParami(ALCcontext *context, const ChorusProps &props, ALenum param, int *val) { switch(param) { case AL_CHORUS_WAVEFORM: *val = EnumFromWaveform(props.Waveform); return; case AL_CHORUS_PHASE: *val = props.Phase; return; } context->throw_error(AL_INVALID_ENUM, "Invalid chorus integer property {:#04x}", as_unsigned(param)); } void ChorusEffectHandler::GetParamiv(ALCcontext *context, const ChorusProps &props, ALenum param, int *vals) { GetParami(context, props, param, vals); } void ChorusEffectHandler::GetParamf(ALCcontext *context, const ChorusProps &props, ALenum param, float *val) { switch(param) { case AL_CHORUS_RATE: *val = props.Rate; return; case AL_CHORUS_DEPTH: *val = props.Depth; return; case AL_CHORUS_FEEDBACK: *val = props.Feedback; return; case AL_CHORUS_DELAY: *val = props.Delay; return; } context->throw_error(AL_INVALID_ENUM, "Invalid chorus float property {:#04x}", as_unsigned(param)); } void ChorusEffectHandler::GetParamfv(ALCcontext *context, const ChorusProps &props, ALenum param, float *vals) { GetParamf(context, props, param, vals); } const EffectProps FlangerEffectProps{genDefaultFlangerProps()}; void FlangerEffectHandler::SetParami(ALCcontext *context, ChorusProps &props, ALenum param, int val) { switch(param) { case AL_FLANGER_WAVEFORM: if(auto formopt = WaveformFromEnum(val)) props.Waveform = *formopt; else context->throw_error(AL_INVALID_VALUE, "Invalid flanger waveform: {:#04x}", as_unsigned(val)); return; case AL_FLANGER_PHASE: if(!(val >= AL_FLANGER_MIN_PHASE && val <= AL_FLANGER_MAX_PHASE)) context->throw_error(AL_INVALID_VALUE, "Flanger phase out of range: {}", val); props.Phase = val; return; } context->throw_error(AL_INVALID_ENUM, "Invalid flanger integer property {:#04x}", as_unsigned(param)); } void FlangerEffectHandler::SetParamiv(ALCcontext *context, ChorusProps &props, ALenum param, const int *vals) { SetParami(context, props, param, *vals); } void FlangerEffectHandler::SetParamf(ALCcontext *context, ChorusProps &props, ALenum param, float val) { switch(param) { case AL_FLANGER_RATE: if(!(val >= AL_FLANGER_MIN_RATE && val <= AL_FLANGER_MAX_RATE)) context->throw_error(AL_INVALID_VALUE, "Flanger rate out of range: {:f}", val); props.Rate = val; return; case AL_FLANGER_DEPTH: if(!(val >= AL_FLANGER_MIN_DEPTH && val <= AL_FLANGER_MAX_DEPTH)) context->throw_error(AL_INVALID_VALUE, "Flanger depth out of range: {:f}", val); props.Depth = val; return; case AL_FLANGER_FEEDBACK: if(!(val >= AL_FLANGER_MIN_FEEDBACK && val <= AL_FLANGER_MAX_FEEDBACK)) context->throw_error(AL_INVALID_VALUE, "Flanger feedback out of range: {:f}", val); props.Feedback = val; return; case AL_FLANGER_DELAY: if(!(val >= AL_FLANGER_MIN_DELAY && val <= AL_FLANGER_MAX_DELAY)) context->throw_error(AL_INVALID_VALUE, "Flanger delay out of range: {:f}", val); props.Delay = val; return; } context->throw_error(AL_INVALID_ENUM, "Invalid flanger float property {:#04x}", as_unsigned(param)); } void FlangerEffectHandler::SetParamfv(ALCcontext *context, ChorusProps &props, ALenum param, const float *vals) { SetParamf(context, props, param, *vals); } void FlangerEffectHandler::GetParami(ALCcontext *context, const ChorusProps &props, ALenum param, int *val) { switch(param) { case AL_FLANGER_WAVEFORM: *val = EnumFromWaveform(props.Waveform); return; case AL_FLANGER_PHASE: *val = props.Phase; return; } context->throw_error(AL_INVALID_ENUM, "Invalid flanger integer property {:#04x}", as_unsigned(param)); } void FlangerEffectHandler::GetParamiv(ALCcontext *context, const ChorusProps &props, ALenum param, int *vals) { GetParami(context, props, param, vals); } void FlangerEffectHandler::GetParamf(ALCcontext *context, const ChorusProps &props, ALenum param, float *val) { switch(param) { case AL_FLANGER_RATE: *val = props.Rate; return; case AL_FLANGER_DEPTH: *val = props.Depth; return; case AL_FLANGER_FEEDBACK: *val = props.Feedback; return; case AL_FLANGER_DELAY: *val = props.Delay; return; } context->throw_error(AL_INVALID_ENUM, "Invalid flanger float property {:#04x}", as_unsigned(param)); } void FlangerEffectHandler::GetParamfv(ALCcontext *context, const ChorusProps &props, ALenum param, float *vals) { GetParamf(context, props, param, vals); } #if ALSOFT_EAX namespace { struct EaxChorusTraits { using EaxProps = EAXCHORUSPROPERTIES; using Committer = EaxChorusCommitter; static constexpr auto efx_effect() { return AL_EFFECT_CHORUS; } static constexpr auto eax_none_param_id() { return EAXCHORUS_NONE; } static constexpr auto eax_allparameters_param_id() { return EAXCHORUS_ALLPARAMETERS; } static constexpr auto eax_waveform_param_id() { return EAXCHORUS_WAVEFORM; } static constexpr auto eax_phase_param_id() { return EAXCHORUS_PHASE; } static constexpr auto eax_rate_param_id() { return EAXCHORUS_RATE; } static constexpr auto eax_depth_param_id() { return EAXCHORUS_DEPTH; } static constexpr auto eax_feedback_param_id() { return EAXCHORUS_FEEDBACK; } static constexpr auto eax_delay_param_id() { return EAXCHORUS_DELAY; } static constexpr auto eax_min_waveform() { return EAXCHORUS_MINWAVEFORM; } static constexpr auto eax_min_phase() { return EAXCHORUS_MINPHASE; } static constexpr auto eax_min_rate() { return EAXCHORUS_MINRATE; } static constexpr auto eax_min_depth() { return EAXCHORUS_MINDEPTH; } static constexpr auto eax_min_feedback() { return EAXCHORUS_MINFEEDBACK; } static constexpr auto eax_min_delay() { return EAXCHORUS_MINDELAY; } static constexpr auto eax_max_waveform() { return EAXCHORUS_MAXWAVEFORM; } static constexpr auto eax_max_phase() { return EAXCHORUS_MAXPHASE; } static constexpr auto eax_max_rate() { return EAXCHORUS_MAXRATE; } static constexpr auto eax_max_depth() { return EAXCHORUS_MAXDEPTH; } static constexpr auto eax_max_feedback() { return EAXCHORUS_MAXFEEDBACK; } static constexpr auto eax_max_delay() { return EAXCHORUS_MAXDELAY; } static constexpr auto eax_default_waveform() { return EAXCHORUS_DEFAULTWAVEFORM; } static constexpr auto eax_default_phase() { return EAXCHORUS_DEFAULTPHASE; } static constexpr auto eax_default_rate() { return EAXCHORUS_DEFAULTRATE; } static constexpr auto eax_default_depth() { return EAXCHORUS_DEFAULTDEPTH; } static constexpr auto eax_default_feedback() { return EAXCHORUS_DEFAULTFEEDBACK; } static constexpr auto eax_default_delay() { return EAXCHORUS_DEFAULTDELAY; } static constexpr auto efx_min_waveform() { return AL_CHORUS_MIN_WAVEFORM; } static constexpr auto efx_min_phase() { return AL_CHORUS_MIN_PHASE; } static constexpr auto efx_min_rate() { return AL_CHORUS_MIN_RATE; } static constexpr auto efx_min_depth() { return AL_CHORUS_MIN_DEPTH; } static constexpr auto efx_min_feedback() { return AL_CHORUS_MIN_FEEDBACK; } static constexpr auto efx_min_delay() { return AL_CHORUS_MIN_DELAY; } static constexpr auto efx_max_waveform() { return AL_CHORUS_MAX_WAVEFORM; } static constexpr auto efx_max_phase() { return AL_CHORUS_MAX_PHASE; } static constexpr auto efx_max_rate() { return AL_CHORUS_MAX_RATE; } static constexpr auto efx_max_depth() { return AL_CHORUS_MAX_DEPTH; } static constexpr auto efx_max_feedback() { return AL_CHORUS_MAX_FEEDBACK; } static constexpr auto efx_max_delay() { return AL_CHORUS_MAX_DELAY; } static constexpr auto efx_default_waveform() { return AL_CHORUS_DEFAULT_WAVEFORM; } static constexpr auto efx_default_phase() { return AL_CHORUS_DEFAULT_PHASE; } static constexpr auto efx_default_rate() { return AL_CHORUS_DEFAULT_RATE; } static constexpr auto efx_default_depth() { return AL_CHORUS_DEFAULT_DEPTH; } static constexpr auto efx_default_feedback() { return AL_CHORUS_DEFAULT_FEEDBACK; } static constexpr auto efx_default_delay() { return AL_CHORUS_DEFAULT_DELAY; } static ChorusWaveform eax_waveform(unsigned long type) { if(type == EAX_CHORUS_SINUSOID) return ChorusWaveform::Sinusoid; if(type == EAX_CHORUS_TRIANGLE) return ChorusWaveform::Triangle; return ChorusWaveform::Sinusoid; } }; // EaxChorusTraits struct EaxFlangerTraits { using EaxProps = EAXFLANGERPROPERTIES; using Committer = EaxFlangerCommitter; static constexpr auto efx_effect() { return AL_EFFECT_FLANGER; } static constexpr auto eax_none_param_id() { return EAXFLANGER_NONE; } static constexpr auto eax_allparameters_param_id() { return EAXFLANGER_ALLPARAMETERS; } static constexpr auto eax_waveform_param_id() { return EAXFLANGER_WAVEFORM; } static constexpr auto eax_phase_param_id() { return EAXFLANGER_PHASE; } static constexpr auto eax_rate_param_id() { return EAXFLANGER_RATE; } static constexpr auto eax_depth_param_id() { return EAXFLANGER_DEPTH; } static constexpr auto eax_feedback_param_id() { return EAXFLANGER_FEEDBACK; } static constexpr auto eax_delay_param_id() { return EAXFLANGER_DELAY; } static constexpr auto eax_min_waveform() { return EAXFLANGER_MINWAVEFORM; } static constexpr auto eax_min_phase() { return EAXFLANGER_MINPHASE; } static constexpr auto eax_min_rate() { return EAXFLANGER_MINRATE; } static constexpr auto eax_min_depth() { return EAXFLANGER_MINDEPTH; } static constexpr auto eax_min_feedback() { return EAXFLANGER_MINFEEDBACK; } static constexpr auto eax_min_delay() { return EAXFLANGER_MINDELAY; } static constexpr auto eax_max_waveform() { return EAXFLANGER_MAXWAVEFORM; } static constexpr auto eax_max_phase() { return EAXFLANGER_MAXPHASE; } static constexpr auto eax_max_rate() { return EAXFLANGER_MAXRATE; } static constexpr auto eax_max_depth() { return EAXFLANGER_MAXDEPTH; } static constexpr auto eax_max_feedback() { return EAXFLANGER_MAXFEEDBACK; } static constexpr auto eax_max_delay() { return EAXFLANGER_MAXDELAY; } static constexpr auto eax_default_waveform() { return EAXFLANGER_DEFAULTWAVEFORM; } static constexpr auto eax_default_phase() { return EAXFLANGER_DEFAULTPHASE; } static constexpr auto eax_default_rate() { return EAXFLANGER_DEFAULTRATE; } static constexpr auto eax_default_depth() { return EAXFLANGER_DEFAULTDEPTH; } static constexpr auto eax_default_feedback() { return EAXFLANGER_DEFAULTFEEDBACK; } static constexpr auto eax_default_delay() { return EAXFLANGER_DEFAULTDELAY; } static constexpr auto efx_min_waveform() { return AL_FLANGER_MIN_WAVEFORM; } static constexpr auto efx_min_phase() { return AL_FLANGER_MIN_PHASE; } static constexpr auto efx_min_rate() { return AL_FLANGER_MIN_RATE; } static constexpr auto efx_min_depth() { return AL_FLANGER_MIN_DEPTH; } static constexpr auto efx_min_feedback() { return AL_FLANGER_MIN_FEEDBACK; } static constexpr auto efx_min_delay() { return AL_FLANGER_MIN_DELAY; } static constexpr auto efx_max_waveform() { return AL_FLANGER_MAX_WAVEFORM; } static constexpr auto efx_max_phase() { return AL_FLANGER_MAX_PHASE; } static constexpr auto efx_max_rate() { return AL_FLANGER_MAX_RATE; } static constexpr auto efx_max_depth() { return AL_FLANGER_MAX_DEPTH; } static constexpr auto efx_max_feedback() { return AL_FLANGER_MAX_FEEDBACK; } static constexpr auto efx_max_delay() { return AL_FLANGER_MAX_DELAY; } static constexpr auto efx_default_waveform() { return AL_FLANGER_DEFAULT_WAVEFORM; } static constexpr auto efx_default_phase() { return AL_FLANGER_DEFAULT_PHASE; } static constexpr auto efx_default_rate() { return AL_FLANGER_DEFAULT_RATE; } static constexpr auto efx_default_depth() { return AL_FLANGER_DEFAULT_DEPTH; } static constexpr auto efx_default_feedback() { return AL_FLANGER_DEFAULT_FEEDBACK; } static constexpr auto efx_default_delay() { return AL_FLANGER_DEFAULT_DELAY; } static ChorusWaveform eax_waveform(unsigned long type) { if(type == EAX_FLANGER_SINUSOID) return ChorusWaveform::Sinusoid; if(type == EAX_FLANGER_TRIANGLE) return ChorusWaveform::Triangle; return ChorusWaveform::Sinusoid; } }; // EaxFlangerTraits template struct ChorusFlangerEffect { using Traits = TTraits; using EaxProps = typename Traits::EaxProps; using Committer = typename Traits::Committer; using Exception = typename Committer::Exception; struct WaveformValidator { void operator()(unsigned long ulWaveform) const { eax_validate_range( "Waveform", ulWaveform, Traits::eax_min_waveform(), Traits::eax_max_waveform()); } }; // WaveformValidator struct PhaseValidator { void operator()(long lPhase) const { eax_validate_range( "Phase", lPhase, Traits::eax_min_phase(), Traits::eax_max_phase()); } }; // PhaseValidator struct RateValidator { void operator()(float flRate) const { eax_validate_range( "Rate", flRate, Traits::eax_min_rate(), Traits::eax_max_rate()); } }; // RateValidator struct DepthValidator { void operator()(float flDepth) const { eax_validate_range( "Depth", flDepth, Traits::eax_min_depth(), Traits::eax_max_depth()); } }; // DepthValidator struct FeedbackValidator { void operator()(float flFeedback) const { eax_validate_range( "Feedback", flFeedback, Traits::eax_min_feedback(), Traits::eax_max_feedback()); } }; // FeedbackValidator struct DelayValidator { void operator()(float flDelay) const { eax_validate_range( "Delay", flDelay, Traits::eax_min_delay(), Traits::eax_max_delay()); } }; // DelayValidator struct AllValidator { void operator()(const EaxProps& all) const { WaveformValidator{}(all.ulWaveform); PhaseValidator{}(all.lPhase); RateValidator{}(all.flRate); DepthValidator{}(all.flDepth); FeedbackValidator{}(all.flFeedback); DelayValidator{}(all.flDelay); } }; // AllValidator public: static void SetDefaults(EaxEffectProps &props) { auto&& all = props.emplace(); all.ulWaveform = Traits::eax_default_waveform(); all.lPhase = Traits::eax_default_phase(); all.flRate = Traits::eax_default_rate(); all.flDepth = Traits::eax_default_depth(); all.flFeedback = Traits::eax_default_feedback(); all.flDelay = Traits::eax_default_delay(); } static void Get(const EaxCall &call, const EaxProps &all) { switch(call.get_property_id()) { case Traits::eax_none_param_id(): break; case Traits::eax_allparameters_param_id(): call.template set_value(all); break; case Traits::eax_waveform_param_id(): call.template set_value(all.ulWaveform); break; case Traits::eax_phase_param_id(): call.template set_value(all.lPhase); break; case Traits::eax_rate_param_id(): call.template set_value(all.flRate); break; case Traits::eax_depth_param_id(): call.template set_value(all.flDepth); break; case Traits::eax_feedback_param_id(): call.template set_value(all.flFeedback); break; case Traits::eax_delay_param_id(): call.template set_value(all.flDelay); break; default: Committer::fail_unknown_property_id(); } } static void Set(const EaxCall &call, EaxProps &all) { switch(call.get_property_id()) { case Traits::eax_none_param_id(): break; case Traits::eax_allparameters_param_id(): Committer::template defer(call, all); break; case Traits::eax_waveform_param_id(): Committer::template defer(call, all.ulWaveform); break; case Traits::eax_phase_param_id(): Committer::template defer(call, all.lPhase); break; case Traits::eax_rate_param_id(): Committer::template defer(call, all.flRate); break; case Traits::eax_depth_param_id(): Committer::template defer(call, all.flDepth); break; case Traits::eax_feedback_param_id(): Committer::template defer(call, all.flFeedback); break; case Traits::eax_delay_param_id(): Committer::template defer(call, all.flDelay); break; default: Committer::fail_unknown_property_id(); } } static bool Commit(const EaxProps &props, EaxEffectProps &props_, ChorusProps &al_props_) { if(auto *cur = std::get_if(&props_); cur && *cur == props) return false; props_ = props; al_props_.Waveform = Traits::eax_waveform(props.ulWaveform); al_props_.Phase = static_cast(props.lPhase); al_props_.Rate = props.flRate; al_props_.Depth = props.flDepth; al_props_.Feedback = props.flFeedback; al_props_.Delay = props.flDelay; return true; } }; // EaxChorusFlangerEffect using ChorusCommitter = EaxCommitter; using FlangerCommitter = EaxCommitter; } // namespace template<> struct ChorusCommitter::Exception : public EaxException { explicit Exception(const char *message) : EaxException{"EAX_CHORUS_EFFECT", message} { } }; template<> [[noreturn]] void ChorusCommitter::fail(const char *message) { throw Exception{message}; } bool EaxChorusCommitter::commit(const EAXCHORUSPROPERTIES &props) { using Committer = ChorusFlangerEffect; return Committer::Commit(props, mEaxProps, mAlProps.emplace()); } void EaxChorusCommitter::SetDefaults(EaxEffectProps &props) { using Committer = ChorusFlangerEffect; Committer::SetDefaults(props); } void EaxChorusCommitter::Get(const EaxCall &call, const EAXCHORUSPROPERTIES &props) { using Committer = ChorusFlangerEffect; Committer::Get(call, props); } void EaxChorusCommitter::Set(const EaxCall &call, EAXCHORUSPROPERTIES &props) { using Committer = ChorusFlangerEffect; Committer::Set(call, props); } template<> struct FlangerCommitter::Exception : public EaxException { explicit Exception(const char *message) : EaxException{"EAX_FLANGER_EFFECT", message} { } }; template<> [[noreturn]] void FlangerCommitter::fail(const char *message) { throw Exception{message}; } bool EaxFlangerCommitter::commit(const EAXFLANGERPROPERTIES &props) { using Committer = ChorusFlangerEffect; return Committer::Commit(props, mEaxProps, mAlProps.emplace()); } void EaxFlangerCommitter::SetDefaults(EaxEffectProps &props) { using Committer = ChorusFlangerEffect; Committer::SetDefaults(props); } void EaxFlangerCommitter::Get(const EaxCall &call, const EAXFLANGERPROPERTIES &props) { using Committer = ChorusFlangerEffect; Committer::Get(call, props); } void EaxFlangerCommitter::Set(const EaxCall &call, EAXFLANGERPROPERTIES &props) { using Committer = ChorusFlangerEffect; Committer::Set(call, props); } #endif // ALSOFT_EAX openal-soft-1.24.2/al/effects/compressor.cpp000066400000000000000000000107701474041540300207510ustar00rootroot00000000000000 #include "config.h" #include "AL/al.h" #include "AL/efx.h" #include "alc/context.h" #include "alnumeric.h" #include "effects.h" #if ALSOFT_EAX #include "al/eax/effect.h" #include "al/eax/exception.h" #include "al/eax/utils.h" #endif // ALSOFT_EAX namespace { constexpr EffectProps genDefaultProps() noexcept { CompressorProps props{}; props.OnOff = AL_COMPRESSOR_DEFAULT_ONOFF; return props; } } // namespace const EffectProps CompressorEffectProps{genDefaultProps()}; void CompressorEffectHandler::SetParami(ALCcontext *context, CompressorProps &props, ALenum param, int val) { switch(param) { case AL_COMPRESSOR_ONOFF: if(!(val >= AL_COMPRESSOR_MIN_ONOFF && val <= AL_COMPRESSOR_MAX_ONOFF)) context->throw_error(AL_INVALID_VALUE, "Compressor state out of range"); props.OnOff = (val != AL_FALSE); return; } context->throw_error(AL_INVALID_ENUM, "Invalid compressor integer property {:#04x}", as_unsigned(param)); } void CompressorEffectHandler::SetParamiv(ALCcontext *context, CompressorProps &props, ALenum param, const int *vals) { SetParami(context, props, param, *vals); } void CompressorEffectHandler::SetParamf(ALCcontext *context, CompressorProps&, ALenum param, float) { context->throw_error(AL_INVALID_ENUM, "Invalid compressor float property {:#04x}", as_unsigned(param)); } void CompressorEffectHandler::SetParamfv(ALCcontext *context, CompressorProps&, ALenum param, const float*) { context->throw_error(AL_INVALID_ENUM, "Invalid compressor float-vector property {:#04x}", as_unsigned(param)); } void CompressorEffectHandler::GetParami(ALCcontext *context, const CompressorProps &props, ALenum param, int *val) { switch(param) { case AL_COMPRESSOR_ONOFF: *val = props.OnOff; return; } context->throw_error(AL_INVALID_ENUM, "Invalid compressor integer property {:#04x}", as_unsigned(param)); } void CompressorEffectHandler::GetParamiv(ALCcontext *context, const CompressorProps &props, ALenum param, int *vals) { GetParami(context, props, param, vals); } void CompressorEffectHandler::GetParamf(ALCcontext *context, const CompressorProps&, ALenum param, float*) { context->throw_error(AL_INVALID_ENUM, "Invalid compressor float property {:#04x}", as_unsigned(param)); } void CompressorEffectHandler::GetParamfv(ALCcontext *context, const CompressorProps&, ALenum param, float*) { context->throw_error(AL_INVALID_ENUM, "Invalid compressor float-vector property {:#04x}", as_unsigned(param)); } #if ALSOFT_EAX namespace { using CompressorCommitter = EaxCommitter; struct OnOffValidator { void operator()(unsigned long ulOnOff) const { eax_validate_range( "On-Off", ulOnOff, EAXAGCCOMPRESSOR_MINONOFF, EAXAGCCOMPRESSOR_MAXONOFF); } }; // OnOffValidator struct AllValidator { void operator()(const EAXAGCCOMPRESSORPROPERTIES& all) const { OnOffValidator{}(all.ulOnOff); } }; // AllValidator } // namespace template<> struct CompressorCommitter::Exception : public EaxException { explicit Exception(const char *message) : EaxException{"EAX_CHORUS_EFFECT", message} { } }; template<> [[noreturn]] void CompressorCommitter::fail(const char *message) { throw Exception{message}; } bool EaxCompressorCommitter::commit(const EAXAGCCOMPRESSORPROPERTIES &props) { if(auto *cur = std::get_if(&mEaxProps); cur && *cur == props) return false; mEaxProps = props; mAlProps = CompressorProps{props.ulOnOff != 0}; return true; } void EaxCompressorCommitter::SetDefaults(EaxEffectProps &props) { props = EAXAGCCOMPRESSORPROPERTIES{EAXAGCCOMPRESSOR_DEFAULTONOFF}; } void EaxCompressorCommitter::Get(const EaxCall &call, const EAXAGCCOMPRESSORPROPERTIES &props) { switch(call.get_property_id()) { case EAXAGCCOMPRESSOR_NONE: break; case EAXAGCCOMPRESSOR_ALLPARAMETERS: call.set_value(props); break; case EAXAGCCOMPRESSOR_ONOFF: call.set_value(props.ulOnOff); break; default: fail_unknown_property_id(); } } void EaxCompressorCommitter::Set(const EaxCall &call, EAXAGCCOMPRESSORPROPERTIES &props) { switch(call.get_property_id()) { case EAXAGCCOMPRESSOR_NONE: break; case EAXAGCCOMPRESSOR_ALLPARAMETERS: defer(call, props); break; case EAXAGCCOMPRESSOR_ONOFF: defer(call, props.ulOnOff); break; default: fail_unknown_property_id(); } } #endif // ALSOFT_EAX openal-soft-1.24.2/al/effects/convolution.cpp000066400000000000000000000056631474041540300211410ustar00rootroot00000000000000 #include "config.h" #include #include #include #include "AL/al.h" #include "alc/context.h" #include "alc/inprogext.h" #include "alnumeric.h" #include "alspan.h" #include "effects.h" namespace { constexpr EffectProps genDefaultProps() noexcept { ConvolutionProps props{}; props.OrientAt = {0.0f, 0.0f, -1.0f}; props.OrientUp = {0.0f, 1.0f, 0.0f}; return props; } } // namespace const EffectProps ConvolutionEffectProps{genDefaultProps()}; void ConvolutionEffectHandler::SetParami(ALCcontext *context, ConvolutionProps& /*props*/, ALenum param, int /*val*/) { context->throw_error(AL_INVALID_ENUM, "Invalid convolution effect integer property {:#04x}", as_unsigned(param)); } void ConvolutionEffectHandler::SetParamiv(ALCcontext *context, ConvolutionProps &props, ALenum param, const int *vals) { SetParami(context, props, param, *vals); } void ConvolutionEffectHandler::SetParamf(ALCcontext *context, ConvolutionProps& /*props*/, ALenum param, float /*val*/) { context->throw_error(AL_INVALID_ENUM, "Invalid convolution effect float property {:#04x}", as_unsigned(param)); } void ConvolutionEffectHandler::SetParamfv(ALCcontext *context, ConvolutionProps &props, ALenum param, const float *values) { static constexpr auto finite_checker = [](float val) -> bool { return std::isfinite(val); }; switch(param) { case AL_CONVOLUTION_ORIENTATION_SOFT: auto vals = al::span{values, 6_uz}; if(!std::all_of(vals.cbegin(), vals.cend(), finite_checker)) context->throw_error(AL_INVALID_VALUE, "Convolution orientation out of range", param); std::copy_n(vals.cbegin(), props.OrientAt.size(), props.OrientAt.begin()); std::copy_n(vals.cbegin()+3, props.OrientUp.size(), props.OrientUp.begin()); return; } SetParamf(context, props, param, *values); } void ConvolutionEffectHandler::GetParami(ALCcontext *context, const ConvolutionProps& /*props*/, ALenum param, int* /*val*/) { context->throw_error(AL_INVALID_ENUM, "Invalid convolution effect integer property {:#04x}", as_unsigned(param)); } void ConvolutionEffectHandler::GetParamiv(ALCcontext *context, const ConvolutionProps &props, ALenum param, int *vals) { GetParami(context, props, param, vals); } void ConvolutionEffectHandler::GetParamf(ALCcontext *context, const ConvolutionProps& /*props*/, ALenum param, float* /*val*/) { context->throw_error(AL_INVALID_ENUM, "Invalid convolution effect float property {:#04x}", as_unsigned(param)); } void ConvolutionEffectHandler::GetParamfv(ALCcontext *context, const ConvolutionProps &props, ALenum param, float *values) { switch(param) { case AL_CONVOLUTION_ORIENTATION_SOFT: auto vals = al::span{values, 6_uz}; std::copy(props.OrientAt.cbegin(), props.OrientAt.cend(), vals.begin()); std::copy(props.OrientUp.cbegin(), props.OrientUp.cend(), vals.begin()+3); return; } GetParamf(context, props, param, values); } openal-soft-1.24.2/al/effects/dedicated.cpp000066400000000000000000000110561474041540300204610ustar00rootroot00000000000000 #include "config.h" #include #include "AL/al.h" #include "AL/alext.h" #include "alc/context.h" #include "alnumeric.h" #include "effects.h" namespace { constexpr EffectProps genDefaultDialogProps() noexcept { DedicatedProps props{}; props.Target = DedicatedProps::Dialog; props.Gain = 1.0f; return props; } constexpr EffectProps genDefaultLfeProps() noexcept { DedicatedProps props{}; props.Target = DedicatedProps::Lfe; props.Gain = 1.0f; return props; } } // namespace const EffectProps DedicatedDialogEffectProps{genDefaultDialogProps()}; void DedicatedDialogEffectHandler::SetParami(ALCcontext *context, DedicatedProps&, ALenum param, int) { context->throw_error(AL_INVALID_ENUM, "Invalid dedicated integer property {:#04x}", as_unsigned(param)); } void DedicatedDialogEffectHandler::SetParamiv(ALCcontext *context, DedicatedProps&, ALenum param, const int*) { context->throw_error(AL_INVALID_ENUM, "Invalid dedicated integer-vector property {:#04x}", as_unsigned(param)); } void DedicatedDialogEffectHandler::SetParamf(ALCcontext *context, DedicatedProps &props, ALenum param, float val) { switch(param) { case AL_DEDICATED_GAIN: if(!(val >= 0.0f && std::isfinite(val))) context->throw_error(AL_INVALID_VALUE, "Dedicated gain out of range"); props.Gain = val; return; } context->throw_error(AL_INVALID_ENUM, "Invalid dedicated float property {:#04x}", as_unsigned(param)); } void DedicatedDialogEffectHandler::SetParamfv(ALCcontext *context, DedicatedProps &props, ALenum param, const float *vals) { SetParamf(context, props, param, *vals); } void DedicatedDialogEffectHandler::GetParami(ALCcontext *context, const DedicatedProps&, ALenum param, int*) { context->throw_error(AL_INVALID_ENUM, "Invalid dedicated integer property {:#04x}", as_unsigned(param)); } void DedicatedDialogEffectHandler::GetParamiv(ALCcontext *context, const DedicatedProps&, ALenum param, int*) { context->throw_error(AL_INVALID_ENUM, "Invalid dedicated integer-vector property {:#04x}", as_unsigned(param)); } void DedicatedDialogEffectHandler::GetParamf(ALCcontext *context, const DedicatedProps &props, ALenum param, float *val) { switch(param) { case AL_DEDICATED_GAIN: *val = props.Gain; return; } context->throw_error(AL_INVALID_ENUM, "Invalid dedicated float property {:#04x}", as_unsigned(param)); } void DedicatedDialogEffectHandler::GetParamfv(ALCcontext *context, const DedicatedProps &props, ALenum param, float *vals) { GetParamf(context, props, param, vals); } const EffectProps DedicatedLfeEffectProps{genDefaultLfeProps()}; void DedicatedLfeEffectHandler::SetParami(ALCcontext *context, DedicatedProps&, ALenum param, int) { context->throw_error(AL_INVALID_ENUM, "Invalid dedicated integer property {:#04x}", as_unsigned(param)); } void DedicatedLfeEffectHandler::SetParamiv(ALCcontext *context, DedicatedProps&, ALenum param, const int*) { context->throw_error(AL_INVALID_ENUM, "Invalid dedicated integer-vector property {:#04x}", as_unsigned(param)); } void DedicatedLfeEffectHandler::SetParamf(ALCcontext *context, DedicatedProps &props, ALenum param, float val) { switch(param) { case AL_DEDICATED_GAIN: if(!(val >= 0.0f && std::isfinite(val))) context->throw_error(AL_INVALID_VALUE, "Dedicated gain out of range"); props.Gain = val; return; } context->throw_error(AL_INVALID_ENUM, "Invalid dedicated float property {:#04x}", as_unsigned(param)); } void DedicatedLfeEffectHandler::SetParamfv(ALCcontext *context, DedicatedProps &props, ALenum param, const float *vals) { SetParamf(context, props, param, *vals); } void DedicatedLfeEffectHandler::GetParami(ALCcontext *context, const DedicatedProps&, ALenum param, int*) { context->throw_error(AL_INVALID_ENUM, "Invalid dedicated integer property {:#04x}", as_unsigned(param)); } void DedicatedLfeEffectHandler::GetParamiv(ALCcontext *context, const DedicatedProps&, ALenum param, int*) { context->throw_error(AL_INVALID_ENUM, "Invalid dedicated integer-vector property {:#04x}", as_unsigned(param)); } void DedicatedLfeEffectHandler::GetParamf(ALCcontext *context, const DedicatedProps &props, ALenum param, float *val) { switch(param) { case AL_DEDICATED_GAIN: *val = props.Gain; return; } context->throw_error(AL_INVALID_ENUM, "Invalid dedicated float property {:#04x}", as_unsigned(param)); } void DedicatedLfeEffectHandler::GetParamfv(ALCcontext *context, const DedicatedProps &props, ALenum param, float *vals) { GetParamf(context, props, param, vals); } openal-soft-1.24.2/al/effects/distortion.cpp000066400000000000000000000211311474041540300207440ustar00rootroot00000000000000 #include "config.h" #include "AL/al.h" #include "AL/efx.h" #include "alc/context.h" #include "alnumeric.h" #include "effects.h" #if ALSOFT_EAX #include "al/eax/effect.h" #include "al/eax/exception.h" #include "al/eax/utils.h" #endif // ALSOFT_EAX namespace { constexpr EffectProps genDefaultProps() noexcept { DistortionProps props{}; props.Edge = AL_DISTORTION_DEFAULT_EDGE; props.Gain = AL_DISTORTION_DEFAULT_GAIN; props.LowpassCutoff = AL_DISTORTION_DEFAULT_LOWPASS_CUTOFF; props.EQCenter = AL_DISTORTION_DEFAULT_EQCENTER; props.EQBandwidth = AL_DISTORTION_DEFAULT_EQBANDWIDTH; return props; } } // namespace const EffectProps DistortionEffectProps{genDefaultProps()}; void DistortionEffectHandler::SetParami(ALCcontext *context, DistortionProps&, ALenum param, int) { context->throw_error(AL_INVALID_ENUM, "Invalid distortion integer property {:#04x}", as_unsigned(param)); } void DistortionEffectHandler::SetParamiv(ALCcontext *context, DistortionProps&, ALenum param, const int*) { context->throw_error(AL_INVALID_ENUM, "Invalid distortion integer-vector property {:#04x}", as_unsigned(param)); } void DistortionEffectHandler::SetParamf(ALCcontext *context, DistortionProps &props, ALenum param, float val) { switch(param) { case AL_DISTORTION_EDGE: if(!(val >= AL_DISTORTION_MIN_EDGE && val <= AL_DISTORTION_MAX_EDGE)) context->throw_error(AL_INVALID_VALUE, "Distortion edge out of range"); props.Edge = val; return; case AL_DISTORTION_GAIN: if(!(val >= AL_DISTORTION_MIN_GAIN && val <= AL_DISTORTION_MAX_GAIN)) context->throw_error(AL_INVALID_VALUE, "Distortion gain out of range"); props.Gain = val; return; case AL_DISTORTION_LOWPASS_CUTOFF: if(!(val >= AL_DISTORTION_MIN_LOWPASS_CUTOFF && val <= AL_DISTORTION_MAX_LOWPASS_CUTOFF)) context->throw_error(AL_INVALID_VALUE, "Distortion low-pass cutoff out of range"); props.LowpassCutoff = val; return; case AL_DISTORTION_EQCENTER: if(!(val >= AL_DISTORTION_MIN_EQCENTER && val <= AL_DISTORTION_MAX_EQCENTER)) context->throw_error(AL_INVALID_VALUE, "Distortion EQ center out of range"); props.EQCenter = val; return; case AL_DISTORTION_EQBANDWIDTH: if(!(val >= AL_DISTORTION_MIN_EQBANDWIDTH && val <= AL_DISTORTION_MAX_EQBANDWIDTH)) context->throw_error(AL_INVALID_VALUE, "Distortion EQ bandwidth out of range"); props.EQBandwidth = val; return; } context->throw_error(AL_INVALID_ENUM, "Invalid distortion float property {:#04x}", as_unsigned(param)); } void DistortionEffectHandler::SetParamfv(ALCcontext *context, DistortionProps &props, ALenum param, const float *vals) { SetParamf(context, props, param, *vals); } void DistortionEffectHandler::GetParami(ALCcontext *context, const DistortionProps&, ALenum param, int*) { context->throw_error(AL_INVALID_ENUM, "Invalid distortion integer property {:#04x}", as_unsigned(param)); } void DistortionEffectHandler::GetParamiv(ALCcontext *context, const DistortionProps&, ALenum param, int*) { context->throw_error(AL_INVALID_ENUM, "Invalid distortion integer-vector property {:#04x}", as_unsigned(param)); } void DistortionEffectHandler::GetParamf(ALCcontext *context, const DistortionProps &props, ALenum param, float *val) { switch(param) { case AL_DISTORTION_EDGE: *val = props.Edge; return; case AL_DISTORTION_GAIN: *val = props.Gain; return; case AL_DISTORTION_LOWPASS_CUTOFF: *val = props.LowpassCutoff; return; case AL_DISTORTION_EQCENTER: *val = props.EQCenter; return; case AL_DISTORTION_EQBANDWIDTH: *val = props.EQBandwidth; return; } context->throw_error(AL_INVALID_ENUM, "Invalid distortion float property {:#04x}", as_unsigned(param)); } void DistortionEffectHandler::GetParamfv(ALCcontext *context, const DistortionProps &props, ALenum param, float *vals) { GetParamf(context, props, param, vals); } #if ALSOFT_EAX namespace { using DistortionCommitter = EaxCommitter; struct EdgeValidator { void operator()(float flEdge) const { eax_validate_range( "Edge", flEdge, EAXDISTORTION_MINEDGE, EAXDISTORTION_MAXEDGE); } }; // EdgeValidator struct GainValidator { void operator()(long lGain) const { eax_validate_range( "Gain", lGain, EAXDISTORTION_MINGAIN, EAXDISTORTION_MAXGAIN); } }; // GainValidator struct LowPassCutOffValidator { void operator()(float flLowPassCutOff) const { eax_validate_range( "Low-pass Cut-off", flLowPassCutOff, EAXDISTORTION_MINLOWPASSCUTOFF, EAXDISTORTION_MAXLOWPASSCUTOFF); } }; // LowPassCutOffValidator struct EqCenterValidator { void operator()(float flEQCenter) const { eax_validate_range( "EQ Center", flEQCenter, EAXDISTORTION_MINEQCENTER, EAXDISTORTION_MAXEQCENTER); } }; // EqCenterValidator struct EqBandwidthValidator { void operator()(float flEQBandwidth) const { eax_validate_range( "EQ Bandwidth", flEQBandwidth, EAXDISTORTION_MINEQBANDWIDTH, EAXDISTORTION_MAXEQBANDWIDTH); } }; // EqBandwidthValidator struct AllValidator { void operator()(const EAXDISTORTIONPROPERTIES& all) const { EdgeValidator{}(all.flEdge); GainValidator{}(all.lGain); LowPassCutOffValidator{}(all.flLowPassCutOff); EqCenterValidator{}(all.flEQCenter); EqBandwidthValidator{}(all.flEQBandwidth); } }; // AllValidator } // namespace template<> struct DistortionCommitter::Exception : public EaxException { explicit Exception(const char *message) : EaxException{"EAX_DISTORTION_EFFECT", message} { } }; template<> [[noreturn]] void DistortionCommitter::fail(const char *message) { throw Exception{message}; } bool EaxDistortionCommitter::commit(const EAXDISTORTIONPROPERTIES &props) { if(auto *cur = std::get_if(&mEaxProps); cur && *cur == props) return false; mEaxProps = props; mAlProps = [&]{ DistortionProps ret{}; ret.Edge = props.flEdge; ret.Gain = level_mb_to_gain(static_cast(props.lGain)); ret.LowpassCutoff = props.flLowPassCutOff; ret.EQCenter = props.flEQCenter; ret.EQBandwidth = props.flEdge; return ret; }(); return true; } void EaxDistortionCommitter::SetDefaults(EaxEffectProps &props) { static constexpr EAXDISTORTIONPROPERTIES defprops{[] { EAXDISTORTIONPROPERTIES ret{}; ret.flEdge = EAXDISTORTION_DEFAULTEDGE; ret.lGain = EAXDISTORTION_DEFAULTGAIN; ret.flLowPassCutOff = EAXDISTORTION_DEFAULTLOWPASSCUTOFF; ret.flEQCenter = EAXDISTORTION_DEFAULTEQCENTER; ret.flEQBandwidth = EAXDISTORTION_DEFAULTEQBANDWIDTH; return ret; }()}; props = defprops; } void EaxDistortionCommitter::Get(const EaxCall &call, const EAXDISTORTIONPROPERTIES &props) { switch(call.get_property_id()) { case EAXDISTORTION_NONE: break; case EAXDISTORTION_ALLPARAMETERS: call.set_value(props); break; case EAXDISTORTION_EDGE: call.set_value(props.flEdge); break; case EAXDISTORTION_GAIN: call.set_value(props.lGain); break; case EAXDISTORTION_LOWPASSCUTOFF: call.set_value(props.flLowPassCutOff); break; case EAXDISTORTION_EQCENTER: call.set_value(props.flEQCenter); break; case EAXDISTORTION_EQBANDWIDTH: call.set_value(props.flEQBandwidth); break; default: fail_unknown_property_id(); } } void EaxDistortionCommitter::Set(const EaxCall &call, EAXDISTORTIONPROPERTIES &props) { switch(call.get_property_id()) { case EAXDISTORTION_NONE: break; case EAXDISTORTION_ALLPARAMETERS: defer(call, props); break; case EAXDISTORTION_EDGE: defer(call, props.flEdge); break; case EAXDISTORTION_GAIN: defer(call, props.lGain); break; case EAXDISTORTION_LOWPASSCUTOFF: defer(call, props.flLowPassCutOff); break; case EAXDISTORTION_EQCENTER: defer(call, props.flEQCenter); break; case EAXDISTORTION_EQBANDWIDTH: defer(call, props.flEQBandwidth); break; default: fail_unknown_property_id(); } } #endif // ALSOFT_EAX openal-soft-1.24.2/al/effects/echo.cpp000066400000000000000000000176641474041540300175040ustar00rootroot00000000000000 #include "config.h" #include "AL/al.h" #include "AL/efx.h" #include "alc/context.h" #include "alnumeric.h" #include "effects.h" #if ALSOFT_EAX #include "al/eax/effect.h" #include "al/eax/exception.h" #include "al/eax/utils.h" #endif // ALSOFT_EAX namespace { static_assert(EchoMaxDelay >= AL_ECHO_MAX_DELAY, "Echo max delay too short"); static_assert(EchoMaxLRDelay >= AL_ECHO_MAX_LRDELAY, "Echo max left-right delay too short"); constexpr EffectProps genDefaultProps() noexcept { EchoProps props{}; props.Delay = AL_ECHO_DEFAULT_DELAY; props.LRDelay = AL_ECHO_DEFAULT_LRDELAY; props.Damping = AL_ECHO_DEFAULT_DAMPING; props.Feedback = AL_ECHO_DEFAULT_FEEDBACK; props.Spread = AL_ECHO_DEFAULT_SPREAD; return props; } } // namespace const EffectProps EchoEffectProps{genDefaultProps()}; void EchoEffectHandler::SetParami(ALCcontext *context, EchoProps&, ALenum param, int) { context->throw_error(AL_INVALID_ENUM, "Invalid echo integer property {:#04x}", as_unsigned(param)); } void EchoEffectHandler::SetParamiv(ALCcontext *context, EchoProps&, ALenum param, const int*) { context->throw_error(AL_INVALID_ENUM, "Invalid echo integer-vector property {:#04x}", as_unsigned(param)); } void EchoEffectHandler::SetParamf(ALCcontext *context, EchoProps &props, ALenum param, float val) { switch(param) { case AL_ECHO_DELAY: if(!(val >= AL_ECHO_MIN_DELAY && val <= AL_ECHO_MAX_DELAY)) context->throw_error(AL_INVALID_VALUE, "Echo delay out of range"); props.Delay = val; return; case AL_ECHO_LRDELAY: if(!(val >= AL_ECHO_MIN_LRDELAY && val <= AL_ECHO_MAX_LRDELAY)) context->throw_error(AL_INVALID_VALUE, "Echo LR delay out of range"); props.LRDelay = val; return; case AL_ECHO_DAMPING: if(!(val >= AL_ECHO_MIN_DAMPING && val <= AL_ECHO_MAX_DAMPING)) context->throw_error(AL_INVALID_VALUE, "Echo damping out of range"); props.Damping = val; return; case AL_ECHO_FEEDBACK: if(!(val >= AL_ECHO_MIN_FEEDBACK && val <= AL_ECHO_MAX_FEEDBACK)) context->throw_error(AL_INVALID_VALUE, "Echo feedback out of range"); props.Feedback = val; return; case AL_ECHO_SPREAD: if(!(val >= AL_ECHO_MIN_SPREAD && val <= AL_ECHO_MAX_SPREAD)) context->throw_error(AL_INVALID_VALUE, "Echo spread out of range"); props.Spread = val; return; } context->throw_error(AL_INVALID_ENUM, "Invalid echo float property {:#04x}", as_unsigned(param)); } void EchoEffectHandler::SetParamfv(ALCcontext *context, EchoProps &props, ALenum param, const float *vals) { SetParamf(context, props, param, *vals); } void EchoEffectHandler::GetParami(ALCcontext *context, const EchoProps&, ALenum param, int*) { context->throw_error(AL_INVALID_ENUM, "Invalid echo integer property {:#04x}", as_unsigned(param)); } void EchoEffectHandler::GetParamiv(ALCcontext *context, const EchoProps&, ALenum param, int*) { context->throw_error(AL_INVALID_ENUM, "Invalid echo integer-vector property {:#04x}", as_unsigned(param)); } void EchoEffectHandler::GetParamf(ALCcontext *context, const EchoProps &props, ALenum param, float *val) { switch(param) { case AL_ECHO_DELAY: *val = props.Delay; return; case AL_ECHO_LRDELAY: *val = props.LRDelay; return; case AL_ECHO_DAMPING: *val = props.Damping; return; case AL_ECHO_FEEDBACK: *val = props.Feedback; return; case AL_ECHO_SPREAD: *val = props.Spread; return; } context->throw_error(AL_INVALID_ENUM, "Invalid echo float property {:#04x}", as_unsigned(param)); } void EchoEffectHandler::GetParamfv(ALCcontext *context, const EchoProps &props, ALenum param, float *vals) { GetParamf(context, props, param, vals); } #if ALSOFT_EAX namespace { using EchoCommitter = EaxCommitter; struct DelayValidator { void operator()(float flDelay) const { eax_validate_range( "Delay", flDelay, EAXECHO_MINDELAY, EAXECHO_MAXDELAY); } }; // DelayValidator struct LrDelayValidator { void operator()(float flLRDelay) const { eax_validate_range( "LR Delay", flLRDelay, EAXECHO_MINLRDELAY, EAXECHO_MAXLRDELAY); } }; // LrDelayValidator struct DampingValidator { void operator()(float flDamping) const { eax_validate_range( "Damping", flDamping, EAXECHO_MINDAMPING, EAXECHO_MAXDAMPING); } }; // DampingValidator struct FeedbackValidator { void operator()(float flFeedback) const { eax_validate_range( "Feedback", flFeedback, EAXECHO_MINFEEDBACK, EAXECHO_MAXFEEDBACK); } }; // FeedbackValidator struct SpreadValidator { void operator()(float flSpread) const { eax_validate_range( "Spread", flSpread, EAXECHO_MINSPREAD, EAXECHO_MAXSPREAD); } }; // SpreadValidator struct AllValidator { void operator()(const EAXECHOPROPERTIES& all) const { DelayValidator{}(all.flDelay); LrDelayValidator{}(all.flLRDelay); DampingValidator{}(all.flDamping); FeedbackValidator{}(all.flFeedback); SpreadValidator{}(all.flSpread); } }; // AllValidator } // namespace template<> struct EchoCommitter::Exception : public EaxException { explicit Exception(const char* message) : EaxException{"EAX_ECHO_EFFECT", message} { } }; template<> [[noreturn]] void EchoCommitter::fail(const char *message) { throw Exception{message}; } bool EaxEchoCommitter::commit(const EAXECHOPROPERTIES &props) { if(auto *cur = std::get_if(&mEaxProps); cur && *cur == props) return false; mEaxProps = props; mAlProps = [&]{ EchoProps ret{}; ret.Delay = props.flDelay; ret.LRDelay = props.flLRDelay; ret.Damping = props.flDamping; ret.Feedback = props.flFeedback; ret.Spread = props.flSpread; return ret; }(); return true; } void EaxEchoCommitter::SetDefaults(EaxEffectProps &props) { static constexpr EAXECHOPROPERTIES defprops{[] { EAXECHOPROPERTIES ret{}; ret.flDelay = EAXECHO_DEFAULTDELAY; ret.flLRDelay = EAXECHO_DEFAULTLRDELAY; ret.flDamping = EAXECHO_DEFAULTDAMPING; ret.flFeedback = EAXECHO_DEFAULTFEEDBACK; ret.flSpread = EAXECHO_DEFAULTSPREAD; return ret; }()}; props = defprops; } void EaxEchoCommitter::Get(const EaxCall &call, const EAXECHOPROPERTIES &props) { switch(call.get_property_id()) { case EAXECHO_NONE: break; case EAXECHO_ALLPARAMETERS: call.set_value(props); break; case EAXECHO_DELAY: call.set_value(props.flDelay); break; case EAXECHO_LRDELAY: call.set_value(props.flLRDelay); break; case EAXECHO_DAMPING: call.set_value(props.flDamping); break; case EAXECHO_FEEDBACK: call.set_value(props.flFeedback); break; case EAXECHO_SPREAD: call.set_value(props.flSpread); break; default: fail_unknown_property_id(); } } void EaxEchoCommitter::Set(const EaxCall &call, EAXECHOPROPERTIES &props) { switch(call.get_property_id()) { case EAXECHO_NONE: break; case EAXECHO_ALLPARAMETERS: defer(call, props); break; case EAXECHO_DELAY: defer(call, props.flDelay); break; case EAXECHO_LRDELAY: defer(call, props.flLRDelay); break; case EAXECHO_DAMPING: defer(call, props.flDamping); break; case EAXECHO_FEEDBACK: defer(call, props.flFeedback); break; case EAXECHO_SPREAD: defer(call, props.flSpread); break; default: fail_unknown_property_id(); } } #endif // ALSOFT_EAX openal-soft-1.24.2/al/effects/effects.cpp000066400000000000000000000000521474041540300201640ustar00rootroot00000000000000#include "config.h" #include "effects.h" openal-soft-1.24.2/al/effects/effects.h000066400000000000000000000063221474041540300176370ustar00rootroot00000000000000#ifndef AL_EFFECTS_EFFECTS_H #define AL_EFFECTS_EFFECTS_H #include #include "AL/alc.h" #include "AL/al.h" #include "core/effects/base.h" #include "opthelpers.h" #define DECL_HANDLER(N, T) \ struct N { \ using prop_type = T; \ \ static void SetParami(ALCcontext *context, prop_type &props, ALenum param, int val); \ static void SetParamiv(ALCcontext *context, prop_type &props, ALenum param, const int *vals); \ static void SetParamf(ALCcontext *context, prop_type &props, ALenum param, float val); \ static void SetParamfv(ALCcontext *context, prop_type &props, ALenum param, const float *vals);\ static void GetParami(ALCcontext *context, const prop_type &props, ALenum param, int *val); \ static void GetParamiv(ALCcontext *context, const prop_type &props, ALenum param, int *vals); \ static void GetParamf(ALCcontext *context, const prop_type &props, ALenum param, float *val); \ static void GetParamfv(ALCcontext *context, const prop_type &props, ALenum param, float *vals);\ }; DECL_HANDLER(NullEffectHandler, std::monostate) DECL_HANDLER(ReverbEffectHandler, ReverbProps) DECL_HANDLER(StdReverbEffectHandler, ReverbProps) DECL_HANDLER(AutowahEffectHandler, AutowahProps) DECL_HANDLER(ChorusEffectHandler, ChorusProps) DECL_HANDLER(CompressorEffectHandler, CompressorProps) DECL_HANDLER(DistortionEffectHandler, DistortionProps) DECL_HANDLER(EchoEffectHandler, EchoProps) DECL_HANDLER(EqualizerEffectHandler, EqualizerProps) DECL_HANDLER(FlangerEffectHandler, ChorusProps) DECL_HANDLER(FshifterEffectHandler, FshifterProps) DECL_HANDLER(ModulatorEffectHandler, ModulatorProps) DECL_HANDLER(PshifterEffectHandler, PshifterProps) DECL_HANDLER(VmorpherEffectHandler, VmorpherProps) DECL_HANDLER(DedicatedDialogEffectHandler, DedicatedProps) DECL_HANDLER(DedicatedLfeEffectHandler, DedicatedProps) DECL_HANDLER(ConvolutionEffectHandler, ConvolutionProps) #undef DECL_HANDLER /* Default properties for the given effect types. */ DECL_HIDDEN extern const EffectProps NullEffectProps; DECL_HIDDEN extern const EffectProps ReverbEffectProps; DECL_HIDDEN extern const EffectProps StdReverbEffectProps; DECL_HIDDEN extern const EffectProps AutowahEffectProps; DECL_HIDDEN extern const EffectProps ChorusEffectProps; DECL_HIDDEN extern const EffectProps CompressorEffectProps; DECL_HIDDEN extern const EffectProps DistortionEffectProps; DECL_HIDDEN extern const EffectProps EchoEffectProps; DECL_HIDDEN extern const EffectProps EqualizerEffectProps; DECL_HIDDEN extern const EffectProps FlangerEffectProps; DECL_HIDDEN extern const EffectProps FshifterEffectProps; DECL_HIDDEN extern const EffectProps ModulatorEffectProps; DECL_HIDDEN extern const EffectProps PshifterEffectProps; DECL_HIDDEN extern const EffectProps VmorpherEffectProps; DECL_HIDDEN extern const EffectProps DedicatedDialogEffectProps; DECL_HIDDEN extern const EffectProps DedicatedLfeEffectProps; DECL_HIDDEN extern const EffectProps ConvolutionEffectProps; #endif /* AL_EFFECTS_EFFECTS_H */ openal-soft-1.24.2/al/effects/equalizer.cpp000066400000000000000000000332101474041540300205500ustar00rootroot00000000000000 #include "config.h" #include "AL/al.h" #include "AL/efx.h" #include "alc/context.h" #include "alnumeric.h" #include "effects.h" #if ALSOFT_EAX #include "al/eax/effect.h" #include "al/eax/exception.h" #include "al/eax/utils.h" #endif // ALSOFT_EAX namespace { constexpr EffectProps genDefaultProps() noexcept { EqualizerProps props{}; props.LowCutoff = AL_EQUALIZER_DEFAULT_LOW_CUTOFF; props.LowGain = AL_EQUALIZER_DEFAULT_LOW_GAIN; props.Mid1Center = AL_EQUALIZER_DEFAULT_MID1_CENTER; props.Mid1Gain = AL_EQUALIZER_DEFAULT_MID1_GAIN; props.Mid1Width = AL_EQUALIZER_DEFAULT_MID1_WIDTH; props.Mid2Center = AL_EQUALIZER_DEFAULT_MID2_CENTER; props.Mid2Gain = AL_EQUALIZER_DEFAULT_MID2_GAIN; props.Mid2Width = AL_EQUALIZER_DEFAULT_MID2_WIDTH; props.HighCutoff = AL_EQUALIZER_DEFAULT_HIGH_CUTOFF; props.HighGain = AL_EQUALIZER_DEFAULT_HIGH_GAIN; return props; } } // namespace const EffectProps EqualizerEffectProps{genDefaultProps()}; void EqualizerEffectHandler::SetParami(ALCcontext *context, EqualizerProps&, ALenum param, int) { context->throw_error(AL_INVALID_ENUM, "Invalid equalizer integer property {:#04x}", as_unsigned(param)); } void EqualizerEffectHandler::SetParamiv(ALCcontext *context, EqualizerProps&, ALenum param, const int*) { context->throw_error(AL_INVALID_ENUM, "Invalid equalizer integer-vector property {:#04x}", as_unsigned(param)); } void EqualizerEffectHandler::SetParamf(ALCcontext *context, EqualizerProps &props, ALenum param, float val) { switch(param) { case AL_EQUALIZER_LOW_GAIN: if(!(val >= AL_EQUALIZER_MIN_LOW_GAIN && val <= AL_EQUALIZER_MAX_LOW_GAIN)) context->throw_error(AL_INVALID_VALUE, "Equalizer low-band gain out of range"); props.LowGain = val; return; case AL_EQUALIZER_LOW_CUTOFF: if(!(val >= AL_EQUALIZER_MIN_LOW_CUTOFF && val <= AL_EQUALIZER_MAX_LOW_CUTOFF)) context->throw_error(AL_INVALID_VALUE, "Equalizer low-band cutoff out of range"); props.LowCutoff = val; return; case AL_EQUALIZER_MID1_GAIN: if(!(val >= AL_EQUALIZER_MIN_MID1_GAIN && val <= AL_EQUALIZER_MAX_MID1_GAIN)) context->throw_error(AL_INVALID_VALUE, "Equalizer mid1-band gain out of range"); props.Mid1Gain = val; return; case AL_EQUALIZER_MID1_CENTER: if(!(val >= AL_EQUALIZER_MIN_MID1_CENTER && val <= AL_EQUALIZER_MAX_MID1_CENTER)) context->throw_error(AL_INVALID_VALUE, "Equalizer mid1-band center out of range"); props.Mid1Center = val; return; case AL_EQUALIZER_MID1_WIDTH: if(!(val >= AL_EQUALIZER_MIN_MID1_WIDTH && val <= AL_EQUALIZER_MAX_MID1_WIDTH)) context->throw_error(AL_INVALID_VALUE, "Equalizer mid1-band width out of range"); props.Mid1Width = val; return; case AL_EQUALIZER_MID2_GAIN: if(!(val >= AL_EQUALIZER_MIN_MID2_GAIN && val <= AL_EQUALIZER_MAX_MID2_GAIN)) context->throw_error(AL_INVALID_VALUE, "Equalizer mid2-band gain out of range"); props.Mid2Gain = val; return; case AL_EQUALIZER_MID2_CENTER: if(!(val >= AL_EQUALIZER_MIN_MID2_CENTER && val <= AL_EQUALIZER_MAX_MID2_CENTER)) context->throw_error(AL_INVALID_VALUE, "Equalizer mid2-band center out of range"); props.Mid2Center = val; return; case AL_EQUALIZER_MID2_WIDTH: if(!(val >= AL_EQUALIZER_MIN_MID2_WIDTH && val <= AL_EQUALIZER_MAX_MID2_WIDTH)) context->throw_error(AL_INVALID_VALUE, "Equalizer mid2-band width out of range"); props.Mid2Width = val; return; case AL_EQUALIZER_HIGH_GAIN: if(!(val >= AL_EQUALIZER_MIN_HIGH_GAIN && val <= AL_EQUALIZER_MAX_HIGH_GAIN)) context->throw_error(AL_INVALID_VALUE, "Equalizer high-band gain out of range"); props.HighGain = val; return; case AL_EQUALIZER_HIGH_CUTOFF: if(!(val >= AL_EQUALIZER_MIN_HIGH_CUTOFF && val <= AL_EQUALIZER_MAX_HIGH_CUTOFF)) context->throw_error(AL_INVALID_VALUE, "Equalizer high-band cutoff out of range"); props.HighCutoff = val; return; } context->throw_error(AL_INVALID_ENUM, "Invalid equalizer float property {:#04x}", as_unsigned(param)); } void EqualizerEffectHandler::SetParamfv(ALCcontext *context, EqualizerProps &props, ALenum param, const float *vals) { SetParamf(context, props, param, *vals); } void EqualizerEffectHandler::GetParami(ALCcontext *context, const EqualizerProps&, ALenum param, int*) { context->throw_error(AL_INVALID_ENUM, "Invalid equalizer integer property {:#04x}", as_unsigned(param)); } void EqualizerEffectHandler::GetParamiv(ALCcontext *context, const EqualizerProps&, ALenum param, int*) { context->throw_error(AL_INVALID_ENUM, "Invalid equalizer integer-vector property {:#04x}", as_unsigned(param)); } void EqualizerEffectHandler::GetParamf(ALCcontext *context, const EqualizerProps &props, ALenum param, float *val) { switch(param) { case AL_EQUALIZER_LOW_GAIN: *val = props.LowGain; return; case AL_EQUALIZER_LOW_CUTOFF: *val = props.LowCutoff; return; case AL_EQUALIZER_MID1_GAIN: *val = props.Mid1Gain; return; case AL_EQUALIZER_MID1_CENTER: *val = props.Mid1Center; return; case AL_EQUALIZER_MID1_WIDTH: *val = props.Mid1Width; return; case AL_EQUALIZER_MID2_GAIN: *val = props.Mid2Gain; return; case AL_EQUALIZER_MID2_CENTER: *val = props.Mid2Center; return; case AL_EQUALIZER_MID2_WIDTH: *val = props.Mid2Width; return; case AL_EQUALIZER_HIGH_GAIN: *val = props.HighGain; return; case AL_EQUALIZER_HIGH_CUTOFF: *val = props.HighCutoff; return; } context->throw_error(AL_INVALID_ENUM, "Invalid equalizer float property {:#04x}", as_unsigned(param)); } void EqualizerEffectHandler::GetParamfv(ALCcontext *context, const EqualizerProps &props, ALenum param, float *vals) { GetParamf(context, props, param, vals); } #if ALSOFT_EAX namespace { using EqualizerCommitter = EaxCommitter; struct LowGainValidator { void operator()(long lLowGain) const { eax_validate_range( "Low Gain", lLowGain, EAXEQUALIZER_MINLOWGAIN, EAXEQUALIZER_MAXLOWGAIN); } }; // LowGainValidator struct LowCutOffValidator { void operator()(float flLowCutOff) const { eax_validate_range( "Low Cutoff", flLowCutOff, EAXEQUALIZER_MINLOWCUTOFF, EAXEQUALIZER_MAXLOWCUTOFF); } }; // LowCutOffValidator struct Mid1GainValidator { void operator()(long lMid1Gain) const { eax_validate_range( "Mid1 Gain", lMid1Gain, EAXEQUALIZER_MINMID1GAIN, EAXEQUALIZER_MAXMID1GAIN); } }; // Mid1GainValidator struct Mid1CenterValidator { void operator()(float flMid1Center) const { eax_validate_range( "Mid1 Center", flMid1Center, EAXEQUALIZER_MINMID1CENTER, EAXEQUALIZER_MAXMID1CENTER); } }; // Mid1CenterValidator struct Mid1WidthValidator { void operator()(float flMid1Width) const { eax_validate_range( "Mid1 Width", flMid1Width, EAXEQUALIZER_MINMID1WIDTH, EAXEQUALIZER_MAXMID1WIDTH); } }; // Mid1WidthValidator struct Mid2GainValidator { void operator()(long lMid2Gain) const { eax_validate_range( "Mid2 Gain", lMid2Gain, EAXEQUALIZER_MINMID2GAIN, EAXEQUALIZER_MAXMID2GAIN); } }; // Mid2GainValidator struct Mid2CenterValidator { void operator()(float flMid2Center) const { eax_validate_range( "Mid2 Center", flMid2Center, EAXEQUALIZER_MINMID2CENTER, EAXEQUALIZER_MAXMID2CENTER); } }; // Mid2CenterValidator struct Mid2WidthValidator { void operator()(float flMid2Width) const { eax_validate_range( "Mid2 Width", flMid2Width, EAXEQUALIZER_MINMID2WIDTH, EAXEQUALIZER_MAXMID2WIDTH); } }; // Mid2WidthValidator struct HighGainValidator { void operator()(long lHighGain) const { eax_validate_range( "High Gain", lHighGain, EAXEQUALIZER_MINHIGHGAIN, EAXEQUALIZER_MAXHIGHGAIN); } }; // HighGainValidator struct HighCutOffValidator { void operator()(float flHighCutOff) const { eax_validate_range( "High Cutoff", flHighCutOff, EAXEQUALIZER_MINHIGHCUTOFF, EAXEQUALIZER_MAXHIGHCUTOFF); } }; // HighCutOffValidator struct AllValidator { void operator()(const EAXEQUALIZERPROPERTIES& all) const { LowGainValidator{}(all.lLowGain); LowCutOffValidator{}(all.flLowCutOff); Mid1GainValidator{}(all.lMid1Gain); Mid1CenterValidator{}(all.flMid1Center); Mid1WidthValidator{}(all.flMid1Width); Mid2GainValidator{}(all.lMid2Gain); Mid2CenterValidator{}(all.flMid2Center); Mid2WidthValidator{}(all.flMid2Width); HighGainValidator{}(all.lHighGain); HighCutOffValidator{}(all.flHighCutOff); } }; // AllValidator } // namespace template<> struct EqualizerCommitter::Exception : public EaxException { explicit Exception(const char* message) : EaxException{"EAX_EQUALIZER_EFFECT", message} { } }; template<> [[noreturn]] void EqualizerCommitter::fail(const char *message) { throw Exception{message}; } bool EaxEqualizerCommitter::commit(const EAXEQUALIZERPROPERTIES &props) { if(auto *cur = std::get_if(&mEaxProps); cur && *cur == props) return false; mEaxProps = props; mAlProps = [&]{ EqualizerProps ret{}; ret.LowGain = level_mb_to_gain(static_cast(props.lLowGain)); ret.LowCutoff = props.flLowCutOff; ret.Mid1Gain = level_mb_to_gain(static_cast(props.lMid1Gain)); ret.Mid1Center = props.flMid1Center; ret.Mid1Width = props.flMid1Width; ret.Mid2Gain = level_mb_to_gain(static_cast(props.lMid2Gain)); ret.Mid2Center = props.flMid2Center; ret.Mid2Width = props.flMid2Width; ret.HighGain = level_mb_to_gain(static_cast(props.lHighGain)); ret.HighCutoff = props.flHighCutOff; return ret; }(); return true; } void EaxEqualizerCommitter::SetDefaults(EaxEffectProps &props) { static constexpr EAXEQUALIZERPROPERTIES defprops{[] { EAXEQUALIZERPROPERTIES ret{}; ret.lLowGain = EAXEQUALIZER_DEFAULTLOWGAIN; ret.flLowCutOff = EAXEQUALIZER_DEFAULTLOWCUTOFF; ret.lMid1Gain = EAXEQUALIZER_DEFAULTMID1GAIN; ret.flMid1Center = EAXEQUALIZER_DEFAULTMID1CENTER; ret.flMid1Width = EAXEQUALIZER_DEFAULTMID1WIDTH; ret.lMid2Gain = EAXEQUALIZER_DEFAULTMID2GAIN; ret.flMid2Center = EAXEQUALIZER_DEFAULTMID2CENTER; ret.flMid2Width = EAXEQUALIZER_DEFAULTMID2WIDTH; ret.lHighGain = EAXEQUALIZER_DEFAULTHIGHGAIN; ret.flHighCutOff = EAXEQUALIZER_DEFAULTHIGHCUTOFF; return ret; }()}; props = defprops; } void EaxEqualizerCommitter::Get(const EaxCall &call, const EAXEQUALIZERPROPERTIES &props) { switch(call.get_property_id()) { case EAXEQUALIZER_NONE: break; case EAXEQUALIZER_ALLPARAMETERS: call.set_value(props); break; case EAXEQUALIZER_LOWGAIN: call.set_value(props.lLowGain); break; case EAXEQUALIZER_LOWCUTOFF: call.set_value(props.flLowCutOff); break; case EAXEQUALIZER_MID1GAIN: call.set_value(props.lMid1Gain); break; case EAXEQUALIZER_MID1CENTER: call.set_value(props.flMid1Center); break; case EAXEQUALIZER_MID1WIDTH: call.set_value(props.flMid1Width); break; case EAXEQUALIZER_MID2GAIN: call.set_value(props.lMid2Gain); break; case EAXEQUALIZER_MID2CENTER: call.set_value(props.flMid2Center); break; case EAXEQUALIZER_MID2WIDTH: call.set_value(props.flMid2Width); break; case EAXEQUALIZER_HIGHGAIN: call.set_value(props.lHighGain); break; case EAXEQUALIZER_HIGHCUTOFF: call.set_value(props.flHighCutOff); break; default: fail_unknown_property_id(); } } void EaxEqualizerCommitter::Set(const EaxCall &call, EAXEQUALIZERPROPERTIES &props) { switch(call.get_property_id()) { case EAXEQUALIZER_NONE: break; case EAXEQUALIZER_ALLPARAMETERS: defer(call, props); break; case EAXEQUALIZER_LOWGAIN: defer(call, props.lLowGain); break; case EAXEQUALIZER_LOWCUTOFF: defer(call, props.flLowCutOff); break; case EAXEQUALIZER_MID1GAIN: defer(call, props.lMid1Gain); break; case EAXEQUALIZER_MID1CENTER: defer(call, props.flMid1Center); break; case EAXEQUALIZER_MID1WIDTH: defer(call, props.flMid1Width); break; case EAXEQUALIZER_MID2GAIN: defer(call, props.lMid2Gain); break; case EAXEQUALIZER_MID2CENTER: defer(call, props.flMid2Center); break; case EAXEQUALIZER_MID2WIDTH: defer(call, props.flMid2Width); break; case EAXEQUALIZER_HIGHGAIN: defer(call, props.lHighGain); break; case EAXEQUALIZER_HIGHCUTOFF: defer(call, props.flHighCutOff); break; default: fail_unknown_property_id(); } } #endif // ALSOFT_EAX openal-soft-1.24.2/al/effects/fshifter.cpp000066400000000000000000000210721474041540300203640ustar00rootroot00000000000000 #include "config.h" #include #include #include "AL/al.h" #include "AL/efx.h" #include "alc/context.h" #include "alnumeric.h" #include "effects.h" #if ALSOFT_EAX #include #include "al/eax/effect.h" #include "al/eax/exception.h" #include "al/eax/utils.h" #endif // ALSOFT_EAX namespace { constexpr std::optional DirectionFromEmum(ALenum value) noexcept { switch(value) { case AL_FREQUENCY_SHIFTER_DIRECTION_DOWN: return FShifterDirection::Down; case AL_FREQUENCY_SHIFTER_DIRECTION_UP: return FShifterDirection::Up; case AL_FREQUENCY_SHIFTER_DIRECTION_OFF: return FShifterDirection::Off; } return std::nullopt; } constexpr ALenum EnumFromDirection(FShifterDirection dir) { switch(dir) { case FShifterDirection::Down: return AL_FREQUENCY_SHIFTER_DIRECTION_DOWN; case FShifterDirection::Up: return AL_FREQUENCY_SHIFTER_DIRECTION_UP; case FShifterDirection::Off: return AL_FREQUENCY_SHIFTER_DIRECTION_OFF; } throw std::runtime_error{fmt::format("Invalid direction: {}", int{al::to_underlying(dir)})}; } constexpr EffectProps genDefaultProps() noexcept { FshifterProps props{}; props.Frequency = AL_FREQUENCY_SHIFTER_DEFAULT_FREQUENCY; props.LeftDirection = DirectionFromEmum(AL_FREQUENCY_SHIFTER_DEFAULT_LEFT_DIRECTION).value(); props.RightDirection = DirectionFromEmum(AL_FREQUENCY_SHIFTER_DEFAULT_RIGHT_DIRECTION).value(); return props; } } // namespace const EffectProps FshifterEffectProps{genDefaultProps()}; void FshifterEffectHandler::SetParami(ALCcontext *context, FshifterProps &props, ALenum param, int val) { switch(param) { case AL_FREQUENCY_SHIFTER_LEFT_DIRECTION: if(auto diropt = DirectionFromEmum(val)) props.LeftDirection = *diropt; else context->throw_error(AL_INVALID_VALUE, "Unsupported frequency shifter left direction: {:#04x}", as_unsigned(val)); return; case AL_FREQUENCY_SHIFTER_RIGHT_DIRECTION: if(auto diropt = DirectionFromEmum(val)) props.RightDirection = *diropt; else context->throw_error(AL_INVALID_VALUE, "Unsupported frequency shifter right direction: {:#04x}", as_unsigned(val)); return; } context->throw_error(AL_INVALID_ENUM, "Invalid frequency shifter integer property {:#04x}", as_unsigned(param)); } void FshifterEffectHandler::SetParamiv(ALCcontext *context, FshifterProps &props, ALenum param, const int *vals) { SetParami(context, props, param, *vals); } void FshifterEffectHandler::SetParamf(ALCcontext *context, FshifterProps &props, ALenum param, float val) { switch(param) { case AL_FREQUENCY_SHIFTER_FREQUENCY: if(!(val >= AL_FREQUENCY_SHIFTER_MIN_FREQUENCY && val <= AL_FREQUENCY_SHIFTER_MAX_FREQUENCY)) context->throw_error(AL_INVALID_VALUE, "Frequency shifter frequency out of range"); props.Frequency = val; return; } context->throw_error(AL_INVALID_ENUM, "Invalid frequency shifter float property {:#04x}", as_unsigned(param)); } void FshifterEffectHandler::SetParamfv(ALCcontext *context, FshifterProps &props, ALenum param, const float *vals) { SetParamf(context, props, param, *vals); } void FshifterEffectHandler::GetParami(ALCcontext *context, const FshifterProps &props, ALenum param, int *val) { switch(param) { case AL_FREQUENCY_SHIFTER_LEFT_DIRECTION: *val = EnumFromDirection(props.LeftDirection); return; case AL_FREQUENCY_SHIFTER_RIGHT_DIRECTION: *val = EnumFromDirection(props.RightDirection); return; } context->throw_error(AL_INVALID_ENUM, "Invalid frequency shifter integer property {:#04x}", as_unsigned(param)); } void FshifterEffectHandler::GetParamiv(ALCcontext *context, const FshifterProps &props, ALenum param, int *vals) { GetParami(context, props, param, vals); } void FshifterEffectHandler::GetParamf(ALCcontext *context, const FshifterProps &props, ALenum param, float *val) { switch(param) { case AL_FREQUENCY_SHIFTER_FREQUENCY: *val = props.Frequency; return; } context->throw_error(AL_INVALID_ENUM, "Invalid frequency shifter float property {:#04x}", as_unsigned(param)); } void FshifterEffectHandler::GetParamfv(ALCcontext *context, const FshifterProps &props, ALenum param, float *vals) { GetParamf(context, props, param, vals); } #if ALSOFT_EAX namespace { using FrequencyShifterCommitter = EaxCommitter; struct FrequencyValidator { void operator()(float flFrequency) const { eax_validate_range( "Frequency", flFrequency, EAXFREQUENCYSHIFTER_MINFREQUENCY, EAXFREQUENCYSHIFTER_MAXFREQUENCY); } }; // FrequencyValidator struct LeftDirectionValidator { void operator()(unsigned long ulLeftDirection) const { eax_validate_range( "Left Direction", ulLeftDirection, EAXFREQUENCYSHIFTER_MINLEFTDIRECTION, EAXFREQUENCYSHIFTER_MAXLEFTDIRECTION); } }; // LeftDirectionValidator struct RightDirectionValidator { void operator()(unsigned long ulRightDirection) const { eax_validate_range( "Right Direction", ulRightDirection, EAXFREQUENCYSHIFTER_MINRIGHTDIRECTION, EAXFREQUENCYSHIFTER_MAXRIGHTDIRECTION); } }; // RightDirectionValidator struct AllValidator { void operator()(const EAXFREQUENCYSHIFTERPROPERTIES& all) const { FrequencyValidator{}(all.flFrequency); LeftDirectionValidator{}(all.ulLeftDirection); RightDirectionValidator{}(all.ulRightDirection); } }; // AllValidator } // namespace template<> struct FrequencyShifterCommitter::Exception : public EaxException { explicit Exception(const char *message) : EaxException{"EAX_FREQUENCY_SHIFTER_EFFECT", message} { } }; template<> [[noreturn]] void FrequencyShifterCommitter::fail(const char *message) { throw Exception{message}; } bool EaxFrequencyShifterCommitter::commit(const EAXFREQUENCYSHIFTERPROPERTIES &props) { if(auto *cur = std::get_if(&mEaxProps); cur && *cur == props) return false; mEaxProps = props; auto get_direction = [](unsigned long dir) noexcept { if(dir == EAX_FREQUENCYSHIFTER_DOWN) return FShifterDirection::Down; if(dir == EAX_FREQUENCYSHIFTER_UP) return FShifterDirection::Up; return FShifterDirection::Off; }; mAlProps = [&]{ FshifterProps ret{}; ret.Frequency = props.flFrequency; ret.LeftDirection = get_direction(props.ulLeftDirection); ret.RightDirection = get_direction(props.ulRightDirection); return ret; }(); return true; } void EaxFrequencyShifterCommitter::SetDefaults(EaxEffectProps &props) { static constexpr EAXFREQUENCYSHIFTERPROPERTIES defprops{[] { EAXFREQUENCYSHIFTERPROPERTIES ret{}; ret.flFrequency = EAXFREQUENCYSHIFTER_DEFAULTFREQUENCY; ret.ulLeftDirection = EAXFREQUENCYSHIFTER_DEFAULTLEFTDIRECTION; ret.ulRightDirection = EAXFREQUENCYSHIFTER_DEFAULTRIGHTDIRECTION; return ret; }()}; props = defprops; } void EaxFrequencyShifterCommitter::Get(const EaxCall &call, const EAXFREQUENCYSHIFTERPROPERTIES &props) { switch(call.get_property_id()) { case EAXFREQUENCYSHIFTER_NONE: break; case EAXFREQUENCYSHIFTER_ALLPARAMETERS: call.set_value(props); break; case EAXFREQUENCYSHIFTER_FREQUENCY: call.set_value(props.flFrequency); break; case EAXFREQUENCYSHIFTER_LEFTDIRECTION: call.set_value(props.ulLeftDirection); break; case EAXFREQUENCYSHIFTER_RIGHTDIRECTION: call.set_value(props.ulRightDirection); break; default: fail_unknown_property_id(); } } void EaxFrequencyShifterCommitter::Set(const EaxCall &call, EAXFREQUENCYSHIFTERPROPERTIES &props) { switch(call.get_property_id()) { case EAXFREQUENCYSHIFTER_NONE: break; case EAXFREQUENCYSHIFTER_ALLPARAMETERS: defer(call, props); break; case EAXFREQUENCYSHIFTER_FREQUENCY: defer(call, props.flFrequency); break; case EAXFREQUENCYSHIFTER_LEFTDIRECTION: defer(call, props.ulLeftDirection); break; case EAXFREQUENCYSHIFTER_RIGHTDIRECTION: defer(call, props.ulRightDirection); break; default: fail_unknown_property_id(); } } #endif // ALSOFT_EAX openal-soft-1.24.2/al/effects/modulator.cpp000066400000000000000000000211211474041540300205530ustar00rootroot00000000000000 #include "config.h" #include #include #include "AL/al.h" #include "AL/efx.h" #include "alc/context.h" #include "alnumeric.h" #include "effects.h" #if ALSOFT_EAX #include #include "al/eax/effect.h" #include "al/eax/exception.h" #include "al/eax/utils.h" #endif // ALSOFT_EAX namespace { constexpr std::optional WaveformFromEmum(ALenum value) noexcept { switch(value) { case AL_RING_MODULATOR_SINUSOID: return ModulatorWaveform::Sinusoid; case AL_RING_MODULATOR_SAWTOOTH: return ModulatorWaveform::Sawtooth; case AL_RING_MODULATOR_SQUARE: return ModulatorWaveform::Square; } return std::nullopt; } constexpr ALenum EnumFromWaveform(ModulatorWaveform type) { switch(type) { case ModulatorWaveform::Sinusoid: return AL_RING_MODULATOR_SINUSOID; case ModulatorWaveform::Sawtooth: return AL_RING_MODULATOR_SAWTOOTH; case ModulatorWaveform::Square: return AL_RING_MODULATOR_SQUARE; } throw std::runtime_error{fmt::format("Invalid modulator waveform: {}", int{al::to_underlying(type)})}; } constexpr EffectProps genDefaultProps() noexcept { ModulatorProps props{}; props.Frequency = AL_RING_MODULATOR_DEFAULT_FREQUENCY; props.HighPassCutoff = AL_RING_MODULATOR_DEFAULT_HIGHPASS_CUTOFF; props.Waveform = WaveformFromEmum(AL_RING_MODULATOR_DEFAULT_WAVEFORM).value(); return props; } } // namespace const EffectProps ModulatorEffectProps{genDefaultProps()}; void ModulatorEffectHandler::SetParami(ALCcontext *context, ModulatorProps &props, ALenum param, int val) { switch(param) { case AL_RING_MODULATOR_FREQUENCY: case AL_RING_MODULATOR_HIGHPASS_CUTOFF: SetParamf(context, props, param, static_cast(val)); return; case AL_RING_MODULATOR_WAVEFORM: if(auto formopt = WaveformFromEmum(val)) props.Waveform = *formopt; else context->throw_error(AL_INVALID_VALUE, "Invalid modulator waveform: {:#04x}", as_unsigned(val)); return; } context->throw_error(AL_INVALID_ENUM, "Invalid modulator integer property {:#04x}", as_unsigned(param)); } void ModulatorEffectHandler::SetParamiv(ALCcontext *context, ModulatorProps &props, ALenum param, const int *vals) { SetParami(context, props, param, *vals); } void ModulatorEffectHandler::SetParamf(ALCcontext *context, ModulatorProps &props, ALenum param, float val) { switch(param) { case AL_RING_MODULATOR_FREQUENCY: if(!(val >= AL_RING_MODULATOR_MIN_FREQUENCY && val <= AL_RING_MODULATOR_MAX_FREQUENCY)) context->throw_error(AL_INVALID_VALUE, "Modulator frequency out of range: {:f}", val); props.Frequency = val; return; case AL_RING_MODULATOR_HIGHPASS_CUTOFF: if(!(val >= AL_RING_MODULATOR_MIN_HIGHPASS_CUTOFF && val <= AL_RING_MODULATOR_MAX_HIGHPASS_CUTOFF)) context->throw_error(AL_INVALID_VALUE, "Modulator high-pass cutoff out of range: {:f}", val); props.HighPassCutoff = val; return; } context->throw_error(AL_INVALID_ENUM, "Invalid modulator float property {:#04x}", as_unsigned(param)); } void ModulatorEffectHandler::SetParamfv(ALCcontext *context, ModulatorProps &props, ALenum param, const float *vals) { SetParamf(context, props, param, *vals); } void ModulatorEffectHandler::GetParami(ALCcontext *context, const ModulatorProps &props, ALenum param, int *val) { switch(param) { case AL_RING_MODULATOR_FREQUENCY: *val = static_cast(props.Frequency); return; case AL_RING_MODULATOR_HIGHPASS_CUTOFF: *val = static_cast(props.HighPassCutoff); return; case AL_RING_MODULATOR_WAVEFORM: *val = EnumFromWaveform(props.Waveform); return; } context->throw_error(AL_INVALID_ENUM, "Invalid modulator integer property {:#04x}", as_unsigned(param)); } void ModulatorEffectHandler::GetParamiv(ALCcontext *context, const ModulatorProps &props, ALenum param, int *vals) { GetParami(context, props, param, vals); } void ModulatorEffectHandler::GetParamf(ALCcontext *context, const ModulatorProps &props, ALenum param, float *val) { switch(param) { case AL_RING_MODULATOR_FREQUENCY: *val = props.Frequency; return; case AL_RING_MODULATOR_HIGHPASS_CUTOFF: *val = props.HighPassCutoff; return; } context->throw_error(AL_INVALID_ENUM, "Invalid modulator float property {:#04x}", as_unsigned(param)); } void ModulatorEffectHandler::GetParamfv(ALCcontext *context, const ModulatorProps &props, ALenum param, float *vals) { GetParamf(context, props, param, vals); } #if ALSOFT_EAX namespace { using ModulatorCommitter = EaxCommitter; struct FrequencyValidator { void operator()(float flFrequency) const { eax_validate_range( "Frequency", flFrequency, EAXRINGMODULATOR_MINFREQUENCY, EAXRINGMODULATOR_MAXFREQUENCY); } }; // FrequencyValidator struct HighPassCutOffValidator { void operator()(float flHighPassCutOff) const { eax_validate_range( "High-Pass Cutoff", flHighPassCutOff, EAXRINGMODULATOR_MINHIGHPASSCUTOFF, EAXRINGMODULATOR_MAXHIGHPASSCUTOFF); } }; // HighPassCutOffValidator struct WaveformValidator { void operator()(unsigned long ulWaveform) const { eax_validate_range( "Waveform", ulWaveform, EAXRINGMODULATOR_MINWAVEFORM, EAXRINGMODULATOR_MAXWAVEFORM); } }; // WaveformValidator struct AllValidator { void operator()(const EAXRINGMODULATORPROPERTIES& all) const { FrequencyValidator{}(all.flFrequency); HighPassCutOffValidator{}(all.flHighPassCutOff); WaveformValidator{}(all.ulWaveform); } }; // AllValidator } // namespace template<> struct ModulatorCommitter::Exception : public EaxException { explicit Exception(const char *message) : EaxException{"EAX_RING_MODULATOR_EFFECT", message} { } }; template<> [[noreturn]] void ModulatorCommitter::fail(const char *message) { throw Exception{message}; } bool EaxModulatorCommitter::commit(const EAXRINGMODULATORPROPERTIES &props) { if(auto *cur = std::get_if(&mEaxProps); cur && *cur == props) return false; mEaxProps = props; auto get_waveform = [](unsigned long form) { if(form == EAX_RINGMODULATOR_SINUSOID) return ModulatorWaveform::Sinusoid; if(form == EAX_RINGMODULATOR_SAWTOOTH) return ModulatorWaveform::Sawtooth; if(form == EAX_RINGMODULATOR_SQUARE) return ModulatorWaveform::Square; return ModulatorWaveform::Sinusoid; }; mAlProps = [&]{ ModulatorProps ret{}; ret.Frequency = props.flFrequency; ret.HighPassCutoff = props.flHighPassCutOff; ret.Waveform = get_waveform(props.ulWaveform); return ret; }(); return true; } void EaxModulatorCommitter::SetDefaults(EaxEffectProps &props) { static constexpr EAXRINGMODULATORPROPERTIES defprops{[] { EAXRINGMODULATORPROPERTIES ret{}; ret.flFrequency = EAXRINGMODULATOR_DEFAULTFREQUENCY; ret.flHighPassCutOff = EAXRINGMODULATOR_DEFAULTHIGHPASSCUTOFF; ret.ulWaveform = EAXRINGMODULATOR_DEFAULTWAVEFORM; return ret; }()}; props = defprops; } void EaxModulatorCommitter::Get(const EaxCall &call, const EAXRINGMODULATORPROPERTIES &props) { switch(call.get_property_id()) { case EAXRINGMODULATOR_NONE: break; case EAXRINGMODULATOR_ALLPARAMETERS: call.set_value(props); break; case EAXRINGMODULATOR_FREQUENCY: call.set_value(props.flFrequency); break; case EAXRINGMODULATOR_HIGHPASSCUTOFF: call.set_value(props.flHighPassCutOff); break; case EAXRINGMODULATOR_WAVEFORM: call.set_value(props.ulWaveform); break; default: fail_unknown_property_id(); } } void EaxModulatorCommitter::Set(const EaxCall &call, EAXRINGMODULATORPROPERTIES &props) { switch(call.get_property_id()) { case EAXRINGMODULATOR_NONE: break; case EAXRINGMODULATOR_ALLPARAMETERS: defer(call, props); break; case EAXRINGMODULATOR_FREQUENCY: defer(call, props.flFrequency); break; case EAXRINGMODULATOR_HIGHPASSCUTOFF: defer(call, props.flHighPassCutOff); break; case EAXRINGMODULATOR_WAVEFORM: defer(call, props.ulWaveform); break; default: fail_unknown_property_id(); } } #endif // ALSOFT_EAX openal-soft-1.24.2/al/effects/null.cpp000066400000000000000000000056161474041540300175320ustar00rootroot00000000000000 #include "config.h" #include "AL/al.h" #include "AL/efx.h" #include "alc/context.h" #include "alnumeric.h" #include "effects.h" #if ALSOFT_EAX #include "al/eax/effect.h" #include "al/eax/exception.h" #endif // ALSOFT_EAX namespace { constexpr EffectProps genDefaultProps() noexcept { return std::monostate{}; } } // namespace const EffectProps NullEffectProps{genDefaultProps()}; void NullEffectHandler::SetParami(ALCcontext *context, std::monostate& /*props*/, ALenum param, int /*val*/) { context->throw_error(AL_INVALID_ENUM, "Invalid null effect integer property {:#04x}", as_unsigned(param)); } void NullEffectHandler::SetParamiv(ALCcontext *context, std::monostate &props, ALenum param, const int *vals) { SetParami(context, props, param, *vals); } void NullEffectHandler::SetParamf(ALCcontext *context, std::monostate& /*props*/, ALenum param, float /*val*/) { context->throw_error(AL_INVALID_ENUM, "Invalid null effect float property {:#04x}", as_unsigned(param)); } void NullEffectHandler::SetParamfv(ALCcontext *context, std::monostate &props, ALenum param, const float *vals) { SetParamf(context, props, param, *vals); } void NullEffectHandler::GetParami(ALCcontext *context, const std::monostate& /*props*/, ALenum param, int* /*val*/) { context->throw_error(AL_INVALID_ENUM, "Invalid null effect integer property {:#04x}", as_unsigned(param)); } void NullEffectHandler::GetParamiv(ALCcontext *context, const std::monostate &props, ALenum param, int *vals) { GetParami(context, props, param, vals); } void NullEffectHandler::GetParamf(ALCcontext *context, const std::monostate& /*props*/, ALenum param, float* /*val*/) { context->throw_error(AL_INVALID_ENUM, "Invalid null effect float property {:#04x}", as_unsigned(param)); } void NullEffectHandler::GetParamfv(ALCcontext *context, const std::monostate &props, ALenum param, float *vals) { GetParamf(context, props, param, vals); } #if ALSOFT_EAX namespace { using NullCommitter = EaxCommitter; } // namespace template<> struct NullCommitter::Exception : public EaxException { explicit Exception(const char *message) : EaxException{"EAX_NULL_EFFECT", message} { } }; template<> [[noreturn]] void NullCommitter::fail(const char *message) { throw Exception{message}; } bool EaxNullCommitter::commit(const std::monostate &props) { const bool ret{std::holds_alternative(mEaxProps)}; mEaxProps = props; mAlProps = std::monostate{}; return ret; } void EaxNullCommitter::SetDefaults(EaxEffectProps &props) { props = std::monostate{}; } void EaxNullCommitter::Get(const EaxCall &call, const std::monostate&) { if(call.get_property_id() != 0) fail_unknown_property_id(); } void EaxNullCommitter::Set(const EaxCall &call, std::monostate&) { if(call.get_property_id() != 0) fail_unknown_property_id(); } #endif // ALSOFT_EAX openal-soft-1.24.2/al/effects/pshifter.cpp000066400000000000000000000130661474041540300204020ustar00rootroot00000000000000 #include "config.h" #include "AL/al.h" #include "AL/efx.h" #include "alc/context.h" #include "alnumeric.h" #include "effects.h" #if ALSOFT_EAX #include "al/eax/effect.h" #include "al/eax/exception.h" #include "al/eax/utils.h" #endif // ALSOFT_EAX namespace { constexpr EffectProps genDefaultProps() noexcept { PshifterProps props{}; props.CoarseTune = AL_PITCH_SHIFTER_DEFAULT_COARSE_TUNE; props.FineTune = AL_PITCH_SHIFTER_DEFAULT_FINE_TUNE; return props; } } // namespace const EffectProps PshifterEffectProps{genDefaultProps()}; void PshifterEffectHandler::SetParami(ALCcontext *context, PshifterProps &props, ALenum param, int val) { switch(param) { case AL_PITCH_SHIFTER_COARSE_TUNE: if(!(val >= AL_PITCH_SHIFTER_MIN_COARSE_TUNE && val <= AL_PITCH_SHIFTER_MAX_COARSE_TUNE)) context->throw_error(AL_INVALID_VALUE, "Pitch shifter coarse tune out of range"); props.CoarseTune = val; return; case AL_PITCH_SHIFTER_FINE_TUNE: if(!(val >= AL_PITCH_SHIFTER_MIN_FINE_TUNE && val <= AL_PITCH_SHIFTER_MAX_FINE_TUNE)) context->throw_error(AL_INVALID_VALUE, "Pitch shifter fine tune out of range"); props.FineTune = val; return; } context->throw_error(AL_INVALID_ENUM, "Invalid pitch shifter integer property {:#04x}", as_unsigned(param)); } void PshifterEffectHandler::SetParamiv(ALCcontext *context, PshifterProps &props, ALenum param, const int *vals) { SetParami(context, props, param, *vals); } void PshifterEffectHandler::SetParamf(ALCcontext *context, PshifterProps&, ALenum param, float) { context->throw_error(AL_INVALID_ENUM, "Invalid pitch shifter float property {:#04x}", as_unsigned(param)); } void PshifterEffectHandler::SetParamfv(ALCcontext *context, PshifterProps &props, ALenum param, const float *vals) { SetParamf(context, props, param, *vals); } void PshifterEffectHandler::GetParami(ALCcontext *context, const PshifterProps &props, ALenum param, int *val) { switch(param) { case AL_PITCH_SHIFTER_COARSE_TUNE: *val = props.CoarseTune; return; case AL_PITCH_SHIFTER_FINE_TUNE: *val = props.FineTune; return; } context->throw_error(AL_INVALID_ENUM, "Invalid pitch shifter integer property {:#04x}", as_unsigned(param)); } void PshifterEffectHandler::GetParamiv(ALCcontext *context, const PshifterProps &props, ALenum param, int *vals) { GetParami(context, props, param, vals); } void PshifterEffectHandler::GetParamf(ALCcontext *context, const PshifterProps&, ALenum param, float*) { context->throw_error(AL_INVALID_ENUM, "Invalid pitch shifter float property {:#04x}", as_unsigned(param)); } void PshifterEffectHandler::GetParamfv(ALCcontext *context, const PshifterProps &props, ALenum param, float *vals) { GetParamf(context, props, param, vals); } #if ALSOFT_EAX namespace { using PitchShifterCommitter = EaxCommitter; struct CoarseTuneValidator { void operator()(long lCoarseTune) const { eax_validate_range( "Coarse Tune", lCoarseTune, EAXPITCHSHIFTER_MINCOARSETUNE, EAXPITCHSHIFTER_MAXCOARSETUNE); } }; // CoarseTuneValidator struct FineTuneValidator { void operator()(long lFineTune) const { eax_validate_range( "Fine Tune", lFineTune, EAXPITCHSHIFTER_MINFINETUNE, EAXPITCHSHIFTER_MAXFINETUNE); } }; // FineTuneValidator struct AllValidator { void operator()(const EAXPITCHSHIFTERPROPERTIES& all) const { CoarseTuneValidator{}(all.lCoarseTune); FineTuneValidator{}(all.lFineTune); } }; // AllValidator } // namespace template<> struct PitchShifterCommitter::Exception : public EaxException { explicit Exception(const char *message) : EaxException{"EAX_PITCH_SHIFTER_EFFECT", message} { } }; template<> [[noreturn]] void PitchShifterCommitter::fail(const char *message) { throw Exception{message}; } bool EaxPitchShifterCommitter::commit(const EAXPITCHSHIFTERPROPERTIES &props) { if(auto *cur = std::get_if(&mEaxProps); cur && *cur == props) return false; mEaxProps = props; mAlProps = [&]{ PshifterProps ret{}; ret.CoarseTune = static_cast(props.lCoarseTune); ret.FineTune = static_cast(props.lFineTune); return ret; }(); return true; } void EaxPitchShifterCommitter::SetDefaults(EaxEffectProps &props) { props = EAXPITCHSHIFTERPROPERTIES{EAXPITCHSHIFTER_DEFAULTCOARSETUNE, EAXPITCHSHIFTER_DEFAULTFINETUNE}; } void EaxPitchShifterCommitter::Get(const EaxCall &call, const EAXPITCHSHIFTERPROPERTIES &props) { switch(call.get_property_id()) { case EAXPITCHSHIFTER_NONE: break; case EAXPITCHSHIFTER_ALLPARAMETERS: call.set_value(props); break; case EAXPITCHSHIFTER_COARSETUNE: call.set_value(props.lCoarseTune); break; case EAXPITCHSHIFTER_FINETUNE: call.set_value(props.lFineTune); break; default: fail_unknown_property_id(); } } void EaxPitchShifterCommitter::Set(const EaxCall &call, EAXPITCHSHIFTERPROPERTIES &props) { switch(call.get_property_id()) { case EAXPITCHSHIFTER_NONE: break; case EAXPITCHSHIFTER_ALLPARAMETERS: defer(call, props); break; case EAXPITCHSHIFTER_COARSETUNE: defer(call, props.lCoarseTune); break; case EAXPITCHSHIFTER_FINETUNE: defer(call, props.lFineTune); break; default: fail_unknown_property_id(); } } #endif // ALSOFT_EAX openal-soft-1.24.2/al/effects/reverb.cpp000066400000000000000000001407231474041540300200440ustar00rootroot00000000000000 #include "config.h" #include #include #include #include #include "AL/al.h" #include "AL/efx.h" #include "alc/context.h" #include "alnumeric.h" #include "alspan.h" #include "effects.h" #if ALSOFT_EAX #include #include "al/eax/api.h" #include "al/eax/call.h" #include "al/eax/effect.h" #include "al/eax/exception.h" #include "al/eax/utils.h" #endif // ALSOFT_EAX namespace { constexpr EffectProps genDefaultProps() noexcept { ReverbProps props{}; props.Density = AL_EAXREVERB_DEFAULT_DENSITY; props.Diffusion = AL_EAXREVERB_DEFAULT_DIFFUSION; props.Gain = AL_EAXREVERB_DEFAULT_GAIN; props.GainHF = AL_EAXREVERB_DEFAULT_GAINHF; props.GainLF = AL_EAXREVERB_DEFAULT_GAINLF; props.DecayTime = AL_EAXREVERB_DEFAULT_DECAY_TIME; props.DecayHFRatio = AL_EAXREVERB_DEFAULT_DECAY_HFRATIO; props.DecayLFRatio = AL_EAXREVERB_DEFAULT_DECAY_LFRATIO; props.ReflectionsGain = AL_EAXREVERB_DEFAULT_REFLECTIONS_GAIN; props.ReflectionsDelay = AL_EAXREVERB_DEFAULT_REFLECTIONS_DELAY; props.ReflectionsPan[0] = AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ; props.ReflectionsPan[1] = AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ; props.ReflectionsPan[2] = AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ; props.LateReverbGain = AL_EAXREVERB_DEFAULT_LATE_REVERB_GAIN; props.LateReverbDelay = AL_EAXREVERB_DEFAULT_LATE_REVERB_DELAY; props.LateReverbPan[0] = AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ; props.LateReverbPan[1] = AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ; props.LateReverbPan[2] = AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ; props.EchoTime = AL_EAXREVERB_DEFAULT_ECHO_TIME; props.EchoDepth = AL_EAXREVERB_DEFAULT_ECHO_DEPTH; props.ModulationTime = AL_EAXREVERB_DEFAULT_MODULATION_TIME; props.ModulationDepth = AL_EAXREVERB_DEFAULT_MODULATION_DEPTH; props.AirAbsorptionGainHF = AL_EAXREVERB_DEFAULT_AIR_ABSORPTION_GAINHF; props.HFReference = AL_EAXREVERB_DEFAULT_HFREFERENCE; props.LFReference = AL_EAXREVERB_DEFAULT_LFREFERENCE; props.RoomRolloffFactor = AL_EAXREVERB_DEFAULT_ROOM_ROLLOFF_FACTOR; props.DecayHFLimit = AL_EAXREVERB_DEFAULT_DECAY_HFLIMIT; return props; } constexpr EffectProps genDefaultStdProps() noexcept { ReverbProps props{}; props.Density = AL_REVERB_DEFAULT_DENSITY; props.Diffusion = AL_REVERB_DEFAULT_DIFFUSION; props.Gain = AL_REVERB_DEFAULT_GAIN; props.GainHF = AL_REVERB_DEFAULT_GAINHF; props.GainLF = 1.0f; props.DecayTime = AL_REVERB_DEFAULT_DECAY_TIME; props.DecayHFRatio = AL_REVERB_DEFAULT_DECAY_HFRATIO; props.DecayLFRatio = 1.0f; props.ReflectionsGain = AL_REVERB_DEFAULT_REFLECTIONS_GAIN; props.ReflectionsDelay = AL_REVERB_DEFAULT_REFLECTIONS_DELAY; props.ReflectionsPan = {0.0f, 0.0f, 0.0f}; props.LateReverbGain = AL_REVERB_DEFAULT_LATE_REVERB_GAIN; props.LateReverbDelay = AL_REVERB_DEFAULT_LATE_REVERB_DELAY; props.LateReverbPan = {0.0f, 0.0f, 0.0f}; props.EchoTime = 0.25f; props.EchoDepth = 0.0f; props.ModulationTime = 0.25f; props.ModulationDepth = 0.0f; props.AirAbsorptionGainHF = AL_REVERB_DEFAULT_AIR_ABSORPTION_GAINHF; props.HFReference = 5000.0f; props.LFReference = 250.0f; props.RoomRolloffFactor = AL_REVERB_DEFAULT_ROOM_ROLLOFF_FACTOR; props.DecayHFLimit = AL_REVERB_DEFAULT_DECAY_HFLIMIT; return props; } } // namespace const EffectProps ReverbEffectProps{genDefaultProps()}; void ReverbEffectHandler::SetParami(ALCcontext *context, ReverbProps &props, ALenum param, int val) { switch(param) { case AL_EAXREVERB_DECAY_HFLIMIT: if(!(val >= AL_EAXREVERB_MIN_DECAY_HFLIMIT && val <= AL_EAXREVERB_MAX_DECAY_HFLIMIT)) context->throw_error(AL_INVALID_VALUE, "EAX Reverb decay hflimit out of range"); props.DecayHFLimit = val != AL_FALSE; return; } context->throw_error(AL_INVALID_ENUM, "Invalid EAX reverb integer property {:#04x}", as_unsigned(param)); } void ReverbEffectHandler::SetParamiv(ALCcontext *context, ReverbProps &props, ALenum param, const int *vals) { SetParami(context, props, param, *vals); } void ReverbEffectHandler::SetParamf(ALCcontext *context, ReverbProps &props, ALenum param, float val) { switch(param) { case AL_EAXREVERB_DENSITY: if(!(val >= AL_EAXREVERB_MIN_DENSITY && val <= AL_EAXREVERB_MAX_DENSITY)) context->throw_error(AL_INVALID_VALUE, "EAX Reverb density out of range"); props.Density = val; return; case AL_EAXREVERB_DIFFUSION: if(!(val >= AL_EAXREVERB_MIN_DIFFUSION && val <= AL_EAXREVERB_MAX_DIFFUSION)) context->throw_error(AL_INVALID_VALUE, "EAX Reverb diffusion out of range"); props.Diffusion = val; return; case AL_EAXREVERB_GAIN: if(!(val >= AL_EAXREVERB_MIN_GAIN && val <= AL_EAXREVERB_MAX_GAIN)) context->throw_error(AL_INVALID_VALUE, "EAX Reverb gain out of range"); props.Gain = val; return; case AL_EAXREVERB_GAINHF: if(!(val >= AL_EAXREVERB_MIN_GAINHF && val <= AL_EAXREVERB_MAX_GAINHF)) context->throw_error(AL_INVALID_VALUE, "EAX Reverb gainhf out of range"); props.GainHF = val; return; case AL_EAXREVERB_GAINLF: if(!(val >= AL_EAXREVERB_MIN_GAINLF && val <= AL_EAXREVERB_MAX_GAINLF)) context->throw_error(AL_INVALID_VALUE, "EAX Reverb gainlf out of range"); props.GainLF = val; return; case AL_EAXREVERB_DECAY_TIME: if(!(val >= AL_EAXREVERB_MIN_DECAY_TIME && val <= AL_EAXREVERB_MAX_DECAY_TIME)) context->throw_error(AL_INVALID_VALUE, "EAX Reverb decay time out of range"); props.DecayTime = val; return; case AL_EAXREVERB_DECAY_HFRATIO: if(!(val >= AL_EAXREVERB_MIN_DECAY_HFRATIO && val <= AL_EAXREVERB_MAX_DECAY_HFRATIO)) context->throw_error(AL_INVALID_VALUE, "EAX Reverb decay hfratio out of range"); props.DecayHFRatio = val; return; case AL_EAXREVERB_DECAY_LFRATIO: if(!(val >= AL_EAXREVERB_MIN_DECAY_LFRATIO && val <= AL_EAXREVERB_MAX_DECAY_LFRATIO)) context->throw_error(AL_INVALID_VALUE, "EAX Reverb decay lfratio out of range"); props.DecayLFRatio = val; return; case AL_EAXREVERB_REFLECTIONS_GAIN: if(!(val >= AL_EAXREVERB_MIN_REFLECTIONS_GAIN && val <= AL_EAXREVERB_MAX_REFLECTIONS_GAIN)) context->throw_error(AL_INVALID_VALUE, "EAX Reverb reflections gain out of range"); props.ReflectionsGain = val; return; case AL_EAXREVERB_REFLECTIONS_DELAY: if(!(val >= AL_EAXREVERB_MIN_REFLECTIONS_DELAY && val <= AL_EAXREVERB_MAX_REFLECTIONS_DELAY)) context->throw_error(AL_INVALID_VALUE, "EAX Reverb reflections delay out of range"); props.ReflectionsDelay = val; return; case AL_EAXREVERB_LATE_REVERB_GAIN: if(!(val >= AL_EAXREVERB_MIN_LATE_REVERB_GAIN && val <= AL_EAXREVERB_MAX_LATE_REVERB_GAIN)) context->throw_error(AL_INVALID_VALUE, "EAX Reverb late reverb gain out of range"); props.LateReverbGain = val; return; case AL_EAXREVERB_LATE_REVERB_DELAY: if(!(val >= AL_EAXREVERB_MIN_LATE_REVERB_DELAY && val <= AL_EAXREVERB_MAX_LATE_REVERB_DELAY)) context->throw_error(AL_INVALID_VALUE, "EAX Reverb late reverb delay out of range"); props.LateReverbDelay = val; return; case AL_EAXREVERB_ECHO_TIME: if(!(val >= AL_EAXREVERB_MIN_ECHO_TIME && val <= AL_EAXREVERB_MAX_ECHO_TIME)) context->throw_error(AL_INVALID_VALUE, "EAX Reverb echo time out of range"); props.EchoTime = val; return; case AL_EAXREVERB_ECHO_DEPTH: if(!(val >= AL_EAXREVERB_MIN_ECHO_DEPTH && val <= AL_EAXREVERB_MAX_ECHO_DEPTH)) context->throw_error(AL_INVALID_VALUE, "EAX Reverb echo depth out of range"); props.EchoDepth = val; return; case AL_EAXREVERB_MODULATION_TIME: if(!(val >= AL_EAXREVERB_MIN_MODULATION_TIME && val <= AL_EAXREVERB_MAX_MODULATION_TIME)) context->throw_error(AL_INVALID_VALUE, "EAX Reverb modulation time out of range"); props.ModulationTime = val; return; case AL_EAXREVERB_MODULATION_DEPTH: if(!(val >= AL_EAXREVERB_MIN_MODULATION_DEPTH && val <= AL_EAXREVERB_MAX_MODULATION_DEPTH)) context->throw_error(AL_INVALID_VALUE, "EAX Reverb modulation depth out of range"); props.ModulationDepth = val; return; case AL_EAXREVERB_AIR_ABSORPTION_GAINHF: if(!(val >= AL_EAXREVERB_MIN_AIR_ABSORPTION_GAINHF && val <= AL_EAXREVERB_MAX_AIR_ABSORPTION_GAINHF)) context->throw_error(AL_INVALID_VALUE, "EAX Reverb air absorption gainhf out of range"); props.AirAbsorptionGainHF = val; return; case AL_EAXREVERB_HFREFERENCE: if(!(val >= AL_EAXREVERB_MIN_HFREFERENCE && val <= AL_EAXREVERB_MAX_HFREFERENCE)) context->throw_error(AL_INVALID_VALUE, "EAX Reverb hfreference out of range"); props.HFReference = val; return; case AL_EAXREVERB_LFREFERENCE: if(!(val >= AL_EAXREVERB_MIN_LFREFERENCE && val <= AL_EAXREVERB_MAX_LFREFERENCE)) context->throw_error(AL_INVALID_VALUE, "EAX Reverb lfreference out of range"); props.LFReference = val; return; case AL_EAXREVERB_ROOM_ROLLOFF_FACTOR: if(!(val >= AL_EAXREVERB_MIN_ROOM_ROLLOFF_FACTOR && val <= AL_EAXREVERB_MAX_ROOM_ROLLOFF_FACTOR)) context->throw_error(AL_INVALID_VALUE, "EAX Reverb room rolloff factor out of range"); props.RoomRolloffFactor = val; return; } context->throw_error(AL_INVALID_ENUM, "Invalid EAX reverb float property {:#04x}", as_unsigned(param)); } void ReverbEffectHandler::SetParamfv(ALCcontext *context, ReverbProps &props, ALenum param, const float *vals) { static constexpr auto finite_checker = [](float f) -> bool { return std::isfinite(f); }; al::span values; switch(param) { case AL_EAXREVERB_REFLECTIONS_PAN: values = {vals, 3_uz}; if(!std::all_of(values.cbegin(), values.cend(), finite_checker)) context->throw_error(AL_INVALID_VALUE, "EAX Reverb reflections pan out of range"); std::copy(values.cbegin(), values.cend(), props.ReflectionsPan.begin()); return; case AL_EAXREVERB_LATE_REVERB_PAN: values = {vals, 3_uz}; if(!std::all_of(values.cbegin(), values.cend(), finite_checker)) context->throw_error(AL_INVALID_VALUE, "EAX Reverb late reverb pan out of range"); std::copy(values.cbegin(), values.cend(), props.LateReverbPan.begin()); return; } SetParamf(context, props, param, *vals); } void ReverbEffectHandler::GetParami(ALCcontext *context, const ReverbProps &props, ALenum param, int *val) { switch(param) { case AL_EAXREVERB_DECAY_HFLIMIT: *val = props.DecayHFLimit; return; } context->throw_error(AL_INVALID_ENUM, "Invalid EAX reverb integer property {:#04x}", as_unsigned(param)); } void ReverbEffectHandler::GetParamiv(ALCcontext *context, const ReverbProps &props, ALenum param, int *vals) { GetParami(context, props, param, vals); } void ReverbEffectHandler::GetParamf(ALCcontext *context, const ReverbProps &props, ALenum param, float *val) { switch(param) { case AL_EAXREVERB_DENSITY: *val = props.Density; return; case AL_EAXREVERB_DIFFUSION: *val = props.Diffusion; return; case AL_EAXREVERB_GAIN: *val = props.Gain; return; case AL_EAXREVERB_GAINHF: *val = props.GainHF; return; case AL_EAXREVERB_GAINLF: *val = props.GainLF; return; case AL_EAXREVERB_DECAY_TIME: *val = props.DecayTime; return; case AL_EAXREVERB_DECAY_HFRATIO: *val = props.DecayHFRatio; return; case AL_EAXREVERB_DECAY_LFRATIO: *val = props.DecayLFRatio; return; case AL_EAXREVERB_REFLECTIONS_GAIN: *val = props.ReflectionsGain; return; case AL_EAXREVERB_REFLECTIONS_DELAY: *val = props.ReflectionsDelay; return; case AL_EAXREVERB_LATE_REVERB_GAIN: *val = props.LateReverbGain; return; case AL_EAXREVERB_LATE_REVERB_DELAY: *val = props.LateReverbDelay; return; case AL_EAXREVERB_ECHO_TIME: *val = props.EchoTime; return; case AL_EAXREVERB_ECHO_DEPTH: *val = props.EchoDepth; return; case AL_EAXREVERB_MODULATION_TIME: *val = props.ModulationTime; return; case AL_EAXREVERB_MODULATION_DEPTH: *val = props.ModulationDepth; return; case AL_EAXREVERB_AIR_ABSORPTION_GAINHF: *val = props.AirAbsorptionGainHF; return; case AL_EAXREVERB_HFREFERENCE: *val = props.HFReference; return; case AL_EAXREVERB_LFREFERENCE: *val = props.LFReference; return; case AL_EAXREVERB_ROOM_ROLLOFF_FACTOR: *val = props.RoomRolloffFactor; return; } context->throw_error(AL_INVALID_ENUM, "Invalid EAX reverb float property {:#04x}", as_unsigned(param)); } void ReverbEffectHandler::GetParamfv(ALCcontext *context, const ReverbProps &props, ALenum param, float *vals) { al::span values; switch(param) { case AL_EAXREVERB_REFLECTIONS_PAN: values = {vals, 3_uz}; std::copy(props.ReflectionsPan.cbegin(), props.ReflectionsPan.cend(), values.begin()); return; case AL_EAXREVERB_LATE_REVERB_PAN: values = {vals, 3_uz}; std::copy(props.LateReverbPan.cbegin(), props.LateReverbPan.cend(), values.begin()); return; } GetParamf(context, props, param, vals); } const EffectProps StdReverbEffectProps{genDefaultStdProps()}; void StdReverbEffectHandler::SetParami(ALCcontext *context, ReverbProps &props, ALenum param, int val) { switch(param) { case AL_REVERB_DECAY_HFLIMIT: if(!(val >= AL_REVERB_MIN_DECAY_HFLIMIT && val <= AL_REVERB_MAX_DECAY_HFLIMIT)) context->throw_error(AL_INVALID_VALUE, "EAX Reverb decay hflimit out of range"); props.DecayHFLimit = val != AL_FALSE; return; } context->throw_error(AL_INVALID_ENUM, "Invalid EAX reverb integer property {:#04x}", as_unsigned(param)); } void StdReverbEffectHandler::SetParamiv(ALCcontext *context, ReverbProps &props, ALenum param, const int *vals) { SetParami(context, props, param, *vals); } void StdReverbEffectHandler::SetParamf(ALCcontext *context, ReverbProps &props, ALenum param, float val) { switch(param) { case AL_REVERB_DENSITY: if(!(val >= AL_REVERB_MIN_DENSITY && val <= AL_REVERB_MAX_DENSITY)) context->throw_error(AL_INVALID_VALUE, "EAX Reverb density out of range"); props.Density = val; return; case AL_REVERB_DIFFUSION: if(!(val >= AL_REVERB_MIN_DIFFUSION && val <= AL_REVERB_MAX_DIFFUSION)) context->throw_error(AL_INVALID_VALUE, "EAX Reverb diffusion out of range"); props.Diffusion = val; return; case AL_REVERB_GAIN: if(!(val >= AL_REVERB_MIN_GAIN && val <= AL_REVERB_MAX_GAIN)) context->throw_error(AL_INVALID_VALUE, "EAX Reverb gain out of range"); props.Gain = val; return; case AL_REVERB_GAINHF: if(!(val >= AL_REVERB_MIN_GAINHF && val <= AL_REVERB_MAX_GAINHF)) context->throw_error(AL_INVALID_VALUE, "EAX Reverb gainhf out of range"); props.GainHF = val; return; case AL_REVERB_DECAY_TIME: if(!(val >= AL_REVERB_MIN_DECAY_TIME && val <= AL_REVERB_MAX_DECAY_TIME)) context->throw_error(AL_INVALID_VALUE, "EAX Reverb decay time out of range"); props.DecayTime = val; return; case AL_REVERB_DECAY_HFRATIO: if(!(val >= AL_REVERB_MIN_DECAY_HFRATIO && val <= AL_REVERB_MAX_DECAY_HFRATIO)) context->throw_error(AL_INVALID_VALUE, "EAX Reverb decay hfratio out of range"); props.DecayHFRatio = val; return; case AL_REVERB_REFLECTIONS_GAIN: if(!(val >= AL_REVERB_MIN_REFLECTIONS_GAIN && val <= AL_REVERB_MAX_REFLECTIONS_GAIN)) context->throw_error(AL_INVALID_VALUE, "EAX Reverb reflections gain out of range"); props.ReflectionsGain = val; return; case AL_REVERB_REFLECTIONS_DELAY: if(!(val >= AL_REVERB_MIN_REFLECTIONS_DELAY && val <= AL_REVERB_MAX_REFLECTIONS_DELAY)) context->throw_error(AL_INVALID_VALUE, "EAX Reverb reflections delay out of range"); props.ReflectionsDelay = val; return; case AL_REVERB_LATE_REVERB_GAIN: if(!(val >= AL_REVERB_MIN_LATE_REVERB_GAIN && val <= AL_REVERB_MAX_LATE_REVERB_GAIN)) context->throw_error(AL_INVALID_VALUE, "EAX Reverb late reverb gain out of range"); props.LateReverbGain = val; return; case AL_REVERB_LATE_REVERB_DELAY: if(!(val >= AL_REVERB_MIN_LATE_REVERB_DELAY && val <= AL_REVERB_MAX_LATE_REVERB_DELAY)) context->throw_error(AL_INVALID_VALUE, "EAX Reverb late reverb delay out of range"); props.LateReverbDelay = val; return; case AL_REVERB_AIR_ABSORPTION_GAINHF: if(!(val >= AL_REVERB_MIN_AIR_ABSORPTION_GAINHF && val <= AL_REVERB_MAX_AIR_ABSORPTION_GAINHF)) context->throw_error(AL_INVALID_VALUE, "EAX Reverb air absorption gainhf out of range"); props.AirAbsorptionGainHF = val; return; case AL_REVERB_ROOM_ROLLOFF_FACTOR: if(!(val >= AL_REVERB_MIN_ROOM_ROLLOFF_FACTOR && val <= AL_REVERB_MAX_ROOM_ROLLOFF_FACTOR)) context->throw_error(AL_INVALID_VALUE, "EAX Reverb room rolloff factor out of range"); props.RoomRolloffFactor = val; return; } context->throw_error(AL_INVALID_ENUM, "Invalid EAX reverb float property {:#04x}", as_unsigned(param)); } void StdReverbEffectHandler::SetParamfv(ALCcontext *context, ReverbProps &props, ALenum param, const float *vals) { SetParamf(context, props, param, *vals); } void StdReverbEffectHandler::GetParami(ALCcontext *context, const ReverbProps &props, ALenum param, int *val) { switch(param) { case AL_REVERB_DECAY_HFLIMIT: *val = props.DecayHFLimit; return; } context->throw_error(AL_INVALID_ENUM, "Invalid EAX reverb integer property {:#04x}", as_unsigned(param)); } void StdReverbEffectHandler::GetParamiv(ALCcontext *context, const ReverbProps &props, ALenum param, int *vals) { GetParami(context, props, param, vals); } void StdReverbEffectHandler::GetParamf(ALCcontext *context, const ReverbProps &props, ALenum param, float *val) { switch(param) { case AL_REVERB_DENSITY: *val = props.Density; return; case AL_REVERB_DIFFUSION: *val = props.Diffusion; return; case AL_REVERB_GAIN: *val = props.Gain; return; case AL_REVERB_GAINHF: *val = props.GainHF; return; case AL_REVERB_DECAY_TIME: *val = props.DecayTime; return; case AL_REVERB_DECAY_HFRATIO: *val = props.DecayHFRatio; return; case AL_REVERB_REFLECTIONS_GAIN: *val = props.ReflectionsGain; return; case AL_REVERB_REFLECTIONS_DELAY: *val = props.ReflectionsDelay; return; case AL_REVERB_LATE_REVERB_GAIN: *val = props.LateReverbGain; return; case AL_REVERB_LATE_REVERB_DELAY: *val = props.LateReverbDelay; return; case AL_REVERB_AIR_ABSORPTION_GAINHF: *val = props.AirAbsorptionGainHF; return; case AL_REVERB_ROOM_ROLLOFF_FACTOR: *val = props.RoomRolloffFactor; return; } context->throw_error(AL_INVALID_ENUM, "Invalid EAX reverb float property {:#04x}", as_unsigned(param)); } void StdReverbEffectHandler::GetParamfv(ALCcontext *context, const ReverbProps &props, ALenum param, float *vals) { GetParamf(context, props, param, vals); } #if ALSOFT_EAX namespace { class EaxReverbEffectException : public EaxException { public: explicit EaxReverbEffectException(const char* message) : EaxException{"EAX_REVERB_EFFECT", message} {} }; // EaxReverbEffectException struct EnvironmentValidator1 { void operator()(unsigned long ulEnvironment) const { eax_validate_range( "Environment", ulEnvironment, EAXREVERB_MINENVIRONMENT, EAX1REVERB_MAXENVIRONMENT); } }; // EnvironmentValidator1 struct VolumeValidator { void operator()(float volume) const { eax_validate_range( "Volume", volume, EAX1REVERB_MINVOLUME, EAX1REVERB_MAXVOLUME); } }; // VolumeValidator struct DecayTimeValidator { void operator()(float flDecayTime) const { eax_validate_range( "Decay Time", flDecayTime, EAXREVERB_MINDECAYTIME, EAXREVERB_MAXDECAYTIME); } }; // DecayTimeValidator struct DampingValidator { void operator()(float damping) const { eax_validate_range( "Damping", damping, EAX1REVERB_MINDAMPING, EAX1REVERB_MAXDAMPING); } }; // DampingValidator struct AllValidator1 { void operator()(const EAX_REVERBPROPERTIES& all) const { EnvironmentValidator1{}(all.environment); VolumeValidator{}(all.fVolume); DecayTimeValidator{}(all.fDecayTime_sec); DampingValidator{}(all.fDamping); } }; // AllValidator1 struct RoomValidator { void operator()(long lRoom) const { eax_validate_range( "Room", lRoom, EAXREVERB_MINROOM, EAXREVERB_MAXROOM); } }; // RoomValidator struct RoomHFValidator { void operator()(long lRoomHF) const { eax_validate_range( "Room HF", lRoomHF, EAXREVERB_MINROOMHF, EAXREVERB_MAXROOMHF); } }; // RoomHFValidator struct RoomRolloffFactorValidator { void operator()(float flRoomRolloffFactor) const { eax_validate_range( "Room Rolloff Factor", flRoomRolloffFactor, EAXREVERB_MINROOMROLLOFFFACTOR, EAXREVERB_MAXROOMROLLOFFFACTOR); } }; // RoomRolloffFactorValidator struct DecayHFRatioValidator { void operator()(float flDecayHFRatio) const { eax_validate_range( "Decay HF Ratio", flDecayHFRatio, EAXREVERB_MINDECAYHFRATIO, EAXREVERB_MAXDECAYHFRATIO); } }; // DecayHFRatioValidator struct ReflectionsValidator { void operator()(long lReflections) const { eax_validate_range( "Reflections", lReflections, EAXREVERB_MINREFLECTIONS, EAXREVERB_MAXREFLECTIONS); } }; // ReflectionsValidator struct ReflectionsDelayValidator { void operator()(float flReflectionsDelay) const { eax_validate_range( "Reflections Delay", flReflectionsDelay, EAXREVERB_MINREFLECTIONSDELAY, EAXREVERB_MAXREFLECTIONSDELAY); } }; // ReflectionsDelayValidator struct ReverbValidator { void operator()(long lReverb) const { eax_validate_range( "Reverb", lReverb, EAXREVERB_MINREVERB, EAXREVERB_MAXREVERB); } }; // ReverbValidator struct ReverbDelayValidator { void operator()(float flReverbDelay) const { eax_validate_range( "Reverb Delay", flReverbDelay, EAXREVERB_MINREVERBDELAY, EAXREVERB_MAXREVERBDELAY); } }; // ReverbDelayValidator struct EnvironmentSizeValidator { void operator()(float flEnvironmentSize) const { eax_validate_range( "Environment Size", flEnvironmentSize, EAXREVERB_MINENVIRONMENTSIZE, EAXREVERB_MAXENVIRONMENTSIZE); } }; // EnvironmentSizeValidator struct EnvironmentDiffusionValidator { void operator()(float flEnvironmentDiffusion) const { eax_validate_range( "Environment Diffusion", flEnvironmentDiffusion, EAXREVERB_MINENVIRONMENTDIFFUSION, EAXREVERB_MAXENVIRONMENTDIFFUSION); } }; // EnvironmentDiffusionValidator struct AirAbsorptionHFValidator { void operator()(float flAirAbsorptionHF) const { eax_validate_range( "Air Absorbtion HF", flAirAbsorptionHF, EAXREVERB_MINAIRABSORPTIONHF, EAXREVERB_MAXAIRABSORPTIONHF); } }; // AirAbsorptionHFValidator struct FlagsValidator2 { void operator()(unsigned long ulFlags) const { eax_validate_range( "Flags", ulFlags, 0UL, ~EAX2LISTENERFLAGS_RESERVED); } }; // FlagsValidator2 struct AllValidator2 { void operator()(const EAX20LISTENERPROPERTIES& all) const { RoomValidator{}(all.lRoom); RoomHFValidator{}(all.lRoomHF); RoomRolloffFactorValidator{}(all.flRoomRolloffFactor); DecayTimeValidator{}(all.flDecayTime); DecayHFRatioValidator{}(all.flDecayHFRatio); ReflectionsValidator{}(all.lReflections); ReflectionsDelayValidator{}(all.flReflectionsDelay); ReverbValidator{}(all.lReverb); ReverbDelayValidator{}(all.flReverbDelay); EnvironmentValidator1{}(all.dwEnvironment); EnvironmentSizeValidator{}(all.flEnvironmentSize); EnvironmentDiffusionValidator{}(all.flEnvironmentDiffusion); AirAbsorptionHFValidator{}(all.flAirAbsorptionHF); FlagsValidator2{}(all.dwFlags); } }; // AllValidator2 struct EnvironmentValidator3 { void operator()(unsigned long ulEnvironment) const { eax_validate_range( "Environment", ulEnvironment, EAXREVERB_MINENVIRONMENT, EAX30REVERB_MAXENVIRONMENT); } }; // EnvironmentValidator1 struct RoomLFValidator { void operator()(long lRoomLF) const { eax_validate_range( "Room LF", lRoomLF, EAXREVERB_MINROOMLF, EAXREVERB_MAXROOMLF); } }; // RoomLFValidator struct DecayLFRatioValidator { void operator()(float flDecayLFRatio) const { eax_validate_range( "Decay LF Ratio", flDecayLFRatio, EAXREVERB_MINDECAYLFRATIO, EAXREVERB_MAXDECAYLFRATIO); } }; // DecayLFRatioValidator struct VectorValidator { void operator()(const EAXVECTOR&) const {} }; // VectorValidator struct EchoTimeValidator { void operator()(float flEchoTime) const { eax_validate_range( "Echo Time", flEchoTime, EAXREVERB_MINECHOTIME, EAXREVERB_MAXECHOTIME); } }; // EchoTimeValidator struct EchoDepthValidator { void operator()(float flEchoDepth) const { eax_validate_range( "Echo Depth", flEchoDepth, EAXREVERB_MINECHODEPTH, EAXREVERB_MAXECHODEPTH); } }; // EchoDepthValidator struct ModulationTimeValidator { void operator()(float flModulationTime) const { eax_validate_range( "Modulation Time", flModulationTime, EAXREVERB_MINMODULATIONTIME, EAXREVERB_MAXMODULATIONTIME); } }; // ModulationTimeValidator struct ModulationDepthValidator { void operator()(float flModulationDepth) const { eax_validate_range( "Modulation Depth", flModulationDepth, EAXREVERB_MINMODULATIONDEPTH, EAXREVERB_MAXMODULATIONDEPTH); } }; // ModulationDepthValidator struct HFReferenceValidator { void operator()(float flHFReference) const { eax_validate_range( "HF Reference", flHFReference, EAXREVERB_MINHFREFERENCE, EAXREVERB_MAXHFREFERENCE); } }; // HFReferenceValidator struct LFReferenceValidator { void operator()(float flLFReference) const { eax_validate_range( "LF Reference", flLFReference, EAXREVERB_MINLFREFERENCE, EAXREVERB_MAXLFREFERENCE); } }; // LFReferenceValidator struct FlagsValidator3 { void operator()(unsigned long ulFlags) const { eax_validate_range( "Flags", ulFlags, 0UL, ~EAXREVERBFLAGS_RESERVED); } }; // FlagsValidator3 struct AllValidator3 { void operator()(const EAXREVERBPROPERTIES& all) const { EnvironmentValidator3{}(all.ulEnvironment); EnvironmentSizeValidator{}(all.flEnvironmentSize); EnvironmentDiffusionValidator{}(all.flEnvironmentDiffusion); RoomValidator{}(all.lRoom); RoomHFValidator{}(all.lRoomHF); RoomLFValidator{}(all.lRoomLF); DecayTimeValidator{}(all.flDecayTime); DecayHFRatioValidator{}(all.flDecayHFRatio); DecayLFRatioValidator{}(all.flDecayLFRatio); ReflectionsValidator{}(all.lReflections); ReflectionsDelayValidator{}(all.flReflectionsDelay); VectorValidator{}(all.vReflectionsPan); ReverbValidator{}(all.lReverb); ReverbDelayValidator{}(all.flReverbDelay); VectorValidator{}(all.vReverbPan); EchoTimeValidator{}(all.flEchoTime); EchoDepthValidator{}(all.flEchoDepth); ModulationTimeValidator{}(all.flModulationTime); ModulationDepthValidator{}(all.flModulationDepth); AirAbsorptionHFValidator{}(all.flAirAbsorptionHF); HFReferenceValidator{}(all.flHFReference); LFReferenceValidator{}(all.flLFReference); RoomRolloffFactorValidator{}(all.flRoomRolloffFactor); FlagsValidator3{}(all.ulFlags); } }; // AllValidator3 struct EnvironmentDeferrer2 { void operator()(EAX20LISTENERPROPERTIES& props, unsigned long dwEnvironment) const { props = EAX2REVERB_PRESETS[dwEnvironment]; } }; // EnvironmentDeferrer2 struct EnvironmentSizeDeferrer2 { void operator()(EAX20LISTENERPROPERTIES& props, float flEnvironmentSize) const { if (props.flEnvironmentSize == flEnvironmentSize) { return; } const auto scale = flEnvironmentSize / props.flEnvironmentSize; props.flEnvironmentSize = flEnvironmentSize; if ((props.dwFlags & EAX2LISTENERFLAGS_DECAYTIMESCALE) != 0) { props.flDecayTime = std::clamp( props.flDecayTime * scale, EAXREVERB_MINDECAYTIME, EAXREVERB_MAXDECAYTIME); } if ((props.dwFlags & EAX2LISTENERFLAGS_REFLECTIONSSCALE) != 0 && (props.dwFlags & EAX2LISTENERFLAGS_REFLECTIONSDELAYSCALE) != 0) { props.lReflections = std::clamp( props.lReflections - static_cast(gain_to_level_mb(scale)), EAXREVERB_MINREFLECTIONS, EAXREVERB_MAXREFLECTIONS); } if ((props.dwFlags & EAX2LISTENERFLAGS_REFLECTIONSDELAYSCALE) != 0) { props.flReflectionsDelay = std::clamp( props.flReflectionsDelay * scale, EAXREVERB_MINREFLECTIONSDELAY, EAXREVERB_MAXREFLECTIONSDELAY); } if ((props.dwFlags & EAX2LISTENERFLAGS_REVERBSCALE) != 0) { const auto log_scalar = ((props.dwFlags & EAXREVERBFLAGS_DECAYTIMESCALE) != 0) ? 2'000.0F : 3'000.0F; props.lReverb = std::clamp( props.lReverb - static_cast(std::log10(scale) * log_scalar), EAXREVERB_MINREVERB, EAXREVERB_MAXREVERB); } if ((props.dwFlags & EAX2LISTENERFLAGS_REVERBDELAYSCALE) != 0) { props.flReverbDelay = std::clamp( props.flReverbDelay * scale, EAXREVERB_MINREVERBDELAY, EAXREVERB_MAXREVERBDELAY); } } }; // EnvironmentSizeDeferrer2 struct EnvironmentDeferrer3 { void operator()(EAXREVERBPROPERTIES& props, unsigned long ulEnvironment) const { if (ulEnvironment == EAX_ENVIRONMENT_UNDEFINED) { props.ulEnvironment = EAX_ENVIRONMENT_UNDEFINED; return; } props = EAXREVERB_PRESETS[ulEnvironment]; } }; // EnvironmentDeferrer3 struct EnvironmentSizeDeferrer3 { void operator()(EAXREVERBPROPERTIES& props, float flEnvironmentSize) const { if (props.flEnvironmentSize == flEnvironmentSize) { return; } const auto scale = flEnvironmentSize / props.flEnvironmentSize; props.ulEnvironment = EAX_ENVIRONMENT_UNDEFINED; props.flEnvironmentSize = flEnvironmentSize; if ((props.ulFlags & EAXREVERBFLAGS_DECAYTIMESCALE) != 0) { props.flDecayTime = std::clamp( props.flDecayTime * scale, EAXREVERB_MINDECAYTIME, EAXREVERB_MAXDECAYTIME); } if ((props.ulFlags & EAXREVERBFLAGS_REFLECTIONSSCALE) != 0 && (props.ulFlags & EAXREVERBFLAGS_REFLECTIONSDELAYSCALE) != 0) { props.lReflections = std::clamp( props.lReflections - static_cast(gain_to_level_mb(scale)), EAXREVERB_MINREFLECTIONS, EAXREVERB_MAXREFLECTIONS); } if ((props.ulFlags & EAXREVERBFLAGS_REFLECTIONSDELAYSCALE) != 0) { props.flReflectionsDelay = std::clamp( props.flReflectionsDelay * scale, EAXREVERB_MINREFLECTIONSDELAY, EAXREVERB_MAXREFLECTIONSDELAY); } if ((props.ulFlags & EAXREVERBFLAGS_REVERBSCALE) != 0) { const auto log_scalar = ((props.ulFlags & EAXREVERBFLAGS_DECAYTIMESCALE) != 0) ? 2'000.0F : 3'000.0F; props.lReverb = std::clamp( props.lReverb - static_cast(std::log10(scale) * log_scalar), EAXREVERB_MINREVERB, EAXREVERB_MAXREVERB); } if ((props.ulFlags & EAXREVERBFLAGS_REVERBDELAYSCALE) != 0) { props.flReverbDelay = std::clamp( props.flReverbDelay * scale, EAXREVERB_MINREVERBDELAY, EAXREVERB_MAXREVERBDELAY); } if ((props.ulFlags & EAXREVERBFLAGS_ECHOTIMESCALE) != 0) { props.flEchoTime = std::clamp( props.flEchoTime * scale, EAXREVERB_MINECHOTIME, EAXREVERB_MAXECHOTIME); } if ((props.ulFlags & EAXREVERBFLAGS_MODULATIONTIMESCALE) != 0) { props.flModulationTime = std::clamp( props.flModulationTime * scale, EAXREVERB_MINMODULATIONTIME, EAXREVERB_MAXMODULATIONTIME); } } }; // EnvironmentSizeDeferrer3 } // namespace struct EaxReverbCommitter::Exception : public EaxReverbEffectException { using EaxReverbEffectException::EaxReverbEffectException; }; [[noreturn]] void EaxReverbCommitter::fail(const char* message) { throw Exception{message}; } void EaxReverbCommitter::translate(const EAX_REVERBPROPERTIES& src, EAXREVERBPROPERTIES& dst) noexcept { assert(src.environment <= EAX1REVERB_MAXENVIRONMENT); dst = EAXREVERB_PRESETS[src.environment]; dst.flDecayTime = src.fDecayTime_sec; dst.flDecayHFRatio = src.fDamping; dst.lReverb = static_cast(std::min(gain_to_level_mb(src.fVolume), 0.0f)); } void EaxReverbCommitter::translate(const EAX20LISTENERPROPERTIES& src, EAXREVERBPROPERTIES& dst) noexcept { assert(src.dwEnvironment <= EAX1REVERB_MAXENVIRONMENT); dst = EAXREVERB_PRESETS[src.dwEnvironment]; dst.ulEnvironment = src.dwEnvironment; dst.flEnvironmentSize = src.flEnvironmentSize; dst.flEnvironmentDiffusion = src.flEnvironmentDiffusion; dst.lRoom = src.lRoom; dst.lRoomHF = src.lRoomHF; dst.flDecayTime = src.flDecayTime; dst.flDecayHFRatio = src.flDecayHFRatio; dst.lReflections = src.lReflections; dst.flReflectionsDelay = src.flReflectionsDelay; dst.lReverb = src.lReverb; dst.flReverbDelay = src.flReverbDelay; dst.flAirAbsorptionHF = src.flAirAbsorptionHF; dst.flRoomRolloffFactor = src.flRoomRolloffFactor; dst.ulFlags = src.dwFlags; } bool EaxReverbCommitter::commit(const EAX_REVERBPROPERTIES &props) { EAXREVERBPROPERTIES dst{}; translate(props, dst); return commit(dst); } bool EaxReverbCommitter::commit(const EAX20LISTENERPROPERTIES &props) { EAXREVERBPROPERTIES dst{}; translate(props, dst); return commit(dst); } bool EaxReverbCommitter::commit(const EAXREVERBPROPERTIES &props) { if(auto *cur = std::get_if(&mEaxProps); cur && *cur == props) return false; mEaxProps = props; const auto size = props.flEnvironmentSize; const auto density = (size * size * size) / 16.0f; mAlProps = [&]{ ReverbProps ret{}; ret.Density = std::min(density, AL_EAXREVERB_MAX_DENSITY); ret.Diffusion = props.flEnvironmentDiffusion; ret.Gain = level_mb_to_gain(static_cast(props.lRoom)); ret.GainHF = level_mb_to_gain(static_cast(props.lRoomHF)); ret.GainLF = level_mb_to_gain(static_cast(props.lRoomLF)); ret.DecayTime = props.flDecayTime; ret.DecayHFRatio = props.flDecayHFRatio; ret.DecayLFRatio = props.flDecayLFRatio; ret.ReflectionsGain = level_mb_to_gain(static_cast(props.lReflections)); ret.ReflectionsDelay = props.flReflectionsDelay; ret.ReflectionsPan = {props.vReflectionsPan.x, props.vReflectionsPan.y, props.vReflectionsPan.z}; ret.LateReverbGain = level_mb_to_gain(static_cast(props.lReverb)); ret.LateReverbDelay = props.flReverbDelay; ret.LateReverbPan = {props.vReverbPan.x, props.vReverbPan.y, props.vReverbPan.z}; ret.EchoTime = props.flEchoTime; ret.EchoDepth = props.flEchoDepth; ret.ModulationTime = props.flModulationTime; ret.ModulationDepth = props.flModulationDepth; ret.AirAbsorptionGainHF = level_mb_to_gain(props.flAirAbsorptionHF); ret.HFReference = props.flHFReference; ret.LFReference = props.flLFReference; ret.RoomRolloffFactor = props.flRoomRolloffFactor; ret.DecayHFLimit = ((props.ulFlags & EAXREVERBFLAGS_DECAYHFLIMIT) != 0); return ret; }(); return true; } void EaxReverbCommitter::SetDefaults(EAX_REVERBPROPERTIES &props) { props = EAX1REVERB_PRESETS[EAX_ENVIRONMENT_GENERIC]; } void EaxReverbCommitter::SetDefaults(EAX20LISTENERPROPERTIES &props) { props = EAX2REVERB_PRESETS[EAX2_ENVIRONMENT_GENERIC]; props.lRoom = -10'000L; } void EaxReverbCommitter::SetDefaults(EAXREVERBPROPERTIES &props) { props = EAXREVERB_PRESETS[EAX_ENVIRONMENT_GENERIC]; } void EaxReverbCommitter::SetDefaults(EaxEffectProps &props) { SetDefaults(props.emplace()); } void EaxReverbCommitter::Get(const EaxCall &call, const EAX_REVERBPROPERTIES &props) { switch(call.get_property_id()) { case DSPROPERTY_EAX_ALL: call.set_value(props); break; case DSPROPERTY_EAX_ENVIRONMENT: call.set_value(props.environment); break; case DSPROPERTY_EAX_VOLUME: call.set_value(props.fVolume); break; case DSPROPERTY_EAX_DECAYTIME: call.set_value(props.fDecayTime_sec); break; case DSPROPERTY_EAX_DAMPING: call.set_value(props.fDamping); break; default: fail_unknown_property_id(); } } void EaxReverbCommitter::Get(const EaxCall &call, const EAX20LISTENERPROPERTIES &props) { switch(call.get_property_id()) { case DSPROPERTY_EAX20LISTENER_NONE: break; case DSPROPERTY_EAX20LISTENER_ALLPARAMETERS: call.set_value(props); break; case DSPROPERTY_EAX20LISTENER_ROOM: call.set_value(props.lRoom); break; case DSPROPERTY_EAX20LISTENER_ROOMHF: call.set_value(props.lRoomHF); break; case DSPROPERTY_EAX20LISTENER_ROOMROLLOFFFACTOR: call.set_value(props.flRoomRolloffFactor); break; case DSPROPERTY_EAX20LISTENER_DECAYTIME: call.set_value(props.flDecayTime); break; case DSPROPERTY_EAX20LISTENER_DECAYHFRATIO: call.set_value(props.flDecayHFRatio); break; case DSPROPERTY_EAX20LISTENER_REFLECTIONS: call.set_value(props.lReflections); break; case DSPROPERTY_EAX20LISTENER_REFLECTIONSDELAY: call.set_value(props.flReflectionsDelay); break; case DSPROPERTY_EAX20LISTENER_REVERB: call.set_value(props.lReverb); break; case DSPROPERTY_EAX20LISTENER_REVERBDELAY: call.set_value(props.flReverbDelay); break; case DSPROPERTY_EAX20LISTENER_ENVIRONMENT: call.set_value(props.dwEnvironment); break; case DSPROPERTY_EAX20LISTENER_ENVIRONMENTSIZE: call.set_value(props.flEnvironmentSize); break; case DSPROPERTY_EAX20LISTENER_ENVIRONMENTDIFFUSION: call.set_value(props.flEnvironmentDiffusion); break; case DSPROPERTY_EAX20LISTENER_AIRABSORPTIONHF: call.set_value(props.flAirAbsorptionHF); break; case DSPROPERTY_EAX20LISTENER_FLAGS: call.set_value(props.dwFlags); break; default: fail_unknown_property_id(); } } void EaxReverbCommitter::Get(const EaxCall &call, const EAXREVERBPROPERTIES &props) { switch(call.get_property_id()) { case EAXREVERB_NONE: break; case EAXREVERB_ALLPARAMETERS: call.set_value(props); break; case EAXREVERB_ENVIRONMENT: call.set_value(props.ulEnvironment); break; case EAXREVERB_ENVIRONMENTSIZE: call.set_value(props.flEnvironmentSize); break; case EAXREVERB_ENVIRONMENTDIFFUSION: call.set_value(props.flEnvironmentDiffusion); break; case EAXREVERB_ROOM: call.set_value(props.lRoom); break; case EAXREVERB_ROOMHF: call.set_value(props.lRoomHF); break; case EAXREVERB_ROOMLF: call.set_value(props.lRoomLF); break; case EAXREVERB_DECAYTIME: call.set_value(props.flDecayTime); break; case EAXREVERB_DECAYHFRATIO: call.set_value(props.flDecayHFRatio); break; case EAXREVERB_DECAYLFRATIO: call.set_value(props.flDecayLFRatio); break; case EAXREVERB_REFLECTIONS: call.set_value(props.lReflections); break; case EAXREVERB_REFLECTIONSDELAY: call.set_value(props.flReflectionsDelay); break; case EAXREVERB_REFLECTIONSPAN: call.set_value(props.vReflectionsPan); break; case EAXREVERB_REVERB: call.set_value(props.lReverb); break; case EAXREVERB_REVERBDELAY: call.set_value(props.flReverbDelay); break; case EAXREVERB_REVERBPAN: call.set_value(props.vReverbPan); break; case EAXREVERB_ECHOTIME: call.set_value(props.flEchoTime); break; case EAXREVERB_ECHODEPTH: call.set_value(props.flEchoDepth); break; case EAXREVERB_MODULATIONTIME: call.set_value(props.flModulationTime); break; case EAXREVERB_MODULATIONDEPTH: call.set_value(props.flModulationDepth); break; case EAXREVERB_AIRABSORPTIONHF: call.set_value(props.flAirAbsorptionHF); break; case EAXREVERB_HFREFERENCE: call.set_value(props.flHFReference); break; case EAXREVERB_LFREFERENCE: call.set_value(props.flLFReference); break; case EAXREVERB_ROOMROLLOFFFACTOR: call.set_value(props.flRoomRolloffFactor); break; case EAXREVERB_FLAGS: call.set_value(props.ulFlags); break; default: fail_unknown_property_id(); } } void EaxReverbCommitter::Set(const EaxCall &call, EAX_REVERBPROPERTIES &props) { switch(call.get_property_id()) { case DSPROPERTY_EAX_ALL: defer(call, props); break; case DSPROPERTY_EAX_ENVIRONMENT: defer(call, props.environment); break; case DSPROPERTY_EAX_VOLUME: defer(call, props.fVolume); break; case DSPROPERTY_EAX_DECAYTIME: defer(call, props.fDecayTime_sec); break; case DSPROPERTY_EAX_DAMPING: defer(call, props.fDamping); break; default: fail_unknown_property_id(); } } void EaxReverbCommitter::Set(const EaxCall &call, EAX20LISTENERPROPERTIES &props) { switch(call.get_property_id()) { case DSPROPERTY_EAX20LISTENER_NONE: break; case DSPROPERTY_EAX20LISTENER_ALLPARAMETERS: defer(call, props); break; case DSPROPERTY_EAX20LISTENER_ROOM: defer(call, props.lRoom); break; case DSPROPERTY_EAX20LISTENER_ROOMHF: defer(call, props.lRoomHF); break; case DSPROPERTY_EAX20LISTENER_ROOMROLLOFFFACTOR: defer(call, props.flRoomRolloffFactor); break; case DSPROPERTY_EAX20LISTENER_DECAYTIME: defer(call, props.flDecayTime); break; case DSPROPERTY_EAX20LISTENER_DECAYHFRATIO: defer(call, props.flDecayHFRatio); break; case DSPROPERTY_EAX20LISTENER_REFLECTIONS: defer(call, props.lReflections); break; case DSPROPERTY_EAX20LISTENER_REFLECTIONSDELAY: defer(call, props.flReverbDelay); break; case DSPROPERTY_EAX20LISTENER_REVERB: defer(call, props.lReverb); break; case DSPROPERTY_EAX20LISTENER_REVERBDELAY: defer(call, props.flReverbDelay); break; case DSPROPERTY_EAX20LISTENER_ENVIRONMENT: defer(call, props, props.dwEnvironment); break; case DSPROPERTY_EAX20LISTENER_ENVIRONMENTSIZE: defer(call, props, props.flEnvironmentSize); break; case DSPROPERTY_EAX20LISTENER_ENVIRONMENTDIFFUSION: defer(call, props.flEnvironmentDiffusion); break; case DSPROPERTY_EAX20LISTENER_AIRABSORPTIONHF: defer(call, props.flAirAbsorptionHF); break; case DSPROPERTY_EAX20LISTENER_FLAGS: defer(call, props.dwFlags); break; default: fail_unknown_property_id(); } } void EaxReverbCommitter::Set(const EaxCall &call, EAXREVERBPROPERTIES &props) { switch(call.get_property_id()) { case EAXREVERB_NONE: break; case EAXREVERB_ALLPARAMETERS: defer(call, props); break; case EAXREVERB_ENVIRONMENT: defer(call, props, props.ulEnvironment); break; case EAXREVERB_ENVIRONMENTSIZE: defer(call, props, props.flEnvironmentSize); break; case EAXREVERB_ENVIRONMENTDIFFUSION: defer3(call, props, props.flEnvironmentDiffusion); break; case EAXREVERB_ROOM: defer3(call, props, props.lRoom); break; case EAXREVERB_ROOMHF: defer3(call, props, props.lRoomHF); break; case EAXREVERB_ROOMLF: defer3(call, props, props.lRoomLF); break; case EAXREVERB_DECAYTIME: defer3(call, props, props.flDecayTime); break; case EAXREVERB_DECAYHFRATIO: defer3(call, props, props.flDecayHFRatio); break; case EAXREVERB_DECAYLFRATIO: defer3(call, props, props.flDecayLFRatio); break; case EAXREVERB_REFLECTIONS: defer3(call, props, props.lReflections); break; case EAXREVERB_REFLECTIONSDELAY: defer3(call, props, props.flReflectionsDelay); break; case EAXREVERB_REFLECTIONSPAN: defer3(call, props, props.vReflectionsPan); break; case EAXREVERB_REVERB: defer3(call, props, props.lReverb); break; case EAXREVERB_REVERBDELAY: defer3(call, props, props.flReverbDelay); break; case EAXREVERB_REVERBPAN: defer3(call, props, props.vReverbPan); break; case EAXREVERB_ECHOTIME: defer3(call, props, props.flEchoTime); break; case EAXREVERB_ECHODEPTH: defer3(call, props, props.flEchoDepth); break; case EAXREVERB_MODULATIONTIME: defer3(call, props, props.flModulationTime); break; case EAXREVERB_MODULATIONDEPTH: defer3(call, props, props.flModulationDepth); break; case EAXREVERB_AIRABSORPTIONHF: defer3(call, props, props.flAirAbsorptionHF); break; case EAXREVERB_HFREFERENCE: defer3(call, props, props.flHFReference); break; case EAXREVERB_LFREFERENCE: defer3(call, props, props.flLFReference); break; case EAXREVERB_ROOMROLLOFFFACTOR: defer3(call, props, props.flRoomRolloffFactor); break; case EAXREVERB_FLAGS: defer3(call, props, props.ulFlags); break; default: fail_unknown_property_id(); } } #endif // ALSOFT_EAX openal-soft-1.24.2/al/effects/vmorpher.cpp000066400000000000000000000354371474041540300204260ustar00rootroot00000000000000 #include "config.h" #include #include #include "AL/al.h" #include "AL/efx.h" #include "alc/context.h" #include "alnumeric.h" #include "effects.h" #if ALSOFT_EAX #include #include "al/eax/effect.h" #include "al/eax/exception.h" #include "al/eax/utils.h" #endif // ALSOFT_EAX namespace { constexpr std::optional PhenomeFromEnum(ALenum val) noexcept { #define HANDLE_PHENOME(x) case AL_VOCAL_MORPHER_PHONEME_ ## x: \ return VMorpherPhenome::x switch(val) { HANDLE_PHENOME(A); HANDLE_PHENOME(E); HANDLE_PHENOME(I); HANDLE_PHENOME(O); HANDLE_PHENOME(U); HANDLE_PHENOME(AA); HANDLE_PHENOME(AE); HANDLE_PHENOME(AH); HANDLE_PHENOME(AO); HANDLE_PHENOME(EH); HANDLE_PHENOME(ER); HANDLE_PHENOME(IH); HANDLE_PHENOME(IY); HANDLE_PHENOME(UH); HANDLE_PHENOME(UW); HANDLE_PHENOME(B); HANDLE_PHENOME(D); HANDLE_PHENOME(F); HANDLE_PHENOME(G); HANDLE_PHENOME(J); HANDLE_PHENOME(K); HANDLE_PHENOME(L); HANDLE_PHENOME(M); HANDLE_PHENOME(N); HANDLE_PHENOME(P); HANDLE_PHENOME(R); HANDLE_PHENOME(S); HANDLE_PHENOME(T); HANDLE_PHENOME(V); HANDLE_PHENOME(Z); } return std::nullopt; #undef HANDLE_PHENOME } constexpr ALenum EnumFromPhenome(VMorpherPhenome phenome) { #define HANDLE_PHENOME(x) case VMorpherPhenome::x: return AL_VOCAL_MORPHER_PHONEME_ ## x switch(phenome) { HANDLE_PHENOME(A); HANDLE_PHENOME(E); HANDLE_PHENOME(I); HANDLE_PHENOME(O); HANDLE_PHENOME(U); HANDLE_PHENOME(AA); HANDLE_PHENOME(AE); HANDLE_PHENOME(AH); HANDLE_PHENOME(AO); HANDLE_PHENOME(EH); HANDLE_PHENOME(ER); HANDLE_PHENOME(IH); HANDLE_PHENOME(IY); HANDLE_PHENOME(UH); HANDLE_PHENOME(UW); HANDLE_PHENOME(B); HANDLE_PHENOME(D); HANDLE_PHENOME(F); HANDLE_PHENOME(G); HANDLE_PHENOME(J); HANDLE_PHENOME(K); HANDLE_PHENOME(L); HANDLE_PHENOME(M); HANDLE_PHENOME(N); HANDLE_PHENOME(P); HANDLE_PHENOME(R); HANDLE_PHENOME(S); HANDLE_PHENOME(T); HANDLE_PHENOME(V); HANDLE_PHENOME(Z); } throw std::runtime_error{fmt::format("Invalid phenome: {}", int{al::to_underlying(phenome)})}; #undef HANDLE_PHENOME } constexpr std::optional WaveformFromEmum(ALenum value) noexcept { switch(value) { case AL_VOCAL_MORPHER_WAVEFORM_SINUSOID: return VMorpherWaveform::Sinusoid; case AL_VOCAL_MORPHER_WAVEFORM_TRIANGLE: return VMorpherWaveform::Triangle; case AL_VOCAL_MORPHER_WAVEFORM_SAWTOOTH: return VMorpherWaveform::Sawtooth; } return std::nullopt; } constexpr ALenum EnumFromWaveform(VMorpherWaveform type) { switch(type) { case VMorpherWaveform::Sinusoid: return AL_VOCAL_MORPHER_WAVEFORM_SINUSOID; case VMorpherWaveform::Triangle: return AL_VOCAL_MORPHER_WAVEFORM_TRIANGLE; case VMorpherWaveform::Sawtooth: return AL_VOCAL_MORPHER_WAVEFORM_SAWTOOTH; } throw std::runtime_error{fmt::format("Invalid vocal morpher waveform: {}", int{al::to_underlying(type)})}; } constexpr EffectProps genDefaultProps() noexcept { VmorpherProps props{}; props.Rate = AL_VOCAL_MORPHER_DEFAULT_RATE; props.PhonemeA = PhenomeFromEnum(AL_VOCAL_MORPHER_DEFAULT_PHONEMEA).value(); props.PhonemeB = PhenomeFromEnum(AL_VOCAL_MORPHER_DEFAULT_PHONEMEB).value(); props.PhonemeACoarseTuning = AL_VOCAL_MORPHER_DEFAULT_PHONEMEA_COARSE_TUNING; props.PhonemeBCoarseTuning = AL_VOCAL_MORPHER_DEFAULT_PHONEMEB_COARSE_TUNING; props.Waveform = WaveformFromEmum(AL_VOCAL_MORPHER_DEFAULT_WAVEFORM).value(); return props; } } // namespace const EffectProps VmorpherEffectProps{genDefaultProps()}; void VmorpherEffectHandler::SetParami(ALCcontext *context, VmorpherProps &props, ALenum param, int val) { switch(param) { case AL_VOCAL_MORPHER_PHONEMEA: if(auto phenomeopt = PhenomeFromEnum(val)) props.PhonemeA = *phenomeopt; else context->throw_error(AL_INVALID_VALUE, "Vocal morpher phoneme-a out of range: {:#04x}", as_unsigned(val)); return; case AL_VOCAL_MORPHER_PHONEMEA_COARSE_TUNING: if(!(val >= AL_VOCAL_MORPHER_MIN_PHONEMEA_COARSE_TUNING && val <= AL_VOCAL_MORPHER_MAX_PHONEMEA_COARSE_TUNING)) context->throw_error(AL_INVALID_VALUE, "Vocal morpher phoneme-a coarse tuning out of range"); props.PhonemeACoarseTuning = val; return; case AL_VOCAL_MORPHER_PHONEMEB: if(auto phenomeopt = PhenomeFromEnum(val)) props.PhonemeB = *phenomeopt; else context->throw_error(AL_INVALID_VALUE, "Vocal morpher phoneme-b out of range: {:#04x}", as_unsigned(val)); return; case AL_VOCAL_MORPHER_PHONEMEB_COARSE_TUNING: if(!(val >= AL_VOCAL_MORPHER_MIN_PHONEMEB_COARSE_TUNING && val <= AL_VOCAL_MORPHER_MAX_PHONEMEB_COARSE_TUNING)) context->throw_error(AL_INVALID_VALUE, "Vocal morpher phoneme-b coarse tuning out of range"); props.PhonemeBCoarseTuning = val; return; case AL_VOCAL_MORPHER_WAVEFORM: if(auto formopt = WaveformFromEmum(val)) props.Waveform = *formopt; else context->throw_error(AL_INVALID_VALUE, "Vocal morpher waveform out of range: {:#04x}", as_unsigned(val)); return; } context->throw_error(AL_INVALID_ENUM, "Invalid vocal morpher integer property {:#04x}", as_unsigned(param)); } void VmorpherEffectHandler::SetParamiv(ALCcontext *context, VmorpherProps &props, ALenum param, const int *vals) { SetParami(context, props, param, *vals); } void VmorpherEffectHandler::SetParamf(ALCcontext *context, VmorpherProps &props, ALenum param, float val) { switch(param) { case AL_VOCAL_MORPHER_RATE: if(!(val >= AL_VOCAL_MORPHER_MIN_RATE && val <= AL_VOCAL_MORPHER_MAX_RATE)) context->throw_error(AL_INVALID_VALUE, "Vocal morpher rate out of range"); props.Rate = val; return; } context->throw_error(AL_INVALID_ENUM, "Invalid vocal morpher float property {:#04x}", as_unsigned(param)); } void VmorpherEffectHandler::SetParamfv(ALCcontext *context, VmorpherProps &props, ALenum param, const float *vals) { SetParamf(context, props, param, *vals); } void VmorpherEffectHandler::GetParami(ALCcontext *context, const VmorpherProps &props, ALenum param, int* val) { switch(param) { case AL_VOCAL_MORPHER_PHONEMEA: *val = EnumFromPhenome(props.PhonemeA); return; case AL_VOCAL_MORPHER_PHONEMEA_COARSE_TUNING: *val = props.PhonemeACoarseTuning; return; case AL_VOCAL_MORPHER_PHONEMEB: *val = EnumFromPhenome(props.PhonemeB); return; case AL_VOCAL_MORPHER_PHONEMEB_COARSE_TUNING: *val = props.PhonemeBCoarseTuning; return; case AL_VOCAL_MORPHER_WAVEFORM: *val = EnumFromWaveform(props.Waveform); return; } context->throw_error(AL_INVALID_ENUM, "Invalid vocal morpher integer property {:#04x}", as_unsigned(param)); } void VmorpherEffectHandler::GetParamiv(ALCcontext *context, const VmorpherProps &props, ALenum param, int *vals) { GetParami(context, props, param, vals); } void VmorpherEffectHandler::GetParamf(ALCcontext *context, const VmorpherProps &props, ALenum param, float *val) { switch(param) { case AL_VOCAL_MORPHER_RATE: *val = props.Rate; return; } context->throw_error(AL_INVALID_ENUM, "Invalid vocal morpher float property {:#04x}", as_unsigned(param)); } void VmorpherEffectHandler::GetParamfv(ALCcontext *context, const VmorpherProps &props, ALenum param, float *vals) { GetParamf(context, props, param, vals); } #if ALSOFT_EAX namespace { using VocalMorpherCommitter = EaxCommitter; struct PhonemeAValidator { void operator()(unsigned long ulPhonemeA) const { eax_validate_range( "Phoneme A", ulPhonemeA, EAXVOCALMORPHER_MINPHONEMEA, EAXVOCALMORPHER_MAXPHONEMEA); } }; // PhonemeAValidator struct PhonemeACoarseTuningValidator { void operator()(long lPhonemeACoarseTuning) const { eax_validate_range( "Phoneme A Coarse Tuning", lPhonemeACoarseTuning, EAXVOCALMORPHER_MINPHONEMEACOARSETUNING, EAXVOCALMORPHER_MAXPHONEMEACOARSETUNING); } }; // PhonemeACoarseTuningValidator struct PhonemeBValidator { void operator()(unsigned long ulPhonemeB) const { eax_validate_range( "Phoneme B", ulPhonemeB, EAXVOCALMORPHER_MINPHONEMEB, EAXVOCALMORPHER_MAXPHONEMEB); } }; // PhonemeBValidator struct PhonemeBCoarseTuningValidator { void operator()(long lPhonemeBCoarseTuning) const { eax_validate_range( "Phoneme B Coarse Tuning", lPhonemeBCoarseTuning, EAXVOCALMORPHER_MINPHONEMEBCOARSETUNING, EAXVOCALMORPHER_MAXPHONEMEBCOARSETUNING); } }; // PhonemeBCoarseTuningValidator struct WaveformValidator { void operator()(unsigned long ulWaveform) const { eax_validate_range( "Waveform", ulWaveform, EAXVOCALMORPHER_MINWAVEFORM, EAXVOCALMORPHER_MAXWAVEFORM); } }; // WaveformValidator struct RateValidator { void operator()(float flRate) const { eax_validate_range( "Rate", flRate, EAXVOCALMORPHER_MINRATE, EAXVOCALMORPHER_MAXRATE); } }; // RateValidator struct AllValidator { void operator()(const EAXVOCALMORPHERPROPERTIES& all) const { PhonemeAValidator{}(all.ulPhonemeA); PhonemeACoarseTuningValidator{}(all.lPhonemeACoarseTuning); PhonemeBValidator{}(all.ulPhonemeB); PhonemeBCoarseTuningValidator{}(all.lPhonemeBCoarseTuning); WaveformValidator{}(all.ulWaveform); RateValidator{}(all.flRate); } }; // AllValidator } // namespace template<> struct VocalMorpherCommitter::Exception : public EaxException { explicit Exception(const char *message) : EaxException{"EAX_VOCAL_MORPHER_EFFECT", message} { } }; template<> [[noreturn]] void VocalMorpherCommitter::fail(const char *message) { throw Exception{message}; } bool EaxVocalMorpherCommitter::commit(const EAXVOCALMORPHERPROPERTIES &props) { if(auto *cur = std::get_if(&mEaxProps); cur && *cur == props) return false; mEaxProps = props; auto get_phoneme = [](unsigned long phoneme) noexcept { #define HANDLE_PHENOME(x) case x: return VMorpherPhenome::x switch(phoneme) { HANDLE_PHENOME(A); HANDLE_PHENOME(E); HANDLE_PHENOME(I); HANDLE_PHENOME(O); HANDLE_PHENOME(U); HANDLE_PHENOME(AA); HANDLE_PHENOME(AE); HANDLE_PHENOME(AH); HANDLE_PHENOME(AO); HANDLE_PHENOME(EH); HANDLE_PHENOME(ER); HANDLE_PHENOME(IH); HANDLE_PHENOME(IY); HANDLE_PHENOME(UH); HANDLE_PHENOME(UW); HANDLE_PHENOME(B); HANDLE_PHENOME(D); HANDLE_PHENOME(F); HANDLE_PHENOME(G); HANDLE_PHENOME(J); HANDLE_PHENOME(K); HANDLE_PHENOME(L); HANDLE_PHENOME(M); HANDLE_PHENOME(N); HANDLE_PHENOME(P); HANDLE_PHENOME(R); HANDLE_PHENOME(S); HANDLE_PHENOME(T); HANDLE_PHENOME(V); HANDLE_PHENOME(Z); } return VMorpherPhenome::A; #undef HANDLE_PHENOME }; auto get_waveform = [](unsigned long form) noexcept { if(form == EAX_VOCALMORPHER_SINUSOID) return VMorpherWaveform::Sinusoid; if(form == EAX_VOCALMORPHER_TRIANGLE) return VMorpherWaveform::Triangle; if(form == EAX_VOCALMORPHER_SAWTOOTH) return VMorpherWaveform::Sawtooth; return VMorpherWaveform::Sinusoid; }; mAlProps = [&]{ VmorpherProps ret{}; ret.PhonemeA = get_phoneme(props.ulPhonemeA); ret.PhonemeACoarseTuning = static_cast(props.lPhonemeACoarseTuning); ret.PhonemeB = get_phoneme(props.ulPhonemeB); ret.PhonemeBCoarseTuning = static_cast(props.lPhonemeBCoarseTuning); ret.Waveform = get_waveform(props.ulWaveform); ret.Rate = props.flRate; return ret; }(); return true; } void EaxVocalMorpherCommitter::SetDefaults(EaxEffectProps &props) { static constexpr EAXVOCALMORPHERPROPERTIES defprops{[] { EAXVOCALMORPHERPROPERTIES ret{}; ret.ulPhonemeA = EAXVOCALMORPHER_DEFAULTPHONEMEA; ret.lPhonemeACoarseTuning = EAXVOCALMORPHER_DEFAULTPHONEMEACOARSETUNING; ret.ulPhonemeB = EAXVOCALMORPHER_DEFAULTPHONEMEB; ret.lPhonemeBCoarseTuning = EAXVOCALMORPHER_DEFAULTPHONEMEBCOARSETUNING; ret.ulWaveform = EAXVOCALMORPHER_DEFAULTWAVEFORM; ret.flRate = EAXVOCALMORPHER_DEFAULTRATE; return ret; }()}; props = defprops; } void EaxVocalMorpherCommitter::Get(const EaxCall &call, const EAXVOCALMORPHERPROPERTIES &props) { switch(call.get_property_id()) { case EAXVOCALMORPHER_NONE: break; case EAXVOCALMORPHER_ALLPARAMETERS: call.set_value(props); break; case EAXVOCALMORPHER_PHONEMEA: call.set_value(props.ulPhonemeA); break; case EAXVOCALMORPHER_PHONEMEACOARSETUNING: call.set_value(props.lPhonemeACoarseTuning); break; case EAXVOCALMORPHER_PHONEMEB: call.set_value(props.ulPhonemeB); break; case EAXVOCALMORPHER_PHONEMEBCOARSETUNING: call.set_value(props.lPhonemeBCoarseTuning); break; case EAXVOCALMORPHER_WAVEFORM: call.set_value(props.ulWaveform); break; case EAXVOCALMORPHER_RATE: call.set_value(props.flRate); break; default: fail_unknown_property_id(); } } void EaxVocalMorpherCommitter::Set(const EaxCall &call, EAXVOCALMORPHERPROPERTIES &props) { switch(call.get_property_id()) { case EAXVOCALMORPHER_NONE: break; case EAXVOCALMORPHER_ALLPARAMETERS: defer(call, props); break; case EAXVOCALMORPHER_PHONEMEA: defer(call, props.ulPhonemeA); break; case EAXVOCALMORPHER_PHONEMEACOARSETUNING: defer(call, props.lPhonemeACoarseTuning); break; case EAXVOCALMORPHER_PHONEMEB: defer(call, props.ulPhonemeB); break; case EAXVOCALMORPHER_PHONEMEBCOARSETUNING: defer(call, props.lPhonemeBCoarseTuning); break; case EAXVOCALMORPHER_WAVEFORM: defer(call, props.ulWaveform); break; case EAXVOCALMORPHER_RATE: defer(call, props.flRate); break; default: fail_unknown_property_id(); } } #endif // ALSOFT_EAX openal-soft-1.24.2/al/error.cpp000066400000000000000000000073661474041540300162760ustar00rootroot00000000000000/** * OpenAL cross platform audio library * Copyright (C) 1999-2000 by authors. * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * Or go to http://www.gnu.org/copyleft/lgpl.html */ #include "config.h" #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #include #endif #include #include #include #include #include #include #include #include #include "AL/al.h" #include "AL/alc.h" #include "al/debug.h" #include "alc/alconfig.h" #include "alc/context.h" #include "alnumeric.h" #include "core/except.h" #include "core/logging.h" #include "opthelpers.h" #include "strutils.h" void ALCcontext::setErrorImpl(ALenum errorCode, const fmt::string_view fmt, fmt::format_args args) { const auto msg = fmt::vformat(fmt, std::move(args)); WARN("Error generated on context {}, code {:#04x}, \"{}\"", decltype(std::declval()){this}, as_unsigned(errorCode), msg); if(TrapALError) { #ifdef _WIN32 /* DebugBreak will cause an exception if there is no debugger */ if(IsDebuggerPresent()) DebugBreak(); #elif defined(SIGTRAP) raise(SIGTRAP); #endif } if(mLastThreadError.get() == AL_NO_ERROR) mLastThreadError.set(errorCode); debugMessage(DebugSource::API, DebugType::Error, static_cast(errorCode), DebugSeverity::High, msg); } void ALCcontext::throw_error_impl(ALenum errorCode, const fmt::string_view fmt, fmt::format_args args) { setErrorImpl(errorCode, fmt, std::move(args)); throw al::base_exception{}; } /* Special-case alGetError since it (potentially) raises a debug signal and * returns a non-default value for a null context. */ AL_API auto AL_APIENTRY alGetError() noexcept -> ALenum { if(auto context = GetContextRef()) LIKELY return alGetErrorDirect(context.get()); auto get_value = [](const char *envname, const char *optname) -> ALenum { auto optstr = al::getenv(envname); if(!optstr) optstr = ConfigValueStr({}, "game_compat", optname); if(optstr) { try { auto idx = 0_uz; auto value = std::stoi(*optstr, &idx, 0); if(idx >= optstr->size() || std::isspace(optstr->at(idx))) return static_cast(value); } catch(...) { } ERR("Invalid default error value: \"{}\"", *optstr); } return AL_INVALID_OPERATION; }; static const ALenum deferror{get_value("__ALSOFT_DEFAULT_ERROR", "default-error")}; WARN("Querying error state on null context (implicitly {:#04x})", as_unsigned(deferror)); if(TrapALError) { #ifdef _WIN32 if(IsDebuggerPresent()) DebugBreak(); #elif defined(SIGTRAP) raise(SIGTRAP); #endif } return deferror; } FORCE_ALIGN ALenum AL_APIENTRY alGetErrorDirect(ALCcontext *context) noexcept { ALenum ret{context->mLastThreadError.get()}; if(ret != AL_NO_ERROR) UNLIKELY context->mLastThreadError.set(AL_NO_ERROR); return ret; } openal-soft-1.24.2/al/event.cpp000066400000000000000000000172471474041540300162650ustar00rootroot00000000000000 #include "config.h" #include "event.h" #include #include #include #include #include #include #include #include #include #include #include #include "AL/al.h" #include "AL/alc.h" #include "AL/alext.h" #include "alc/context.h" #include "alnumeric.h" #include "alsem.h" #include "alspan.h" #include "alstring.h" #include "core/async_event.h" #include "core/context.h" #include "core/effects/base.h" #include "core/except.h" #include "core/logging.h" #include "debug.h" #include "direct_defs.h" #include "intrusive_ptr.h" #include "opthelpers.h" #include "ringbuffer.h" namespace { template struct overloaded : Ts... { using Ts::operator()...; }; template overloaded(Ts...) -> overloaded; int EventThread(ALCcontext *context) { RingBuffer *ring{context->mAsyncEvents.get()}; bool quitnow{false}; while(!quitnow) { auto evt_data = ring->getReadVector()[0]; if(evt_data.len == 0) { context->mEventSem.wait(); continue; } auto eventlock = std::lock_guard{context->mEventCbLock}; const auto enabledevts = context->mEnabledEvts.load(std::memory_order_acquire); auto evt_span = al::span{std::launder(reinterpret_cast(evt_data.buf)), evt_data.len}; for(auto &event : evt_span) { quitnow = std::holds_alternative(event); if(quitnow) UNLIKELY break; auto proc_killthread = [](AsyncKillThread&) { }; auto proc_release = [](AsyncEffectReleaseEvent &evt) { al::intrusive_ptr{evt.mEffectState}; }; auto proc_srcstate = [context,enabledevts](AsyncSourceStateEvent &evt) { if(!context->mEventCb || !enabledevts.test(al::to_underlying(AsyncEnableBits::SourceState))) return; ALuint state{}; std::string msg{"Source ID " + std::to_string(evt.mId)}; msg += " state has changed to "; switch(evt.mState) { case AsyncSrcState::Reset: msg += "AL_INITIAL"; state = AL_INITIAL; break; case AsyncSrcState::Stop: msg += "AL_STOPPED"; state = AL_STOPPED; break; case AsyncSrcState::Play: msg += "AL_PLAYING"; state = AL_PLAYING; break; case AsyncSrcState::Pause: msg += "AL_PAUSED"; state = AL_PAUSED; break; } context->mEventCb(AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT, evt.mId, state, al::sizei(msg), msg.c_str(), context->mEventParam); }; auto proc_buffercomp = [context,enabledevts](AsyncBufferCompleteEvent &evt) { if(!context->mEventCb || !enabledevts.test(al::to_underlying(AsyncEnableBits::BufferCompleted))) return; std::string msg{std::to_string(evt.mCount)}; if(evt.mCount == 1) msg += " buffer completed"; else msg += " buffers completed"; context->mEventCb(AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT, evt.mId, evt.mCount, al::sizei(msg), msg.c_str(), context->mEventParam); }; auto proc_disconnect = [context,enabledevts](AsyncDisconnectEvent &evt) { if(!context->mEventCb || !enabledevts.test(al::to_underlying(AsyncEnableBits::Disconnected))) return; context->mEventCb(AL_EVENT_TYPE_DISCONNECTED_SOFT, 0, 0, al::sizei(evt.msg), evt.msg.c_str(), context->mEventParam); }; std::visit(overloaded{proc_srcstate, proc_buffercomp, proc_release, proc_disconnect, proc_killthread}, event); } std::destroy(evt_span.begin(), evt_span.end()); ring->readAdvance(evt_span.size()); } return 0; } constexpr std::optional GetEventType(ALenum etype) noexcept { switch(etype) { case AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT: return AsyncEnableBits::BufferCompleted; case AL_EVENT_TYPE_DISCONNECTED_SOFT: return AsyncEnableBits::Disconnected; case AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT: return AsyncEnableBits::SourceState; } return std::nullopt; } } // namespace void StartEventThrd(ALCcontext *ctx) { try { ctx->mEventThread = std::thread{EventThread, ctx}; } catch(std::exception& e) { ERR("Failed to start event thread: {}", e.what()); } catch(...) { ERR("Failed to start event thread! Expect problems."); } } void StopEventThrd(ALCcontext *ctx) { RingBuffer *ring{ctx->mAsyncEvents.get()}; auto evt_data = ring->getWriteVector()[0]; if(evt_data.len == 0) { do { std::this_thread::yield(); evt_data = ring->getWriteVector()[0]; } while(evt_data.len == 0); } std::ignore = InitAsyncEvent(evt_data.buf); ring->writeAdvance(1); ctx->mEventSem.post(); if(ctx->mEventThread.joinable()) ctx->mEventThread.join(); } AL_API DECL_FUNCEXT3(void, alEventControl,SOFT, ALsizei,count, const ALenum*,types, ALboolean,enable) FORCE_ALIGN void AL_APIENTRY alEventControlDirectSOFT(ALCcontext *context, ALsizei count, const ALenum *types, ALboolean enable) noexcept try { if(count < 0) context->throw_error(AL_INVALID_VALUE, "Controlling {} events", count); if(count <= 0) UNLIKELY return; if(!types) context->throw_error(AL_INVALID_VALUE, "NULL pointer"); ContextBase::AsyncEventBitset flags{}; for(ALenum evttype : al::span{types, static_cast(count)}) { auto etype = GetEventType(evttype); if(!etype) context->throw_error(AL_INVALID_ENUM, "Invalid event type {:#04x}", as_unsigned(evttype)); flags.set(al::to_underlying(*etype)); } if(enable) { auto enabledevts = context->mEnabledEvts.load(std::memory_order_relaxed); while(context->mEnabledEvts.compare_exchange_weak(enabledevts, enabledevts|flags, std::memory_order_acq_rel, std::memory_order_acquire) == 0) { /* enabledevts is (re-)filled with the current value on failure, so * just try again. */ } } else { auto enabledevts = context->mEnabledEvts.load(std::memory_order_relaxed); while(context->mEnabledEvts.compare_exchange_weak(enabledevts, enabledevts&~flags, std::memory_order_acq_rel, std::memory_order_acquire) == 0) { } /* Wait to ensure the event handler sees the changed flags before * returning. */ std::lock_guard eventlock{context->mEventCbLock}; } } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNCEXT2(void, alEventCallback,SOFT, ALEVENTPROCSOFT,callback, void*,userParam) FORCE_ALIGN void AL_APIENTRY alEventCallbackDirectSOFT(ALCcontext *context, ALEVENTPROCSOFT callback, void *userParam) noexcept try { std::lock_guard eventlock{context->mEventCbLock}; context->mEventCb = callback; context->mEventParam = userParam; } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } openal-soft-1.24.2/al/event.h000066400000000000000000000002161474041540300157160ustar00rootroot00000000000000#ifndef AL_EVENT_H #define AL_EVENT_H struct ALCcontext; void StartEventThrd(ALCcontext *ctx); void StopEventThrd(ALCcontext *ctx); #endif openal-soft-1.24.2/al/extension.cpp000066400000000000000000000043731474041540300171540ustar00rootroot00000000000000/** * OpenAL cross platform audio library * Copyright (C) 1999-2007 by authors. * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * Or go to http://www.gnu.org/copyleft/lgpl.html */ #include "config.h" #include #include "AL/al.h" #include "AL/alc.h" #include "alc/context.h" #include "alstring.h" #include "direct_defs.h" #include "opthelpers.h" AL_API DECL_FUNC1(ALboolean, alIsExtensionPresent, const ALchar*,extName) FORCE_ALIGN ALboolean AL_APIENTRY alIsExtensionPresentDirect(ALCcontext *context, const ALchar *extName) noexcept { if(!extName) UNLIKELY { context->setError(AL_INVALID_VALUE, "NULL pointer"); return AL_FALSE; } const std::string_view tofind{extName}; for(std::string_view ext : context->mExtensions) { if(al::case_compare(ext, tofind) == 0) return AL_TRUE; } return AL_FALSE; } AL_API ALvoid* AL_APIENTRY alGetProcAddress(const ALchar *funcName) noexcept { if(!funcName) return nullptr; return alcGetProcAddress(nullptr, funcName); } FORCE_ALIGN ALvoid* AL_APIENTRY alGetProcAddressDirect(ALCcontext*, const ALchar *funcName) noexcept { if(!funcName) return nullptr; return alcGetProcAddress(nullptr, funcName); } AL_API ALenum AL_APIENTRY alGetEnumValue(const ALchar *enumName) noexcept { if(!enumName) return ALenum{0}; return alcGetEnumValue(nullptr, enumName); } FORCE_ALIGN ALenum AL_APIENTRY alGetEnumValueDirect(ALCcontext*, const ALchar *enumName) noexcept { if(!enumName) return ALenum{0}; return alcGetEnumValue(nullptr, enumName); } openal-soft-1.24.2/al/filter.cpp000066400000000000000000000573151474041540300164310ustar00rootroot00000000000000/** * OpenAL cross platform audio library * Copyright (C) 1999-2007 by authors. * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * Or go to http://www.gnu.org/copyleft/lgpl.html */ #include "config.h" #include "filter.h" #include #include #include #include #include #include #include #include #include #include #include "AL/al.h" #include "AL/alc.h" #include "AL/efx.h" #include "albit.h" #include "alc/context.h" #include "alc/device.h" #include "almalloc.h" #include "alnumeric.h" #include "alspan.h" #include "core/except.h" #include "core/logging.h" #include "direct_defs.h" #include "intrusive_ptr.h" #include "opthelpers.h" namespace { using SubListAllocator = al::allocator>; void InitFilterParams(ALfilter *filter, ALenum type) { if(type == AL_FILTER_LOWPASS) { filter->Gain = AL_LOWPASS_DEFAULT_GAIN; filter->GainHF = AL_LOWPASS_DEFAULT_GAINHF; filter->HFReference = LowPassFreqRef; filter->GainLF = 1.0f; filter->LFReference = HighPassFreqRef; filter->mTypeVariant.emplace(); } else if(type == AL_FILTER_HIGHPASS) { filter->Gain = AL_HIGHPASS_DEFAULT_GAIN; filter->GainHF = 1.0f; filter->HFReference = LowPassFreqRef; filter->GainLF = AL_HIGHPASS_DEFAULT_GAINLF; filter->LFReference = HighPassFreqRef; filter->mTypeVariant.emplace(); } else if(type == AL_FILTER_BANDPASS) { filter->Gain = AL_BANDPASS_DEFAULT_GAIN; filter->GainHF = AL_BANDPASS_DEFAULT_GAINHF; filter->HFReference = LowPassFreqRef; filter->GainLF = AL_BANDPASS_DEFAULT_GAINLF; filter->LFReference = HighPassFreqRef; filter->mTypeVariant.emplace(); } else { filter->Gain = 1.0f; filter->GainHF = 1.0f; filter->HFReference = LowPassFreqRef; filter->GainLF = 1.0f; filter->LFReference = HighPassFreqRef; filter->mTypeVariant.emplace(); } filter->type = type; } [[nodiscard]] auto EnsureFilters(al::Device *device, size_t needed) noexcept -> bool try { size_t count{std::accumulate(device->FilterList.cbegin(), device->FilterList.cend(), 0_uz, [](size_t cur, const FilterSubList &sublist) noexcept -> size_t { return cur + static_cast(al::popcount(sublist.FreeMask)); })}; while(needed > count) { if(device->FilterList.size() >= 1<<25) UNLIKELY return false; FilterSubList sublist{}; sublist.FreeMask = ~0_u64; sublist.Filters = SubListAllocator{}.allocate(1); device->FilterList.emplace_back(std::move(sublist)); count += std::tuple_size_v; } return true; } catch(...) { return false; } [[nodiscard]] auto AllocFilter(al::Device *device) noexcept -> ALfilter* { auto sublist = std::find_if(device->FilterList.begin(), device->FilterList.end(), [](const FilterSubList &entry) noexcept -> bool { return entry.FreeMask != 0; }); auto lidx = static_cast(std::distance(device->FilterList.begin(), sublist)); auto slidx = static_cast(al::countr_zero(sublist->FreeMask)); ASSUME(slidx < 64); ALfilter *filter{al::construct_at(al::to_address(sublist->Filters->begin() + slidx))}; InitFilterParams(filter, AL_FILTER_NULL); /* Add 1 to avoid filter ID 0. */ filter->id = ((lidx<<6) | slidx) + 1; sublist->FreeMask &= ~(1_u64 << slidx); return filter; } void FreeFilter(al::Device *device, ALfilter *filter) { device->mFilterNames.erase(filter->id); const ALuint id{filter->id - 1}; const size_t lidx{id >> 6}; const ALuint slidx{id & 0x3f}; std::destroy_at(filter); device->FilterList[lidx].FreeMask |= 1_u64 << slidx; } [[nodiscard]] auto LookupFilter(al::Device *device, ALuint id) noexcept -> ALfilter* { const size_t lidx{(id-1) >> 6}; const ALuint slidx{(id-1) & 0x3f}; if(lidx >= device->FilterList.size()) UNLIKELY return nullptr; FilterSubList &sublist = device->FilterList[lidx]; if(sublist.FreeMask & (1_u64 << slidx)) UNLIKELY return nullptr; return al::to_address(sublist.Filters->begin() + slidx); } } // namespace /* Null filter parameter handlers */ template<> void FilterTable::setParami(ALCcontext *context, ALfilter*, ALenum param, int) { context->throw_error(AL_INVALID_ENUM, "Invalid null filter property {:#04x}", as_unsigned(param)); } template<> void FilterTable::setParamiv(ALCcontext *context, ALfilter*, ALenum param, const int*) { context->throw_error(AL_INVALID_ENUM, "Invalid null filter property {:#04x}", as_unsigned(param)); } template<> void FilterTable::setParamf(ALCcontext *context, ALfilter*, ALenum param, float) { context->throw_error(AL_INVALID_ENUM, "Invalid null filter property {:#04x}", as_unsigned(param)); } template<> void FilterTable::setParamfv(ALCcontext *context, ALfilter*, ALenum param, const float*) { context->throw_error(AL_INVALID_ENUM, "Invalid null filter property {:#04x}", as_unsigned(param)); } template<> void FilterTable::getParami(ALCcontext *context, const ALfilter*, ALenum param, int*) { context->throw_error(AL_INVALID_ENUM, "Invalid null filter property {:#04x}", as_unsigned(param)); } template<> void FilterTable::getParamiv(ALCcontext *context, const ALfilter*, ALenum param, int*) { context->throw_error(AL_INVALID_ENUM, "Invalid null filter property {:#04x}", as_unsigned(param)); } template<> void FilterTable::getParamf(ALCcontext *context, const ALfilter*, ALenum param, float*) { context->throw_error(AL_INVALID_ENUM, "Invalid null filter property {:#04x}", as_unsigned(param)); } template<> void FilterTable::getParamfv(ALCcontext *context, const ALfilter*, ALenum param, float*) { context->throw_error(AL_INVALID_ENUM, "Invalid null filter property {:#04x}", as_unsigned(param)); } /* Lowpass parameter handlers */ template<> void FilterTable::setParami(ALCcontext *context, ALfilter*, ALenum param, int) { context->throw_error(AL_INVALID_ENUM, "Invalid low-pass integer property {:#04x}", as_unsigned(param)); } template<> void FilterTable::setParamiv(ALCcontext *context, ALfilter *filter, ALenum param, const int *values) { setParami(context, filter, param, *values); } template<> void FilterTable::setParamf(ALCcontext *context, ALfilter *filter, ALenum param, float val) { switch(param) { case AL_LOWPASS_GAIN: if(!(val >= AL_LOWPASS_MIN_GAIN && val <= AL_LOWPASS_MAX_GAIN)) context->throw_error(AL_INVALID_VALUE, "Low-pass gain {:f} out of range", val); filter->Gain = val; return; case AL_LOWPASS_GAINHF: if(!(val >= AL_LOWPASS_MIN_GAINHF && val <= AL_LOWPASS_MAX_GAINHF)) context->throw_error(AL_INVALID_VALUE, "Low-pass gainhf {:f} out of range", val); filter->GainHF = val; return; } context->throw_error(AL_INVALID_ENUM, "Invalid low-pass float property {:#04x}", as_unsigned(param)); } template<> void FilterTable::setParamfv(ALCcontext *context, ALfilter *filter, ALenum param, const float *vals) { setParamf(context, filter, param, *vals); } template<> void FilterTable::getParami(ALCcontext *context, const ALfilter*, ALenum param, int*) { context->throw_error(AL_INVALID_ENUM, "Invalid low-pass integer property {:#04x}", as_unsigned(param)); } template<> void FilterTable::getParamiv(ALCcontext *context, const ALfilter *filter, ALenum param, int *values) { getParami(context, filter, param, values); } template<> void FilterTable::getParamf(ALCcontext *context, const ALfilter *filter, ALenum param, float *val) { switch(param) { case AL_LOWPASS_GAIN: *val = filter->Gain; return; case AL_LOWPASS_GAINHF: *val = filter->GainHF; return; } context->throw_error(AL_INVALID_ENUM, "Invalid low-pass float property {:#04x}", as_unsigned(param)); } template<> void FilterTable::getParamfv(ALCcontext *context, const ALfilter *filter, ALenum param, float *vals) { getParamf(context, filter, param, vals); } /* Highpass parameter handlers */ template<> void FilterTable::setParami(ALCcontext *context, ALfilter*, ALenum param, int) { context->throw_error(AL_INVALID_ENUM, "Invalid high-pass integer property {:#04x}", as_unsigned(param)); } template<> void FilterTable::setParamiv(ALCcontext *context, ALfilter *filter, ALenum param, const int *values) { setParami(context, filter, param, *values); } template<> void FilterTable::setParamf(ALCcontext *context, ALfilter *filter, ALenum param, float val) { switch(param) { case AL_HIGHPASS_GAIN: if(!(val >= AL_HIGHPASS_MIN_GAIN && val <= AL_HIGHPASS_MAX_GAIN)) context->throw_error(AL_INVALID_VALUE, "High-pass gain {:f} out of range", val); filter->Gain = val; return; case AL_HIGHPASS_GAINLF: if(!(val >= AL_HIGHPASS_MIN_GAINLF && val <= AL_HIGHPASS_MAX_GAINLF)) context->throw_error(AL_INVALID_VALUE, "High-pass gainlf {:f} out of range", val); filter->GainLF = val; return; } context->throw_error(AL_INVALID_ENUM, "Invalid high-pass float property {:#04x}", as_unsigned(param)); } template<> void FilterTable::setParamfv(ALCcontext *context, ALfilter *filter, ALenum param, const float *vals) { setParamf(context, filter, param, *vals); } template<> void FilterTable::getParami(ALCcontext *context, const ALfilter*, ALenum param, int*) { context->throw_error(AL_INVALID_ENUM, "Invalid high-pass integer property {:#04x}", as_unsigned(param)); } template<> void FilterTable::getParamiv(ALCcontext *context, const ALfilter *filter, ALenum param, int *values) { getParami(context, filter, param, values); } template<> void FilterTable::getParamf(ALCcontext *context, const ALfilter *filter, ALenum param, float *val) { switch(param) { case AL_HIGHPASS_GAIN: *val = filter->Gain; return; case AL_HIGHPASS_GAINLF: *val = filter->GainLF; return; } context->throw_error(AL_INVALID_ENUM, "Invalid high-pass float property {:#04x}", as_unsigned(param)); } template<> void FilterTable::getParamfv(ALCcontext *context, const ALfilter *filter, ALenum param, float *vals) { getParamf(context, filter, param, vals); } /* Bandpass parameter handlers */ template<> void FilterTable::setParami(ALCcontext *context, ALfilter*, ALenum param, int) { context->throw_error(AL_INVALID_ENUM, "Invalid band-pass integer property {:#04x}", as_unsigned(param)); } template<> void FilterTable::setParamiv(ALCcontext *context, ALfilter *filter, ALenum param, const int *values) { setParami(context, filter, param, *values); } template<> void FilterTable::setParamf(ALCcontext *context, ALfilter *filter, ALenum param, float val) { switch(param) { case AL_BANDPASS_GAIN: if(!(val >= AL_BANDPASS_MIN_GAIN && val <= AL_BANDPASS_MAX_GAIN)) context->throw_error(AL_INVALID_VALUE, "Band-pass gain {:f} out of range", val); filter->Gain = val; return; case AL_BANDPASS_GAINHF: if(!(val >= AL_BANDPASS_MIN_GAINHF && val <= AL_BANDPASS_MAX_GAINHF)) context->throw_error(AL_INVALID_VALUE, "Band-pass gainhf {:f} out of range", val); filter->GainHF = val; return; case AL_BANDPASS_GAINLF: if(!(val >= AL_BANDPASS_MIN_GAINLF && val <= AL_BANDPASS_MAX_GAINLF)) context->throw_error(AL_INVALID_VALUE, "Band-pass gainlf {:f} out of range", val); filter->GainLF = val; return; } context->throw_error(AL_INVALID_ENUM, "Invalid band-pass float property {:#04x}", as_unsigned(param)); } template<> void FilterTable::setParamfv(ALCcontext *context, ALfilter *filter, ALenum param, const float *vals) { setParamf(context, filter, param, *vals); } template<> void FilterTable::getParami(ALCcontext *context, const ALfilter*, ALenum param, int*) { context->throw_error(AL_INVALID_ENUM, "Invalid band-pass integer property {:#04x}", as_unsigned(param)); } template<> void FilterTable::getParamiv(ALCcontext *context, const ALfilter *filter, ALenum param, int *values) { getParami(context, filter, param, values); } template<> void FilterTable::getParamf(ALCcontext *context, const ALfilter *filter, ALenum param, float *val) { switch(param) { case AL_BANDPASS_GAIN: *val = filter->Gain; return; case AL_BANDPASS_GAINHF: *val = filter->GainHF; return; case AL_BANDPASS_GAINLF: *val = filter->GainLF; return; } context->throw_error(AL_INVALID_ENUM, "Invalid band-pass float property {:#04x}", as_unsigned(param)); } template<> void FilterTable::getParamfv(ALCcontext *context, const ALfilter *filter, ALenum param, float *vals) { getParamf(context, filter, param, vals); } AL_API DECL_FUNC2(void, alGenFilters, ALsizei,n, ALuint*,filters) FORCE_ALIGN void AL_APIENTRY alGenFiltersDirect(ALCcontext *context, ALsizei n, ALuint *filters) noexcept try { if(n < 0) context->throw_error(AL_INVALID_VALUE, "Generating {} filters", n); if(n <= 0) UNLIKELY return; auto *device = context->mALDevice.get(); auto filterlock = std::lock_guard{device->FilterLock}; const al::span fids{filters, static_cast(n)}; if(!EnsureFilters(device, fids.size())) context->throw_error(AL_OUT_OF_MEMORY, "Failed to allocate {} filter{}", n, (n==1) ? "" : "s"); std::generate(fids.begin(), fids.end(), [device]{ return AllocFilter(device)->id; }); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC2(void, alDeleteFilters, ALsizei,n, const ALuint*,filters) FORCE_ALIGN void AL_APIENTRY alDeleteFiltersDirect(ALCcontext *context, ALsizei n, const ALuint *filters) noexcept try { if(n < 0) context->throw_error(AL_INVALID_VALUE, "Deleting {} filters", n); if(n <= 0) UNLIKELY return; auto *device = context->mALDevice.get(); auto filterlock = std::lock_guard{device->FilterLock}; /* First try to find any filters that are invalid. */ auto validate_filter = [device](const ALuint fid) -> bool { return !fid || LookupFilter(device, fid) != nullptr; }; const al::span fids{filters, static_cast(n)}; auto invflt = std::find_if_not(fids.begin(), fids.end(), validate_filter); if(invflt != fids.end()) context->throw_error(AL_INVALID_NAME, "Invalid filter ID {}", *invflt); /* All good. Delete non-0 filter IDs. */ auto delete_filter = [device](const ALuint fid) -> void { if(ALfilter *filter{fid ? LookupFilter(device, fid) : nullptr}) FreeFilter(device, filter); }; std::for_each(fids.begin(), fids.end(), delete_filter); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC1(ALboolean, alIsFilter, ALuint,filter) FORCE_ALIGN ALboolean AL_APIENTRY alIsFilterDirect(ALCcontext *context, ALuint filter) noexcept { auto *device = context->mALDevice.get(); auto filterlock = std::lock_guard{device->FilterLock}; if(!filter || LookupFilter(device, filter)) return AL_TRUE; return AL_FALSE; } AL_API DECL_FUNC3(void, alFilteri, ALuint,filter, ALenum,param, ALint,value) FORCE_ALIGN void AL_APIENTRY alFilteriDirect(ALCcontext *context, ALuint filter, ALenum param, ALint value) noexcept try { auto *device = context->mALDevice.get(); auto filterlock = std::lock_guard{device->FilterLock}; ALfilter *alfilt{LookupFilter(device, filter)}; if(!alfilt) context->throw_error(AL_INVALID_NAME, "Invalid filter ID {}", filter); switch(param) { case AL_FILTER_TYPE: if(!(value == AL_FILTER_NULL || value == AL_FILTER_LOWPASS || value == AL_FILTER_HIGHPASS || value == AL_FILTER_BANDPASS)) context->throw_error(AL_INVALID_VALUE, "Invalid filter type {:#04x}", as_unsigned(value)); InitFilterParams(alfilt, value); return; } /* Call the appropriate handler */ std::visit([context,alfilt,param,value](auto&& thunk) { thunk.setParami(context, alfilt, param, value); }, alfilt->mTypeVariant); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alFilteriv, ALuint,filter, ALenum,param, const ALint*,values) FORCE_ALIGN void AL_APIENTRY alFilterivDirect(ALCcontext *context, ALuint filter, ALenum param, const ALint *values) noexcept try { switch(param) { case AL_FILTER_TYPE: alFilteriDirect(context, filter, param, *values); return; } auto *device = context->mALDevice.get(); auto filterlock = std::lock_guard{device->FilterLock}; ALfilter *alfilt{LookupFilter(device, filter)}; if(!alfilt) context->throw_error(AL_INVALID_NAME, "Invalid filter ID {}", filter); /* Call the appropriate handler */ std::visit([context,alfilt,param,values](auto&& thunk) { thunk.setParamiv(context, alfilt, param, values); }, alfilt->mTypeVariant); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alFilterf, ALuint,filter, ALenum,param, ALfloat,value) FORCE_ALIGN void AL_APIENTRY alFilterfDirect(ALCcontext *context, ALuint filter, ALenum param, ALfloat value) noexcept try { auto *device = context->mALDevice.get(); auto filterlock = std::lock_guard{device->FilterLock}; ALfilter *alfilt{LookupFilter(device, filter)}; if(!alfilt) context->throw_error(AL_INVALID_NAME, "Invalid filter ID {}", filter); /* Call the appropriate handler */ std::visit([context,alfilt,param,value](auto&& thunk) { thunk.setParamf(context, alfilt, param, value); }, alfilt->mTypeVariant); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alFilterfv, ALuint,filter, ALenum,param, const ALfloat*,values) FORCE_ALIGN void AL_APIENTRY alFilterfvDirect(ALCcontext *context, ALuint filter, ALenum param, const ALfloat *values) noexcept try { auto *device = context->mALDevice.get(); auto filterlock = std::lock_guard{device->FilterLock}; ALfilter *alfilt{LookupFilter(device, filter)}; if(!alfilt) context->throw_error(AL_INVALID_NAME, "Invalid filter ID {}", filter); /* Call the appropriate handler */ std::visit([context,alfilt,param,values](auto&& thunk) { thunk.setParamfv(context, alfilt, param, values); }, alfilt->mTypeVariant); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alGetFilteri, ALuint,filter, ALenum,param, ALint*,value) FORCE_ALIGN void AL_APIENTRY alGetFilteriDirect(ALCcontext *context, ALuint filter, ALenum param, ALint *value) noexcept try { auto *device = context->mALDevice.get(); auto filterlock = std::lock_guard{device->FilterLock}; const ALfilter *alfilt{LookupFilter(device, filter)}; if(!alfilt) context->throw_error(AL_INVALID_NAME, "Invalid filter ID {}", filter); switch(param) { case AL_FILTER_TYPE: *value = alfilt->type; return; } /* Call the appropriate handler */ std::visit([context,alfilt,param,value](auto&& thunk) { thunk.getParami(context, alfilt, param, value); }, alfilt->mTypeVariant); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alGetFilteriv, ALuint,filter, ALenum,param, ALint*,values) FORCE_ALIGN void AL_APIENTRY alGetFilterivDirect(ALCcontext *context, ALuint filter, ALenum param, ALint *values) noexcept try { switch(param) { case AL_FILTER_TYPE: alGetFilteriDirect(context, filter, param, values); return; } auto *device = context->mALDevice.get(); auto filterlock = std::lock_guard{device->FilterLock}; const ALfilter *alfilt{LookupFilter(device, filter)}; if(!alfilt) context->throw_error(AL_INVALID_NAME, "Invalid filter ID {}", filter); /* Call the appropriate handler */ std::visit([context,alfilt,param,values](auto&& thunk) { thunk.getParamiv(context, alfilt, param, values); }, alfilt->mTypeVariant); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alGetFilterf, ALuint,filter, ALenum,param, ALfloat*,value) FORCE_ALIGN void AL_APIENTRY alGetFilterfDirect(ALCcontext *context, ALuint filter, ALenum param, ALfloat *value) noexcept try { auto *device = context->mALDevice.get(); auto filterlock = std::lock_guard{device->FilterLock}; const ALfilter *alfilt{LookupFilter(device, filter)}; if(!alfilt) context->throw_error(AL_INVALID_NAME, "Invalid filter ID {}", filter); /* Call the appropriate handler */ std::visit([context,alfilt,param,value](auto&& thunk) { thunk.getParamf(context, alfilt, param, value); }, alfilt->mTypeVariant); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alGetFilterfv, ALuint,filter, ALenum,param, ALfloat*,values) FORCE_ALIGN void AL_APIENTRY alGetFilterfvDirect(ALCcontext *context, ALuint filter, ALenum param, ALfloat *values) noexcept try { auto *device = context->mALDevice.get(); auto filterlock = std::lock_guard{device->FilterLock}; const ALfilter *alfilt{LookupFilter(device, filter)}; if(!alfilt) context->throw_error(AL_INVALID_NAME, "Invalid filter ID {}", filter); /* Call the appropriate handler */ std::visit([context,alfilt,param,values](auto&& thunk) { thunk.getParamfv(context, alfilt, param, values); }, alfilt->mTypeVariant); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } void ALfilter::SetName(ALCcontext *context, ALuint id, std::string_view name) { auto *device = context->mALDevice.get(); auto filterlock = std::lock_guard{device->FilterLock}; auto filter = LookupFilter(device, id); if(!filter) context->throw_error(AL_INVALID_NAME, "Invalid filter ID {}", id); device->mFilterNames.insert_or_assign(id, name); } FilterSubList::~FilterSubList() { if(!Filters) return; uint64_t usemask{~FreeMask}; while(usemask) { const int idx{al::countr_zero(usemask)}; std::destroy_at(al::to_address(Filters->begin() + idx)); usemask &= ~(1_u64 << idx); } FreeMask = ~usemask; SubListAllocator{}.deallocate(Filters, 1); Filters = nullptr; } openal-soft-1.24.2/al/filter.h000066400000000000000000000045461474041540300160740ustar00rootroot00000000000000#ifndef AL_FILTER_H #define AL_FILTER_H #include #include #include #include #include #include "AL/al.h" #include "AL/alc.h" #include "AL/efx.h" #include "almalloc.h" #include "alnumeric.h" struct ALfilter; inline constexpr float LowPassFreqRef{5000.0f}; inline constexpr float HighPassFreqRef{250.0f}; template struct FilterTable { static void setParami(ALCcontext*, ALfilter*, ALenum, int); static void setParamiv(ALCcontext*, ALfilter*, ALenum, const int*); static void setParamf(ALCcontext*, ALfilter*, ALenum, float); static void setParamfv(ALCcontext*, ALfilter*, ALenum, const float*); static void getParami(ALCcontext*, const ALfilter*, ALenum, int*); static void getParamiv(ALCcontext*, const ALfilter*, ALenum, int*); static void getParamf(ALCcontext*, const ALfilter*, ALenum, float*); static void getParamfv(ALCcontext*, const ALfilter*, ALenum, float*); private: FilterTable() = default; friend T; }; struct NullFilterTable : public FilterTable { }; struct LowpassFilterTable : public FilterTable { }; struct HighpassFilterTable : public FilterTable { }; struct BandpassFilterTable : public FilterTable { }; struct ALfilter { ALenum type{AL_FILTER_NULL}; float Gain{1.0f}; float GainHF{1.0f}; float HFReference{LowPassFreqRef}; float GainLF{1.0f}; float LFReference{HighPassFreqRef}; using TableTypes = std::variant; TableTypes mTypeVariant; /* Self ID */ ALuint id{0}; static void SetName(ALCcontext *context, ALuint id, std::string_view name); DISABLE_ALLOC }; struct FilterSubList { uint64_t FreeMask{~0_u64}; gsl::owner*> Filters{nullptr}; FilterSubList() noexcept = default; FilterSubList(const FilterSubList&) = delete; FilterSubList(FilterSubList&& rhs) noexcept : FreeMask{rhs.FreeMask}, Filters{rhs.Filters} { rhs.FreeMask = ~0_u64; rhs.Filters = nullptr; } ~FilterSubList(); FilterSubList& operator=(const FilterSubList&) = delete; FilterSubList& operator=(FilterSubList&& rhs) noexcept { std::swap(FreeMask, rhs.FreeMask); std::swap(Filters, rhs.Filters); return *this; } }; #endif openal-soft-1.24.2/al/listener.cpp000066400000000000000000000335021474041540300167610ustar00rootroot00000000000000/** * OpenAL cross platform audio library * Copyright (C) 1999-2000 by authors. * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * Or go to http://www.gnu.org/copyleft/lgpl.html */ #include "config.h" #include "listener.h" #include #include #include #include "AL/al.h" #include "AL/alc.h" #include "AL/efx.h" #include "alc/context.h" #include "alnumeric.h" #include "alspan.h" #include "core/except.h" #include "core/logging.h" #include "direct_defs.h" namespace { inline void UpdateProps(ALCcontext *context) { if(!context->mDeferUpdates) { UpdateContextProps(context); return; } context->mPropsDirty = true; } inline void CommitAndUpdateProps(ALCcontext *context) { if(!context->mDeferUpdates) { #if ALSOFT_EAX if(context->eaxNeedsCommit()) { context->mPropsDirty = true; context->applyAllUpdates(); return; } #endif UpdateContextProps(context); return; } context->mPropsDirty = true; } } // namespace AL_API DECL_FUNC2(void, alListenerf, ALenum,param, ALfloat,value) FORCE_ALIGN void AL_APIENTRY alListenerfDirect(ALCcontext *context, ALenum param, ALfloat value) noexcept try { ALlistener &listener = context->mListener; std::lock_guard proplock{context->mPropLock}; switch(param) { case AL_GAIN: if(!(value >= 0.0f && std::isfinite(value))) context->throw_error(AL_INVALID_VALUE, "Listener gain {:f} out of range", value); listener.Gain = value; UpdateProps(context); return; case AL_METERS_PER_UNIT: if(!(value >= AL_MIN_METERS_PER_UNIT && value <= AL_MAX_METERS_PER_UNIT)) context->throw_error(AL_INVALID_VALUE, "Listener meters per unit {:f} out of range", value); listener.mMetersPerUnit = value; UpdateProps(context); return; } context->throw_error(AL_INVALID_ENUM, "Invalid listener float property {:#04x}", as_unsigned(param)); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC4(void, alListener3f, ALenum,param, ALfloat,value1, ALfloat,value2, ALfloat,value3) FORCE_ALIGN void AL_APIENTRY alListener3fDirect(ALCcontext *context, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3) noexcept try { ALlistener &listener = context->mListener; std::lock_guard proplock{context->mPropLock}; switch(param) { case AL_POSITION: if(!(std::isfinite(value1) && std::isfinite(value2) && std::isfinite(value3))) context->throw_error(AL_INVALID_VALUE, "Listener position out of range"); listener.Position[0] = value1; listener.Position[1] = value2; listener.Position[2] = value3; CommitAndUpdateProps(context); return; case AL_VELOCITY: if(!(std::isfinite(value1) && std::isfinite(value2) && std::isfinite(value3))) context->throw_error(AL_INVALID_VALUE, "Listener velocity out of range"); listener.Velocity[0] = value1; listener.Velocity[1] = value2; listener.Velocity[2] = value3; CommitAndUpdateProps(context); return; } context->throw_error(AL_INVALID_ENUM, "Invalid listener 3-float property {:#04x}", as_unsigned(param)); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC2(void, alListenerfv, ALenum,param, const ALfloat*,values) FORCE_ALIGN void AL_APIENTRY alListenerfvDirect(ALCcontext *context, ALenum param, const ALfloat *values) noexcept try { if(!values) context->throw_error(AL_INVALID_VALUE, "NULL pointer"); switch(param) { case AL_GAIN: case AL_METERS_PER_UNIT: alListenerfDirect(context, param, *values); return; case AL_POSITION: case AL_VELOCITY: auto vals = al::span{values, 3_uz}; alListener3fDirect(context, param, vals[0], vals[1], vals[2]); return; } ALlistener &listener = context->mListener; std::lock_guard proplock{context->mPropLock}; switch(param) { case AL_ORIENTATION: auto vals = al::span{values, 6_uz}; if(!std::all_of(vals.cbegin(), vals.cend(), [](float f) { return std::isfinite(f); })) context->throw_error(AL_INVALID_VALUE, "Listener orientation out of range"); /* AT then UP */ std::copy_n(vals.cbegin(), 3, listener.OrientAt.begin()); std::copy_n(vals.cbegin()+3, 3, listener.OrientUp.begin()); CommitAndUpdateProps(context); return; } context->throw_error(AL_INVALID_ENUM, "Invalid listener float-vector property {:#04x}", as_unsigned(param)); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC2(void, alListeneri, ALenum,param, ALint,value) FORCE_ALIGN void AL_APIENTRY alListeneriDirect(ALCcontext *context, ALenum param, ALint /*value*/) noexcept try { std::lock_guard proplock{context->mPropLock}; context->throw_error(AL_INVALID_ENUM, "Invalid listener integer property {:#04x}", as_unsigned(param)); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC4(void, alListener3i, ALenum,param, ALint,value1, ALint,value2, ALint,value3) FORCE_ALIGN void AL_APIENTRY alListener3iDirect(ALCcontext *context, ALenum param, ALint value1, ALint value2, ALint value3) noexcept try { switch(param) { case AL_POSITION: case AL_VELOCITY: alListener3fDirect(context, param, static_cast(value1), static_cast(value2), static_cast(value3)); return; } std::lock_guard proplock{context->mPropLock}; context->throw_error(AL_INVALID_ENUM, "Invalid listener 3-integer property {:#04x}", as_unsigned(param)); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC2(void, alListeneriv, ALenum,param, const ALint*,values) FORCE_ALIGN void AL_APIENTRY alListenerivDirect(ALCcontext *context, ALenum param, const ALint *values) noexcept try { if(!values) context->throw_error(AL_INVALID_VALUE, "NULL pointer"); al::span vals; switch(param) { case AL_POSITION: case AL_VELOCITY: vals = {values, 3_uz}; alListener3fDirect(context, param, static_cast(vals[0]), static_cast(vals[1]), static_cast(vals[2])); return; case AL_ORIENTATION: vals = {values, 6_uz}; const std::array fvals{static_cast(vals[0]), static_cast(vals[1]), static_cast(vals[2]), static_cast(vals[3]), static_cast(vals[4]), static_cast(vals[5]), }; alListenerfvDirect(context, param, fvals.data()); return; } std::lock_guard proplock{context->mPropLock}; context->throw_error(AL_INVALID_ENUM, "Invalid listener integer-vector property {:#04x}", as_unsigned(param)); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC2(void, alGetListenerf, ALenum,param, ALfloat*,value) FORCE_ALIGN void AL_APIENTRY alGetListenerfDirect(ALCcontext *context, ALenum param, ALfloat *value) noexcept try { if(!value) context->throw_error(AL_INVALID_VALUE, "NULL pointer"); ALlistener &listener = context->mListener; std::lock_guard proplock{context->mPropLock}; switch(param) { case AL_GAIN: *value = listener.Gain; return; case AL_METERS_PER_UNIT: *value = listener.mMetersPerUnit; return; } context->throw_error(AL_INVALID_ENUM, "Invalid listener float property {:#04x}", as_unsigned(param)); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC4(void, alGetListener3f, ALenum,param, ALfloat*,value1, ALfloat*,value2, ALfloat*,value3) FORCE_ALIGN void AL_APIENTRY alGetListener3fDirect(ALCcontext *context, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3) noexcept try { if(!value1 || !value2 || !value3) context->throw_error(AL_INVALID_VALUE, "NULL pointer"); ALlistener &listener = context->mListener; std::lock_guard proplock{context->mPropLock}; switch(param) { case AL_POSITION: *value1 = listener.Position[0]; *value2 = listener.Position[1]; *value3 = listener.Position[2]; return; case AL_VELOCITY: *value1 = listener.Velocity[0]; *value2 = listener.Velocity[1]; *value3 = listener.Velocity[2]; return; } context->throw_error(AL_INVALID_ENUM, "Invalid listener 3-float property {:#04x}", as_unsigned(param)); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC2(void, alGetListenerfv, ALenum,param, ALfloat*,values) FORCE_ALIGN void AL_APIENTRY alGetListenerfvDirect(ALCcontext *context, ALenum param, ALfloat *values) noexcept try { if(!values) context->throw_error(AL_INVALID_VALUE, "NULL pointer"); switch(param) { case AL_GAIN: case AL_METERS_PER_UNIT: alGetListenerfDirect(context, param, values); return; case AL_POSITION: case AL_VELOCITY: auto vals = al::span{values, 3_uz}; alGetListener3fDirect(context, param, &vals[0], &vals[1], &vals[2]); return; } ALlistener &listener = context->mListener; std::lock_guard proplock{context->mPropLock}; switch(param) { case AL_ORIENTATION: al::span vals{values, 6_uz}; // AT then UP std::copy_n(listener.OrientAt.cbegin(), 3, vals.begin()); std::copy_n(listener.OrientUp.cbegin(), 3, vals.begin()+3); return; } context->throw_error(AL_INVALID_ENUM, "Invalid listener float-vector property {:#04x}", as_unsigned(param)); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC2(void, alGetListeneri, ALenum,param, ALint*,value) FORCE_ALIGN void AL_APIENTRY alGetListeneriDirect(ALCcontext *context, ALenum param, ALint *value) noexcept try { if(!value) context->throw_error(AL_INVALID_VALUE, "NULL pointer"); std::lock_guard proplock{context->mPropLock}; context->throw_error(AL_INVALID_ENUM, "Invalid listener integer property {:#04x}", as_unsigned(param)); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC4(void, alGetListener3i, ALenum,param, ALint*,value1, ALint*,value2, ALint*,value3) FORCE_ALIGN void AL_APIENTRY alGetListener3iDirect(ALCcontext *context, ALenum param, ALint *value1, ALint *value2, ALint *value3) noexcept try { if(!value1 || !value2 || !value3) context->throw_error(AL_INVALID_VALUE, "NULL pointer"); ALlistener &listener = context->mListener; std::lock_guard proplock{context->mPropLock}; switch(param) { case AL_POSITION: *value1 = static_cast(listener.Position[0]); *value2 = static_cast(listener.Position[1]); *value3 = static_cast(listener.Position[2]); return; case AL_VELOCITY: *value1 = static_cast(listener.Velocity[0]); *value2 = static_cast(listener.Velocity[1]); *value3 = static_cast(listener.Velocity[2]); return; } context->throw_error(AL_INVALID_ENUM, "Invalid listener 3-integer property {:#04x}", as_unsigned(param)); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC2(void, alGetListeneriv, ALenum,param, ALint*,values) FORCE_ALIGN void AL_APIENTRY alGetListenerivDirect(ALCcontext *context, ALenum param, ALint *values) noexcept try { if(!values) context->throw_error(AL_INVALID_VALUE, "NULL pointer"); switch(param) { case AL_POSITION: case AL_VELOCITY: auto vals = al::span{values, 3_uz}; alGetListener3iDirect(context, param, &vals[0], &vals[1], &vals[2]); return; } ALlistener &listener = context->mListener; std::lock_guard proplock{context->mPropLock}; static constexpr auto f2i = [](const float val) noexcept { return static_cast(val); }; switch(param) { case AL_ORIENTATION: auto vals = al::span{values, 6_uz}; // AT then UP std::transform(listener.OrientAt.cbegin(), listener.OrientAt.cend(), vals.begin(), f2i); std::transform(listener.OrientUp.cbegin(), listener.OrientUp.cend(), vals.begin()+3, f2i); return; } context->throw_error(AL_INVALID_ENUM, "Invalid listener integer-vector property {:#04x}", as_unsigned(param)); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } openal-soft-1.24.2/al/listener.h000066400000000000000000000007031474041540300164230ustar00rootroot00000000000000#ifndef AL_LISTENER_H #define AL_LISTENER_H #include #include "AL/efx.h" #include "almalloc.h" struct ALlistener { std::array Position{{0.0f, 0.0f, 0.0f}}; std::array Velocity{{0.0f, 0.0f, 0.0f}}; std::array OrientAt{{0.0f, 0.0f, -1.0f}}; std::array OrientUp{{0.0f, 1.0f, 0.0f}}; float Gain{1.0f}; float mMetersPerUnit{AL_DEFAULT_METERS_PER_UNIT}; DISABLE_ALLOC }; #endif openal-soft-1.24.2/al/source.cpp000066400000000000000000005175631474041540300164520ustar00rootroot00000000000000/** * OpenAL cross platform audio library * Copyright (C) 1999-2007 by authors. * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * Or go to http://www.gnu.org/copyleft/lgpl.html */ #include "config.h" #include "source.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "AL/al.h" #include "AL/alc.h" #include "AL/alext.h" #include "AL/efx.h" #include "albit.h" #include "alc/backends/base.h" #include "alc/context.h" #include "alc/device.h" #include "alc/inprogext.h" #include "almalloc.h" #include "alnumeric.h" #include "alspan.h" #include "atomic.h" #include "auxeffectslot.h" #include "buffer.h" #include "core/buffer_storage.h" #include "core/except.h" #include "core/logging.h" #include "core/mixer/defs.h" #include "core/voice_change.h" #include "direct_defs.h" #include "filter.h" #include "flexarray.h" #include "intrusive_ptr.h" #include "opthelpers.h" #if ALSOFT_EAX #include "eax/api.h" #include "eax/call.h" #include "eax/fx_slot_index.h" #include "eax/utils.h" #endif namespace { using SubListAllocator = al::allocator>; using std::chrono::nanoseconds; using seconds_d = std::chrono::duration; using source_store_array = std::array; using source_store_vector = std::vector; using source_store_variant = std::variant; using namespace std::string_view_literals; Voice *GetSourceVoice(ALsource *source, ALCcontext *context) { auto voicelist = context->getVoicesSpan(); ALuint idx{source->VoiceIdx}; if(idx < voicelist.size()) { ALuint sid{source->id}; Voice *voice = voicelist[idx]; if(voice->mSourceID.load(std::memory_order_acquire) == sid) return voice; } source->VoiceIdx = InvalidVoiceIndex; return nullptr; } void UpdateSourceProps(const ALsource *source, Voice *voice, ALCcontext *context) { /* Get an unused property container, or allocate a new one as needed. */ VoicePropsItem *props{context->mFreeVoiceProps.load(std::memory_order_acquire)}; if(!props) { context->allocVoiceProps(); props = context->mFreeVoiceProps.load(std::memory_order_acquire); } VoicePropsItem *next; do { next = props->next.load(std::memory_order_relaxed); } while(context->mFreeVoiceProps.compare_exchange_weak(props, next, std::memory_order_acq_rel, std::memory_order_acquire) == false); props->Pitch = source->Pitch; props->Gain = source->Gain; props->OuterGain = source->OuterGain; props->MinGain = source->MinGain; props->MaxGain = source->MaxGain; props->InnerAngle = source->InnerAngle; props->OuterAngle = source->OuterAngle; props->RefDistance = source->RefDistance; props->MaxDistance = source->MaxDistance; props->RolloffFactor = source->RolloffFactor #if ALSOFT_EAX + source->RolloffFactor2 #endif ; props->Position = source->Position; props->Velocity = source->Velocity; props->Direction = source->Direction; props->OrientAt = source->OrientAt; props->OrientUp = source->OrientUp; props->HeadRelative = source->HeadRelative; props->mDistanceModel = source->mDistanceModel; props->mResampler = source->mResampler; props->DirectChannels = source->DirectChannels; props->mSpatializeMode = source->mSpatialize; props->DryGainHFAuto = source->DryGainHFAuto; props->WetGainAuto = source->WetGainAuto; props->WetGainHFAuto = source->WetGainHFAuto; props->OuterGainHF = source->OuterGainHF; props->AirAbsorptionFactor = source->AirAbsorptionFactor; props->RoomRolloffFactor = source->RoomRolloffFactor; props->DopplerFactor = source->DopplerFactor; props->StereoPan = source->StereoPan; props->Radius = source->Radius; props->EnhWidth = source->EnhWidth; props->Panning = source->mPanningEnabled ? source->mPan : 0.0f; props->Direct.Gain = source->Direct.Gain; props->Direct.GainHF = source->Direct.GainHF; props->Direct.HFReference = source->Direct.HFReference; props->Direct.GainLF = source->Direct.GainLF; props->Direct.LFReference = source->Direct.LFReference; auto copy_send = [](const ALsource::SendData &srcsend) noexcept -> VoiceProps::SendData { VoiceProps::SendData ret{}; ret.Slot = srcsend.Slot ? srcsend.Slot->mSlot : nullptr; ret.Gain = srcsend.Gain; ret.GainHF = srcsend.GainHF; ret.HFReference = srcsend.HFReference; ret.GainLF = srcsend.GainLF; ret.LFReference = srcsend.LFReference; return ret; }; std::transform(source->Send.cbegin(), source->Send.cend(), props->Send.begin(), copy_send); if(!props->Send[0].Slot && context->mDefaultSlot) props->Send[0].Slot = context->mDefaultSlot->mSlot; /* Set the new container for updating internal parameters. */ props = voice->mUpdate.exchange(props, std::memory_order_acq_rel); if(props) { /* If there was an unused update container, put it back in the * freelist. */ AtomicReplaceHead(context->mFreeVoiceProps, props); } } /* GetSourceSampleOffset * * Gets the current read offset for the given Source, in 32.32 fixed-point * samples. The offset is relative to the start of the queue (not the start of * the current buffer). */ int64_t GetSourceSampleOffset(ALsource *Source, ALCcontext *context, nanoseconds *clocktime) { auto *device = context->mALDevice.get(); const VoiceBufferItem *Current{}; int64_t readPos{}; uint refcount{}; Voice *voice{}; do { refcount = device->waitForMix(); *clocktime = device->getClockTime(); voice = GetSourceVoice(Source, context); if(voice) { Current = voice->mCurrentBuffer.load(std::memory_order_relaxed); readPos = int64_t{voice->mPosition.load(std::memory_order_relaxed)} << MixerFracBits; readPos += voice->mPositionFrac.load(std::memory_order_relaxed); } std::atomic_thread_fence(std::memory_order_acquire); } while(refcount != device->mMixCount.load(std::memory_order_relaxed)); if(!voice) return 0; for(auto &item : Source->mQueue) { if(&item == Current) break; readPos += int64_t{item.mSampleLen} << MixerFracBits; } if(readPos > std::numeric_limits::max() >> (32-MixerFracBits)) return std::numeric_limits::max(); return readPos << (32-MixerFracBits); } /* GetSourceSecOffset * * Gets the current read offset for the given Source, in seconds. The offset is * relative to the start of the queue (not the start of the current buffer). */ double GetSourceSecOffset(ALsource *Source, ALCcontext *context, nanoseconds *clocktime) { auto *device = context->mALDevice.get(); const VoiceBufferItem *Current{}; int64_t readPos{}; uint refcount{}; Voice *voice{}; do { refcount = device->waitForMix(); *clocktime = device->getClockTime(); voice = GetSourceVoice(Source, context); if(voice) { Current = voice->mCurrentBuffer.load(std::memory_order_relaxed); readPos = int64_t{voice->mPosition.load(std::memory_order_relaxed)} << MixerFracBits; readPos += voice->mPositionFrac.load(std::memory_order_relaxed); } std::atomic_thread_fence(std::memory_order_acquire); } while(refcount != device->mMixCount.load(std::memory_order_relaxed)); if(!voice) return 0.0f; const ALbuffer *BufferFmt{nullptr}; auto BufferList = Source->mQueue.cbegin(); while(BufferList != Source->mQueue.cend() && al::to_address(BufferList) != Current) { if(!BufferFmt) BufferFmt = BufferList->mBuffer; readPos += int64_t{BufferList->mSampleLen} << MixerFracBits; ++BufferList; } while(BufferList != Source->mQueue.cend() && !BufferFmt) { BufferFmt = BufferList->mBuffer; ++BufferList; } ASSUME(BufferFmt != nullptr); return static_cast(readPos) / double{MixerFracOne} / BufferFmt->mSampleRate; } /* GetSourceOffset * * Gets the current read offset for the given Source, in the appropriate format * (Bytes, Samples or Seconds). The offset is relative to the start of the * queue (not the start of the current buffer). */ template NOINLINE T GetSourceOffset(ALsource *Source, ALenum name, ALCcontext *context) { auto *device = context->mALDevice.get(); const VoiceBufferItem *Current{}; int64_t readPos{}; uint readPosFrac{}; uint refcount; Voice *voice; do { refcount = device->waitForMix(); voice = GetSourceVoice(Source, context); if(voice) { Current = voice->mCurrentBuffer.load(std::memory_order_relaxed); readPos = voice->mPosition.load(std::memory_order_relaxed); readPosFrac = voice->mPositionFrac.load(std::memory_order_relaxed); } std::atomic_thread_fence(std::memory_order_acquire); } while(refcount != device->mMixCount.load(std::memory_order_relaxed)); if(!voice) return T{0}; const ALbuffer *BufferFmt{nullptr}; auto BufferList = Source->mQueue.cbegin(); while(BufferList != Source->mQueue.cend() && al::to_address(BufferList) != Current) { if(!BufferFmt) BufferFmt = BufferList->mBuffer; readPos += BufferList->mSampleLen; ++BufferList; } while(BufferList != Source->mQueue.cend() && !BufferFmt) { BufferFmt = BufferList->mBuffer; ++BufferList; } ASSUME(BufferFmt != nullptr); T offset{}; switch(name) { case AL_SEC_OFFSET: if constexpr(std::is_floating_point_v) { offset = static_cast(readPos) + static_cast(readPosFrac)/T{MixerFracOne}; offset /= static_cast(BufferFmt->mSampleRate); } else { readPos /= BufferFmt->mSampleRate; offset = static_cast(std::clamp(readPos, std::numeric_limits::min(), std::numeric_limits::max())); } break; case AL_SAMPLE_OFFSET: if constexpr(std::is_floating_point_v) offset = static_cast(readPos) + static_cast(readPosFrac)/T{MixerFracOne}; else offset = static_cast(std::clamp(readPos, std::numeric_limits::min(), std::numeric_limits::max())); break; case AL_BYTE_OFFSET: const ALuint BlockSamples{BufferFmt->mBlockAlign}; const ALuint BlockSize{BufferFmt->blockSizeFromFmt()}; /* Round down to the block boundary. */ readPos = readPos / BlockSamples * BlockSize; if constexpr(std::is_floating_point_v) offset = static_cast(readPos); else { if(readPos > std::numeric_limits::max()) offset = RoundDown(std::numeric_limits::max(), static_cast(BlockSize)); else if(readPos < std::numeric_limits::min()) offset = RoundUp(std::numeric_limits::min(), static_cast(BlockSize)); else offset = static_cast(readPos); } break; } return offset; } /* GetSourceLength * * Gets the length of the given Source's buffer queue, in the appropriate * format (Bytes, Samples or Seconds). */ template NOINLINE T GetSourceLength(const ALsource *source, ALenum name) { uint64_t length{0}; const ALbuffer *BufferFmt{nullptr}; for(auto &listitem : source->mQueue) { if(!BufferFmt) BufferFmt = listitem.mBuffer; length += listitem.mSampleLen; } if(length == 0) return T{0}; ASSUME(BufferFmt != nullptr); switch(name) { case AL_SEC_LENGTH_SOFT: if constexpr(std::is_floating_point_v) return static_cast(length) / static_cast(BufferFmt->mSampleRate); else return static_cast(std::min(length/BufferFmt->mSampleRate, std::numeric_limits::max())); case AL_SAMPLE_LENGTH_SOFT: if constexpr(std::is_floating_point_v) return static_cast(length); else return static_cast(std::min(length, std::numeric_limits::max())); case AL_BYTE_LENGTH_SOFT: const ALuint BlockSamples{BufferFmt->mBlockAlign}; const ALuint BlockSize{BufferFmt->blockSizeFromFmt()}; /* Round down to the block boundary. */ length = length / BlockSamples * BlockSize; if constexpr(std::is_floating_point_v) return static_cast(length); else { if(length > uint64_t{std::numeric_limits::max()}) return RoundDown(std::numeric_limits::max(), static_cast(BlockSize)); return static_cast(length); } } return T{0}; } struct VoicePos { int pos; uint frac; ALbufferQueueItem *bufferitem; }; /** * GetSampleOffset * * Retrieves the voice position, fixed-point fraction, and bufferlist item * using the given offset type and offset. If the offset is out of range, * returns an empty optional. */ std::optional GetSampleOffset(std::deque &BufferList, ALenum OffsetType, double Offset) { /* Find the first valid Buffer in the Queue */ const ALbuffer *BufferFmt{nullptr}; for(auto &item : BufferList) { BufferFmt = item.mBuffer; if(BufferFmt) break; } if(!BufferFmt) UNLIKELY return std::nullopt; /* Get sample frame offset */ int64_t offset{}; uint frac{}; double dbloff, dblfrac; switch(OffsetType) { case AL_SEC_OFFSET: dblfrac = std::modf(Offset*BufferFmt->mSampleRate, &dbloff); if(dblfrac < 0.0) { /* If there's a negative fraction, reduce the offset to "floor" it, * and convert the fraction to a percentage to the next value (e.g. * -2.75 -> -3 + 0.25). */ dbloff -= 1.0; dblfrac += 1.0; } offset = static_cast(dbloff); frac = static_cast(std::min(dblfrac*MixerFracOne, MixerFracOne-1.0)); break; case AL_SAMPLE_OFFSET: dblfrac = std::modf(Offset, &dbloff); if(dblfrac < 0.0) { dbloff -= 1.0; dblfrac += 1.0; } offset = static_cast(dbloff); frac = static_cast(std::min(dblfrac*MixerFracOne, MixerFracOne-1.0)); break; case AL_BYTE_OFFSET: /* Determine the ByteOffset (and ensure it is block aligned) */ Offset = std::floor(Offset / BufferFmt->blockSizeFromFmt()); offset = static_cast(Offset) * BufferFmt->mBlockAlign; frac = 0; break; } /* Find the bufferlist item this offset belongs to. */ if(offset < 0) { if(offset < std::numeric_limits::min()) return std::nullopt; return VoicePos{static_cast(offset), frac, &BufferList.front()}; } if(BufferFmt->mCallback) return std::nullopt; int64_t totalBufferLen{0}; for(auto &item : BufferList) { if(totalBufferLen > offset) break; if(item.mSampleLen > offset-totalBufferLen) { /* Offset is in this buffer */ return VoicePos{static_cast(offset-totalBufferLen), frac, &item}; } totalBufferLen += item.mSampleLen; } /* Offset is out of range of the queue */ return std::nullopt; } void InitVoice(Voice *voice, ALsource *source, ALbufferQueueItem *BufferList, ALCcontext *context, al::Device *device) { voice->mLoopBuffer.store(source->Looping ? &source->mQueue.front() : nullptr, std::memory_order_relaxed); ALbuffer *buffer{BufferList->mBuffer}; voice->mFrequency = buffer->mSampleRate; if(buffer->mChannels == FmtMono && source->mPanningEnabled) voice->mFmtChannels = FmtMonoDup; else if(buffer->mChannels == FmtStereo && source->mStereoMode == SourceStereo::Enhanced) voice->mFmtChannels = FmtSuperStereo; else voice->mFmtChannels = buffer->mChannels; voice->mFmtType = buffer->mType; voice->mFrameStep = buffer->channelsFromFmt(); voice->mBytesPerBlock = buffer->blockSizeFromFmt(); voice->mSamplesPerBlock = buffer->mBlockAlign; voice->mAmbiLayout = IsUHJ(voice->mFmtChannels) ? AmbiLayout::FuMa : buffer->mAmbiLayout; voice->mAmbiScaling = IsUHJ(voice->mFmtChannels) ? AmbiScaling::UHJ : buffer->mAmbiScaling; voice->mAmbiOrder = (voice->mFmtChannels == FmtSuperStereo) ? 1 : buffer->mAmbiOrder; if(buffer->mCallback) voice->mFlags.set(VoiceIsCallback); else if(source->SourceType == AL_STATIC) voice->mFlags.set(VoiceIsStatic); voice->mNumCallbackBlocks = 0; voice->mCallbackBlockBase = 0; voice->prepare(device); source->mPropsDirty = false; UpdateSourceProps(source, voice, context); voice->mSourceID.store(source->id, std::memory_order_release); } VoiceChange *GetVoiceChanger(ALCcontext *ctx) { VoiceChange *vchg{ctx->mVoiceChangeTail}; if(vchg == ctx->mCurrentVoiceChange.load(std::memory_order_acquire)) UNLIKELY { ctx->allocVoiceChanges(); vchg = ctx->mVoiceChangeTail; } ctx->mVoiceChangeTail = vchg->mNext.exchange(nullptr, std::memory_order_relaxed); return vchg; } void SendVoiceChanges(ALCcontext *ctx, VoiceChange *tail) { auto *device = ctx->mALDevice.get(); VoiceChange *oldhead{ctx->mCurrentVoiceChange.load(std::memory_order_acquire)}; while(VoiceChange *next{oldhead->mNext.load(std::memory_order_relaxed)}) oldhead = next; oldhead->mNext.store(tail, std::memory_order_release); const bool connected{device->Connected.load(std::memory_order_acquire)}; std::ignore = device->waitForMix(); if(!connected) UNLIKELY { if(ctx->mStopVoicesOnDisconnect.load(std::memory_order_acquire)) { /* If the device is disconnected and voices are stopped, just * ignore all pending changes. */ VoiceChange *cur{ctx->mCurrentVoiceChange.load(std::memory_order_acquire)}; while(VoiceChange *next{cur->mNext.load(std::memory_order_acquire)}) { cur = next; if(Voice *voice{cur->mVoice}) voice->mSourceID.store(0, std::memory_order_relaxed); } ctx->mCurrentVoiceChange.store(cur, std::memory_order_release); } } } auto SetVoiceOffset(Voice *oldvoice, const VoicePos &vpos, ALsource *source, ALCcontext *context, al::Device *device) -> bool { /* First, get a free voice to start at the new offset. */ auto voicelist = context->getVoicesSpan(); Voice *newvoice{}; ALuint vidx{0}; for(Voice *voice : voicelist) { if(voice->mPlayState.load(std::memory_order_acquire) == Voice::Stopped && voice->mSourceID.load(std::memory_order_relaxed) == 0u && voice->mPendingChange.load(std::memory_order_relaxed) == false) { newvoice = voice; break; } ++vidx; } if(!newvoice) UNLIKELY { auto &allvoices = *context->mVoices.load(std::memory_order_relaxed); if(allvoices.size() == voicelist.size()) context->allocVoices(1); context->mActiveVoiceCount.fetch_add(1, std::memory_order_release); voicelist = context->getVoicesSpan(); vidx = 0; for(Voice *voice : voicelist) { if(voice->mPlayState.load(std::memory_order_acquire) == Voice::Stopped && voice->mSourceID.load(std::memory_order_relaxed) == 0u && voice->mPendingChange.load(std::memory_order_relaxed) == false) { newvoice = voice; break; } ++vidx; } ASSUME(newvoice != nullptr); } /* Initialize the new voice and set its starting offset. * TODO: It might be better to have the VoiceChange processing copy the old * voice's mixing parameters (and pending update) insead of initializing it * all here. This would just need to set the minimum properties to link the * voice to the source and its position-dependent properties (including the * fading flag). */ newvoice->mPlayState.store(Voice::Pending, std::memory_order_relaxed); newvoice->mPosition.store(vpos.pos, std::memory_order_relaxed); newvoice->mPositionFrac.store(vpos.frac, std::memory_order_relaxed); newvoice->mCurrentBuffer.store(vpos.bufferitem, std::memory_order_relaxed); newvoice->mStartTime = oldvoice->mStartTime; newvoice->mFlags.reset(); if(vpos.pos > 0 || (vpos.pos == 0 && vpos.frac > 0) || vpos.bufferitem != &source->mQueue.front()) newvoice->mFlags.set(VoiceIsFading); InitVoice(newvoice, source, vpos.bufferitem, context, device); source->VoiceIdx = vidx; /* Set the old voice as having a pending change, and send it off with the * new one with a new offset voice change. */ oldvoice->mPendingChange.store(true, std::memory_order_relaxed); VoiceChange *vchg{GetVoiceChanger(context)}; vchg->mOldVoice = oldvoice; vchg->mVoice = newvoice; vchg->mSourceID = source->id; vchg->mState = VChangeState::Restart; SendVoiceChanges(context, vchg); /* If the old voice still has a sourceID, it's still active and the change- * over will work on the next update. */ if(oldvoice->mSourceID.load(std::memory_order_acquire) != 0u) LIKELY return true; /* Otherwise, if the new voice's state is not pending, the change-over * already happened. */ if(newvoice->mPlayState.load(std::memory_order_acquire) != Voice::Pending) return true; /* Otherwise, wait for any current mix to finish and check one last time. */ std::ignore = device->waitForMix(); if(newvoice->mPlayState.load(std::memory_order_acquire) != Voice::Pending) return true; /* The change-over failed because the old voice stopped before the new * voice could start at the new offset. Let go of the new voice and have * the caller store the source offset since it's stopped. */ newvoice->mCurrentBuffer.store(nullptr, std::memory_order_relaxed); newvoice->mLoopBuffer.store(nullptr, std::memory_order_relaxed); newvoice->mSourceID.store(0u, std::memory_order_relaxed); newvoice->mPlayState.store(Voice::Stopped, std::memory_order_relaxed); return false; } /** * Returns if the last known state for the source was playing or paused. Does * not sync with the mixer voice. */ inline bool IsPlayingOrPaused(ALsource *source) { return source->state == AL_PLAYING || source->state == AL_PAUSED; } /** * Returns an updated source state using the matching voice's status (or lack * thereof). */ inline ALenum GetSourceState(ALsource *source, Voice *voice) { if(!voice && source->state == AL_PLAYING) source->state = AL_STOPPED; return source->state; } bool EnsureSources(ALCcontext *context, size_t needed) { size_t count{std::accumulate(context->mSourceList.cbegin(), context->mSourceList.cend(), 0_uz, [](size_t cur, const SourceSubList &sublist) noexcept -> size_t { return cur + static_cast(al::popcount(sublist.FreeMask)); })}; try { while(needed > count) { if(context->mSourceList.size() >= 1<<25) UNLIKELY return false; SourceSubList sublist{}; sublist.FreeMask = ~0_u64; sublist.Sources = SubListAllocator{}.allocate(1); context->mSourceList.emplace_back(std::move(sublist)); count += std::tuple_size_v; } } catch(...) { return false; } return true; } ALsource *AllocSource(ALCcontext *context) noexcept { auto sublist = std::find_if(context->mSourceList.begin(), context->mSourceList.end(), [](const SourceSubList &entry) noexcept -> bool { return entry.FreeMask != 0; }); auto lidx = static_cast(std::distance(context->mSourceList.begin(), sublist)); auto slidx = static_cast(al::countr_zero(sublist->FreeMask)); ASSUME(slidx < 64); ALsource *source{al::construct_at(al::to_address(sublist->Sources->begin() + slidx))}; #if ALSOFT_EAX source->eaxInitialize(context); #endif // ALSOFT_EAX /* Add 1 to avoid source ID 0. */ source->id = ((lidx<<6) | slidx) + 1; context->mNumSources += 1; sublist->FreeMask &= ~(1_u64 << slidx); return source; } void FreeSource(ALCcontext *context, ALsource *source) { context->mSourceNames.erase(source->id); const ALuint id{source->id - 1}; const size_t lidx{id >> 6}; const ALuint slidx{id & 0x3f}; if(Voice *voice{GetSourceVoice(source, context)}) { VoiceChange *vchg{GetVoiceChanger(context)}; voice->mPendingChange.store(true, std::memory_order_relaxed); vchg->mVoice = voice; vchg->mSourceID = source->id; vchg->mState = VChangeState::Stop; SendVoiceChanges(context, vchg); } std::destroy_at(source); context->mSourceList[lidx].FreeMask |= 1_u64 << slidx; context->mNumSources--; } inline ALsource *LookupSource(ALCcontext *context, ALuint id) noexcept { const size_t lidx{(id-1) >> 6}; const ALuint slidx{(id-1) & 0x3f}; if(lidx >= context->mSourceList.size()) UNLIKELY return nullptr; SourceSubList &sublist{context->mSourceList[lidx]}; if(sublist.FreeMask & (1_u64 << slidx)) UNLIKELY return nullptr; return al::to_address(sublist.Sources->begin() + slidx); } auto LookupBuffer = [](al::Device *device, auto id) noexcept -> ALbuffer* { const auto lidx{(id-1) >> 6}; const auto slidx{(id-1) & 0x3f}; if(lidx >= device->BufferList.size()) UNLIKELY return nullptr; BufferSubList &sublist = device->BufferList[static_cast(lidx)]; if(sublist.FreeMask & (1_u64 << slidx)) UNLIKELY return nullptr; return al::to_address(sublist.Buffers->begin() + static_cast(slidx)); }; auto LookupFilter = [](al::Device *device, auto id) noexcept -> ALfilter* { const auto lidx{(id-1) >> 6}; const auto slidx{(id-1) & 0x3f}; if(lidx >= device->FilterList.size()) UNLIKELY return nullptr; FilterSubList &sublist = device->FilterList[static_cast(lidx)]; if(sublist.FreeMask & (1_u64 << slidx)) UNLIKELY return nullptr; return al::to_address(sublist.Filters->begin() + static_cast(slidx)); }; auto LookupEffectSlot = [](ALCcontext *context, auto id) noexcept -> ALeffectslot* { const auto lidx{(id-1) >> 6}; const auto slidx{(id-1) & 0x3f}; if(lidx >= context->mEffectSlotList.size()) UNLIKELY return nullptr; EffectSlotSubList &sublist{context->mEffectSlotList[static_cast(lidx)]}; if(sublist.FreeMask & (1_u64 << slidx)) UNLIKELY return nullptr; return al::to_address(sublist.EffectSlots->begin() + static_cast(slidx)); }; auto StereoModeFromEnum = [](auto mode) noexcept -> std::optional { switch(mode) { case AL_NORMAL_SOFT: return SourceStereo::Normal; case AL_SUPER_STEREO_SOFT: return SourceStereo::Enhanced; } return std::nullopt; }; ALenum EnumFromStereoMode(SourceStereo mode) { switch(mode) { case SourceStereo::Normal: return AL_NORMAL_SOFT; case SourceStereo::Enhanced: return AL_SUPER_STEREO_SOFT; } throw std::runtime_error{"Invalid SourceStereo: "+std::to_string(int(mode))}; } auto SpatializeModeFromEnum = [](auto mode) noexcept -> std::optional { switch(mode) { case AL_FALSE: return SpatializeMode::Off; case AL_TRUE: return SpatializeMode::On; case AL_AUTO_SOFT: return SpatializeMode::Auto; } return std::nullopt; }; ALenum EnumFromSpatializeMode(SpatializeMode mode) { switch(mode) { case SpatializeMode::Off: return AL_FALSE; case SpatializeMode::On: return AL_TRUE; case SpatializeMode::Auto: return AL_AUTO_SOFT; } throw std::runtime_error{fmt::format("Invalid SpatializeMode: {}", int{al::to_underlying(mode)})}; } auto DirectModeFromEnum = [](auto mode) noexcept -> std::optional { switch(mode) { case AL_FALSE: return DirectMode::Off; case AL_DROP_UNMATCHED_SOFT: return DirectMode::DropMismatch; case AL_REMIX_UNMATCHED_SOFT: return DirectMode::RemixMismatch; } return std::nullopt; }; ALenum EnumFromDirectMode(DirectMode mode) { switch(mode) { case DirectMode::Off: return AL_FALSE; case DirectMode::DropMismatch: return AL_DROP_UNMATCHED_SOFT; case DirectMode::RemixMismatch: return AL_REMIX_UNMATCHED_SOFT; } throw std::runtime_error{fmt::format("Invalid DirectMode: {}", int{al::to_underlying(mode)})}; } auto DistanceModelFromALenum = [](auto model) noexcept -> std::optional { switch(model) { case AL_NONE: return DistanceModel::Disable; case AL_INVERSE_DISTANCE: return DistanceModel::Inverse; case AL_INVERSE_DISTANCE_CLAMPED: return DistanceModel::InverseClamped; case AL_LINEAR_DISTANCE: return DistanceModel::Linear; case AL_LINEAR_DISTANCE_CLAMPED: return DistanceModel::LinearClamped; case AL_EXPONENT_DISTANCE: return DistanceModel::Exponent; case AL_EXPONENT_DISTANCE_CLAMPED: return DistanceModel::ExponentClamped; } return std::nullopt; }; ALenum ALenumFromDistanceModel(DistanceModel model) { switch(model) { case DistanceModel::Disable: return AL_NONE; case DistanceModel::Inverse: return AL_INVERSE_DISTANCE; case DistanceModel::InverseClamped: return AL_INVERSE_DISTANCE_CLAMPED; case DistanceModel::Linear: return AL_LINEAR_DISTANCE; case DistanceModel::LinearClamped: return AL_LINEAR_DISTANCE_CLAMPED; case DistanceModel::Exponent: return AL_EXPONENT_DISTANCE; case DistanceModel::ExponentClamped: return AL_EXPONENT_DISTANCE_CLAMPED; } throw std::runtime_error{fmt::format("Unexpected distance model: {}", int{al::to_underlying(model)})}; } enum SourceProp : ALenum { srcPitch = AL_PITCH, srcGain = AL_GAIN, srcMinGain = AL_MIN_GAIN, srcMaxGain = AL_MAX_GAIN, srcMaxDistance = AL_MAX_DISTANCE, srcRolloffFactor = AL_ROLLOFF_FACTOR, srcDopplerFactor = AL_DOPPLER_FACTOR, srcConeOuterGain = AL_CONE_OUTER_GAIN, srcSecOffset = AL_SEC_OFFSET, srcSampleOffset = AL_SAMPLE_OFFSET, srcByteOffset = AL_BYTE_OFFSET, srcConeInnerAngle = AL_CONE_INNER_ANGLE, srcConeOuterAngle = AL_CONE_OUTER_ANGLE, srcRefDistance = AL_REFERENCE_DISTANCE, srcPosition = AL_POSITION, srcVelocity = AL_VELOCITY, srcDirection = AL_DIRECTION, srcSourceRelative = AL_SOURCE_RELATIVE, srcLooping = AL_LOOPING, srcBuffer = AL_BUFFER, srcSourceState = AL_SOURCE_STATE, srcBuffersQueued = AL_BUFFERS_QUEUED, srcBuffersProcessed = AL_BUFFERS_PROCESSED, srcSourceType = AL_SOURCE_TYPE, /* ALC_EXT_EFX */ srcConeOuterGainHF = AL_CONE_OUTER_GAINHF, srcAirAbsorptionFactor = AL_AIR_ABSORPTION_FACTOR, srcRoomRolloffFactor = AL_ROOM_ROLLOFF_FACTOR, srcDirectFilterGainHFAuto = AL_DIRECT_FILTER_GAINHF_AUTO, srcAuxSendFilterGainAuto = AL_AUXILIARY_SEND_FILTER_GAIN_AUTO, srcAuxSendFilterGainHFAuto = AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO, srcDirectFilter = AL_DIRECT_FILTER, srcAuxSendFilter = AL_AUXILIARY_SEND_FILTER, /* AL_SOFT_direct_channels */ srcDirectChannelsSOFT = AL_DIRECT_CHANNELS_SOFT, /* AL_EXT_source_distance_model */ srcDistanceModel = AL_DISTANCE_MODEL, /* AL_SOFT_source_latency */ srcSampleOffsetLatencySOFT = AL_SAMPLE_OFFSET_LATENCY_SOFT, srcSecOffsetLatencySOFT = AL_SEC_OFFSET_LATENCY_SOFT, /* AL_EXT_STEREO_ANGLES */ srcAngles = AL_STEREO_ANGLES, /* AL_EXT_SOURCE_RADIUS */ srcRadius = AL_SOURCE_RADIUS, /* AL_EXT_BFORMAT */ srcOrientation = AL_ORIENTATION, /* AL_SOFT_source_length */ srcByteLength = AL_BYTE_LENGTH_SOFT, srcSampleLength = AL_SAMPLE_LENGTH_SOFT, srcSecLength = AL_SEC_LENGTH_SOFT, /* AL_SOFT_source_resampler */ srcResampler = AL_SOURCE_RESAMPLER_SOFT, /* AL_SOFT_source_spatialize */ srcSpatialize = AL_SOURCE_SPATIALIZE_SOFT, /* ALC_SOFT_device_clock */ srcSampleOffsetClockSOFT = AL_SAMPLE_OFFSET_CLOCK_SOFT, srcSecOffsetClockSOFT = AL_SEC_OFFSET_CLOCK_SOFT, /* AL_SOFT_UHJ */ srcStereoMode = AL_STEREO_MODE_SOFT, srcSuperStereoWidth = AL_SUPER_STEREO_WIDTH_SOFT, /* AL_SOFT_buffer_sub_data */ srcByteRWOffsetsSOFT = AL_BYTE_RW_OFFSETS_SOFT, srcSampleRWOffsetsSOFT = AL_SAMPLE_RW_OFFSETS_SOFT, /* AL_SOFT_source_panning */ srcPanningEnabledSOFT = AL_PANNING_ENABLED_SOFT, srcPanSOFT = AL_PAN_SOFT, }; constexpr ALuint IntValsByProp(ALenum prop) { switch(static_cast(prop)) { case AL_SOURCE_STATE: case AL_SOURCE_TYPE: case AL_BUFFERS_QUEUED: case AL_BUFFERS_PROCESSED: case AL_BYTE_LENGTH_SOFT: case AL_SAMPLE_LENGTH_SOFT: case AL_SOURCE_RELATIVE: case AL_LOOPING: case AL_BUFFER: case AL_SAMPLE_OFFSET: case AL_BYTE_OFFSET: case AL_DIRECT_FILTER: case AL_DIRECT_FILTER_GAINHF_AUTO: case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO: case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO: case AL_DIRECT_CHANNELS_SOFT: case AL_DISTANCE_MODEL: case AL_SOURCE_RESAMPLER_SOFT: case AL_SOURCE_SPATIALIZE_SOFT: case AL_STEREO_MODE_SOFT: case AL_PANNING_ENABLED_SOFT: case AL_PAN_SOFT: return 1; case AL_SOURCE_RADIUS: /*AL_BYTE_RW_OFFSETS_SOFT:*/ if(sBufferSubDataCompat) return 2; /*fall-through*/ case AL_CONE_INNER_ANGLE: case AL_CONE_OUTER_ANGLE: case AL_PITCH: case AL_GAIN: case AL_MIN_GAIN: case AL_MAX_GAIN: case AL_REFERENCE_DISTANCE: case AL_ROLLOFF_FACTOR: case AL_CONE_OUTER_GAIN: case AL_MAX_DISTANCE: case AL_SEC_OFFSET: case AL_DOPPLER_FACTOR: case AL_CONE_OUTER_GAINHF: case AL_AIR_ABSORPTION_FACTOR: case AL_ROOM_ROLLOFF_FACTOR: case AL_SEC_LENGTH_SOFT: case AL_SUPER_STEREO_WIDTH_SOFT: return 1; /* 1x float */ case AL_SAMPLE_RW_OFFSETS_SOFT: if(sBufferSubDataCompat) return 2; break; case AL_AUXILIARY_SEND_FILTER: return 3; case AL_POSITION: case AL_VELOCITY: case AL_DIRECTION: return 3; /* 3x float */ case AL_ORIENTATION: return 6; /* 6x float */ case AL_SAMPLE_OFFSET_LATENCY_SOFT: case AL_SAMPLE_OFFSET_CLOCK_SOFT: case AL_STEREO_ANGLES: break; /* i64 only */ case AL_SEC_OFFSET_LATENCY_SOFT: case AL_SEC_OFFSET_CLOCK_SOFT: break; /* double only */ } return 0; } constexpr ALuint Int64ValsByProp(ALenum prop) { switch(static_cast(prop)) { case AL_SOURCE_STATE: case AL_SOURCE_TYPE: case AL_BUFFERS_QUEUED: case AL_BUFFERS_PROCESSED: case AL_BYTE_LENGTH_SOFT: case AL_SAMPLE_LENGTH_SOFT: case AL_SOURCE_RELATIVE: case AL_LOOPING: case AL_BUFFER: case AL_SAMPLE_OFFSET: case AL_BYTE_OFFSET: case AL_DIRECT_FILTER: case AL_DIRECT_FILTER_GAINHF_AUTO: case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO: case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO: case AL_DIRECT_CHANNELS_SOFT: case AL_DISTANCE_MODEL: case AL_SOURCE_RESAMPLER_SOFT: case AL_SOURCE_SPATIALIZE_SOFT: case AL_STEREO_MODE_SOFT: case AL_PANNING_ENABLED_SOFT: case AL_PAN_SOFT: return 1; case AL_SOURCE_RADIUS: /*AL_BYTE_RW_OFFSETS_SOFT:*/ if(sBufferSubDataCompat) return 2; /*fall-through*/ case AL_CONE_INNER_ANGLE: case AL_CONE_OUTER_ANGLE: case AL_PITCH: case AL_GAIN: case AL_MIN_GAIN: case AL_MAX_GAIN: case AL_REFERENCE_DISTANCE: case AL_ROLLOFF_FACTOR: case AL_CONE_OUTER_GAIN: case AL_MAX_DISTANCE: case AL_SEC_OFFSET: case AL_DOPPLER_FACTOR: case AL_CONE_OUTER_GAINHF: case AL_AIR_ABSORPTION_FACTOR: case AL_ROOM_ROLLOFF_FACTOR: case AL_SEC_LENGTH_SOFT: case AL_SUPER_STEREO_WIDTH_SOFT: return 1; /* 1x float */ case AL_SAMPLE_RW_OFFSETS_SOFT: if(sBufferSubDataCompat) return 2; break; case AL_SAMPLE_OFFSET_LATENCY_SOFT: case AL_SAMPLE_OFFSET_CLOCK_SOFT: case AL_STEREO_ANGLES: return 2; case AL_AUXILIARY_SEND_FILTER: return 3; case AL_POSITION: case AL_VELOCITY: case AL_DIRECTION: return 3; /* 3x float */ case AL_ORIENTATION: return 6; /* 6x float */ case AL_SEC_OFFSET_LATENCY_SOFT: case AL_SEC_OFFSET_CLOCK_SOFT: break; /* double only */ } return 0; } constexpr ALuint FloatValsByProp(ALenum prop) { switch(static_cast(prop)) { case AL_PITCH: case AL_GAIN: case AL_MIN_GAIN: case AL_MAX_GAIN: case AL_MAX_DISTANCE: case AL_ROLLOFF_FACTOR: case AL_DOPPLER_FACTOR: case AL_CONE_OUTER_GAIN: case AL_SEC_OFFSET: case AL_SAMPLE_OFFSET: case AL_BYTE_OFFSET: case AL_CONE_INNER_ANGLE: case AL_CONE_OUTER_ANGLE: case AL_REFERENCE_DISTANCE: case AL_CONE_OUTER_GAINHF: case AL_AIR_ABSORPTION_FACTOR: case AL_ROOM_ROLLOFF_FACTOR: case AL_DIRECT_FILTER_GAINHF_AUTO: case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO: case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO: case AL_DIRECT_CHANNELS_SOFT: case AL_DISTANCE_MODEL: case AL_SOURCE_RELATIVE: case AL_LOOPING: case AL_SOURCE_STATE: case AL_BUFFERS_QUEUED: case AL_BUFFERS_PROCESSED: case AL_SOURCE_TYPE: case AL_SOURCE_RESAMPLER_SOFT: case AL_SOURCE_SPATIALIZE_SOFT: case AL_BYTE_LENGTH_SOFT: case AL_SAMPLE_LENGTH_SOFT: case AL_SEC_LENGTH_SOFT: case AL_STEREO_MODE_SOFT: case AL_SUPER_STEREO_WIDTH_SOFT: case AL_PANNING_ENABLED_SOFT: case AL_PAN_SOFT: return 1; case AL_SOURCE_RADIUS: /*AL_BYTE_RW_OFFSETS_SOFT:*/ if(!sBufferSubDataCompat) return 1; /*fall-through*/ case AL_SAMPLE_RW_OFFSETS_SOFT: break; case AL_STEREO_ANGLES: return 2; case AL_POSITION: case AL_VELOCITY: case AL_DIRECTION: return 3; case AL_ORIENTATION: return 6; case AL_SEC_OFFSET_LATENCY_SOFT: case AL_SEC_OFFSET_CLOCK_SOFT: break; /* Double only */ case AL_BUFFER: case AL_DIRECT_FILTER: case AL_AUXILIARY_SEND_FILTER: break; /* i/i64 only */ case AL_SAMPLE_OFFSET_LATENCY_SOFT: case AL_SAMPLE_OFFSET_CLOCK_SOFT: break; /* i64 only */ } return 0; } constexpr ALuint DoubleValsByProp(ALenum prop) { switch(static_cast(prop)) { case AL_PITCH: case AL_GAIN: case AL_MIN_GAIN: case AL_MAX_GAIN: case AL_MAX_DISTANCE: case AL_ROLLOFF_FACTOR: case AL_DOPPLER_FACTOR: case AL_CONE_OUTER_GAIN: case AL_SEC_OFFSET: case AL_SAMPLE_OFFSET: case AL_BYTE_OFFSET: case AL_CONE_INNER_ANGLE: case AL_CONE_OUTER_ANGLE: case AL_REFERENCE_DISTANCE: case AL_CONE_OUTER_GAINHF: case AL_AIR_ABSORPTION_FACTOR: case AL_ROOM_ROLLOFF_FACTOR: case AL_DIRECT_FILTER_GAINHF_AUTO: case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO: case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO: case AL_DIRECT_CHANNELS_SOFT: case AL_DISTANCE_MODEL: case AL_SOURCE_RELATIVE: case AL_LOOPING: case AL_SOURCE_STATE: case AL_BUFFERS_QUEUED: case AL_BUFFERS_PROCESSED: case AL_SOURCE_TYPE: case AL_SOURCE_RESAMPLER_SOFT: case AL_SOURCE_SPATIALIZE_SOFT: case AL_BYTE_LENGTH_SOFT: case AL_SAMPLE_LENGTH_SOFT: case AL_SEC_LENGTH_SOFT: case AL_STEREO_MODE_SOFT: case AL_SUPER_STEREO_WIDTH_SOFT: case AL_PANNING_ENABLED_SOFT: case AL_PAN_SOFT: return 1; case AL_SOURCE_RADIUS: /*AL_BYTE_RW_OFFSETS_SOFT:*/ if(!sBufferSubDataCompat) return 1; /*fall-through*/ case AL_SAMPLE_RW_OFFSETS_SOFT: break; case AL_SEC_OFFSET_LATENCY_SOFT: case AL_SEC_OFFSET_CLOCK_SOFT: case AL_STEREO_ANGLES: return 2; case AL_POSITION: case AL_VELOCITY: case AL_DIRECTION: return 3; case AL_ORIENTATION: return 6; case AL_BUFFER: case AL_DIRECT_FILTER: case AL_AUXILIARY_SEND_FILTER: break; /* i/i64 only */ case AL_SAMPLE_OFFSET_LATENCY_SOFT: case AL_SAMPLE_OFFSET_CLOCK_SOFT: break; /* i64 only */ } return 0; } void UpdateSourceProps(ALsource *source, ALCcontext *context) { if(!context->mDeferUpdates) { if(Voice *voice{GetSourceVoice(source, context)}) { UpdateSourceProps(source, voice, context); return; } } source->mPropsDirty = true; } #if ALSOFT_EAX void CommitAndUpdateSourceProps(ALsource *source, ALCcontext *context) { if(!context->mDeferUpdates) { if(context->hasEax()) source->eaxCommit(); if(Voice *voice{GetSourceVoice(source, context)}) { UpdateSourceProps(source, voice, context); return; } } source->mPropsDirty = true; } #else inline void CommitAndUpdateSourceProps(ALsource *source, ALCcontext *context) { UpdateSourceProps(source, context); } #endif template auto PropTypeName() -> std::string_view = delete; template<> auto PropTypeName() -> std::string_view { return "integer"sv; } template<> auto PropTypeName() -> std::string_view { return "int64"sv; } template<> auto PropTypeName() -> std::string_view { return "float"sv; } template<> auto PropTypeName() -> std::string_view { return "double"sv; } /** * Returns a pair of lambdas to check the following setter. * * The first lambda checks the size of the span is valid for the required size, * throwing a context error if it fails. * * The second lambda tests the validity of the value check, throwing a context * error if it failed. */ template struct PairStruct { T First; U Second; }; template PairStruct(T,U) -> PairStruct; template auto GetCheckers(ALCcontext *context, const SourceProp prop, const al::span values) { return PairStruct{ [=](size_t expect) -> void { if(values.size() == expect) return; context->throw_error(AL_INVALID_ENUM, "Property {:#04x} expects {} value{}, got {}", as_unsigned(al::to_underlying(prop)), expect, (expect==1) ? "" : "s", values.size()); }, [context](bool passed) -> void { if(passed) return; context->throw_error(AL_INVALID_VALUE, "Value out of range"); } }; } template NOINLINE void SetProperty(ALsource *const Source, ALCcontext *const Context, const SourceProp prop, const al::span values) { auto [CheckSize, CheckValue] = GetCheckers(Context, prop, values); auto *device = Context->mALDevice.get(); switch(prop) { case AL_SOURCE_STATE: case AL_SOURCE_TYPE: case AL_BUFFERS_QUEUED: case AL_BUFFERS_PROCESSED: if constexpr(std::is_integral_v) { /* Query only */ Context->throw_error(AL_INVALID_OPERATION, "Setting read-only source property {:#04x}", as_unsigned(al::to_underlying(prop))); } break; case AL_BYTE_LENGTH_SOFT: case AL_SAMPLE_LENGTH_SOFT: case AL_SEC_LENGTH_SOFT: case AL_SAMPLE_OFFSET_LATENCY_SOFT: case AL_SEC_OFFSET_LATENCY_SOFT: case AL_SAMPLE_OFFSET_CLOCK_SOFT: case AL_SEC_OFFSET_CLOCK_SOFT: /* Query only */ Context->throw_error(AL_INVALID_OPERATION, "Setting read-only source property {:#04x}", as_unsigned(al::to_underlying(prop))); case AL_PITCH: CheckSize(1); if constexpr(std::is_floating_point_v) CheckValue(values[0] >= T{0} && std::isfinite(static_cast(values[0]))); else CheckValue(values[0] >= T{0}); Source->Pitch = static_cast(values[0]); return UpdateSourceProps(Source, Context); case AL_CONE_INNER_ANGLE: CheckSize(1); CheckValue(values[0] >= T{0} && values[0] <= T{360}); Source->InnerAngle = static_cast(values[0]); return CommitAndUpdateSourceProps(Source, Context); case AL_CONE_OUTER_ANGLE: CheckSize(1); CheckValue(values[0] >= T{0} && values[0] <= T{360}); Source->OuterAngle = static_cast(values[0]); return CommitAndUpdateSourceProps(Source, Context); case AL_GAIN: CheckSize(1); if constexpr(std::is_floating_point_v) CheckValue(values[0] >= T{0} && std::isfinite(static_cast(values[0]))); else CheckValue(values[0] >= T{0}); Source->Gain = static_cast(values[0]); return UpdateSourceProps(Source, Context); case AL_MAX_DISTANCE: CheckSize(1); if constexpr(std::is_floating_point_v) CheckValue(values[0] >= T{0} && std::isfinite(static_cast(values[0]))); else CheckValue(values[0] >= T{0}); Source->MaxDistance = static_cast(values[0]); return CommitAndUpdateSourceProps(Source, Context); case AL_ROLLOFF_FACTOR: CheckSize(1); if constexpr(std::is_floating_point_v) CheckValue(values[0] >= T{0} && std::isfinite(static_cast(values[0]))); else CheckValue(values[0] >= T{0}); Source->RolloffFactor = static_cast(values[0]); return CommitAndUpdateSourceProps(Source, Context); case AL_REFERENCE_DISTANCE: CheckSize(1); if constexpr(std::is_floating_point_v) CheckValue(values[0] >= T{0} && std::isfinite(static_cast(values[0]))); else CheckValue(values[0] >= T{0}); Source->RefDistance = static_cast(values[0]); return CommitAndUpdateSourceProps(Source, Context); case AL_MIN_GAIN: CheckSize(1); if constexpr(std::is_floating_point_v) CheckValue(values[0] >= T{0} && std::isfinite(static_cast(values[0]))); else CheckValue(values[0] >= T{0}); Source->MinGain = static_cast(values[0]); return UpdateSourceProps(Source, Context); case AL_MAX_GAIN: CheckSize(1); if constexpr(std::is_floating_point_v) CheckValue(values[0] >= T{0} && std::isfinite(static_cast(values[0]))); else CheckValue(values[0] >= T{0}); Source->MaxGain = static_cast(values[0]); return UpdateSourceProps(Source, Context); case AL_CONE_OUTER_GAIN: CheckSize(1); CheckValue(values[0] >= T{0} && values[0] <= T{1}); Source->OuterGain = static_cast(values[0]); return UpdateSourceProps(Source, Context); case AL_CONE_OUTER_GAINHF: CheckSize(1); CheckValue(values[0] >= T{0} && values[0] <= T{1}); Source->OuterGainHF = static_cast(values[0]); return UpdateSourceProps(Source, Context); case AL_AIR_ABSORPTION_FACTOR: CheckSize(1); CheckValue(values[0] >= T{0} && values[0] <= T{10}); Source->AirAbsorptionFactor = static_cast(values[0]); return UpdateSourceProps(Source, Context); case AL_ROOM_ROLLOFF_FACTOR: CheckSize(1); CheckValue(values[0] >= T{0} && values[0] <= T{1}); Source->RoomRolloffFactor = static_cast(values[0]); return UpdateSourceProps(Source, Context); case AL_DOPPLER_FACTOR: CheckSize(1); CheckValue(values[0] >= T{0} && values[0] <= T{1}); Source->DopplerFactor = static_cast(values[0]); return UpdateSourceProps(Source, Context); case AL_SOURCE_RELATIVE: if constexpr(std::is_integral_v) { CheckSize(1); CheckValue(values[0] == AL_FALSE || values[0] == AL_TRUE); Source->HeadRelative = values[0] != AL_FALSE; return CommitAndUpdateSourceProps(Source, Context); } break; case AL_LOOPING: if constexpr(std::is_integral_v) { CheckSize(1); CheckValue(values[0] == AL_FALSE || values[0] == AL_TRUE); Source->Looping = values[0] != AL_FALSE; if(Voice *voice{GetSourceVoice(Source, Context)}) { if(Source->Looping) voice->mLoopBuffer.store(&Source->mQueue.front(), std::memory_order_release); else voice->mLoopBuffer.store(nullptr, std::memory_order_release); /* If the source is playing, wait for the current mix to finish * to ensure it isn't currently looping back or reaching the * end. */ std::ignore = device->waitForMix(); } return; } break; case AL_BUFFER: if constexpr(std::is_integral_v) { CheckSize(1); if(const ALenum state{GetSourceState(Source, GetSourceVoice(Source, Context))}; state == AL_PLAYING || state == AL_PAUSED) Context->throw_error(AL_INVALID_OPERATION, "Setting buffer on playing or paused source {}", Source->id); std::deque oldlist; if(values[0]) { using UT = std::make_unsigned_t; std::lock_guard buflock{device->BufferLock}; ALbuffer *buffer{LookupBuffer(device, static_cast(values[0]))}; if(!buffer) Context->throw_error(AL_INVALID_VALUE, "Invalid buffer ID {}", values[0]); if(buffer->MappedAccess && !(buffer->MappedAccess&AL_MAP_PERSISTENT_BIT_SOFT)) Context->throw_error(AL_INVALID_OPERATION, "Setting non-persistently mapped buffer {}", buffer->id); if(buffer->mCallback && buffer->ref.load(std::memory_order_relaxed) != 0) Context->throw_error(AL_INVALID_OPERATION, "Setting already-set callback buffer {}", buffer->id); /* Add the selected buffer to a one-item queue */ std::deque newlist; newlist.emplace_back(); newlist.back().mCallback = buffer->mCallback; newlist.back().mUserData = buffer->mUserData; newlist.back().mBlockAlign = buffer->mBlockAlign; newlist.back().mSampleLen = buffer->mSampleLen; newlist.back().mLoopStart = buffer->mLoopStart; newlist.back().mLoopEnd = buffer->mLoopEnd; newlist.back().mSamples = buffer->mData; newlist.back().mBuffer = buffer; IncrementRef(buffer->ref); /* Source is now Static */ Source->SourceType = AL_STATIC; Source->mQueue.swap(oldlist); Source->mQueue.swap(newlist); } else { /* Source is now Undetermined */ Source->SourceType = AL_UNDETERMINED; Source->mQueue.swap(oldlist); } /* Delete all elements in the previous queue */ for(auto &item : oldlist) { if(ALbuffer *buffer{item.mBuffer}) DecrementRef(buffer->ref); } return; } break; case AL_SEC_OFFSET: case AL_SAMPLE_OFFSET: case AL_BYTE_OFFSET: CheckSize(1); if constexpr(std::is_floating_point_v) CheckValue(std::isfinite(values[0])); if(Voice *voice{GetSourceVoice(Source, Context)}) { auto vpos = GetSampleOffset(Source->mQueue, prop, static_cast(values[0])); if(!vpos) Context->throw_error(AL_INVALID_VALUE, "Invalid offset"); if(SetVoiceOffset(voice, *vpos, Source, Context, Context->mALDevice.get())) return; } Source->OffsetType = prop; Source->Offset = static_cast(values[0]); return; case AL_SAMPLE_RW_OFFSETS_SOFT: if(sBufferSubDataCompat) { if constexpr(std::is_integral_v) { /* Query only */ Context->throw_error(AL_INVALID_OPERATION, "Setting read-only source property {:#04x}", as_unsigned(al::to_underlying(prop))); } } break; case AL_SOURCE_RADIUS: /*AL_BYTE_RW_OFFSETS_SOFT:*/ if(sBufferSubDataCompat) { if constexpr(std::is_integral_v) { /* Query only */ Context->throw_error(AL_INVALID_OPERATION, "Setting read-only source property {:#04x}", as_unsigned(al::to_underlying(prop))); } break; } CheckSize(1); if constexpr(std::is_floating_point_v) CheckValue(values[0] >= T{0} && std::isfinite(static_cast(values[0]))); else CheckValue(values[0] >= T{0}); Source->Radius = static_cast(values[0]); return UpdateSourceProps(Source, Context); case AL_SUPER_STEREO_WIDTH_SOFT: CheckSize(1); CheckValue(values[0] >= T{0} && values[0] <= T{1}); Source->EnhWidth = static_cast(values[0]); return UpdateSourceProps(Source, Context); case AL_PANNING_ENABLED_SOFT: CheckSize(1); if(const ALenum state{GetSourceState(Source, GetSourceVoice(Source, Context))}; state == AL_PLAYING || state == AL_PAUSED) Context->throw_error(AL_INVALID_OPERATION, "Modifying panning enabled on playing or paused source {}", Source->id); CheckValue(values[0] == AL_FALSE || values[0] == AL_TRUE); Source->mPanningEnabled = values[0] != AL_FALSE; return UpdateSourceProps(Source, Context); case AL_PAN_SOFT: CheckSize(1); CheckValue(values[0] >= T{-1} && values[0] <= T{1}); Source->mPan = static_cast(values[0]); return UpdateSourceProps(Source, Context); case AL_STEREO_ANGLES: CheckSize(2); if constexpr(std::is_floating_point_v) CheckValue(std::isfinite(static_cast(values[0])) && std::isfinite(static_cast(values[1]))); Source->StereoPan[0] = static_cast(values[0]); Source->StereoPan[1] = static_cast(values[1]); return UpdateSourceProps(Source, Context); case AL_POSITION: CheckSize(3); if constexpr(std::is_floating_point_v) CheckValue(std::isfinite(static_cast(values[0])) && std::isfinite(static_cast(values[1])) && std::isfinite(static_cast(values[2]))); Source->Position[0] = static_cast(values[0]); Source->Position[1] = static_cast(values[1]); Source->Position[2] = static_cast(values[2]); return CommitAndUpdateSourceProps(Source, Context); case AL_VELOCITY: CheckSize(3); if constexpr(std::is_floating_point_v) CheckValue(std::isfinite(static_cast(values[0])) && std::isfinite(static_cast(values[1])) && std::isfinite(static_cast(values[2]))); Source->Velocity[0] = static_cast(values[0]); Source->Velocity[1] = static_cast(values[1]); Source->Velocity[2] = static_cast(values[2]); return CommitAndUpdateSourceProps(Source, Context); case AL_DIRECTION: CheckSize(3); if constexpr(std::is_floating_point_v) CheckValue(std::isfinite(static_cast(values[0])) && std::isfinite(static_cast(values[1])) && std::isfinite(static_cast(values[2]))); Source->Direction[0] = static_cast(values[0]); Source->Direction[1] = static_cast(values[1]); Source->Direction[2] = static_cast(values[2]); return CommitAndUpdateSourceProps(Source, Context); case AL_ORIENTATION: CheckSize(6); if constexpr(std::is_floating_point_v) CheckValue(std::isfinite(static_cast(values[0])) && std::isfinite(static_cast(values[1])) && std::isfinite(static_cast(values[2])) && std::isfinite(static_cast(values[3])) && std::isfinite(static_cast(values[4])) && std::isfinite(static_cast(values[5]))); Source->OrientAt[0] = static_cast(values[0]); Source->OrientAt[1] = static_cast(values[1]); Source->OrientAt[2] = static_cast(values[2]); Source->OrientUp[0] = static_cast(values[3]); Source->OrientUp[1] = static_cast(values[4]); Source->OrientUp[2] = static_cast(values[5]); return UpdateSourceProps(Source, Context); case AL_DIRECT_FILTER: if constexpr(std::is_integral_v) { CheckSize(1); const auto filterid = static_cast>(values[0]); if(values[0]) { std::lock_guard filterlock{device->FilterLock}; ALfilter *filter{LookupFilter(device, filterid)}; if(!filter) Context->throw_error(AL_INVALID_VALUE, "Invalid filter ID {}", filterid); Source->Direct.Gain = filter->Gain; Source->Direct.GainHF = filter->GainHF; Source->Direct.HFReference = filter->HFReference; Source->Direct.GainLF = filter->GainLF; Source->Direct.LFReference = filter->LFReference; } else { Source->Direct.Gain = 1.0f; Source->Direct.GainHF = 1.0f; Source->Direct.HFReference = LowPassFreqRef; Source->Direct.GainLF = 1.0f; Source->Direct.LFReference = HighPassFreqRef; } return UpdateSourceProps(Source, Context); } break; case AL_DIRECT_FILTER_GAINHF_AUTO: if constexpr(std::is_integral_v) { CheckSize(1); CheckValue(values[0] == AL_FALSE || values[0] == AL_TRUE); Source->DryGainHFAuto = values[0] != AL_FALSE; return UpdateSourceProps(Source, Context); } break; case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO: if constexpr(std::is_integral_v) { CheckSize(1); CheckValue(values[0] == AL_FALSE || values[0] == AL_TRUE); Source->WetGainAuto = values[0] != AL_FALSE; return UpdateSourceProps(Source, Context); } break; case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO: if constexpr(std::is_integral_v) { CheckSize(1); CheckValue(values[0] == AL_FALSE || values[0] == AL_TRUE); Source->WetGainHFAuto = values[0] != AL_FALSE; return UpdateSourceProps(Source, Context); } break; case AL_DIRECT_CHANNELS_SOFT: if constexpr(std::is_integral_v) { CheckSize(1); if(auto mode = DirectModeFromEnum(values[0])) { Source->DirectChannels = *mode; return UpdateSourceProps(Source, Context); } Context->throw_error(AL_INVALID_VALUE, "Invalid direct channels mode: {:#x}", as_unsigned(values[0])); } break; case AL_DISTANCE_MODEL: if constexpr(std::is_integral_v) { CheckSize(1); if(auto model = DistanceModelFromALenum(values[0])) { Source->mDistanceModel = *model; if(Context->mSourceDistanceModel) UpdateSourceProps(Source, Context); return; } Context->throw_error(AL_INVALID_VALUE, "Invalid distance model: {:#x}", as_unsigned(values[0])); } break; case AL_SOURCE_RESAMPLER_SOFT: if constexpr(std::is_integral_v) { CheckSize(1); CheckValue(values[0] >= 0 && values[0] <= static_cast(Resampler::Max)); Source->mResampler = static_cast(values[0]); return UpdateSourceProps(Source, Context); } break; case AL_SOURCE_SPATIALIZE_SOFT: if constexpr(std::is_integral_v) { CheckSize(1); if(auto mode = SpatializeModeFromEnum(values[0])) { Source->mSpatialize = *mode; return UpdateSourceProps(Source, Context); } Context->throw_error(AL_INVALID_VALUE, "Invalid source spatialize mode: {}", values[0]); } break; case AL_STEREO_MODE_SOFT: if constexpr(std::is_integral_v) { CheckSize(1); if(const ALenum state{GetSourceState(Source, GetSourceVoice(Source, Context))}; state == AL_PLAYING || state == AL_PAUSED) Context->throw_error(AL_INVALID_OPERATION, "Modifying stereo mode on playing or paused source {}", Source->id); if(auto mode = StereoModeFromEnum(values[0])) { Source->mStereoMode = *mode; return; } Context->throw_error(AL_INVALID_VALUE, "Invalid stereo mode: {:#x}", as_unsigned(values[0])); } break; case AL_AUXILIARY_SEND_FILTER: if constexpr(std::is_integral_v) { CheckSize(3); const auto slotid = static_cast>(values[0]); const auto sendidx = static_cast>(values[1]); const auto filterid = static_cast>(values[2]); std::unique_lock slotlock{Context->mEffectSlotLock}; ALeffectslot *slot{}; if(values[0]) { slot = LookupEffectSlot(Context, slotid); if(!slot) Context->throw_error(AL_INVALID_VALUE, "Invalid effect ID {}", slotid); } if(sendidx >= device->NumAuxSends) Context->throw_error(AL_INVALID_VALUE, "Invalid send {}", sendidx); auto &send = Source->Send[static_cast(sendidx)]; if(values[2]) { std::lock_guard filterlock{device->FilterLock}; ALfilter *filter{LookupFilter(device, filterid)}; if(!filter) Context->throw_error(AL_INVALID_VALUE, "Invalid filter ID {}", filterid); send.Gain = filter->Gain; send.GainHF = filter->GainHF; send.HFReference = filter->HFReference; send.GainLF = filter->GainLF; send.LFReference = filter->LFReference; } else { /* Disable filter */ send.Gain = 1.0f; send.GainHF = 1.0f; send.HFReference = LowPassFreqRef; send.GainLF = 1.0f; send.LFReference = HighPassFreqRef; } /* We must force an update if the current auxiliary slot is valid * and about to be changed on an active source, in case the old * slot is about to be deleted. */ if(send.Slot && slot != send.Slot && IsPlayingOrPaused(Source)) { /* Add refcount on the new slot, and release the previous slot */ if(slot) IncrementRef(slot->ref); if(auto *oldslot = send.Slot) DecrementRef(oldslot->ref); send.Slot = slot; Voice *voice{GetSourceVoice(Source, Context)}; if(voice) UpdateSourceProps(Source, voice, Context); else Source->mPropsDirty = true; } else { if(slot) IncrementRef(slot->ref); if(auto *oldslot = send.Slot) DecrementRef(oldslot->ref); send.Slot = slot; UpdateSourceProps(Source, Context); } return; } break; } Context->throw_error(AL_INVALID_ENUM, "Invalid source {} property {:#04x}", PropTypeName(), as_unsigned(al::to_underlying(prop))); } template auto GetSizeChecker(ALCcontext *context, const SourceProp prop, const al::span values) { return [=](size_t expect) -> void { if(values.size() == expect) LIKELY return; context->throw_error(AL_INVALID_ENUM, "Property {:#04x} expects {} value{}, got {}", as_unsigned(al::to_underlying(prop)), expect, (expect==1) ? "" : "s", values.size()); }; } template NOINLINE void GetProperty(ALsource *const Source, ALCcontext *const Context, const SourceProp prop, const al::span values) { using std::chrono::duration_cast; auto CheckSize = GetSizeChecker(Context, prop, values); auto *device = Context->mALDevice.get(); switch(prop) { case AL_GAIN: CheckSize(1); values[0] = static_cast(Source->Gain); return; case AL_PITCH: CheckSize(1); values[0] = static_cast(Source->Pitch); return; case AL_MAX_DISTANCE: CheckSize(1); values[0] = static_cast(Source->MaxDistance); return; case AL_ROLLOFF_FACTOR: CheckSize(1); values[0] = static_cast(Source->RolloffFactor); return; case AL_REFERENCE_DISTANCE: CheckSize(1); values[0] = static_cast(Source->RefDistance); return; case AL_CONE_INNER_ANGLE: CheckSize(1); values[0] = static_cast(Source->InnerAngle); return; case AL_CONE_OUTER_ANGLE: CheckSize(1); values[0] = static_cast(Source->OuterAngle); return; case AL_MIN_GAIN: CheckSize(1); values[0] = static_cast(Source->MinGain); return; case AL_MAX_GAIN: CheckSize(1); values[0] = static_cast(Source->MaxGain); return; case AL_CONE_OUTER_GAIN: CheckSize(1); values[0] = static_cast(Source->OuterGain); return; case AL_SEC_OFFSET: case AL_SAMPLE_OFFSET: case AL_BYTE_OFFSET: CheckSize(1); values[0] = GetSourceOffset(Source, prop, Context); return; case AL_CONE_OUTER_GAINHF: CheckSize(1); values[0] = static_cast(Source->OuterGainHF); return; case AL_AIR_ABSORPTION_FACTOR: CheckSize(1); values[0] = static_cast(Source->AirAbsorptionFactor); return; case AL_ROOM_ROLLOFF_FACTOR: CheckSize(1); values[0] = static_cast(Source->RoomRolloffFactor); return; case AL_DOPPLER_FACTOR: CheckSize(1); values[0] = static_cast(Source->DopplerFactor); return; case AL_SAMPLE_RW_OFFSETS_SOFT: if constexpr(std::is_integral_v) { if(sBufferSubDataCompat) { CheckSize(2); values[0] = GetSourceOffset(Source, AL_SAMPLE_OFFSET, Context); /* FIXME: values[1] should be ahead of values[0] by the device * update time. It needs to clamp or wrap the length of the * buffer queue. */ values[1] = values[0]; return; } } break; case AL_SOURCE_RADIUS: /*AL_BYTE_RW_OFFSETS_SOFT:*/ if constexpr(std::is_floating_point_v) { if(sBufferSubDataCompat) break; CheckSize(1); values[0] = static_cast(Source->Radius); return; } else { if(sBufferSubDataCompat) { CheckSize(2); values[0] = GetSourceOffset(Source, AL_BYTE_OFFSET, Context); /* FIXME: values[1] should be ahead of values[0] by the device * update time. It needs to clamp or wrap the length of the * buffer queue. */ values[1] = values[0]; return; } break; } case AL_SUPER_STEREO_WIDTH_SOFT: CheckSize(1); values[0] = static_cast(Source->EnhWidth); return; case AL_BYTE_LENGTH_SOFT: case AL_SAMPLE_LENGTH_SOFT: case AL_SEC_LENGTH_SOFT: CheckSize(1); values[0] = GetSourceLength(Source, prop); return; case AL_PANNING_ENABLED_SOFT: CheckSize(1); values[0] = Source->mPanningEnabled; return; case AL_PAN_SOFT: CheckSize(1); values[0] = static_cast(Source->mPan); return; case AL_STEREO_ANGLES: if constexpr(std::is_floating_point_v) { CheckSize(2); values[0] = static_cast(Source->StereoPan[0]); values[1] = static_cast(Source->StereoPan[1]); return; } break; case AL_SAMPLE_OFFSET_LATENCY_SOFT: if constexpr(std::is_same_v) { CheckSize(2); /* Get the source offset with the clock time first. Then get the * clock time with the device latency. Order is important. */ ClockLatency clocktime{}; nanoseconds srcclock{}; values[0] = GetSourceSampleOffset(Source, Context, &srcclock); { std::lock_guard statelock{device->StateLock}; clocktime = GetClockLatency(device, device->Backend.get()); } if(srcclock == clocktime.ClockTime) values[1] = nanoseconds{clocktime.Latency}.count(); else { /* If the clock time incremented, reduce the latency by that * much since it's that much closer to the source offset it got * earlier. */ const auto diff = std::min(clocktime.Latency, clocktime.ClockTime-srcclock); values[1] = nanoseconds{clocktime.Latency - diff}.count(); } return; } break; case AL_SAMPLE_OFFSET_CLOCK_SOFT: if constexpr(std::is_same_v) { CheckSize(2); nanoseconds srcclock{}; values[0] = GetSourceSampleOffset(Source, Context, &srcclock); values[1] = srcclock.count(); return; } break; case AL_SEC_OFFSET_LATENCY_SOFT: if constexpr(std::is_same_v) { CheckSize(2); /* Get the source offset with the clock time first. Then get the * clock time with the device latency. Order is important. */ ClockLatency clocktime{}; nanoseconds srcclock{}; values[0] = GetSourceSecOffset(Source, Context, &srcclock); { std::lock_guard statelock{device->StateLock}; clocktime = GetClockLatency(device, device->Backend.get()); } if(srcclock == clocktime.ClockTime) values[1] = duration_cast(clocktime.Latency).count(); else { /* If the clock time incremented, reduce the latency by that * much since it's that much closer to the source offset it got * earlier. */ const auto diff = std::min(clocktime.Latency, clocktime.ClockTime-srcclock); values[1] = duration_cast(clocktime.Latency - diff).count(); } return; } break; case AL_SEC_OFFSET_CLOCK_SOFT: if constexpr(std::is_same_v) { CheckSize(2); nanoseconds srcclock{}; values[0] = GetSourceSecOffset(Source, Context, &srcclock); values[1] = duration_cast(srcclock).count(); return; } break; case AL_POSITION: CheckSize(3); values[0] = static_cast(Source->Position[0]); values[1] = static_cast(Source->Position[1]); values[2] = static_cast(Source->Position[2]); return; case AL_VELOCITY: CheckSize(3); values[0] = static_cast(Source->Velocity[0]); values[1] = static_cast(Source->Velocity[1]); values[2] = static_cast(Source->Velocity[2]); return; case AL_DIRECTION: CheckSize(3); values[0] = static_cast(Source->Direction[0]); values[1] = static_cast(Source->Direction[1]); values[2] = static_cast(Source->Direction[2]); return; case AL_ORIENTATION: CheckSize(6); values[0] = static_cast(Source->OrientAt[0]); values[1] = static_cast(Source->OrientAt[1]); values[2] = static_cast(Source->OrientAt[2]); values[3] = static_cast(Source->OrientUp[0]); values[4] = static_cast(Source->OrientUp[1]); values[5] = static_cast(Source->OrientUp[2]); return; case AL_SOURCE_RELATIVE: if constexpr(std::is_integral_v) { CheckSize(1); values[0] = Source->HeadRelative; return; } break; case AL_LOOPING: if constexpr(std::is_integral_v) { CheckSize(1); values[0] = Source->Looping; return; } break; case AL_BUFFER: if constexpr(std::is_integral_v) { CheckSize(1); const ALbufferQueueItem *BufferList{}; /* HACK: This query should technically only return the buffer set * on a static source. However, some apps had used it to detect * when a streaming source changed buffers, so report the current * buffer's ID when playing. */ if(Source->SourceType == AL_STATIC || Source->state == AL_INITIAL) { if(!Source->mQueue.empty()) BufferList = &Source->mQueue.front(); } else if(Voice *voice{GetSourceVoice(Source, Context)}) { VoiceBufferItem *Current{voice->mCurrentBuffer.load(std::memory_order_relaxed)}; const auto iter = std::find_if(Source->mQueue.cbegin(), Source->mQueue.cend(), [Current](const ALbufferQueueItem &item) noexcept -> bool { return &item == Current; }); BufferList = (iter != Source->mQueue.cend()) ? al::to_address(iter) : nullptr; } ALbuffer *buffer{BufferList ? BufferList->mBuffer : nullptr}; values[0] = buffer ? static_cast(buffer->id) : T{0}; return; } break; case AL_SOURCE_STATE: if constexpr(std::is_integral_v) { CheckSize(1); values[0] = GetSourceState(Source, GetSourceVoice(Source, Context)); return; } break; case AL_BUFFERS_QUEUED: if constexpr(std::is_integral_v) { CheckSize(1); values[0] = static_cast(Source->mQueue.size()); return; } break; case AL_BUFFERS_PROCESSED: if constexpr(std::is_integral_v) { CheckSize(1); if(Source->Looping || Source->SourceType != AL_STREAMING) { /* Buffers on a looping source are in a perpetual state of * PENDING, so don't report any as PROCESSED */ values[0] = 0; } else { int played{0}; if(Source->state != AL_INITIAL) { const VoiceBufferItem *Current{nullptr}; if(Voice *voice{GetSourceVoice(Source, Context)}) Current = voice->mCurrentBuffer.load(std::memory_order_relaxed); for(auto &item : Source->mQueue) { if(&item == Current) break; ++played; } } values[0] = played; } return; } break; case AL_SOURCE_TYPE: if constexpr(std::is_integral_v) { CheckSize(1); values[0] = Source->SourceType; return; } break; case AL_DIRECT_FILTER_GAINHF_AUTO: if constexpr(std::is_integral_v) { CheckSize(1); values[0] = Source->DryGainHFAuto; return; } break; case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO: if constexpr(std::is_integral_v) { CheckSize(1); values[0] = Source->WetGainAuto; return; } break; case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO: if constexpr(std::is_integral_v) { CheckSize(1); values[0] = Source->WetGainHFAuto; return; } break; case AL_DIRECT_CHANNELS_SOFT: if constexpr(std::is_integral_v) { CheckSize(1); values[0] = EnumFromDirectMode(Source->DirectChannels); return; } break; case AL_DISTANCE_MODEL: if constexpr(std::is_integral_v) { CheckSize(1); values[0] = ALenumFromDistanceModel(Source->mDistanceModel); return; } break; case AL_SOURCE_RESAMPLER_SOFT: if constexpr(std::is_integral_v) { CheckSize(1); values[0] = static_cast(Source->mResampler); return; } break; case AL_SOURCE_SPATIALIZE_SOFT: if constexpr(std::is_integral_v) { CheckSize(1); values[0] = EnumFromSpatializeMode(Source->mSpatialize); return; } break; case AL_STEREO_MODE_SOFT: if constexpr(std::is_integral_v) { CheckSize(1); values[0] = EnumFromStereoMode(Source->mStereoMode); return; } break; case AL_DIRECT_FILTER: case AL_AUXILIARY_SEND_FILTER: break; } Context->throw_error(AL_INVALID_ENUM, "Invalid source {} query property {:#04x}", PropTypeName(), as_unsigned(al::to_underlying(prop))); } void StartSources(ALCcontext *const context, const al::span srchandles, const nanoseconds start_time=nanoseconds::min()) { auto *device = context->mALDevice.get(); /* If the device is disconnected, and voices stop on disconnect, go right * to stopped. */ if(!device->Connected.load(std::memory_order_acquire)) UNLIKELY { if(context->mStopVoicesOnDisconnect.load(std::memory_order_acquire)) { for(ALsource *source : srchandles) { /* TODO: Send state change event? */ source->Offset = 0.0; source->OffsetType = AL_NONE; source->state = AL_STOPPED; } return; } } /* Count the number of reusable voices. */ auto voicelist = context->getVoicesSpan(); size_t free_voices{0}; for(const Voice *voice : voicelist) { free_voices += (voice->mPlayState.load(std::memory_order_acquire) == Voice::Stopped && voice->mSourceID.load(std::memory_order_relaxed) == 0u && voice->mPendingChange.load(std::memory_order_relaxed) == false); if(free_voices == srchandles.size()) break; } if(srchandles.size() != free_voices) UNLIKELY { const size_t inc_amount{srchandles.size() - free_voices}; auto &allvoices = *context->mVoices.load(std::memory_order_relaxed); if(inc_amount > allvoices.size() - voicelist.size()) { /* Increase the number of voices to handle the request. */ context->allocVoices(inc_amount - (allvoices.size() - voicelist.size())); } context->mActiveVoiceCount.fetch_add(inc_amount, std::memory_order_release); voicelist = context->getVoicesSpan(); } auto voiceiter = voicelist.begin(); ALuint vidx{0}; VoiceChange *tail{}, *cur{}; for(ALsource *source : srchandles) { /* Check that there is a queue containing at least one valid, non zero * length buffer. */ auto find_buffer = [](ALbufferQueueItem &entry) noexcept { return entry.mSampleLen != 0 || entry.mCallback != nullptr; }; auto BufferList = std::find_if(source->mQueue.begin(), source->mQueue.end(), find_buffer); /* If there's nothing to play, go right to stopped. */ if(BufferList == source->mQueue.end()) UNLIKELY { /* NOTE: A source without any playable buffers should not have a * Voice since it shouldn't be in a playing or paused state. So * there's no need to look up its voice and clear the source. */ source->Offset = 0.0; source->OffsetType = AL_NONE; source->state = AL_STOPPED; continue; } if(!cur) cur = tail = GetVoiceChanger(context); else { cur->mNext.store(GetVoiceChanger(context), std::memory_order_relaxed); cur = cur->mNext.load(std::memory_order_relaxed); } Voice *voice{GetSourceVoice(source, context)}; switch(GetSourceState(source, voice)) { case AL_PAUSED: /* A source that's paused simply resumes. If there's no voice, it * was lost from a disconnect, so just start over with a new one. */ cur->mOldVoice = nullptr; if(!voice) break; cur->mVoice = voice; cur->mSourceID = source->id; cur->mState = VChangeState::Play; source->state = AL_PLAYING; #if ALSOFT_EAX if(context->hasEax()) source->eaxCommit(); #endif // ALSOFT_EAX continue; case AL_PLAYING: /* A source that's already playing is restarted from the beginning. * Stop the current voice and start a new one so it properly cross- * fades back to the beginning. */ if(voice) voice->mPendingChange.store(true, std::memory_order_relaxed); cur->mOldVoice = voice; voice = nullptr; break; default: assert(voice == nullptr); cur->mOldVoice = nullptr; #if ALSOFT_EAX if(context->hasEax()) source->eaxCommit(); #endif // ALSOFT_EAX break; } /* Find the next unused voice to play this source with. */ for(;voiceiter != voicelist.end();++voiceiter,++vidx) { Voice *v{*voiceiter}; if(v->mPlayState.load(std::memory_order_acquire) == Voice::Stopped && v->mSourceID.load(std::memory_order_relaxed) == 0u && v->mPendingChange.load(std::memory_order_relaxed) == false) { voice = v; break; } } ASSUME(voice != nullptr); voice->mPosition.store(0, std::memory_order_relaxed); voice->mPositionFrac.store(0, std::memory_order_relaxed); voice->mCurrentBuffer.store(&source->mQueue.front(), std::memory_order_relaxed); voice->mStartTime = start_time; voice->mFlags.reset(); /* A source that's not playing or paused has any offset applied when it * starts playing. */ if(const ALenum offsettype{source->OffsetType}) { const double offset{source->Offset}; source->OffsetType = AL_NONE; source->Offset = 0.0; if(auto vpos = GetSampleOffset(source->mQueue, offsettype, offset)) { voice->mPosition.store(vpos->pos, std::memory_order_relaxed); voice->mPositionFrac.store(vpos->frac, std::memory_order_relaxed); voice->mCurrentBuffer.store(vpos->bufferitem, std::memory_order_relaxed); if(vpos->pos > 0 || (vpos->pos == 0 && vpos->frac > 0) || vpos->bufferitem != &source->mQueue.front()) voice->mFlags.set(VoiceIsFading); } } InitVoice(voice, source, al::to_address(BufferList), context, device); source->VoiceIdx = vidx; source->state = AL_PLAYING; cur->mVoice = voice; cur->mSourceID = source->id; cur->mState = VChangeState::Play; } if(tail) LIKELY SendVoiceChanges(context, tail); } } // namespace AL_API DECL_FUNC2(void, alGenSources, ALsizei,n, ALuint*,sources) FORCE_ALIGN void AL_APIENTRY alGenSourcesDirect(ALCcontext *context, ALsizei n, ALuint *sources) noexcept try { if(n < 0) context->throw_error(AL_INVALID_VALUE, "Generating {} sources", n); if(n <= 0) UNLIKELY return; auto srclock = std::unique_lock{context->mSourceLock}; auto *device = context->mALDevice.get(); const al::span sids{sources, static_cast(n)}; if(context->mNumSources > device->SourcesMax || sids.size() > device->SourcesMax-context->mNumSources) context->throw_error(AL_OUT_OF_MEMORY, "Exceeding {} source limit ({} + {})", device->SourcesMax, context->mNumSources, n); if(!EnsureSources(context, sids.size())) context->throw_error(AL_OUT_OF_MEMORY, "Failed to allocate {} source{}", n, (n==1) ? "" : "s"); std::generate(sids.begin(), sids.end(), [context]{ return AllocSource(context)->id; }); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC2(void, alDeleteSources, ALsizei,n, const ALuint*,sources) FORCE_ALIGN void AL_APIENTRY alDeleteSourcesDirect(ALCcontext *context, ALsizei n, const ALuint *sources) noexcept try { if(n < 0) context->throw_error(AL_INVALID_VALUE, "Deleting {} sources", n); if(n <= 0) UNLIKELY return; std::lock_guard srclock{context->mSourceLock}; /* Check that all Sources are valid */ auto validate_source = [context](const ALuint sid) -> bool { return LookupSource(context, sid) != nullptr; }; const al::span sids{sources, static_cast(n)}; auto invsrc = std::find_if_not(sids.begin(), sids.end(), validate_source); if(invsrc != sids.end()) context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", *invsrc); /* All good. Delete source IDs. */ auto delete_source = [&context](const ALuint sid) -> void { if(ALsource *src{LookupSource(context, sid)}) FreeSource(context, src); }; std::for_each(sids.begin(), sids.end(), delete_source); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC1(ALboolean, alIsSource, ALuint,source) FORCE_ALIGN ALboolean AL_APIENTRY alIsSourceDirect(ALCcontext *context, ALuint source) noexcept { std::lock_guard srclock{context->mSourceLock}; if(LookupSource(context, source) != nullptr) return AL_TRUE; return AL_FALSE; } AL_API DECL_FUNC3(void, alSourcef, ALuint,source, ALenum,param, ALfloat,value) FORCE_ALIGN void AL_APIENTRY alSourcefDirect(ALCcontext *context, ALuint source, ALenum param, ALfloat value) noexcept try { std::lock_guard proplock{context->mPropLock}; std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); SetProperty(Source, context, static_cast(param), {&value, 1u}); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC5(void, alSource3f, ALuint,source, ALenum,param, ALfloat,value1, ALfloat,value2, ALfloat,value3) FORCE_ALIGN void AL_APIENTRY alSource3fDirect(ALCcontext *context, ALuint source, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3) noexcept try { std::lock_guard proplock{context->mPropLock}; std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); const std::array fvals{value1, value2, value3}; SetProperty(Source, context, static_cast(param), fvals); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alSourcefv, ALuint,source, ALenum,param, const ALfloat*,values) FORCE_ALIGN void AL_APIENTRY alSourcefvDirect(ALCcontext *context, ALuint source, ALenum param, const ALfloat *values) noexcept try { std::lock_guard proplock{context->mPropLock}; std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); if(!values) context->throw_error(AL_INVALID_VALUE, "NULL pointer"); const ALuint count{FloatValsByProp(param)}; SetProperty(Source, context, static_cast(param), al::span{values, count}); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNCEXT3(void, alSourced,SOFT, ALuint,source, ALenum,param, ALdouble,value) FORCE_ALIGN void AL_APIENTRY alSourcedDirectSOFT(ALCcontext *context, ALuint source, ALenum param, ALdouble value) noexcept try { std::lock_guard proplock{context->mPropLock}; std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); SetProperty(Source, context, static_cast(param), {&value, 1}); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNCEXT5(void, alSource3d,SOFT, ALuint,source, ALenum,param, ALdouble,value1, ALdouble,value2, ALdouble,value3) FORCE_ALIGN void AL_APIENTRY alSource3dDirectSOFT(ALCcontext *context, ALuint source, ALenum param, ALdouble value1, ALdouble value2, ALdouble value3) noexcept try { std::lock_guard proplock{context->mPropLock}; std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); const std::array dvals{value1, value2, value3}; SetProperty(Source, context, static_cast(param), dvals); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNCEXT3(void, alSourcedv,SOFT, ALuint,source, ALenum,param, const ALdouble*,values) FORCE_ALIGN void AL_APIENTRY alSourcedvDirectSOFT(ALCcontext *context, ALuint source, ALenum param, const ALdouble *values) noexcept try { std::lock_guard proplock{context->mPropLock}; std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); if(!values) context->throw_error(AL_INVALID_VALUE, "NULL pointer"); const ALuint count{DoubleValsByProp(param)}; SetProperty(Source, context, static_cast(param), al::span{values, count}); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alSourcei, ALuint,source, ALenum,param, ALint,value) FORCE_ALIGN void AL_APIENTRY alSourceiDirect(ALCcontext *context, ALuint source, ALenum param, ALint value) noexcept try { std::lock_guard proplock{context->mPropLock}; std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); SetProperty(Source, context, static_cast(param), {&value, 1u}); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC5(void, alSource3i, ALuint,buffer, ALenum,param, ALint,value1, ALint,value2, ALint,value3) FORCE_ALIGN void AL_APIENTRY alSource3iDirect(ALCcontext *context, ALuint source, ALenum param, ALint value1, ALint value2, ALint value3) noexcept try { std::lock_guard proplock{context->mPropLock}; std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); const std::array ivals{value1, value2, value3}; SetProperty(Source, context, static_cast(param), ivals); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alSourceiv, ALuint,source, ALenum,param, const ALint*,values) FORCE_ALIGN void AL_APIENTRY alSourceivDirect(ALCcontext *context, ALuint source, ALenum param, const ALint *values) noexcept try { std::lock_guard proplock{context->mPropLock}; std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); if(!values) context->throw_error(AL_INVALID_VALUE, "NULL pointer"); const ALuint count{IntValsByProp(param)}; SetProperty(Source, context, static_cast(param), al::span{values, count}); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNCEXT3(void, alSourcei64,SOFT, ALuint,source, ALenum,param, ALint64SOFT,value) FORCE_ALIGN void AL_APIENTRY alSourcei64DirectSOFT(ALCcontext *context, ALuint source, ALenum param, ALint64SOFT value) noexcept try { std::lock_guard proplock{context->mPropLock}; std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); SetProperty(Source, context, static_cast(param), {&value, 1u}); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNCEXT5(void, alSource3i64,SOFT, ALuint,source, ALenum,param, ALint64SOFT,value1, ALint64SOFT,value2, ALint64SOFT,value3) FORCE_ALIGN void AL_APIENTRY alSource3i64DirectSOFT(ALCcontext *context, ALuint source, ALenum param, ALint64SOFT value1, ALint64SOFT value2, ALint64SOFT value3) noexcept try { std::lock_guard proplock{context->mPropLock}; std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); const std::array i64vals{value1, value2, value3}; SetProperty(Source, context, static_cast(param), i64vals); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNCEXT3(void, alSourcei64v,SOFT, ALuint,source, ALenum,param, const ALint64SOFT*,values) FORCE_ALIGN void AL_APIENTRY alSourcei64vDirectSOFT(ALCcontext *context, ALuint source, ALenum param, const ALint64SOFT *values) noexcept try { std::lock_guard proplock{context->mPropLock}; std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); if(!values) context->throw_error(AL_INVALID_VALUE, "NULL pointer"); const ALuint count{Int64ValsByProp(param)}; SetProperty(Source, context, static_cast(param), al::span{values, count}); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alGetSourcef, ALuint,source, ALenum,param, ALfloat*,value) FORCE_ALIGN void AL_APIENTRY alGetSourcefDirect(ALCcontext *context, ALuint source, ALenum param, ALfloat *value) noexcept try { std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); if(!value) context->throw_error(AL_INVALID_VALUE, "NULL pointer"); GetProperty(Source, context, static_cast(param), al::span{value, 1u}); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC5(void, alGetSource3f, ALuint,source, ALenum,param, ALfloat*,value1, ALfloat*,value2, ALfloat*,value3) FORCE_ALIGN void AL_APIENTRY alGetSource3fDirect(ALCcontext *context, ALuint source, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3) noexcept try { std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); if(!(value1 && value2 && value3)) context->throw_error(AL_INVALID_VALUE, "NULL pointer"); std::array fvals{}; GetProperty(Source, context, static_cast(param), fvals); *value1 = fvals[0]; *value2 = fvals[1]; *value3 = fvals[2]; } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alGetSourcefv, ALuint,source, ALenum,param, ALfloat*,values) FORCE_ALIGN void AL_APIENTRY alGetSourcefvDirect(ALCcontext *context, ALuint source, ALenum param, ALfloat *values) noexcept try { std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); if(!values) context->throw_error(AL_INVALID_VALUE, "NULL pointer"); const ALuint count{FloatValsByProp(param)}; GetProperty(Source, context, static_cast(param), al::span{values, count}); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNCEXT3(void, alGetSourced,SOFT, ALuint,source, ALenum,param, ALdouble*,value) FORCE_ALIGN void AL_APIENTRY alGetSourcedDirectSOFT(ALCcontext *context, ALuint source, ALenum param, ALdouble *value) noexcept try { std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); if(!value) context->throw_error(AL_INVALID_VALUE, "NULL pointer"); GetProperty(Source, context, static_cast(param), al::span{value, 1u}); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNCEXT5(void, alGetSource3d,SOFT, ALuint,source, ALenum,param, ALdouble*,value1, ALdouble*,value2, ALdouble*,value3) FORCE_ALIGN void AL_APIENTRY alGetSource3dDirectSOFT(ALCcontext *context, ALuint source, ALenum param, ALdouble *value1, ALdouble *value2, ALdouble *value3) noexcept try { std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); if(!(value1 && value2 && value3)) context->throw_error(AL_INVALID_VALUE, "NULL pointer"); std::array dvals{}; GetProperty(Source, context, static_cast(param), dvals); *value1 = dvals[0]; *value2 = dvals[1]; *value3 = dvals[2]; } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNCEXT3(void, alGetSourcedv,SOFT, ALuint,source, ALenum,param, ALdouble*,values) FORCE_ALIGN void AL_APIENTRY alGetSourcedvDirectSOFT(ALCcontext *context, ALuint source, ALenum param, ALdouble *values) noexcept try { std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); if(!values) context->throw_error(AL_INVALID_VALUE, "NULL pointer"); const ALuint count{DoubleValsByProp(param)}; GetProperty(Source, context, static_cast(param), al::span{values, count}); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alGetSourcei, ALuint,source, ALenum,param, ALint*,value) FORCE_ALIGN void AL_APIENTRY alGetSourceiDirect(ALCcontext *context, ALuint source, ALenum param, ALint *value) noexcept try { std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); if(!value) context->throw_error(AL_INVALID_VALUE, "NULL pointer"); GetProperty(Source, context, static_cast(param), al::span{value, 1u}); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC5(void, alGetSource3i, ALuint,source, ALenum,param, ALint*,value1, ALint*,value2, ALint*,value3) FORCE_ALIGN void AL_APIENTRY alGetSource3iDirect(ALCcontext *context, ALuint source, ALenum param, ALint *value1, ALint *value2, ALint *value3) noexcept try { std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); if(!(value1 && value2 && value3)) context->throw_error(AL_INVALID_VALUE, "NULL pointer"); std::array ivals{}; GetProperty(Source, context, static_cast(param), ivals); *value1 = ivals[0]; *value2 = ivals[1]; *value3 = ivals[2]; } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alGetSourceiv, ALuint,source, ALenum,param, ALint*,values) FORCE_ALIGN void AL_APIENTRY alGetSourceivDirect(ALCcontext *context, ALuint source, ALenum param, ALint *values) noexcept try { std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); if(!values) context->throw_error(AL_INVALID_VALUE, "NULL pointer"); const ALuint count{IntValsByProp(param)}; GetProperty(Source, context, static_cast(param), al::span{values, count}); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNCEXT3(void, alGetSourcei64,SOFT, ALuint,source, ALenum,param, ALint64SOFT*,value) FORCE_ALIGN void AL_APIENTRY alGetSourcei64DirectSOFT(ALCcontext *context, ALuint source, ALenum param, ALint64SOFT *value) noexcept try { std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); if(!value) context->throw_error(AL_INVALID_VALUE, "NULL pointer"); GetProperty(Source, context, static_cast(param), al::span{value, 1u}); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNCEXT5(void, alGetSource3i64,SOFT, ALuint,source, ALenum,param, ALint64SOFT*,value1, ALint64SOFT*,value2, ALint64SOFT*,value3) FORCE_ALIGN void AL_APIENTRY alGetSource3i64DirectSOFT(ALCcontext *context, ALuint source, ALenum param, ALint64SOFT *value1, ALint64SOFT *value2, ALint64SOFT *value3) noexcept try { std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); if(!(value1 && value2 && value3)) context->throw_error(AL_INVALID_VALUE, "NULL pointer"); std::array i64vals{}; GetProperty(Source, context, static_cast(param), i64vals); *value1 = i64vals[0]; *value2 = i64vals[1]; *value3 = i64vals[2]; } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNCEXT3(void, alGetSourcei64v,SOFT, ALuint,source, ALenum,param, ALint64SOFT*,values) FORCE_ALIGN void AL_APIENTRY alGetSourcei64vDirectSOFT(ALCcontext *context, ALuint source, ALenum param, ALint64SOFT *values) noexcept try { std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); if(!values) context->throw_error(AL_INVALID_VALUE, "NULL pointer"); const ALuint count{Int64ValsByProp(param)}; GetProperty(Source, context, static_cast(param), al::span{values, count}); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC1(void, alSourcePlay, ALuint,source) FORCE_ALIGN void AL_APIENTRY alSourcePlayDirect(ALCcontext *context, ALuint source) noexcept try { std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); StartSources(context, {&Source, 1}); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } FORCE_ALIGN DECL_FUNCEXT2(void, alSourcePlayAtTime,SOFT, ALuint,source, ALint64SOFT,start_time) FORCE_ALIGN void AL_APIENTRY alSourcePlayAtTimeDirectSOFT(ALCcontext *context, ALuint source, ALint64SOFT start_time) noexcept try { if(start_time < 0) context->throw_error(AL_INVALID_VALUE, "Invalid time point {}", start_time); std::lock_guard sourcelock{context->mSourceLock}; ALsource *Source{LookupSource(context, source)}; if(!Source) context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", source); StartSources(context, {&Source, 1}, nanoseconds{start_time}); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC2(void, alSourcePlayv, ALsizei,n, const ALuint*,sources) FORCE_ALIGN void AL_APIENTRY alSourcePlayvDirect(ALCcontext *context, ALsizei n, const ALuint *sources) noexcept try { if(n < 0) context->throw_error(AL_INVALID_VALUE, "Playing {} sources", n); if(n <= 0) UNLIKELY return; al::span sids{sources, static_cast(n)}; source_store_variant source_store; const auto srchandles = [&source_store](size_t count) -> al::span { if(count > std::tuple_size_v) return al::span{source_store.emplace(count)}; return al::span{source_store.emplace()}.first(count); }(sids.size()); std::lock_guard sourcelock{context->mSourceLock}; auto lookup_src = [context](const ALuint sid) -> ALsource* { if(ALsource *src{LookupSource(context, sid)}) return src; context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", sid); }; std::transform(sids.cbegin(), sids.cend(), srchandles.begin(), lookup_src); StartSources(context, srchandles); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } FORCE_ALIGN DECL_FUNCEXT3(void, alSourcePlayAtTimev,SOFT, ALsizei,n, const ALuint*,sources, ALint64SOFT,start_time) FORCE_ALIGN void AL_APIENTRY alSourcePlayAtTimevDirectSOFT(ALCcontext *context, ALsizei n, const ALuint *sources, ALint64SOFT start_time) noexcept try { if(n < 0) context->throw_error(AL_INVALID_VALUE, "Playing {} sources", n); if(n <= 0) UNLIKELY return; if(start_time < 0) context->throw_error(AL_INVALID_VALUE, "Invalid time point {}", start_time); al::span sids{sources, static_cast(n)}; source_store_variant source_store; const auto srchandles = [&source_store](size_t count) -> al::span { if(count > std::tuple_size_v) return al::span{source_store.emplace(count)}; return al::span{source_store.emplace()}.first(count); }(sids.size()); std::lock_guard sourcelock{context->mSourceLock}; auto lookup_src = [context](const ALuint sid) -> ALsource* { if(ALsource *src{LookupSource(context, sid)}) return src; context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", sid); }; std::transform(sids.cbegin(), sids.cend(), srchandles.begin(), lookup_src); StartSources(context, srchandles, nanoseconds{start_time}); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC1(void, alSourcePause, ALuint,source) FORCE_ALIGN void AL_APIENTRY alSourcePauseDirect(ALCcontext *context, ALuint source) noexcept { alSourcePausevDirect(context, 1, &source); } AL_API DECL_FUNC2(void, alSourcePausev, ALsizei,n, const ALuint*,sources) FORCE_ALIGN void AL_APIENTRY alSourcePausevDirect(ALCcontext *context, ALsizei n, const ALuint *sources) noexcept try { if(n < 0) context->throw_error(AL_INVALID_VALUE, "Pausing {} sources", n); if(n <= 0) UNLIKELY return; al::span sids{sources, static_cast(n)}; source_store_variant source_store; const auto srchandles = [&source_store](size_t count) -> al::span { if(count > std::tuple_size_v) return al::span{source_store.emplace(count)}; return al::span{source_store.emplace()}.first(count); }(sids.size()); std::lock_guard sourcelock{context->mSourceLock}; auto lookup_src = [context](const ALuint sid) -> ALsource* { if(ALsource *src{LookupSource(context, sid)}) return src; context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", sid); }; std::transform(sids.cbegin(), sids.cend(), srchandles.begin(), lookup_src); /* Pausing has to be done in two steps. First, for each source that's * detected to be playing, chamge the voice (asynchronously) to * stopping/paused. */ VoiceChange *tail{}, *cur{}; for(ALsource *source : srchandles) { Voice *voice{GetSourceVoice(source, context)}; if(GetSourceState(source, voice) == AL_PLAYING) { if(!cur) cur = tail = GetVoiceChanger(context); else { cur->mNext.store(GetVoiceChanger(context), std::memory_order_relaxed); cur = cur->mNext.load(std::memory_order_relaxed); } cur->mVoice = voice; cur->mSourceID = source->id; cur->mState = VChangeState::Pause; } } if(tail) LIKELY { SendVoiceChanges(context, tail); /* Second, now that the voice changes have been sent, because it's * possible that the voice stopped after it was detected playing and * before the voice got paused, recheck that the source is still * considered playing and set it to paused if so. */ for(ALsource *source : srchandles) { Voice *voice{GetSourceVoice(source, context)}; if(GetSourceState(source, voice) == AL_PLAYING) source->state = AL_PAUSED; } } } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC1(void, alSourceStop, ALuint,source) FORCE_ALIGN void AL_APIENTRY alSourceStopDirect(ALCcontext *context, ALuint source) noexcept { alSourceStopvDirect(context, 1, &source); } AL_API DECL_FUNC2(void, alSourceStopv, ALsizei,n, const ALuint*,sources) FORCE_ALIGN void AL_APIENTRY alSourceStopvDirect(ALCcontext *context, ALsizei n, const ALuint *sources) noexcept try { if(n < 0) context->throw_error(AL_INVALID_VALUE, "Stopping {} sources", n); if(n <= 0) UNLIKELY return; al::span sids{sources, static_cast(n)}; source_store_variant source_store; const auto srchandles = [&source_store](size_t count) -> al::span { if(count > std::tuple_size_v) return al::span{source_store.emplace(count)}; return al::span{source_store.emplace()}.first(count); }(sids.size()); std::lock_guard sourcelock{context->mSourceLock}; auto lookup_src = [context](const ALuint sid) -> ALsource* { if(ALsource *src{LookupSource(context, sid)}) return src; context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", sid); }; std::transform(sids.cbegin(), sids.cend(), srchandles.begin(), lookup_src); VoiceChange *tail{}, *cur{}; for(ALsource *source : srchandles) { if(Voice *voice{GetSourceVoice(source, context)}) { if(!cur) cur = tail = GetVoiceChanger(context); else { cur->mNext.store(GetVoiceChanger(context), std::memory_order_relaxed); cur = cur->mNext.load(std::memory_order_relaxed); } voice->mPendingChange.store(true, std::memory_order_relaxed); cur->mVoice = voice; cur->mSourceID = source->id; cur->mState = VChangeState::Stop; source->state = AL_STOPPED; } source->Offset = 0.0; source->OffsetType = AL_NONE; source->VoiceIdx = InvalidVoiceIndex; } if(tail) LIKELY SendVoiceChanges(context, tail); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC1(void, alSourceRewind, ALuint,source) FORCE_ALIGN void AL_APIENTRY alSourceRewindDirect(ALCcontext *context, ALuint source) noexcept { alSourceRewindvDirect(context, 1, &source); } AL_API DECL_FUNC2(void, alSourceRewindv, ALsizei,n, const ALuint*,sources) FORCE_ALIGN void AL_APIENTRY alSourceRewindvDirect(ALCcontext *context, ALsizei n, const ALuint *sources) noexcept try { if(n < 0) context->throw_error(AL_INVALID_VALUE, "Rewinding {} sources", n); if(n <= 0) UNLIKELY return; al::span sids{sources, static_cast(n)}; source_store_variant source_store; const auto srchandles = [&source_store](size_t count) -> al::span { if(count > std::tuple_size_v) return al::span{source_store.emplace(count)}; return al::span{source_store.emplace()}.first(count); }(sids.size()); std::lock_guard sourcelock{context->mSourceLock}; auto lookup_src = [context](const ALuint sid) -> ALsource* { if(ALsource *src{LookupSource(context, sid)}) return src; context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", sid); }; std::transform(sids.cbegin(), sids.cend(), srchandles.begin(), lookup_src); VoiceChange *tail{}, *cur{}; for(ALsource *source : srchandles) { Voice *voice{GetSourceVoice(source, context)}; if(source->state != AL_INITIAL) { if(!cur) cur = tail = GetVoiceChanger(context); else { cur->mNext.store(GetVoiceChanger(context), std::memory_order_relaxed); cur = cur->mNext.load(std::memory_order_relaxed); } if(voice) voice->mPendingChange.store(true, std::memory_order_relaxed); cur->mVoice = voice; cur->mSourceID = source->id; cur->mState = VChangeState::Reset; source->state = AL_INITIAL; } source->Offset = 0.0; source->OffsetType = AL_NONE; source->VoiceIdx = InvalidVoiceIndex; } if(tail) LIKELY SendVoiceChanges(context, tail); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alSourceQueueBuffers, ALuint,source, ALsizei,nb, const ALuint*,buffers) FORCE_ALIGN void AL_APIENTRY alSourceQueueBuffersDirect(ALCcontext *context, ALuint src, ALsizei nb, const ALuint *buffers) noexcept try { if(nb < 0) context->throw_error(AL_INVALID_VALUE, "Queueing {} buffers", nb); if(nb <= 0) UNLIKELY return; std::lock_guard sourcelock{context->mSourceLock}; ALsource *source{LookupSource(context,src)}; if(!source) context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", src); /* Can't queue on a Static Source */ if(source->SourceType == AL_STATIC) context->throw_error(AL_INVALID_OPERATION, "Queueing onto static source {}", src); /* Check for a valid Buffer, for its frequency and format */ auto *device = context->mALDevice.get(); ALbuffer *BufferFmt{nullptr}; for(auto &item : source->mQueue) { BufferFmt = item.mBuffer; if(BufferFmt) break; } std::unique_lock buflock{device->BufferLock}; const auto bids = al::span{buffers, static_cast(nb)}; const size_t NewListStart{source->mQueue.size()}; try { ALbufferQueueItem *BufferList{nullptr}; std::for_each(bids.cbegin(), bids.cend(), [context,source,device,&BufferFmt,&BufferList](const ALuint bid) { ALbuffer *buffer{bid ? LookupBuffer(device, bid) : nullptr}; if(bid && !buffer) context->throw_error(AL_INVALID_NAME, "Queueing invalid buffer ID {}", bid); if(buffer) { if(buffer->mSampleRate < 1) context->throw_error(AL_INVALID_OPERATION, "Queueing buffer {} with no format", buffer->id); if(buffer->mCallback) context->throw_error(AL_INVALID_OPERATION, "Queueing callback buffer {}", buffer->id); if(buffer->MappedAccess != 0 && !(buffer->MappedAccess&AL_MAP_PERSISTENT_BIT_SOFT)) context->throw_error(AL_INVALID_OPERATION, "Queueing non-persistently mapped buffer {}", buffer->id); } source->mQueue.emplace_back(); if(!BufferList) BufferList = &source->mQueue.back(); else { auto &item = source->mQueue.back(); BufferList->mNext.store(&item, std::memory_order_relaxed); BufferList = &item; } if(!buffer) return; BufferList->mBlockAlign = buffer->mBlockAlign; BufferList->mSampleLen = buffer->mSampleLen; BufferList->mLoopEnd = buffer->mSampleLen; BufferList->mSamples = buffer->mData; BufferList->mBuffer = buffer; IncrementRef(buffer->ref); bool fmt_mismatch{false}; if(BufferFmt == nullptr) BufferFmt = buffer; else { fmt_mismatch |= BufferFmt->mSampleRate != buffer->mSampleRate; fmt_mismatch |= BufferFmt->mChannels != buffer->mChannels; fmt_mismatch |= BufferFmt->mType != buffer->mType; if(BufferFmt->isBFormat()) { fmt_mismatch |= BufferFmt->mAmbiLayout != buffer->mAmbiLayout; fmt_mismatch |= BufferFmt->mAmbiScaling != buffer->mAmbiScaling; } fmt_mismatch |= BufferFmt->mAmbiOrder != buffer->mAmbiOrder; } if(fmt_mismatch) context->throw_error(AL_INVALID_OPERATION, "Queueing buffer with mismatched format\n" " Expected: {}hz, {}, {} ; Got: {}hz, {}, {}\n", BufferFmt->mSampleRate, NameFromFormat(BufferFmt->mType), NameFromFormat(BufferFmt->mChannels), buffer->mSampleRate, NameFromFormat(buffer->mType), NameFromFormat(buffer->mChannels)); }); } catch(...) { /* A buffer failed (invalid ID or format), or there was some other * unexpected error, so unlock and release each buffer we had. */ auto iter = source->mQueue.begin() + ptrdiff_t(NewListStart); for(;iter != source->mQueue.end();++iter) { if(ALbuffer *buf{iter->mBuffer}) DecrementRef(buf->ref); } source->mQueue.resize(NewListStart); throw; } /* All buffers good. */ buflock.unlock(); /* Source is now streaming */ source->SourceType = AL_STREAMING; if(NewListStart != 0) { auto iter = source->mQueue.begin() + ptrdiff_t(NewListStart); (iter-1)->mNext.store(al::to_address(iter), std::memory_order_release); } } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API DECL_FUNC3(void, alSourceUnqueueBuffers, ALuint,source, ALsizei,nb, ALuint*,buffers) FORCE_ALIGN void AL_APIENTRY alSourceUnqueueBuffersDirect(ALCcontext *context, ALuint src, ALsizei nb, ALuint *buffers) noexcept try { if(nb < 0) context->throw_error(AL_INVALID_VALUE, "Unqueueing {} buffers", nb); if(nb <= 0) UNLIKELY return; std::lock_guard sourcelock{context->mSourceLock}; ALsource *source{LookupSource(context,src)}; if(!source) context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", src); if(source->SourceType != AL_STREAMING) context->throw_error(AL_INVALID_VALUE, "Unqueueing from a non-streaming source {}", src); if(source->Looping) context->throw_error(AL_INVALID_VALUE, "Unqueueing from looping source {}", src); /* Make sure enough buffers have been processed to unqueue. */ const al::span bids{buffers, static_cast(nb)}; size_t processed{0}; if(source->state != AL_INITIAL) LIKELY { VoiceBufferItem *Current{nullptr}; if(Voice *voice{GetSourceVoice(source, context)}) Current = voice->mCurrentBuffer.load(std::memory_order_relaxed); for(auto &item : source->mQueue) { if(&item == Current) break; ++processed; } } if(processed < bids.size()) context->throw_error(AL_INVALID_VALUE, "Unqueueing {} buffer{} (only {} processed)", nb, (nb==1) ? "" : "s", processed); std::generate(bids.begin(), bids.end(), [source]() noexcept -> ALuint { auto &head = source->mQueue.front(); ALuint bid{0}; if(ALbuffer *buffer{head.mBuffer}) { bid = buffer->id; DecrementRef(buffer->ref); } source->mQueue.pop_front(); return bid; }); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } AL_API void AL_APIENTRY alSourceQueueBufferLayersSOFT(ALuint, ALsizei, const ALuint*) noexcept { ContextRef context{GetContextRef()}; if(!context) UNLIKELY return; context->setError(AL_INVALID_OPERATION, "alSourceQueueBufferLayersSOFT not supported"); } ALsource::ALsource() noexcept { Direct.Gain = 1.0f; Direct.GainHF = 1.0f; Direct.HFReference = LowPassFreqRef; Direct.GainLF = 1.0f; Direct.LFReference = HighPassFreqRef; for(auto &send : Send) { send.Slot = nullptr; send.Gain = 1.0f; send.GainHF = 1.0f; send.HFReference = LowPassFreqRef; send.GainLF = 1.0f; send.LFReference = HighPassFreqRef; } } ALsource::~ALsource() { for(auto &item : mQueue) { if(ALbuffer *buffer{item.mBuffer}) DecrementRef(buffer->ref); } auto clear_send = [](ALsource::SendData &send) -> void { if(send.Slot) DecrementRef(send.Slot->ref); }; std::for_each(Send.begin(), Send.end(), clear_send); } void UpdateAllSourceProps(ALCcontext *context) { std::lock_guard srclock{context->mSourceLock}; auto voicelist = context->getVoicesSpan(); ALuint vidx{0u}; for(Voice *voice : voicelist) { ALuint sid{voice->mSourceID.load(std::memory_order_acquire)}; ALsource *source{sid ? LookupSource(context, sid) : nullptr}; if(source && source->VoiceIdx == vidx) { if(std::exchange(source->mPropsDirty, false)) UpdateSourceProps(source, voice, context); } ++vidx; } } void ALsource::SetName(ALCcontext *context, ALuint id, std::string_view name) { std::lock_guard srclock{context->mSourceLock}; auto source = LookupSource(context, id); if(!source) context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", id); context->mSourceNames.insert_or_assign(id, name); } SourceSubList::~SourceSubList() { if(!Sources) return; uint64_t usemask{~FreeMask}; while(usemask) { const int idx{al::countr_zero(usemask)}; usemask &= ~(1_u64 << idx); std::destroy_at(al::to_address(Sources->begin() + idx)); } FreeMask = ~usemask; SubListAllocator{}.deallocate(Sources, 1); Sources = nullptr; } #if ALSOFT_EAX void ALsource::eaxInitialize(ALCcontext *context) noexcept { assert(context != nullptr); mEaxAlContext = context; mEaxPrimaryFxSlotId = context->eaxGetPrimaryFxSlotIndex(); eax_set_defaults(); eax1_translate(mEax1.i, mEax); mEaxVersion = 1; mEaxChanged = true; } void ALsource::eaxDispatch(const EaxCall& call) { call.is_get() ? eax_get(call) : eax_set(call); } ALsource* ALsource::EaxLookupSource(ALCcontext& al_context, ALuint source_id) noexcept { return LookupSource(&al_context, source_id); } [[noreturn]] void ALsource::eax_fail(const char* message) { throw Exception{message}; } [[noreturn]] void ALsource::eax_fail_unknown_property_id() { eax_fail("Unknown property id."); } [[noreturn]] void ALsource::eax_fail_unknown_version() { eax_fail("Unknown version."); } [[noreturn]] void ALsource::eax_fail_unknown_active_fx_slot_id() { eax_fail("Unknown active FX slot ID."); } [[noreturn]] void ALsource::eax_fail_unknown_receiving_fx_slot_id() { eax_fail("Unknown receiving FX slot ID."); } void ALsource::eax_set_sends_defaults(EaxSends& sends, const EaxFxSlotIds& ids) noexcept { for(size_t i{0};i < EAX_MAX_FXSLOTS;++i) { auto& send = sends[i]; send.guidReceivingFXSlotID = *(ids[i]); send.lSend = EAXSOURCE_DEFAULTSEND; send.lSendHF = EAXSOURCE_DEFAULTSENDHF; send.lOcclusion = EAXSOURCE_DEFAULTOCCLUSION; send.flOcclusionLFRatio = EAXSOURCE_DEFAULTOCCLUSIONLFRATIO; send.flOcclusionRoomRatio = EAXSOURCE_DEFAULTOCCLUSIONROOMRATIO; send.flOcclusionDirectRatio = EAXSOURCE_DEFAULTOCCLUSIONDIRECTRATIO; send.lExclusion = EAXSOURCE_DEFAULTEXCLUSION; send.flExclusionLFRatio = EAXSOURCE_DEFAULTEXCLUSIONLFRATIO; } } void ALsource::eax1_set_defaults(Eax1Props& props) noexcept { props.fMix = EAX_REVERBMIX_USEDISTANCE; } void ALsource::eax1_set_defaults() noexcept { eax1_set_defaults(mEax1.i); mEax1.d = mEax1.i; } void ALsource::eax2_set_defaults(Eax2Props& props) noexcept { props.lDirect = EAXSOURCE_DEFAULTDIRECT; props.lDirectHF = EAXSOURCE_DEFAULTDIRECTHF; props.lRoom = EAXSOURCE_DEFAULTROOM; props.lRoomHF = EAXSOURCE_DEFAULTROOMHF; props.flRoomRolloffFactor = EAXSOURCE_DEFAULTROOMROLLOFFFACTOR; props.lObstruction = EAXSOURCE_DEFAULTOBSTRUCTION; props.flObstructionLFRatio = EAXSOURCE_DEFAULTOBSTRUCTIONLFRATIO; props.lOcclusion = EAXSOURCE_DEFAULTOCCLUSION; props.flOcclusionLFRatio = EAXSOURCE_DEFAULTOCCLUSIONLFRATIO; props.flOcclusionRoomRatio = EAXSOURCE_DEFAULTOCCLUSIONROOMRATIO; props.lOutsideVolumeHF = EAXSOURCE_DEFAULTOUTSIDEVOLUMEHF; props.flAirAbsorptionFactor = EAXSOURCE_DEFAULTAIRABSORPTIONFACTOR; props.dwFlags = EAXSOURCE_DEFAULTFLAGS; } void ALsource::eax2_set_defaults() noexcept { eax2_set_defaults(mEax2.i); mEax2.d = mEax2.i; } void ALsource::eax3_set_defaults(Eax3Props& props) noexcept { props.lDirect = EAXSOURCE_DEFAULTDIRECT; props.lDirectHF = EAXSOURCE_DEFAULTDIRECTHF; props.lRoom = EAXSOURCE_DEFAULTROOM; props.lRoomHF = EAXSOURCE_DEFAULTROOMHF; props.lObstruction = EAXSOURCE_DEFAULTOBSTRUCTION; props.flObstructionLFRatio = EAXSOURCE_DEFAULTOBSTRUCTIONLFRATIO; props.lOcclusion = EAXSOURCE_DEFAULTOCCLUSION; props.flOcclusionLFRatio = EAXSOURCE_DEFAULTOCCLUSIONLFRATIO; props.flOcclusionRoomRatio = EAXSOURCE_DEFAULTOCCLUSIONROOMRATIO; props.flOcclusionDirectRatio = EAXSOURCE_DEFAULTOCCLUSIONDIRECTRATIO; props.lExclusion = EAXSOURCE_DEFAULTEXCLUSION; props.flExclusionLFRatio = EAXSOURCE_DEFAULTEXCLUSIONLFRATIO; props.lOutsideVolumeHF = EAXSOURCE_DEFAULTOUTSIDEVOLUMEHF; props.flDopplerFactor = EAXSOURCE_DEFAULTDOPPLERFACTOR; props.flRolloffFactor = EAXSOURCE_DEFAULTROLLOFFFACTOR; props.flRoomRolloffFactor = EAXSOURCE_DEFAULTROOMROLLOFFFACTOR; props.flAirAbsorptionFactor = EAXSOURCE_DEFAULTAIRABSORPTIONFACTOR; props.ulFlags = EAXSOURCE_DEFAULTFLAGS; } void ALsource::eax3_set_defaults() noexcept { eax3_set_defaults(mEax3.i); mEax3.d = mEax3.i; } void ALsource::eax4_set_sends_defaults(EaxSends& sends) noexcept { eax_set_sends_defaults(sends, eax4_fx_slot_ids); } void ALsource::eax4_set_active_fx_slots_defaults(EAX40ACTIVEFXSLOTS& slots) noexcept { slots = EAX40SOURCE_DEFAULTACTIVEFXSLOTID; } void ALsource::eax4_set_defaults() noexcept { eax3_set_defaults(mEax4.i.source); eax4_set_sends_defaults(mEax4.i.sends); eax4_set_active_fx_slots_defaults(mEax4.i.active_fx_slots); mEax4.d = mEax4.i; } void ALsource::eax5_set_source_defaults(EAX50SOURCEPROPERTIES& props) noexcept { eax3_set_defaults(static_cast(props)); props.flMacroFXFactor = EAXSOURCE_DEFAULTMACROFXFACTOR; } void ALsource::eax5_set_sends_defaults(EaxSends& sends) noexcept { eax_set_sends_defaults(sends, eax5_fx_slot_ids); } void ALsource::eax5_set_active_fx_slots_defaults(EAX50ACTIVEFXSLOTS& slots) noexcept { slots = EAX50SOURCE_3DDEFAULTACTIVEFXSLOTID; } void ALsource::eax5_set_speaker_levels_defaults(EaxSpeakerLevels& speaker_levels) noexcept { for(size_t i{0};i < eax_max_speakers;++i) { auto& speaker_level = speaker_levels[i]; speaker_level.lSpeakerID = static_cast(EAXSPEAKER_FRONT_LEFT + i); speaker_level.lLevel = EAXSOURCE_DEFAULTSPEAKERLEVEL; } } void ALsource::eax5_set_defaults(Eax5Props& props) noexcept { eax5_set_source_defaults(props.source); eax5_set_sends_defaults(props.sends); eax5_set_active_fx_slots_defaults(props.active_fx_slots); eax5_set_speaker_levels_defaults(props.speaker_levels); } void ALsource::eax5_set_defaults() noexcept { eax5_set_defaults(mEax5.i); mEax5.d = mEax5.i; } void ALsource::eax_set_defaults() noexcept { eax1_set_defaults(); eax2_set_defaults(); eax3_set_defaults(); eax4_set_defaults(); eax5_set_defaults(); } void ALsource::eax1_translate(const Eax1Props& src, Eax5Props& dst) noexcept { eax5_set_defaults(dst); if (src.fMix == EAX_REVERBMIX_USEDISTANCE) { dst.source.ulFlags |= EAXSOURCEFLAGS_ROOMAUTO; dst.sends[0].lSend = 0; } else { dst.source.ulFlags &= ~EAXSOURCEFLAGS_ROOMAUTO; dst.sends[0].lSend = std::clamp(static_cast(gain_to_level_mb(src.fMix)), EAXSOURCE_MINSEND, EAXSOURCE_MAXSEND); } } void ALsource::eax2_translate(const Eax2Props& src, Eax5Props& dst) noexcept { // Source. // dst.source.lDirect = src.lDirect; dst.source.lDirectHF = src.lDirectHF; dst.source.lRoom = src.lRoom; dst.source.lRoomHF = src.lRoomHF; dst.source.lObstruction = src.lObstruction; dst.source.flObstructionLFRatio = src.flObstructionLFRatio; dst.source.lOcclusion = src.lOcclusion; dst.source.flOcclusionLFRatio = src.flOcclusionLFRatio; dst.source.flOcclusionRoomRatio = src.flOcclusionRoomRatio; dst.source.flOcclusionDirectRatio = EAXSOURCE_DEFAULTOCCLUSIONDIRECTRATIO; dst.source.lExclusion = EAXSOURCE_DEFAULTEXCLUSION; dst.source.flExclusionLFRatio = EAXSOURCE_DEFAULTEXCLUSIONLFRATIO; dst.source.lOutsideVolumeHF = src.lOutsideVolumeHF; dst.source.flDopplerFactor = EAXSOURCE_DEFAULTDOPPLERFACTOR; dst.source.flRolloffFactor = EAXSOURCE_DEFAULTROLLOFFFACTOR; dst.source.flRoomRolloffFactor = src.flRoomRolloffFactor; dst.source.flAirAbsorptionFactor = src.flAirAbsorptionFactor; dst.source.ulFlags = src.dwFlags; dst.source.flMacroFXFactor = EAXSOURCE_DEFAULTMACROFXFACTOR; // Set everything else to defaults. // eax5_set_sends_defaults(dst.sends); eax5_set_active_fx_slots_defaults(dst.active_fx_slots); eax5_set_speaker_levels_defaults(dst.speaker_levels); } void ALsource::eax3_translate(const Eax3Props& src, Eax5Props& dst) noexcept { // Source. // static_cast(dst.source) = src; dst.source.flMacroFXFactor = EAXSOURCE_DEFAULTMACROFXFACTOR; // Set everything else to defaults. // eax5_set_sends_defaults(dst.sends); eax5_set_active_fx_slots_defaults(dst.active_fx_slots); eax5_set_speaker_levels_defaults(dst.speaker_levels); } void ALsource::eax4_translate(const Eax4Props& src, Eax5Props& dst) noexcept { // Source. // static_cast(dst.source) = src.source; dst.source.flMacroFXFactor = EAXSOURCE_DEFAULTMACROFXFACTOR; // Sends. // dst.sends = src.sends; for(size_t i{0};i < EAX_MAX_FXSLOTS;++i) dst.sends[i].guidReceivingFXSlotID = *(eax5_fx_slot_ids[i]); // Active FX slots. // auto translate_slotid = [](const GUID &src_id) -> GUID { if(src_id == EAX_NULL_GUID) return EAX_NULL_GUID; if(src_id == EAX_PrimaryFXSlotID) return EAX_PrimaryFXSlotID; if(src_id == EAXPROPERTYID_EAX40_FXSlot0) return EAXPROPERTYID_EAX50_FXSlot0; if(src_id == EAXPROPERTYID_EAX40_FXSlot1) return EAXPROPERTYID_EAX50_FXSlot1; if(src_id == EAXPROPERTYID_EAX40_FXSlot2) return EAXPROPERTYID_EAX50_FXSlot2; if(src_id == EAXPROPERTYID_EAX40_FXSlot3) return EAXPROPERTYID_EAX50_FXSlot3; UNLIKELY ERR("Unexpected active FX slot ID"); return EAX_NULL_GUID; }; const auto src_slots = al::span{src.active_fx_slots.guidActiveFXSlots}; const auto dst_slots = al::span{dst.active_fx_slots.guidActiveFXSlots}; auto dstiter = std::transform(src_slots.cbegin(), src_slots.cend(), dst_slots.begin(), translate_slotid); std::fill(dstiter, dst_slots.end(), EAX_NULL_GUID); // Speaker levels. // eax5_set_speaker_levels_defaults(dst.speaker_levels); } float ALsource::eax_calculate_dst_occlusion_mb( long src_occlusion_mb, float path_ratio, float lf_ratio) noexcept { const auto ratio_1 = path_ratio + lf_ratio - 1.0F; const auto ratio_2 = path_ratio * lf_ratio; const auto ratio = (ratio_2 > ratio_1) ? ratio_2 : ratio_1; const auto dst_occlustion_mb = static_cast(src_occlusion_mb) * ratio; return dst_occlustion_mb; } EaxAlLowPassParam ALsource::eax_create_direct_filter_param() const noexcept { const auto &source = mEax.source; auto gain_mb = static_cast(source.lObstruction) * source.flObstructionLFRatio; auto gainhf_mb = static_cast(source.lObstruction); for(size_t i{0};i < EAX_MAX_FXSLOTS;++i) { if(!mEaxActiveFxSlots[i]) continue; if(source.lOcclusion != 0) { const auto& fx_slot = mEaxAlContext->eaxGetFxSlot(i); const auto& fx_slot_eax = fx_slot.eax_get_eax_fx_slot(); const auto is_environmental_fx = ((fx_slot_eax.ulFlags&EAXFXSLOTFLAGS_ENVIRONMENT) != 0); const auto is_primary = (mEaxPrimaryFxSlotId.value_or(-1) == fx_slot.eax_get_index()); if(is_environmental_fx && is_primary) { gain_mb += eax_calculate_dst_occlusion_mb(source.lOcclusion, source.flOcclusionDirectRatio, source.flOcclusionLFRatio); gainhf_mb += static_cast(source.lOcclusion) * source.flOcclusionDirectRatio; } } const auto& send = mEax.sends[i]; if(send.lOcclusion != 0) { gain_mb += eax_calculate_dst_occlusion_mb(send.lOcclusion, send.flOcclusionDirectRatio, send.flOcclusionLFRatio); gainhf_mb += static_cast(send.lOcclusion) * send.flOcclusionDirectRatio; } } /* gainhf_mb is the absolute mBFS of the filter's high-frequency volume, * and gain_mb is the absolute mBFS of the filter's low-frequency volume. * Adjust the HF volume to be relative to the LF volume, to make the * appropriate main and relative HF filter volumes. * * Also add the Direct and DirectHF properties to the filter, which are * already the main and relative HF volumes. */ gainhf_mb -= gain_mb - static_cast(source.lDirectHF); gain_mb += static_cast(source.lDirect); return EaxAlLowPassParam{level_mb_to_gain(gain_mb), std::min(level_mb_to_gain(gainhf_mb), 1.0f)}; } EaxAlLowPassParam ALsource::eax_create_room_filter_param( const ALeffectslot& fx_slot, const EAXSOURCEALLSENDPROPERTIES& send) const noexcept { const auto& fx_slot_eax = fx_slot.eax_get_eax_fx_slot(); const auto is_environmental_fx = bool{(fx_slot_eax.ulFlags & EAXFXSLOTFLAGS_ENVIRONMENT) != 0}; const auto is_primary = bool{mEaxPrimaryFxSlotId.value_or(-1) == fx_slot.eax_get_index()}; auto gain_mb = (static_cast(fx_slot_eax.lOcclusion) * fx_slot_eax.flOcclusionLFRatio) + eax_calculate_dst_occlusion_mb(send.lOcclusion, send.flOcclusionRoomRatio, send.flOcclusionLFRatio) + (static_cast(send.lExclusion) * send.flExclusionLFRatio); auto gainhf_mb = static_cast(fx_slot_eax.lOcclusion) + (static_cast(send.lOcclusion) * send.flOcclusionRoomRatio); if(is_environmental_fx && is_primary) { const auto &source = mEax.source; gain_mb += eax_calculate_dst_occlusion_mb(source.lOcclusion, source.flOcclusionRoomRatio, source.flOcclusionLFRatio); gain_mb += static_cast(source.lExclusion) * source.flExclusionLFRatio; gainhf_mb += static_cast(source.lOcclusion) * source.flOcclusionRoomRatio; gainhf_mb += static_cast(source.lExclusion + send.lExclusion); } gainhf_mb -= gain_mb - static_cast(send.lSendHF); gain_mb += static_cast(send.lSend); if(is_environmental_fx) { const auto &source = mEax.source; gain_mb += static_cast(source.lRoom); gainhf_mb += static_cast(source.lRoomHF); } return EaxAlLowPassParam{level_mb_to_gain(gain_mb), std::min(level_mb_to_gain(gainhf_mb), 1.0f)}; } void ALsource::eax_update_direct_filter() { const auto& direct_param = eax_create_direct_filter_param(); Direct.Gain = direct_param.gain; Direct.GainHF = direct_param.gain_hf; Direct.HFReference = LowPassFreqRef; Direct.GainLF = 1.0f; Direct.LFReference = HighPassFreqRef; mPropsDirty = true; } void ALsource::eax_update_room_filters() { for(size_t i{0};i < EAX_MAX_FXSLOTS;++i) { if(!mEaxActiveFxSlots[i]) continue; auto& fx_slot = mEaxAlContext->eaxGetFxSlot(i); const auto& send = mEax.sends[i]; const auto& room_param = eax_create_room_filter_param(fx_slot, send); eax_set_al_source_send(&fx_slot, i, room_param); } } void ALsource::eax_set_efx_outer_gain_hf() { OuterGainHF = std::clamp( level_mb_to_gain(static_cast(mEax.source.lOutsideVolumeHF)), AL_MIN_CONE_OUTER_GAINHF, AL_MAX_CONE_OUTER_GAINHF); } void ALsource::eax_set_efx_doppler_factor() { DopplerFactor = mEax.source.flDopplerFactor; } void ALsource::eax_set_efx_rolloff_factor() { RolloffFactor2 = mEax.source.flRolloffFactor; } void ALsource::eax_set_efx_room_rolloff_factor() { RoomRolloffFactor = mEax.source.flRoomRolloffFactor; } void ALsource::eax_set_efx_air_absorption_factor() { AirAbsorptionFactor = mEax.source.flAirAbsorptionFactor; } void ALsource::eax_set_efx_dry_gain_hf_auto() { DryGainHFAuto = ((mEax.source.ulFlags & EAXSOURCEFLAGS_DIRECTHFAUTO) != 0); } void ALsource::eax_set_efx_wet_gain_auto() { WetGainAuto = ((mEax.source.ulFlags & EAXSOURCEFLAGS_ROOMAUTO) != 0); } void ALsource::eax_set_efx_wet_gain_hf_auto() { WetGainHFAuto = ((mEax.source.ulFlags & EAXSOURCEFLAGS_ROOMHFAUTO) != 0); } void ALsource::eax1_set(const EaxCall& call, Eax1Props& props) { switch (call.get_property_id()) { case DSPROPERTY_EAXBUFFER_ALL: eax_defer(call, props); break; case DSPROPERTY_EAXBUFFER_REVERBMIX: eax_defer(call, props.fMix); break; default: eax_fail_unknown_property_id(); } } void ALsource::eax2_set(const EaxCall& call, Eax2Props& props) { switch (call.get_property_id()) { case DSPROPERTY_EAX20BUFFER_NONE: break; case DSPROPERTY_EAX20BUFFER_ALLPARAMETERS: eax_defer(call, props); break; case DSPROPERTY_EAX20BUFFER_DIRECT: eax_defer(call, props.lDirect); break; case DSPROPERTY_EAX20BUFFER_DIRECTHF: eax_defer(call, props.lDirectHF); break; case DSPROPERTY_EAX20BUFFER_ROOM: eax_defer(call, props.lRoom); break; case DSPROPERTY_EAX20BUFFER_ROOMHF: eax_defer(call, props.lRoomHF); break; case DSPROPERTY_EAX20BUFFER_ROOMROLLOFFFACTOR: eax_defer(call, props.flRoomRolloffFactor); break; case DSPROPERTY_EAX20BUFFER_OBSTRUCTION: eax_defer(call, props.lObstruction); break; case DSPROPERTY_EAX20BUFFER_OBSTRUCTIONLFRATIO: eax_defer(call, props.flObstructionLFRatio); break; case DSPROPERTY_EAX20BUFFER_OCCLUSION: eax_defer(call, props.lOcclusion); break; case DSPROPERTY_EAX20BUFFER_OCCLUSIONLFRATIO: eax_defer(call, props.flOcclusionLFRatio); break; case DSPROPERTY_EAX20BUFFER_OCCLUSIONROOMRATIO: eax_defer(call, props.flOcclusionRoomRatio); break; case DSPROPERTY_EAX20BUFFER_OUTSIDEVOLUMEHF: eax_defer(call, props.lOutsideVolumeHF); break; case DSPROPERTY_EAX20BUFFER_AIRABSORPTIONFACTOR: eax_defer(call, props.flAirAbsorptionFactor); break; case DSPROPERTY_EAX20BUFFER_FLAGS: eax_defer(call, props.dwFlags); break; default: eax_fail_unknown_property_id(); } } void ALsource::eax3_set(const EaxCall& call, Eax3Props& props) { switch (call.get_property_id()) { case EAXSOURCE_NONE: break; case EAXSOURCE_ALLPARAMETERS: eax_defer(call, props); break; case EAXSOURCE_OBSTRUCTIONPARAMETERS: eax_defer_sub(call, props.lObstruction); break; case EAXSOURCE_OCCLUSIONPARAMETERS: eax_defer_sub(call, props.lOcclusion); break; case EAXSOURCE_EXCLUSIONPARAMETERS: eax_defer_sub(call, props.lExclusion); break; case EAXSOURCE_DIRECT: eax_defer(call, props.lDirect); break; case EAXSOURCE_DIRECTHF: eax_defer(call, props.lDirectHF); break; case EAXSOURCE_ROOM: eax_defer(call, props.lRoom); break; case EAXSOURCE_ROOMHF: eax_defer(call, props.lRoomHF); break; case EAXSOURCE_OBSTRUCTION: eax_defer(call, props.lObstruction); break; case EAXSOURCE_OBSTRUCTIONLFRATIO: eax_defer(call, props.flObstructionLFRatio); break; case EAXSOURCE_OCCLUSION: eax_defer(call, props.lOcclusion); break; case EAXSOURCE_OCCLUSIONLFRATIO: eax_defer(call, props.flOcclusionLFRatio); break; case EAXSOURCE_OCCLUSIONROOMRATIO: eax_defer(call, props.flOcclusionRoomRatio); break; case EAXSOURCE_OCCLUSIONDIRECTRATIO: eax_defer(call, props.flOcclusionDirectRatio); break; case EAXSOURCE_EXCLUSION: eax_defer(call, props.lExclusion); break; case EAXSOURCE_EXCLUSIONLFRATIO: eax_defer(call, props.flExclusionLFRatio); break; case EAXSOURCE_OUTSIDEVOLUMEHF: eax_defer(call, props.lOutsideVolumeHF); break; case EAXSOURCE_DOPPLERFACTOR: eax_defer(call, props.flDopplerFactor); break; case EAXSOURCE_ROLLOFFFACTOR: eax_defer(call, props.flRolloffFactor); break; case EAXSOURCE_ROOMROLLOFFFACTOR: eax_defer(call, props.flRoomRolloffFactor); break; case EAXSOURCE_AIRABSORPTIONFACTOR: eax_defer(call, props.flAirAbsorptionFactor); break; case EAXSOURCE_FLAGS: eax_defer(call, props.ulFlags); break; default: eax_fail_unknown_property_id(); } } void ALsource::eax4_set(const EaxCall& call, Eax4Props& props) { switch (call.get_property_id()) { case EAXSOURCE_NONE: case EAXSOURCE_ALLPARAMETERS: case EAXSOURCE_OBSTRUCTIONPARAMETERS: case EAXSOURCE_OCCLUSIONPARAMETERS: case EAXSOURCE_EXCLUSIONPARAMETERS: case EAXSOURCE_DIRECT: case EAXSOURCE_DIRECTHF: case EAXSOURCE_ROOM: case EAXSOURCE_ROOMHF: case EAXSOURCE_OBSTRUCTION: case EAXSOURCE_OBSTRUCTIONLFRATIO: case EAXSOURCE_OCCLUSION: case EAXSOURCE_OCCLUSIONLFRATIO: case EAXSOURCE_OCCLUSIONROOMRATIO: case EAXSOURCE_OCCLUSIONDIRECTRATIO: case EAXSOURCE_EXCLUSION: case EAXSOURCE_EXCLUSIONLFRATIO: case EAXSOURCE_OUTSIDEVOLUMEHF: case EAXSOURCE_DOPPLERFACTOR: case EAXSOURCE_ROLLOFFFACTOR: case EAXSOURCE_ROOMROLLOFFFACTOR: case EAXSOURCE_AIRABSORPTIONFACTOR: case EAXSOURCE_FLAGS: eax3_set(call, props.source); break; case EAXSOURCE_SENDPARAMETERS: eax4_defer_sends(call, props.sends); break; case EAXSOURCE_ALLSENDPARAMETERS: eax4_defer_sends(call, props.sends); break; case EAXSOURCE_OCCLUSIONSENDPARAMETERS: eax4_defer_sends(call, props.sends); break; case EAXSOURCE_EXCLUSIONSENDPARAMETERS: eax4_defer_sends(call, props.sends); break; case EAXSOURCE_ACTIVEFXSLOTID: eax4_defer_active_fx_slot_id(call, al::span{props.active_fx_slots.guidActiveFXSlots}); break; default: eax_fail_unknown_property_id(); } } void ALsource::eax5_defer_all_2d(const EaxCall& call, EAX50SOURCEPROPERTIES& props) { const auto& src_props = call.get_value(); Eax5SourceAll2dValidator{}(src_props); props.lDirect = src_props.lDirect; props.lDirectHF = src_props.lDirectHF; props.lRoom = src_props.lRoom; props.lRoomHF = src_props.lRoomHF; props.ulFlags = src_props.ulFlags; } void ALsource::eax5_defer_speaker_levels(const EaxCall& call, EaxSpeakerLevels& props) { const auto values = call.get_values(eax_max_speakers); std::for_each(values.cbegin(), values.cend(), Eax5SpeakerAllValidator{}); for (const auto& value : values) { const auto index = static_cast(value.lSpeakerID - EAXSPEAKER_FRONT_LEFT); props[index].lLevel = value.lLevel; } } void ALsource::eax5_set(const EaxCall& call, Eax5Props& props) { switch (call.get_property_id()) { case EAXSOURCE_NONE: break; case EAXSOURCE_ALLPARAMETERS: eax_defer(call, props.source); break; case EAXSOURCE_OBSTRUCTIONPARAMETERS: case EAXSOURCE_OCCLUSIONPARAMETERS: case EAXSOURCE_EXCLUSIONPARAMETERS: case EAXSOURCE_DIRECT: case EAXSOURCE_DIRECTHF: case EAXSOURCE_ROOM: case EAXSOURCE_ROOMHF: case EAXSOURCE_OBSTRUCTION: case EAXSOURCE_OBSTRUCTIONLFRATIO: case EAXSOURCE_OCCLUSION: case EAXSOURCE_OCCLUSIONLFRATIO: case EAXSOURCE_OCCLUSIONROOMRATIO: case EAXSOURCE_OCCLUSIONDIRECTRATIO: case EAXSOURCE_EXCLUSION: case EAXSOURCE_EXCLUSIONLFRATIO: case EAXSOURCE_OUTSIDEVOLUMEHF: case EAXSOURCE_DOPPLERFACTOR: case EAXSOURCE_ROLLOFFFACTOR: case EAXSOURCE_ROOMROLLOFFFACTOR: case EAXSOURCE_AIRABSORPTIONFACTOR: eax3_set(call, props.source); break; case EAXSOURCE_FLAGS: eax_defer(call, props.source.ulFlags); break; case EAXSOURCE_SENDPARAMETERS: eax5_defer_sends(call, props.sends); break; case EAXSOURCE_ALLSENDPARAMETERS: eax5_defer_sends(call, props.sends); break; case EAXSOURCE_OCCLUSIONSENDPARAMETERS: eax5_defer_sends(call, props.sends); break; case EAXSOURCE_EXCLUSIONSENDPARAMETERS: eax5_defer_sends(call, props.sends); break; case EAXSOURCE_ACTIVEFXSLOTID: eax5_defer_active_fx_slot_id(call, al::span{props.active_fx_slots.guidActiveFXSlots}); break; case EAXSOURCE_MACROFXFACTOR: eax_defer(call, props.source.flMacroFXFactor); break; case EAXSOURCE_SPEAKERLEVELS: eax5_defer_speaker_levels(call, props.speaker_levels); break; case EAXSOURCE_ALL2DPARAMETERS: eax5_defer_all_2d(call, props.source); break; default: eax_fail_unknown_property_id(); } } void ALsource::eax_set(const EaxCall& call) { const auto eax_version = call.get_version(); switch(eax_version) { case 1: eax1_set(call, mEax1.d); break; case 2: eax2_set(call, mEax2.d); break; case 3: eax3_set(call, mEax3.d); break; case 4: eax4_set(call, mEax4.d); break; case 5: eax5_set(call, mEax5.d); break; default: eax_fail_unknown_property_id(); } mEaxChanged = true; mEaxVersion = eax_version; } void ALsource::eax_get_active_fx_slot_id(const EaxCall& call, const al::span src_ids) { assert(src_ids.size()==EAX40_MAX_ACTIVE_FXSLOTS || src_ids.size()==EAX50_MAX_ACTIVE_FXSLOTS); const auto dst_ids = call.get_values(src_ids.size()); std::uninitialized_copy_n(src_ids.begin(), dst_ids.size(), dst_ids.begin()); } void ALsource::eax1_get(const EaxCall& call, const Eax1Props& props) { switch (call.get_property_id()) { case DSPROPERTY_EAXBUFFER_ALL: case DSPROPERTY_EAXBUFFER_REVERBMIX: call.set_value(props.fMix); break; default: eax_fail_unknown_property_id(); } } void ALsource::eax2_get(const EaxCall& call, const Eax2Props& props) { switch (call.get_property_id()) { case DSPROPERTY_EAX20BUFFER_NONE: break; case DSPROPERTY_EAX20BUFFER_ALLPARAMETERS: call.set_value(props); break; case DSPROPERTY_EAX20BUFFER_DIRECT: call.set_value(props.lDirect); break; case DSPROPERTY_EAX20BUFFER_DIRECTHF: call.set_value(props.lDirectHF); break; case DSPROPERTY_EAX20BUFFER_ROOM: call.set_value(props.lRoom); break; case DSPROPERTY_EAX20BUFFER_ROOMHF: call.set_value(props.lRoomHF); break; case DSPROPERTY_EAX20BUFFER_ROOMROLLOFFFACTOR: call.set_value(props.flRoomRolloffFactor); break; case DSPROPERTY_EAX20BUFFER_OBSTRUCTION: call.set_value(props.lObstruction); break; case DSPROPERTY_EAX20BUFFER_OBSTRUCTIONLFRATIO: call.set_value(props.flObstructionLFRatio); break; case DSPROPERTY_EAX20BUFFER_OCCLUSION: call.set_value(props.lOcclusion); break; case DSPROPERTY_EAX20BUFFER_OCCLUSIONLFRATIO: call.set_value(props.flOcclusionLFRatio); break; case DSPROPERTY_EAX20BUFFER_OCCLUSIONROOMRATIO: call.set_value(props.flOcclusionRoomRatio); break; case DSPROPERTY_EAX20BUFFER_OUTSIDEVOLUMEHF: call.set_value(props.lOutsideVolumeHF); break; case DSPROPERTY_EAX20BUFFER_AIRABSORPTIONFACTOR: call.set_value(props.flAirAbsorptionFactor); break; case DSPROPERTY_EAX20BUFFER_FLAGS: call.set_value(props.dwFlags); break; default: eax_fail_unknown_property_id(); } } void ALsource::eax3_get_obstruction(const EaxCall& call, const Eax3Props& props) { const auto& subprops = reinterpret_cast(props.lObstruction); call.set_value(subprops); } void ALsource::eax3_get_occlusion(const EaxCall& call, const Eax3Props& props) { const auto& subprops = reinterpret_cast(props.lOcclusion); call.set_value(subprops); } void ALsource::eax3_get_exclusion(const EaxCall& call, const Eax3Props& props) { const auto& subprops = reinterpret_cast(props.lExclusion); call.set_value(subprops); } void ALsource::eax3_get(const EaxCall& call, const Eax3Props& props) { switch (call.get_property_id()) { case EAXSOURCE_NONE: break; case EAXSOURCE_ALLPARAMETERS: call.set_value(props); break; case EAXSOURCE_OBSTRUCTIONPARAMETERS: eax3_get_obstruction(call, props); break; case EAXSOURCE_OCCLUSIONPARAMETERS: eax3_get_occlusion(call, props); break; case EAXSOURCE_EXCLUSIONPARAMETERS: eax3_get_exclusion(call, props); break; case EAXSOURCE_DIRECT: call.set_value(props.lDirect); break; case EAXSOURCE_DIRECTHF: call.set_value(props.lDirectHF); break; case EAXSOURCE_ROOM: call.set_value(props.lRoom); break; case EAXSOURCE_ROOMHF: call.set_value(props.lRoomHF); break; case EAXSOURCE_OBSTRUCTION: call.set_value(props.lObstruction); break; case EAXSOURCE_OBSTRUCTIONLFRATIO: call.set_value(props.flObstructionLFRatio); break; case EAXSOURCE_OCCLUSION: call.set_value(props.lOcclusion); break; case EAXSOURCE_OCCLUSIONLFRATIO: call.set_value(props.flOcclusionLFRatio); break; case EAXSOURCE_OCCLUSIONROOMRATIO: call.set_value(props.flOcclusionRoomRatio); break; case EAXSOURCE_OCCLUSIONDIRECTRATIO: call.set_value(props.flOcclusionDirectRatio); break; case EAXSOURCE_EXCLUSION: call.set_value(props.lExclusion); break; case EAXSOURCE_EXCLUSIONLFRATIO: call.set_value(props.flExclusionLFRatio); break; case EAXSOURCE_OUTSIDEVOLUMEHF: call.set_value(props.lOutsideVolumeHF); break; case EAXSOURCE_DOPPLERFACTOR: call.set_value(props.flDopplerFactor); break; case EAXSOURCE_ROLLOFFFACTOR: call.set_value(props.flRolloffFactor); break; case EAXSOURCE_ROOMROLLOFFFACTOR: call.set_value(props.flRoomRolloffFactor); break; case EAXSOURCE_AIRABSORPTIONFACTOR: call.set_value(props.flAirAbsorptionFactor); break; case EAXSOURCE_FLAGS: call.set_value(props.ulFlags); break; default: eax_fail_unknown_property_id(); } } void ALsource::eax4_get(const EaxCall& call, const Eax4Props& props) { switch (call.get_property_id()) { case EAXSOURCE_NONE: break; case EAXSOURCE_ALLPARAMETERS: case EAXSOURCE_OBSTRUCTIONPARAMETERS: case EAXSOURCE_OCCLUSIONPARAMETERS: case EAXSOURCE_EXCLUSIONPARAMETERS: case EAXSOURCE_DIRECT: case EAXSOURCE_DIRECTHF: case EAXSOURCE_ROOM: case EAXSOURCE_ROOMHF: case EAXSOURCE_OBSTRUCTION: case EAXSOURCE_OBSTRUCTIONLFRATIO: case EAXSOURCE_OCCLUSION: case EAXSOURCE_OCCLUSIONLFRATIO: case EAXSOURCE_OCCLUSIONROOMRATIO: case EAXSOURCE_OCCLUSIONDIRECTRATIO: case EAXSOURCE_EXCLUSION: case EAXSOURCE_EXCLUSIONLFRATIO: case EAXSOURCE_OUTSIDEVOLUMEHF: case EAXSOURCE_DOPPLERFACTOR: case EAXSOURCE_ROLLOFFFACTOR: case EAXSOURCE_ROOMROLLOFFFACTOR: case EAXSOURCE_AIRABSORPTIONFACTOR: case EAXSOURCE_FLAGS: eax3_get(call, props.source); break; case EAXSOURCE_SENDPARAMETERS: eax_get_sends(call, props.sends); break; case EAXSOURCE_ALLSENDPARAMETERS: eax_get_sends(call, props.sends); break; case EAXSOURCE_OCCLUSIONSENDPARAMETERS: eax_get_sends(call, props.sends); break; case EAXSOURCE_EXCLUSIONSENDPARAMETERS: eax_get_sends(call, props.sends); break; case EAXSOURCE_ACTIVEFXSLOTID: eax_get_active_fx_slot_id(call, props.active_fx_slots.guidActiveFXSlots); break; default: eax_fail_unknown_property_id(); } } void ALsource::eax5_get_all_2d(const EaxCall& call, const EAX50SOURCEPROPERTIES& props) { auto& subprops = call.get_value(); subprops.lDirect = props.lDirect; subprops.lDirectHF = props.lDirectHF; subprops.lRoom = props.lRoom; subprops.lRoomHF = props.lRoomHF; subprops.ulFlags = props.ulFlags; } void ALsource::eax5_get_speaker_levels(const EaxCall& call, const EaxSpeakerLevels& props) { const auto subprops = call.get_values(eax_max_speakers); std::uninitialized_copy_n(props.cbegin(), subprops.size(), subprops.begin()); } void ALsource::eax5_get(const EaxCall& call, const Eax5Props& props) { switch (call.get_property_id()) { case EAXSOURCE_NONE: break; case EAXSOURCE_ALLPARAMETERS: case EAXSOURCE_OBSTRUCTIONPARAMETERS: case EAXSOURCE_OCCLUSIONPARAMETERS: case EAXSOURCE_EXCLUSIONPARAMETERS: case EAXSOURCE_DIRECT: case EAXSOURCE_DIRECTHF: case EAXSOURCE_ROOM: case EAXSOURCE_ROOMHF: case EAXSOURCE_OBSTRUCTION: case EAXSOURCE_OBSTRUCTIONLFRATIO: case EAXSOURCE_OCCLUSION: case EAXSOURCE_OCCLUSIONLFRATIO: case EAXSOURCE_OCCLUSIONROOMRATIO: case EAXSOURCE_OCCLUSIONDIRECTRATIO: case EAXSOURCE_EXCLUSION: case EAXSOURCE_EXCLUSIONLFRATIO: case EAXSOURCE_OUTSIDEVOLUMEHF: case EAXSOURCE_DOPPLERFACTOR: case EAXSOURCE_ROLLOFFFACTOR: case EAXSOURCE_ROOMROLLOFFFACTOR: case EAXSOURCE_AIRABSORPTIONFACTOR: case EAXSOURCE_FLAGS: eax3_get(call, props.source); break; case EAXSOURCE_SENDPARAMETERS: eax_get_sends(call, props.sends); break; case EAXSOURCE_ALLSENDPARAMETERS: eax_get_sends(call, props.sends); break; case EAXSOURCE_OCCLUSIONSENDPARAMETERS: eax_get_sends(call, props.sends); break; case EAXSOURCE_EXCLUSIONSENDPARAMETERS: eax_get_sends(call, props.sends); break; case EAXSOURCE_ACTIVEFXSLOTID: eax_get_active_fx_slot_id(call, props.active_fx_slots.guidActiveFXSlots); break; case EAXSOURCE_MACROFXFACTOR: call.set_value(props.source.flMacroFXFactor); break; case EAXSOURCE_SPEAKERLEVELS: call.set_value(props.speaker_levels); break; case EAXSOURCE_ALL2DPARAMETERS: eax5_get_all_2d(call, props.source); break; default: eax_fail_unknown_property_id(); } } void ALsource::eax_get(const EaxCall& call) { switch (call.get_version()) { case 1: eax1_get(call, mEax1.i); break; case 2: eax2_get(call, mEax2.i); break; case 3: eax3_get(call, mEax3.i); break; case 4: eax4_get(call, mEax4.i); break; case 5: eax5_get(call, mEax5.i); break; default: eax_fail_unknown_version(); } } void ALsource::eax_set_al_source_send(ALeffectslot *slot, size_t sendidx, const EaxAlLowPassParam &filter) { if(sendidx >= EAX_MAX_FXSLOTS) return; auto &send = Send[sendidx]; send.Gain = filter.gain; send.GainHF = filter.gain_hf; send.HFReference = LowPassFreqRef; send.GainLF = 1.0f; send.LFReference = HighPassFreqRef; if(slot != nullptr) IncrementRef(slot->ref); if(auto *oldslot = send.Slot) DecrementRef(oldslot->ref); send.Slot = slot; mPropsDirty = true; } void ALsource::eax_commit_active_fx_slots() { // Clear all slots to an inactive state. mEaxActiveFxSlots.fill(false); // Mark the set slots as active. for(const auto& slot_id : mEax.active_fx_slots.guidActiveFXSlots) { if(slot_id == EAX_NULL_GUID) { } else if(slot_id == EAX_PrimaryFXSlotID) { // Mark primary FX slot as active. if(mEaxPrimaryFxSlotId.has_value()) mEaxActiveFxSlots[*mEaxPrimaryFxSlotId] = true; } else if(slot_id == EAXPROPERTYID_EAX50_FXSlot0) mEaxActiveFxSlots[0] = true; else if(slot_id == EAXPROPERTYID_EAX50_FXSlot1) mEaxActiveFxSlots[1] = true; else if(slot_id == EAXPROPERTYID_EAX50_FXSlot2) mEaxActiveFxSlots[2] = true; else if(slot_id == EAXPROPERTYID_EAX50_FXSlot3) mEaxActiveFxSlots[3] = true; } // Deactivate EFX auxiliary effect slots for inactive slots. Active slots // will be updated with the room filters. for(size_t i{0};i < EAX_MAX_FXSLOTS;++i) { if(!mEaxActiveFxSlots[i]) eax_set_al_source_send(nullptr, i, EaxAlLowPassParam{1.0f, 1.0f}); } } void ALsource::eax_commit_filters() { eax_update_direct_filter(); eax_update_room_filters(); } void ALsource::eaxCommit() { const auto primary_fx_slot_id = mEaxAlContext->eaxGetPrimaryFxSlotIndex(); const auto is_primary_fx_slot_id_changed = (mEaxPrimaryFxSlotId != primary_fx_slot_id); if(!mEaxChanged && !is_primary_fx_slot_id_changed) return; mEaxPrimaryFxSlotId = primary_fx_slot_id; mEaxChanged = false; switch(mEaxVersion) { case 1: mEax1.i = mEax1.d; eax1_translate(mEax1.i, mEax); break; case 2: mEax2.i = mEax2.d; eax2_translate(mEax2.i, mEax); break; case 3: mEax3.i = mEax3.d; eax3_translate(mEax3.i, mEax); break; case 4: mEax4.i = mEax4.d; eax4_translate(mEax4.i, mEax); break; case 5: mEax5.i = mEax5.d; mEax = mEax5.d; break; } eax_set_efx_outer_gain_hf(); eax_set_efx_doppler_factor(); eax_set_efx_rolloff_factor(); eax_set_efx_room_rolloff_factor(); eax_set_efx_air_absorption_factor(); eax_set_efx_dry_gain_hf_auto(); eax_set_efx_wet_gain_auto(); eax_set_efx_wet_gain_hf_auto(); eax_commit_active_fx_slots(); eax_commit_filters(); } #endif // ALSOFT_EAX openal-soft-1.24.2/al/source.h000066400000000000000000001106761474041540300161110ustar00rootroot00000000000000#ifndef AL_SOURCE_H #define AL_SOURCE_H #include "config.h" #include #include #include #include #include #include #include #include "AL/al.h" #include "AL/alc.h" #include "AL/alext.h" #include "almalloc.h" #include "alnumbers.h" #include "alnumeric.h" #include "alspan.h" #include "core/context.h" #include "core/voice.h" #if ALSOFT_EAX #include "eax/api.h" #include "eax/call.h" #include "eax/exception.h" #include "eax/fx_slot_index.h" #include "eax/utils.h" #endif // ALSOFT_EAX struct ALbuffer; struct ALeffectslot; enum class Resampler : uint8_t; enum class SourceStereo : bool { Normal = AL_NORMAL_SOFT, Enhanced = AL_SUPER_STEREO_SOFT }; inline constexpr size_t DefaultSendCount{2}; inline constexpr ALuint InvalidVoiceIndex{std::numeric_limits::max()}; inline bool sBufferSubDataCompat{false}; struct ALbufferQueueItem : public VoiceBufferItem { ALbuffer *mBuffer{nullptr}; }; #if ALSOFT_EAX class EaxSourceException : public EaxException { public: explicit EaxSourceException(const char* message) : EaxException{"EAX_SOURCE", message} {} }; #endif // ALSOFT_EAX struct ALsource { /** Source properties. */ float Pitch{1.0f}; float Gain{1.0f}; float OuterGain{0.0f}; float MinGain{0.0f}; float MaxGain{1.0f}; float InnerAngle{360.0f}; float OuterAngle{360.0f}; float RefDistance{1.0f}; float MaxDistance{std::numeric_limits::max()}; float RolloffFactor{1.0f}; #if ALSOFT_EAX // For EAXSOURCE_ROLLOFFFACTOR, which is distinct from and added to // AL_ROLLOFF_FACTOR float RolloffFactor2{0.0f}; #endif std::array Position{{0.0f, 0.0f, 0.0f}}; std::array Velocity{{0.0f, 0.0f, 0.0f}}; std::array Direction{{0.0f, 0.0f, 0.0f}}; std::array OrientAt{{0.0f, 0.0f, -1.0f}}; std::array OrientUp{{0.0f, 1.0f, 0.0f}}; bool HeadRelative{false}; bool Looping{false}; DistanceModel mDistanceModel{DistanceModel::Default}; Resampler mResampler{ResamplerDefault}; DirectMode DirectChannels{DirectMode::Off}; SpatializeMode mSpatialize{SpatializeMode::Auto}; SourceStereo mStereoMode{SourceStereo::Normal}; bool mPanningEnabled{false}; bool DryGainHFAuto{true}; bool WetGainAuto{true}; bool WetGainHFAuto{true}; float OuterGainHF{1.0f}; float AirAbsorptionFactor{0.0f}; float RoomRolloffFactor{0.0f}; float DopplerFactor{1.0f}; /* NOTE: Stereo pan angles are specified in radians, counter-clockwise * rather than clockwise. */ std::array StereoPan{{al::numbers::pi_v/6.0f, -al::numbers::pi_v/6.0f}}; float Radius{0.0f}; float EnhWidth{0.593f}; float mPan{0.0f}; /** Direct filter and auxiliary send info. */ struct DirectData { float Gain{}; float GainHF{}; float HFReference{}; float GainLF{}; float LFReference{}; }; DirectData Direct; struct SendData { ALeffectslot *Slot{}; float Gain{}; float GainHF{}; float HFReference{}; float GainLF{}; float LFReference{}; }; std::array Send; /** * Last user-specified offset, and the offset type (bytes, samples, or * seconds). */ double Offset{0.0}; ALenum OffsetType{AL_NONE}; /** Source type (static, streaming, or undetermined) */ ALenum SourceType{AL_UNDETERMINED}; /** Source state (initial, playing, paused, or stopped) */ ALenum state{AL_INITIAL}; /** Source Buffer Queue head. */ std::deque mQueue; bool mPropsDirty{true}; /* Index into the context's Voices array. Lazily updated, only checked and * reset when looking up the voice. */ ALuint VoiceIdx{InvalidVoiceIndex}; /** Self ID */ ALuint id{0}; ALsource() noexcept; ~ALsource(); ALsource(const ALsource&) = delete; ALsource& operator=(const ALsource&) = delete; static void SetName(ALCcontext *context, ALuint id, std::string_view name); DISABLE_ALLOC #if ALSOFT_EAX public: void eaxInitialize(ALCcontext *context) noexcept; void eaxDispatch(const EaxCall& call); void eaxCommit(); void eaxMarkAsChanged() noexcept { mEaxChanged = true; } static ALsource* EaxLookupSource(ALCcontext& al_context, ALuint source_id) noexcept; private: using Exception = EaxSourceException; static constexpr auto eax_max_speakers{9u}; using EaxFxSlotIds = std::array; static constexpr const EaxFxSlotIds eax4_fx_slot_ids{ &EAXPROPERTYID_EAX40_FXSlot0, &EAXPROPERTYID_EAX40_FXSlot1, &EAXPROPERTYID_EAX40_FXSlot2, &EAXPROPERTYID_EAX40_FXSlot3, }; static constexpr const EaxFxSlotIds eax5_fx_slot_ids{ &EAXPROPERTYID_EAX50_FXSlot0, &EAXPROPERTYID_EAX50_FXSlot1, &EAXPROPERTYID_EAX50_FXSlot2, &EAXPROPERTYID_EAX50_FXSlot3, }; using EaxActiveFxSlots = std::array; using EaxSpeakerLevels = std::array; using EaxSends = std::array; using Eax1Props = EAXBUFFER_REVERBPROPERTIES; struct Eax1State { Eax1Props i; // Immediate. Eax1Props d; // Deferred. }; using Eax2Props = EAX20BUFFERPROPERTIES; struct Eax2State { Eax2Props i; // Immediate. Eax2Props d; // Deferred. }; using Eax3Props = EAX30SOURCEPROPERTIES; struct Eax3State { Eax3Props i; // Immediate. Eax3Props d; // Deferred. }; struct Eax4Props { Eax3Props source; EaxSends sends; EAX40ACTIVEFXSLOTS active_fx_slots; }; struct Eax4State { Eax4Props i; // Immediate. Eax4Props d; // Deferred. }; struct Eax5Props { EAX50SOURCEPROPERTIES source; EaxSends sends; EAX50ACTIVEFXSLOTS active_fx_slots; EaxSpeakerLevels speaker_levels; }; struct Eax5State { Eax5Props i; // Immediate. Eax5Props d; // Deferred. }; ALCcontext* mEaxAlContext{}; EaxFxSlotIndex mEaxPrimaryFxSlotId{}; EaxActiveFxSlots mEaxActiveFxSlots{}; int mEaxVersion{}; bool mEaxChanged{}; Eax1State mEax1{}; Eax2State mEax2{}; Eax3State mEax3{}; Eax4State mEax4{}; Eax5State mEax5{}; Eax5Props mEax{}; // ---------------------------------------------------------------------- // Source validators struct Eax1SourceReverbMixValidator { void operator()(float reverb_mix) const { if (reverb_mix == EAX_REVERBMIX_USEDISTANCE) return; eax_validate_range( "Reverb Mix", reverb_mix, EAX_BUFFER_MINREVERBMIX, EAX_BUFFER_MAXREVERBMIX); } }; struct Eax2SourceDirectValidator { void operator()(long lDirect) const { eax_validate_range( "Direct", lDirect, EAXSOURCE_MINDIRECT, EAXSOURCE_MAXDIRECT); } }; struct Eax2SourceDirectHfValidator { void operator()(long lDirectHF) const { eax_validate_range( "Direct HF", lDirectHF, EAXSOURCE_MINDIRECTHF, EAXSOURCE_MAXDIRECTHF); } }; struct Eax2SourceRoomValidator { void operator()(long lRoom) const { eax_validate_range( "Room", lRoom, EAXSOURCE_MINROOM, EAXSOURCE_MAXROOM); } }; struct Eax2SourceRoomHfValidator { void operator()(long lRoomHF) const { eax_validate_range( "Room HF", lRoomHF, EAXSOURCE_MINROOMHF, EAXSOURCE_MAXROOMHF); } }; struct Eax2SourceRoomRolloffFactorValidator { void operator()(float flRoomRolloffFactor) const { eax_validate_range( "Room Rolloff Factor", flRoomRolloffFactor, EAXSOURCE_MINROOMROLLOFFFACTOR, EAXSOURCE_MAXROOMROLLOFFFACTOR); } }; struct Eax2SourceObstructionValidator { void operator()(long lObstruction) const { eax_validate_range( "Obstruction", lObstruction, EAXSOURCE_MINOBSTRUCTION, EAXSOURCE_MAXOBSTRUCTION); } }; struct Eax2SourceObstructionLfRatioValidator { void operator()(float flObstructionLFRatio) const { eax_validate_range( "Obstruction LF Ratio", flObstructionLFRatio, EAXSOURCE_MINOBSTRUCTIONLFRATIO, EAXSOURCE_MAXOBSTRUCTIONLFRATIO); } }; struct Eax2SourceOcclusionValidator { void operator()(long lOcclusion) const { eax_validate_range( "Occlusion", lOcclusion, EAXSOURCE_MINOCCLUSION, EAXSOURCE_MAXOCCLUSION); } }; struct Eax2SourceOcclusionLfRatioValidator { void operator()(float flOcclusionLFRatio) const { eax_validate_range( "Occlusion LF Ratio", flOcclusionLFRatio, EAXSOURCE_MINOCCLUSIONLFRATIO, EAXSOURCE_MAXOCCLUSIONLFRATIO); } }; struct Eax2SourceOcclusionRoomRatioValidator { void operator()(float flOcclusionRoomRatio) const { eax_validate_range( "Occlusion Room Ratio", flOcclusionRoomRatio, EAXSOURCE_MINOCCLUSIONROOMRATIO, EAXSOURCE_MAXOCCLUSIONROOMRATIO); } }; struct Eax2SourceOutsideVolumeHfValidator { void operator()(long lOutsideVolumeHF) const { eax_validate_range( "Outside Volume HF", lOutsideVolumeHF, EAXSOURCE_MINOUTSIDEVOLUMEHF, EAXSOURCE_MAXOUTSIDEVOLUMEHF); } }; struct Eax2SourceAirAbsorptionFactorValidator { void operator()(float flAirAbsorptionFactor) const { eax_validate_range( "Air Absorption Factor", flAirAbsorptionFactor, EAXSOURCE_MINAIRABSORPTIONFACTOR, EAXSOURCE_MAXAIRABSORPTIONFACTOR); } }; struct Eax2SourceFlagsValidator { void operator()(unsigned long dwFlags) const { eax_validate_range( "Flags", dwFlags, 0UL, ~EAX20SOURCEFLAGS_RESERVED); } }; struct Eax3SourceOcclusionDirectRatioValidator { void operator()(float flOcclusionDirectRatio) const { eax_validate_range( "Occlusion Direct Ratio", flOcclusionDirectRatio, EAXSOURCE_MINOCCLUSIONDIRECTRATIO, EAXSOURCE_MAXOCCLUSIONDIRECTRATIO); } }; struct Eax3SourceExclusionValidator { void operator()(long lExclusion) const { eax_validate_range( "Exclusion", lExclusion, EAXSOURCE_MINEXCLUSION, EAXSOURCE_MAXEXCLUSION); } }; struct Eax3SourceExclusionLfRatioValidator { void operator()(float flExclusionLFRatio) const { eax_validate_range( "Exclusion LF Ratio", flExclusionLFRatio, EAXSOURCE_MINEXCLUSIONLFRATIO, EAXSOURCE_MAXEXCLUSIONLFRATIO); } }; struct Eax3SourceDopplerFactorValidator { void operator()(float flDopplerFactor) const { eax_validate_range( "Doppler Factor", flDopplerFactor, EAXSOURCE_MINDOPPLERFACTOR, EAXSOURCE_MAXDOPPLERFACTOR); } }; struct Eax3SourceRolloffFactorValidator { void operator()(float flRolloffFactor) const { eax_validate_range( "Rolloff Factor", flRolloffFactor, EAXSOURCE_MINROLLOFFFACTOR, EAXSOURCE_MAXROLLOFFFACTOR); } }; struct Eax5SourceMacroFXFactorValidator { void operator()(float flMacroFXFactor) const { eax_validate_range( "Macro FX Factor", flMacroFXFactor, EAXSOURCE_MINMACROFXFACTOR, EAXSOURCE_MAXMACROFXFACTOR); } }; struct Eax5SourceFlagsValidator { void operator()(unsigned long dwFlags) const { eax_validate_range( "Flags", dwFlags, 0UL, ~EAX50SOURCEFLAGS_RESERVED); } }; struct Eax1SourceAllValidator { void operator()(const Eax1Props& props) const { Eax1SourceReverbMixValidator{}(props.fMix); } }; struct Eax2SourceAllValidator { void operator()(const Eax2Props& props) const { Eax2SourceDirectValidator{}(props.lDirect); Eax2SourceDirectHfValidator{}(props.lDirectHF); Eax2SourceRoomValidator{}(props.lRoom); Eax2SourceRoomHfValidator{}(props.lRoomHF); Eax2SourceRoomRolloffFactorValidator{}(props.flRoomRolloffFactor); Eax2SourceObstructionValidator{}(props.lObstruction); Eax2SourceObstructionLfRatioValidator{}(props.flObstructionLFRatio); Eax2SourceOcclusionValidator{}(props.lOcclusion); Eax2SourceOcclusionLfRatioValidator{}(props.flOcclusionLFRatio); Eax2SourceOcclusionRoomRatioValidator{}(props.flOcclusionRoomRatio); Eax2SourceOutsideVolumeHfValidator{}(props.lOutsideVolumeHF); Eax2SourceAirAbsorptionFactorValidator{}(props.flAirAbsorptionFactor); Eax2SourceFlagsValidator{}(props.dwFlags); } }; struct Eax3SourceAllValidator { void operator()(const Eax3Props& props) const { Eax2SourceDirectValidator{}(props.lDirect); Eax2SourceDirectHfValidator{}(props.lDirectHF); Eax2SourceRoomValidator{}(props.lRoom); Eax2SourceRoomHfValidator{}(props.lRoomHF); Eax2SourceObstructionValidator{}(props.lObstruction); Eax2SourceObstructionLfRatioValidator{}(props.flObstructionLFRatio); Eax2SourceOcclusionValidator{}(props.lOcclusion); Eax2SourceOcclusionLfRatioValidator{}(props.flOcclusionLFRatio); Eax2SourceOcclusionRoomRatioValidator{}(props.flOcclusionRoomRatio); Eax3SourceOcclusionDirectRatioValidator{}(props.flOcclusionDirectRatio); Eax3SourceExclusionValidator{}(props.lExclusion); Eax3SourceExclusionLfRatioValidator{}(props.flExclusionLFRatio); Eax2SourceOutsideVolumeHfValidator{}(props.lOutsideVolumeHF); Eax3SourceDopplerFactorValidator{}(props.flDopplerFactor); Eax3SourceRolloffFactorValidator{}(props.flRolloffFactor); Eax2SourceRoomRolloffFactorValidator{}(props.flRoomRolloffFactor); Eax2SourceAirAbsorptionFactorValidator{}(props.flAirAbsorptionFactor); Eax2SourceFlagsValidator{}(props.ulFlags); } }; struct Eax5SourceAllValidator { void operator()(const EAX50SOURCEPROPERTIES& props) const { Eax2SourceDirectValidator{}(props.lDirect); Eax2SourceDirectHfValidator{}(props.lDirectHF); Eax2SourceRoomValidator{}(props.lRoom); Eax2SourceRoomHfValidator{}(props.lRoomHF); Eax2SourceObstructionValidator{}(props.lObstruction); Eax2SourceObstructionLfRatioValidator{}(props.flObstructionLFRatio); Eax2SourceOcclusionValidator{}(props.lOcclusion); Eax2SourceOcclusionLfRatioValidator{}(props.flOcclusionLFRatio); Eax2SourceOcclusionRoomRatioValidator{}(props.flOcclusionRoomRatio); Eax3SourceOcclusionDirectRatioValidator{}(props.flOcclusionDirectRatio); Eax3SourceExclusionValidator{}(props.lExclusion); Eax3SourceExclusionLfRatioValidator{}(props.flExclusionLFRatio); Eax2SourceOutsideVolumeHfValidator{}(props.lOutsideVolumeHF); Eax3SourceDopplerFactorValidator{}(props.flDopplerFactor); Eax3SourceRolloffFactorValidator{}(props.flRolloffFactor); Eax2SourceRoomRolloffFactorValidator{}(props.flRoomRolloffFactor); Eax2SourceAirAbsorptionFactorValidator{}(props.flAirAbsorptionFactor); Eax5SourceFlagsValidator{}(props.ulFlags); Eax5SourceMacroFXFactorValidator{}(props.flMacroFXFactor); } }; struct Eax5SourceAll2dValidator { void operator()(const EAXSOURCE2DPROPERTIES& props) const { Eax2SourceDirectValidator{}(props.lDirect); Eax2SourceDirectHfValidator{}(props.lDirectHF); Eax2SourceRoomValidator{}(props.lRoom); Eax2SourceRoomHfValidator{}(props.lRoomHF); Eax5SourceFlagsValidator{}(props.ulFlags); } }; struct Eax4ObstructionValidator { void operator()(const EAXOBSTRUCTIONPROPERTIES& props) const { Eax2SourceObstructionValidator{}(props.lObstruction); Eax2SourceObstructionLfRatioValidator{}(props.flObstructionLFRatio); } }; struct Eax4OcclusionValidator { void operator()(const EAXOCCLUSIONPROPERTIES& props) const { Eax2SourceOcclusionValidator{}(props.lOcclusion); Eax2SourceOcclusionLfRatioValidator{}(props.flOcclusionLFRatio); Eax2SourceOcclusionRoomRatioValidator{}(props.flOcclusionRoomRatio); Eax3SourceOcclusionDirectRatioValidator{}(props.flOcclusionDirectRatio); } }; struct Eax4ExclusionValidator { void operator()(const EAXEXCLUSIONPROPERTIES& props) const { Eax3SourceExclusionValidator{}(props.lExclusion); Eax3SourceExclusionLfRatioValidator{}(props.flExclusionLFRatio); } }; // Source validators // ---------------------------------------------------------------------- // Send validators struct Eax4SendReceivingFxSlotIdValidator { void operator()(const GUID& guidReceivingFXSlotID) const { if (guidReceivingFXSlotID != EAXPROPERTYID_EAX40_FXSlot0 && guidReceivingFXSlotID != EAXPROPERTYID_EAX40_FXSlot1 && guidReceivingFXSlotID != EAXPROPERTYID_EAX40_FXSlot2 && guidReceivingFXSlotID != EAXPROPERTYID_EAX40_FXSlot3) { eax_fail_unknown_receiving_fx_slot_id(); } } }; struct Eax5SendReceivingFxSlotIdValidator { void operator()(const GUID& guidReceivingFXSlotID) const { if (guidReceivingFXSlotID != EAXPROPERTYID_EAX50_FXSlot0 && guidReceivingFXSlotID != EAXPROPERTYID_EAX50_FXSlot1 && guidReceivingFXSlotID != EAXPROPERTYID_EAX50_FXSlot2 && guidReceivingFXSlotID != EAXPROPERTYID_EAX50_FXSlot3) { eax_fail_unknown_receiving_fx_slot_id(); } } }; struct Eax4SendSendValidator { void operator()(long lSend) const { eax_validate_range( "Send", lSend, EAXSOURCE_MINSEND, EAXSOURCE_MAXSEND); } }; struct Eax4SendSendHfValidator { void operator()(long lSendHF) const { eax_validate_range( "Send HF", lSendHF, EAXSOURCE_MINSENDHF, EAXSOURCE_MAXSENDHF); } }; template struct EaxSendValidator { void operator()(const EAXSOURCESENDPROPERTIES& props) const { TIdValidator{}(props.guidReceivingFXSlotID); Eax4SendSendValidator{}(props.lSend); Eax4SendSendHfValidator{}(props.lSendHF); } }; struct Eax4SendValidator : EaxSendValidator {}; struct Eax5SendValidator : EaxSendValidator {}; template struct EaxOcclusionSendValidator { void operator()(const EAXSOURCEOCCLUSIONSENDPROPERTIES& props) const { TIdValidator{}(props.guidReceivingFXSlotID); Eax2SourceOcclusionValidator{}(props.lOcclusion); Eax2SourceOcclusionLfRatioValidator{}(props.flOcclusionLFRatio); Eax2SourceOcclusionRoomRatioValidator{}(props.flOcclusionRoomRatio); Eax3SourceOcclusionDirectRatioValidator{}(props.flOcclusionDirectRatio); } }; struct Eax4OcclusionSendValidator : EaxOcclusionSendValidator {}; struct Eax5OcclusionSendValidator : EaxOcclusionSendValidator {}; template struct EaxExclusionSendValidator { void operator()(const EAXSOURCEEXCLUSIONSENDPROPERTIES& props) const { TIdValidator{}(props.guidReceivingFXSlotID); Eax3SourceExclusionValidator{}(props.lExclusion); Eax3SourceExclusionLfRatioValidator{}(props.flExclusionLFRatio); } }; struct Eax4ExclusionSendValidator : EaxExclusionSendValidator {}; struct Eax5ExclusionSendValidator : EaxExclusionSendValidator {}; template struct EaxAllSendValidator { void operator()(const EAXSOURCEALLSENDPROPERTIES& props) const { TIdValidator{}(props.guidReceivingFXSlotID); Eax4SendSendValidator{}(props.lSend); Eax4SendSendHfValidator{}(props.lSendHF); Eax2SourceOcclusionValidator{}(props.lOcclusion); Eax2SourceOcclusionLfRatioValidator{}(props.flOcclusionLFRatio); Eax2SourceOcclusionRoomRatioValidator{}(props.flOcclusionRoomRatio); Eax3SourceOcclusionDirectRatioValidator{}(props.flOcclusionDirectRatio); Eax3SourceExclusionValidator{}(props.lExclusion); Eax3SourceExclusionLfRatioValidator{}(props.flExclusionLFRatio); } }; struct Eax4AllSendValidator : EaxAllSendValidator {}; struct Eax5AllSendValidator : EaxAllSendValidator {}; // Send validators // ---------------------------------------------------------------------- // Active FX slot ID validators struct Eax4ActiveFxSlotIdValidator { void operator()(const GUID &guid) const { if(guid != EAX_NULL_GUID && guid != EAX_PrimaryFXSlotID && guid != EAXPROPERTYID_EAX40_FXSlot0 && guid != EAXPROPERTYID_EAX40_FXSlot1 && guid != EAXPROPERTYID_EAX40_FXSlot2 && guid != EAXPROPERTYID_EAX40_FXSlot3) { eax_fail_unknown_active_fx_slot_id(); } } }; struct Eax5ActiveFxSlotIdValidator { void operator()(const GUID &guid) const { if(guid != EAX_NULL_GUID && guid != EAX_PrimaryFXSlotID && guid != EAXPROPERTYID_EAX50_FXSlot0 && guid != EAXPROPERTYID_EAX50_FXSlot1 && guid != EAXPROPERTYID_EAX50_FXSlot2 && guid != EAXPROPERTYID_EAX50_FXSlot3) { eax_fail_unknown_active_fx_slot_id(); } } }; // Active FX slot ID validators // ---------------------------------------------------------------------- // Speaker level validators. struct Eax5SpeakerIdValidator { void operator()(long lSpeakerID) const { switch (lSpeakerID) { case EAXSPEAKER_FRONT_LEFT: case EAXSPEAKER_FRONT_CENTER: case EAXSPEAKER_FRONT_RIGHT: case EAXSPEAKER_SIDE_RIGHT: case EAXSPEAKER_REAR_RIGHT: case EAXSPEAKER_REAR_CENTER: case EAXSPEAKER_REAR_LEFT: case EAXSPEAKER_SIDE_LEFT: case EAXSPEAKER_LOW_FREQUENCY: break; default: eax_fail("Unknown speaker ID."); } } }; struct Eax5SpeakerLevelValidator { void operator()(long lLevel) const { // TODO Use a range when the feature will be implemented. if (lLevel != EAXSOURCE_DEFAULTSPEAKERLEVEL) eax_fail("Speaker level out of range."); } }; struct Eax5SpeakerAllValidator { void operator()(const EAXSPEAKERLEVELPROPERTIES& all) const { Eax5SpeakerIdValidator{}(all.lSpeakerID); Eax5SpeakerLevelValidator{}(all.lLevel); } }; // Speaker level validators. // ---------------------------------------------------------------------- struct Eax4SendIndexGetter { EaxFxSlotIndexValue operator()(const GUID &guid) const { if(guid == EAXPROPERTYID_EAX40_FXSlot0) return 0; if(guid == EAXPROPERTYID_EAX40_FXSlot1) return 1; if(guid == EAXPROPERTYID_EAX40_FXSlot2) return 2; if(guid == EAXPROPERTYID_EAX40_FXSlot3) return 3; eax_fail_unknown_receiving_fx_slot_id(); } }; struct Eax5SendIndexGetter { EaxFxSlotIndexValue operator()(const GUID &guid) const { if(guid == EAXPROPERTYID_EAX50_FXSlot0) return 0; if(guid == EAXPROPERTYID_EAX50_FXSlot1) return 1; if(guid == EAXPROPERTYID_EAX50_FXSlot2) return 2; if(guid == EAXPROPERTYID_EAX50_FXSlot3) return 3; eax_fail_unknown_receiving_fx_slot_id(); } }; [[noreturn]] static void eax_fail(const char* message); [[noreturn]] static void eax_fail_unknown_property_id(); [[noreturn]] static void eax_fail_unknown_version(); [[noreturn]] static void eax_fail_unknown_active_fx_slot_id(); [[noreturn]] static void eax_fail_unknown_receiving_fx_slot_id(); static void eax_set_sends_defaults(EaxSends& sends, const EaxFxSlotIds& ids) noexcept; static void eax1_set_defaults(Eax1Props& props) noexcept; void eax1_set_defaults() noexcept; static void eax2_set_defaults(Eax2Props& props) noexcept; void eax2_set_defaults() noexcept; static void eax3_set_defaults(Eax3Props& props) noexcept; void eax3_set_defaults() noexcept; static void eax4_set_sends_defaults(EaxSends& sends) noexcept; static void eax4_set_active_fx_slots_defaults(EAX40ACTIVEFXSLOTS& slots) noexcept; void eax4_set_defaults() noexcept; static void eax5_set_source_defaults(EAX50SOURCEPROPERTIES& props) noexcept; static void eax5_set_sends_defaults(EaxSends& sends) noexcept; static void eax5_set_active_fx_slots_defaults(EAX50ACTIVEFXSLOTS& slots) noexcept; static void eax5_set_speaker_levels_defaults(EaxSpeakerLevels& speaker_levels) noexcept; static void eax5_set_defaults(Eax5Props& props) noexcept; void eax5_set_defaults() noexcept; void eax_set_defaults() noexcept; static void eax1_translate(const Eax1Props& src, Eax5Props& dst) noexcept; static void eax2_translate(const Eax2Props& src, Eax5Props& dst) noexcept; static void eax3_translate(const Eax3Props& src, Eax5Props& dst) noexcept; static void eax4_translate(const Eax4Props& src, Eax5Props& dst) noexcept; static float eax_calculate_dst_occlusion_mb( long src_occlusion_mb, float path_ratio, float lf_ratio) noexcept; [[nodiscard]] auto eax_create_direct_filter_param() const noexcept -> EaxAlLowPassParam; [[nodiscard]] auto eax_create_room_filter_param(const ALeffectslot& fx_slot, const EAXSOURCEALLSENDPROPERTIES& send) const noexcept -> EaxAlLowPassParam; void eax_update_direct_filter(); void eax_update_room_filters(); void eax_commit_filters(); static void eax_copy_send_for_get( const EAXSOURCEALLSENDPROPERTIES& src, EAXSOURCESENDPROPERTIES& dst) noexcept { dst = reinterpret_cast(src); } static void eax_copy_send_for_get( const EAXSOURCEALLSENDPROPERTIES& src, EAXSOURCEALLSENDPROPERTIES& dst) noexcept { dst = src; } static void eax_copy_send_for_get( const EAXSOURCEALLSENDPROPERTIES& src, EAXSOURCEOCCLUSIONSENDPROPERTIES& dst) noexcept { dst.guidReceivingFXSlotID = src.guidReceivingFXSlotID; dst.lOcclusion = src.lOcclusion; dst.flOcclusionLFRatio = src.flOcclusionLFRatio; dst.flOcclusionRoomRatio = src.flOcclusionRoomRatio; dst.flOcclusionDirectRatio = src.flOcclusionDirectRatio; } static void eax_copy_send_for_get( const EAXSOURCEALLSENDPROPERTIES& src, EAXSOURCEEXCLUSIONSENDPROPERTIES& dst) noexcept { dst.guidReceivingFXSlotID = src.guidReceivingFXSlotID; dst.lExclusion = src.lExclusion; dst.flExclusionLFRatio = src.flExclusionLFRatio; } template void eax_get_sends(const EaxCall& call, const EaxSends& src_sends) { const auto dst_sends = call.get_values(EAX_MAX_FXSLOTS); const auto count = dst_sends.size(); for (auto i = decltype(count){}; i < count; ++i) { const auto& src_send = src_sends[i]; auto& dst_send = dst_sends[i]; eax_copy_send_for_get(src_send, dst_send); } } static void eax_get_active_fx_slot_id(const EaxCall& call, const al::span src_ids); static void eax1_get(const EaxCall& call, const Eax1Props& props); static void eax2_get(const EaxCall& call, const Eax2Props& props); static void eax3_get_obstruction(const EaxCall& call, const Eax3Props& props); static void eax3_get_occlusion(const EaxCall& call, const Eax3Props& props); static void eax3_get_exclusion(const EaxCall& call, const Eax3Props& props); static void eax3_get(const EaxCall& call, const Eax3Props& props); void eax4_get(const EaxCall& call, const Eax4Props& props); static void eax5_get_all_2d(const EaxCall& call, const EAX50SOURCEPROPERTIES& props); static void eax5_get_speaker_levels(const EaxCall& call, const EaxSpeakerLevels& props); void eax5_get(const EaxCall& call, const Eax5Props& props); void eax_get(const EaxCall& call); static void eax_copy_send_for_set( const EAXSOURCESENDPROPERTIES& src, EAXSOURCEALLSENDPROPERTIES& dst) noexcept { dst.lSend = src.lSend; dst.lSendHF = src.lSendHF; } static void eax_copy_send_for_set( const EAXSOURCEALLSENDPROPERTIES& src, EAXSOURCEALLSENDPROPERTIES& dst) noexcept { dst.lSend = src.lSend; dst.lSendHF = src.lSendHF; dst.lOcclusion = src.lOcclusion; dst.flOcclusionLFRatio = src.flOcclusionLFRatio; dst.flOcclusionRoomRatio = src.flOcclusionRoomRatio; dst.flOcclusionDirectRatio = src.flOcclusionDirectRatio; dst.lExclusion = src.lExclusion; dst.flExclusionLFRatio = src.flExclusionLFRatio; } static void eax_copy_send_for_set( const EAXSOURCEOCCLUSIONSENDPROPERTIES& src, EAXSOURCEALLSENDPROPERTIES& dst) noexcept { dst.lOcclusion = src.lOcclusion; dst.flOcclusionLFRatio = src.flOcclusionLFRatio; dst.flOcclusionRoomRatio = src.flOcclusionRoomRatio; dst.flOcclusionDirectRatio = src.flOcclusionDirectRatio; } static void eax_copy_send_for_set( const EAXSOURCEEXCLUSIONSENDPROPERTIES& src, EAXSOURCEALLSENDPROPERTIES& dst) noexcept { dst.lExclusion = src.lExclusion; dst.flExclusionLFRatio = src.flExclusionLFRatio; } template void eax_defer_sends(const EaxCall& call, EaxSends& dst_sends) { const auto src_sends = call.get_values(EAX_MAX_FXSLOTS); std::for_each(src_sends.cbegin(), src_sends.cend(), TValidator{}); const auto count = src_sends.size(); const auto index_getter = TIndexGetter{}; for (auto i = decltype(count){}; i < count; ++i) { const auto& src_send = src_sends[i]; const auto dst_index = index_getter(src_send.guidReceivingFXSlotID); auto& dst_send = dst_sends[dst_index]; eax_copy_send_for_set(src_send, dst_send); } } template void eax4_defer_sends(const EaxCall& call, EaxSends& dst_sends) { eax_defer_sends(call, dst_sends); } template void eax5_defer_sends(const EaxCall& call, EaxSends& dst_sends) { eax_defer_sends(call, dst_sends); } template void eax_defer_active_fx_slot_id(const EaxCall& call, const al::span dst_ids) { const auto src_ids = call.get_values(TIdCount); std::for_each(src_ids.cbegin(), src_ids.cend(), TValidator{}); std::uninitialized_copy(src_ids.cbegin(), src_ids.cend(), dst_ids.begin()); } template void eax4_defer_active_fx_slot_id(const EaxCall& call, const al::span dst_ids) { eax_defer_active_fx_slot_id(call, dst_ids); } template void eax5_defer_active_fx_slot_id(const EaxCall& call, const al::span dst_ids) { eax_defer_active_fx_slot_id(call, dst_ids); } template static void eax_defer(const EaxCall& call, TProperty& property) { const auto& value = call.get_value(); TValidator{}(value); property = value; } // Defers source's sub-properties (obstruction, occlusion, exclusion). template void eax_defer_sub(const EaxCall& call, TProperty& property) { const auto& src_props = call.get_value(); TValidator{}(src_props); auto& dst_props = reinterpret_cast(property); dst_props = src_props; } void eax_set_efx_outer_gain_hf(); void eax_set_efx_doppler_factor(); void eax_set_efx_rolloff_factor(); void eax_set_efx_room_rolloff_factor(); void eax_set_efx_air_absorption_factor(); void eax_set_efx_dry_gain_hf_auto(); void eax_set_efx_wet_gain_auto(); void eax_set_efx_wet_gain_hf_auto(); static void eax1_set(const EaxCall& call, Eax1Props& props); static void eax2_set(const EaxCall& call, Eax2Props& props); void eax3_set(const EaxCall& call, Eax3Props& props); void eax4_set(const EaxCall& call, Eax4Props& props); static void eax5_defer_all_2d(const EaxCall& call, EAX50SOURCEPROPERTIES& props); static void eax5_defer_speaker_levels(const EaxCall& call, EaxSpeakerLevels& props); void eax5_set(const EaxCall& call, Eax5Props& props); void eax_set(const EaxCall& call); // `alSource3i(source, AL_AUXILIARY_SEND_FILTER, ...)` void eax_set_al_source_send(ALeffectslot *slot, size_t sendidx, const EaxAlLowPassParam &filter); void eax_commit_active_fx_slots(); #endif // ALSOFT_EAX }; void UpdateAllSourceProps(ALCcontext *context); struct SourceSubList { uint64_t FreeMask{~0_u64}; gsl::owner*> Sources{nullptr}; SourceSubList() noexcept = default; SourceSubList(const SourceSubList&) = delete; SourceSubList(SourceSubList&& rhs) noexcept : FreeMask{rhs.FreeMask}, Sources{rhs.Sources} { rhs.FreeMask = ~0_u64; rhs.Sources = nullptr; } ~SourceSubList(); SourceSubList& operator=(const SourceSubList&) = delete; SourceSubList& operator=(SourceSubList&& rhs) noexcept { std::swap(FreeMask, rhs.FreeMask); std::swap(Sources, rhs.Sources); return *this; } }; #endif openal-soft-1.24.2/al/state.cpp000066400000000000000000000562541474041540300162650ustar00rootroot00000000000000/** * OpenAL cross platform audio library * Copyright (C) 1999-2000 by authors. * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * Or go to http://www.gnu.org/copyleft/lgpl.html */ #include "config.h" #include "version.h" #include #include #include #include #include #include #include #include #include #include "AL/al.h" #include "AL/alc.h" #include "AL/alext.h" #include "al/debug.h" #include "al/listener.h" #include "alc/alu.h" #include "alc/context.h" #include "alc/device.h" #include "alc/inprogext.h" #include "alnumeric.h" #include "atomic.h" #include "core/context.h" #include "core/logging.h" #include "core/mixer/defs.h" #include "core/voice.h" #include "direct_defs.h" #include "intrusive_ptr.h" #include "opthelpers.h" #include "strutils.h" #if ALSOFT_EAX #include "eax/globals.h" #include "eax/x_ram.h" #endif // ALSOFT_EAX namespace { using ALvoidptr = ALvoid*; [[nodiscard]] constexpr auto GetVendorString() noexcept { return "OpenAL Community"; } [[nodiscard]] constexpr auto GetVersionString() noexcept { return "1.1 ALSOFT " ALSOFT_VERSION; } [[nodiscard]] constexpr auto GetRendererString() noexcept { return "OpenAL Soft"; } /* Error Messages */ [[nodiscard]] constexpr auto GetNoErrorString() noexcept { return "No Error"; } [[nodiscard]] constexpr auto GetInvalidNameString() noexcept { return "Invalid Name"; } [[nodiscard]] constexpr auto GetInvalidEnumString() noexcept { return "Invalid Enum"; } [[nodiscard]] constexpr auto GetInvalidValueString() noexcept { return "Invalid Value"; } [[nodiscard]] constexpr auto GetInvalidOperationString() noexcept { return "Invalid Operation"; } [[nodiscard]] constexpr auto GetOutOfMemoryString() noexcept { return "Out of Memory"; } [[nodiscard]] constexpr auto GetStackOverflowString() noexcept { return "Stack Overflow"; } [[nodiscard]] constexpr auto GetStackUnderflowString() noexcept { return "Stack Underflow"; } /* Resampler strings */ template struct ResamplerName { }; template<> struct ResamplerName { static constexpr const ALchar *Get() noexcept { return "Nearest"; } }; template<> struct ResamplerName { static constexpr const ALchar *Get() noexcept { return "Linear"; } }; template<> struct ResamplerName { static constexpr const ALchar *Get() noexcept { return "Cubic Spline"; } }; template<> struct ResamplerName { static constexpr const ALchar *Get() noexcept { return "4-point Gaussian"; } }; template<> struct ResamplerName { static constexpr const ALchar *Get() noexcept { return "11th order Sinc (fast)"; } }; template<> struct ResamplerName { static constexpr const ALchar *Get() noexcept { return "11th order Sinc"; } }; template<> struct ResamplerName { static constexpr const ALchar *Get() noexcept { return "23rd order Sinc (fast)"; } }; template<> struct ResamplerName { static constexpr const ALchar *Get() noexcept { return "23rd order Sinc"; } }; const ALchar *GetResamplerName(const Resampler rtype) { #define HANDLE_RESAMPLER(r) case r: return ResamplerName::Get() switch(rtype) { HANDLE_RESAMPLER(Resampler::Point); HANDLE_RESAMPLER(Resampler::Linear); HANDLE_RESAMPLER(Resampler::Spline); HANDLE_RESAMPLER(Resampler::Gaussian); HANDLE_RESAMPLER(Resampler::FastBSinc12); HANDLE_RESAMPLER(Resampler::BSinc12); HANDLE_RESAMPLER(Resampler::FastBSinc24); HANDLE_RESAMPLER(Resampler::BSinc24); } #undef HANDLE_RESAMPLER /* Should never get here. */ throw std::runtime_error{"Unexpected resampler index"}; } constexpr auto DistanceModelFromALenum(ALenum model) noexcept -> std::optional { switch(model) { case AL_NONE: return DistanceModel::Disable; case AL_INVERSE_DISTANCE: return DistanceModel::Inverse; case AL_INVERSE_DISTANCE_CLAMPED: return DistanceModel::InverseClamped; case AL_LINEAR_DISTANCE: return DistanceModel::Linear; case AL_LINEAR_DISTANCE_CLAMPED: return DistanceModel::LinearClamped; case AL_EXPONENT_DISTANCE: return DistanceModel::Exponent; case AL_EXPONENT_DISTANCE_CLAMPED: return DistanceModel::ExponentClamped; } return std::nullopt; } constexpr auto ALenumFromDistanceModel(DistanceModel model) -> ALenum { switch(model) { case DistanceModel::Disable: return AL_NONE; case DistanceModel::Inverse: return AL_INVERSE_DISTANCE; case DistanceModel::InverseClamped: return AL_INVERSE_DISTANCE_CLAMPED; case DistanceModel::Linear: return AL_LINEAR_DISTANCE; case DistanceModel::LinearClamped: return AL_LINEAR_DISTANCE_CLAMPED; case DistanceModel::Exponent: return AL_EXPONENT_DISTANCE; case DistanceModel::ExponentClamped: return AL_EXPONENT_DISTANCE_CLAMPED; } throw std::runtime_error{"Unexpected distance model "+std::to_string(static_cast(model))}; } enum PropertyValue : ALenum { DopplerFactorProp = AL_DOPPLER_FACTOR, DopplerVelocityProp = AL_DOPPLER_VELOCITY, DistanceModelProp = AL_DISTANCE_MODEL, SpeedOfSoundProp = AL_SPEED_OF_SOUND, DeferredUpdatesProp = AL_DEFERRED_UPDATES_SOFT, GainLimitProp = AL_GAIN_LIMIT_SOFT, NumResamplersProp = AL_NUM_RESAMPLERS_SOFT, DefaultResamplerProp = AL_DEFAULT_RESAMPLER_SOFT, DebugLoggedMessagesProp = AL_DEBUG_LOGGED_MESSAGES_EXT, DebugNextLoggedMessageLengthProp = AL_DEBUG_NEXT_LOGGED_MESSAGE_LENGTH_EXT, MaxDebugMessageLengthProp = AL_MAX_DEBUG_MESSAGE_LENGTH_EXT, MaxDebugLoggedMessagesProp = AL_MAX_DEBUG_LOGGED_MESSAGES_EXT, MaxDebugGroupDepthProp = AL_MAX_DEBUG_GROUP_STACK_DEPTH_EXT, MaxLabelLengthProp = AL_MAX_LABEL_LENGTH_EXT, ContextFlagsProp = AL_CONTEXT_FLAGS_EXT, #if ALSOFT_EAX EaxRamSizeProp = AL_EAX_RAM_SIZE, EaxRamFreeProp = AL_EAX_RAM_FREE, #endif }; template struct PropertyCastType { template constexpr auto operator()(U&& value) const noexcept { return static_cast(std::forward(value)); } }; /* Special-case ALboolean to be an actual bool instead of a char type. */ template<> struct PropertyCastType { template constexpr ALboolean operator()(U&& value) const noexcept { return static_cast(std::forward(value)) ? AL_TRUE : AL_FALSE; } }; template void GetValue(ALCcontext *context, ALenum pname, T *values) { auto cast_value = PropertyCastType{}; switch(static_cast(pname)) { case AL_DOPPLER_FACTOR: *values = cast_value(context->mDopplerFactor); return; case AL_DOPPLER_VELOCITY: if(context->mContextFlags.test(ContextFlags::DebugBit)) UNLIKELY context->debugMessage(DebugSource::API, DebugType::DeprecatedBehavior, 0, DebugSeverity::Medium, "AL_DOPPLER_VELOCITY is deprecated in AL 1.1, use AL_SPEED_OF_SOUND; " "AL_DOPPLER_VELOCITY -> AL_SPEED_OF_SOUND / 343.3f"); *values = cast_value(context->mDopplerVelocity); return; case AL_SPEED_OF_SOUND: *values = cast_value(context->mSpeedOfSound); return; case AL_GAIN_LIMIT_SOFT: *values = cast_value(GainMixMax / context->mGainBoost); return; case AL_DEFERRED_UPDATES_SOFT: *values = cast_value(context->mDeferUpdates ? AL_TRUE : AL_FALSE); return; case AL_DISTANCE_MODEL: *values = cast_value(ALenumFromDistanceModel(context->mDistanceModel)); return; case AL_NUM_RESAMPLERS_SOFT: *values = cast_value(al::to_underlying(Resampler::Max) + 1); return; case AL_DEFAULT_RESAMPLER_SOFT: *values = cast_value(al::to_underlying(ResamplerDefault)); return; case AL_DEBUG_LOGGED_MESSAGES_EXT: { std::lock_guard debuglock{context->mDebugCbLock}; *values = cast_value(context->mDebugLog.size()); return; } case AL_DEBUG_NEXT_LOGGED_MESSAGE_LENGTH_EXT: { std::lock_guard debuglock{context->mDebugCbLock}; *values = cast_value(context->mDebugLog.empty() ? 0_uz : (context->mDebugLog.front().mMessage.size()+1)); return; } case AL_MAX_DEBUG_MESSAGE_LENGTH_EXT: *values = cast_value(MaxDebugMessageLength); return; case AL_MAX_DEBUG_LOGGED_MESSAGES_EXT: *values = cast_value(MaxDebugLoggedMessages); return; case AL_MAX_DEBUG_GROUP_STACK_DEPTH_EXT: *values = cast_value(MaxDebugGroupDepth); return; case AL_MAX_LABEL_LENGTH_EXT: *values = cast_value(MaxObjectLabelLength); return; case AL_CONTEXT_FLAGS_EXT: *values = cast_value(context->mContextFlags.to_ulong()); return; #if ALSOFT_EAX #define EAX_ERROR "[alGetInteger] EAX not enabled" case AL_EAX_RAM_SIZE: if(eax_g_is_enabled) { *values = cast_value(eax_x_ram_max_size); return; } ERR(EAX_ERROR); break; case AL_EAX_RAM_FREE: if(eax_g_is_enabled) { auto device = context->mALDevice.get(); std::lock_guard device_lock{device->BufferLock}; *values = cast_value(device->eax_x_ram_free_size); return; } ERR(EAX_ERROR); break; #undef EAX_ERROR #endif // ALSOFT_EAX } context->setError(AL_INVALID_ENUM, "Invalid context property {:#04x}", as_unsigned(pname)); } inline void UpdateProps(ALCcontext *context) { if(!context->mDeferUpdates) UpdateContextProps(context); else context->mPropsDirty = true; } } // namespace /* WARNING: Non-standard export! Not part of any extension, or exposed in the * alcFunctions list. */ AL_API auto AL_APIENTRY alsoft_get_version() noexcept -> const ALchar* { static const auto spoof = al::getenv("ALSOFT_SPOOF_VERSION"); if(spoof) return spoof->c_str(); return ALSOFT_VERSION; } AL_API DECL_FUNC1(void, alEnable, ALenum,capability) FORCE_ALIGN void AL_APIENTRY alEnableDirect(ALCcontext *context, ALenum capability) noexcept { switch(capability) { case AL_SOURCE_DISTANCE_MODEL: { std::lock_guard proplock{context->mPropLock}; context->mSourceDistanceModel = true; UpdateProps(context); } return; case AL_DEBUG_OUTPUT_EXT: context->mDebugEnabled.store(true); return; case AL_STOP_SOURCES_ON_DISCONNECT_SOFT: context->setError(AL_INVALID_OPERATION, "Re-enabling AL_STOP_SOURCES_ON_DISCONNECT_SOFT not yet supported"); return; } context->setError(AL_INVALID_VALUE, "Invalid enable property {:#04x}", as_unsigned(capability)); } AL_API DECL_FUNC1(void, alDisable, ALenum,capability) FORCE_ALIGN void AL_APIENTRY alDisableDirect(ALCcontext *context, ALenum capability) noexcept { switch(capability) { case AL_SOURCE_DISTANCE_MODEL: { std::lock_guard proplock{context->mPropLock}; context->mSourceDistanceModel = false; UpdateProps(context); } return; case AL_DEBUG_OUTPUT_EXT: context->mDebugEnabled.store(false); return; case AL_STOP_SOURCES_ON_DISCONNECT_SOFT: context->mStopVoicesOnDisconnect.store(false); return; } context->setError(AL_INVALID_VALUE, "Invalid disable property {:#04x}", as_unsigned(capability)); } AL_API DECL_FUNC1(ALboolean, alIsEnabled, ALenum,capability) FORCE_ALIGN ALboolean AL_APIENTRY alIsEnabledDirect(ALCcontext *context, ALenum capability) noexcept { std::lock_guard proplock{context->mPropLock}; switch(capability) { case AL_SOURCE_DISTANCE_MODEL: return context->mSourceDistanceModel ? AL_TRUE : AL_FALSE; case AL_DEBUG_OUTPUT_EXT: return context->mDebugEnabled ? AL_TRUE : AL_FALSE; case AL_STOP_SOURCES_ON_DISCONNECT_SOFT: return context->mStopVoicesOnDisconnect.load() ? AL_TRUE : AL_FALSE; } context->setError(AL_INVALID_VALUE, "Invalid is enabled property {:#04x}", as_unsigned(capability)); return AL_FALSE; } #define DECL_GETFUNC(R, Name, Ext) \ auto AL_APIENTRY Name##Ext(ALenum pname) noexcept -> R \ { \ auto value = R{}; \ auto context = GetContextRef(); \ if(!context) UNLIKELY return value; \ Name##vDirect##Ext(GetContextRef().get(), pname, &value); \ return value; \ } \ FORCE_ALIGN auto AL_APIENTRY Name##Direct##Ext(ALCcontext *context, ALenum pname) noexcept -> R \ { \ auto value = R{}; \ Name##vDirect##Ext(context, pname, &value); \ return value; \ } AL_API DECL_GETFUNC(ALboolean, alGetBoolean,) AL_API DECL_GETFUNC(ALdouble, alGetDouble,) AL_API DECL_GETFUNC(ALfloat, alGetFloat,) AL_API DECL_GETFUNC(ALint, alGetInteger,) DECL_GETFUNC(ALvoidptr, alGetPointer,EXT) AL_API DECL_GETFUNC(ALint64SOFT, alGetInteger64,SOFT) AL_API DECL_GETFUNC(ALvoidptr, alGetPointer,SOFT) #undef DECL_GETFUNC AL_API DECL_FUNC2(void, alGetBooleanv, ALenum,pname, ALboolean*,values) FORCE_ALIGN void AL_APIENTRY alGetBooleanvDirect(ALCcontext *context, ALenum pname, ALboolean *values) noexcept { if(!values) UNLIKELY return context->setError(AL_INVALID_VALUE, "NULL pointer"); GetValue(context, pname, values); } AL_API DECL_FUNC2(void, alGetDoublev, ALenum,pname, ALdouble*,values) FORCE_ALIGN void AL_APIENTRY alGetDoublevDirect(ALCcontext *context, ALenum pname, ALdouble *values) noexcept { if(!values) UNLIKELY return context->setError(AL_INVALID_VALUE, "NULL pointer"); GetValue(context, pname, values); } AL_API DECL_FUNC2(void, alGetFloatv, ALenum,pname, ALfloat*,values) FORCE_ALIGN void AL_APIENTRY alGetFloatvDirect(ALCcontext *context, ALenum pname, ALfloat *values) noexcept { if(!values) UNLIKELY return context->setError(AL_INVALID_VALUE, "NULL pointer"); GetValue(context, pname, values); } AL_API DECL_FUNC2(void, alGetIntegerv, ALenum,pname, ALint*,values) FORCE_ALIGN void AL_APIENTRY alGetIntegervDirect(ALCcontext *context, ALenum pname, ALint *values) noexcept { if(!values) UNLIKELY return context->setError(AL_INVALID_VALUE, "NULL pointer"); GetValue(context, pname, values); } AL_API DECL_FUNCEXT2(void, alGetInteger64v,SOFT, ALenum,pname, ALint64SOFT*,values) FORCE_ALIGN void AL_APIENTRY alGetInteger64vDirectSOFT(ALCcontext *context, ALenum pname, ALint64SOFT *values) noexcept { if(!values) UNLIKELY return context->setError(AL_INVALID_VALUE, "NULL pointer"); GetValue(context, pname, values); } AL_API DECL_FUNCEXT2(void, alGetPointerv,SOFT, ALenum,pname, ALvoid**,values) FORCE_ALIGN void AL_APIENTRY alGetPointervDirectSOFT(ALCcontext *context, ALenum pname, ALvoid **values) noexcept { return alGetPointervDirectEXT(context, pname, values); } FORCE_ALIGN DECL_FUNCEXT2(void, alGetPointerv,EXT, ALenum,pname, ALvoid**,values) FORCE_ALIGN void AL_APIENTRY alGetPointervDirectEXT(ALCcontext *context, ALenum pname, ALvoid **values) noexcept { if(!values) UNLIKELY return context->setError(AL_INVALID_VALUE, "NULL pointer"); switch(pname) { case AL_EVENT_CALLBACK_FUNCTION_SOFT: *values = reinterpret_cast(context->mEventCb); return; case AL_EVENT_CALLBACK_USER_PARAM_SOFT: *values = context->mEventParam; return; case AL_DEBUG_CALLBACK_FUNCTION_EXT: *values = reinterpret_cast(context->mDebugCb); return; case AL_DEBUG_CALLBACK_USER_PARAM_EXT: *values = context->mDebugParam; return; } context->setError(AL_INVALID_ENUM, "Invalid context pointer property {:#04x}", as_unsigned(pname)); } AL_API DECL_FUNC1(const ALchar*, alGetString, ALenum,pname) FORCE_ALIGN const ALchar* AL_APIENTRY alGetStringDirect(ALCcontext *context, ALenum pname) noexcept { switch(pname) { case AL_VENDOR: if(auto device = context->mALDevice.get(); !device->mVendorOverride.empty()) return device->mVendorOverride.c_str(); return GetVendorString(); case AL_VERSION: if(auto device = context->mALDevice.get(); !device->mVersionOverride.empty()) return device->mVersionOverride.c_str(); return GetVersionString(); case AL_RENDERER: if(auto device = context->mALDevice.get(); !device->mRendererOverride.empty()) return device->mRendererOverride.c_str(); return GetRendererString(); case AL_EXTENSIONS: return context->mExtensionsString.c_str(); case AL_NO_ERROR: return GetNoErrorString(); case AL_INVALID_NAME: return GetInvalidNameString(); case AL_INVALID_ENUM: return GetInvalidEnumString(); case AL_INVALID_VALUE: return GetInvalidValueString(); case AL_INVALID_OPERATION: return GetInvalidOperationString(); case AL_OUT_OF_MEMORY: return GetOutOfMemoryString(); case AL_STACK_OVERFLOW_EXT: return GetStackOverflowString(); case AL_STACK_UNDERFLOW_EXT: return GetStackUnderflowString(); } context->setError(AL_INVALID_VALUE, "Invalid string property {:#04x}", as_unsigned(pname)); return nullptr; } AL_API DECL_FUNC1(void, alDopplerFactor, ALfloat,value) FORCE_ALIGN void AL_APIENTRY alDopplerFactorDirect(ALCcontext *context, ALfloat value) noexcept { if(!(value >= 0.0f && std::isfinite(value))) context->setError(AL_INVALID_VALUE, "Doppler factor {:f} out of range", value); else { std::lock_guard proplock{context->mPropLock}; context->mDopplerFactor = value; UpdateProps(context); } } AL_API DECL_FUNC1(void, alSpeedOfSound, ALfloat,value) FORCE_ALIGN void AL_APIENTRY alSpeedOfSoundDirect(ALCcontext *context, ALfloat value) noexcept { if(!(value > 0.0f && std::isfinite(value))) context->setError(AL_INVALID_VALUE, "Speed of sound {:f} out of range", value); else { std::lock_guard proplock{context->mPropLock}; context->mSpeedOfSound = value; UpdateProps(context); } } AL_API DECL_FUNC1(void, alDistanceModel, ALenum,value) FORCE_ALIGN void AL_APIENTRY alDistanceModelDirect(ALCcontext *context, ALenum value) noexcept { if(auto model = DistanceModelFromALenum(value)) { std::lock_guard proplock{context->mPropLock}; context->mDistanceModel = *model; if(!context->mSourceDistanceModel) UpdateProps(context); } else context->setError(AL_INVALID_VALUE, "Distance model {:#04x} out of range", as_unsigned(value)); } AL_API DECL_FUNCEXT(void, alDeferUpdates,SOFT) FORCE_ALIGN void AL_APIENTRY alDeferUpdatesDirectSOFT(ALCcontext *context) noexcept { std::lock_guard proplock{context->mPropLock}; context->deferUpdates(); } AL_API DECL_FUNCEXT(void, alProcessUpdates,SOFT) FORCE_ALIGN void AL_APIENTRY alProcessUpdatesDirectSOFT(ALCcontext *context) noexcept { std::lock_guard proplock{context->mPropLock}; context->processUpdates(); } AL_API DECL_FUNCEXT2(const ALchar*, alGetStringi,SOFT, ALenum,pname, ALsizei,index) FORCE_ALIGN const ALchar* AL_APIENTRY alGetStringiDirectSOFT(ALCcontext *context, ALenum pname, ALsizei index) noexcept { switch(pname) { case AL_RESAMPLER_NAME_SOFT: if(index >= 0 && index <= static_cast(Resampler::Max)) return GetResamplerName(static_cast(index)); context->setError(AL_INVALID_VALUE, "Resampler name index {} out of range", index); return nullptr; } context->setError(AL_INVALID_VALUE, "Invalid string indexed property {:#04x}", as_unsigned(pname)); return nullptr; } AL_API void AL_APIENTRY alDopplerVelocity(ALfloat value) noexcept { ContextRef context{GetContextRef()}; if(!context) UNLIKELY return; if(context->mContextFlags.test(ContextFlags::DebugBit)) UNLIKELY context->debugMessage(DebugSource::API, DebugType::DeprecatedBehavior, 1, DebugSeverity::Medium, "alDopplerVelocity is deprecated in AL 1.1, use alSpeedOfSound; " "alDopplerVelocity(x) -> alSpeedOfSound(343.3f * x)"); if(!(value >= 0.0f && std::isfinite(value))) context->setError(AL_INVALID_VALUE, "Doppler velocity {:f} out of range", value); else { std::lock_guard proplock{context->mPropLock}; context->mDopplerVelocity = value; UpdateProps(context.get()); } } void UpdateContextProps(ALCcontext *context) { /* Get an unused property container, or allocate a new one as needed. */ ContextProps *props{context->mFreeContextProps.load(std::memory_order_acquire)}; if(!props) { context->allocContextProps(); props = context->mFreeContextProps.load(std::memory_order_acquire); } ContextProps *next; do { next = props->next.load(std::memory_order_relaxed); } while(context->mFreeContextProps.compare_exchange_weak(props, next, std::memory_order_acq_rel, std::memory_order_acquire) == false); /* Copy in current property values. */ const auto &listener = context->mListener; props->Position = listener.Position; props->Velocity = listener.Velocity; props->OrientAt = listener.OrientAt; props->OrientUp = listener.OrientUp; props->Gain = listener.Gain; props->MetersPerUnit = listener.mMetersPerUnit; props->AirAbsorptionGainHF = context->mAirAbsorptionGainHF; props->DopplerFactor = context->mDopplerFactor; props->DopplerVelocity = context->mDopplerVelocity; props->SpeedOfSound = context->mSpeedOfSound; #if ALSOFT_EAX props->DistanceFactor = context->eaxGetDistanceFactor(); #endif props->SourceDistanceModel = context->mSourceDistanceModel; props->mDistanceModel = context->mDistanceModel; /* Set the new container for updating internal parameters. */ props = context->mParams.ContextUpdate.exchange(props, std::memory_order_acq_rel); if(props) { /* If there was an unused update container, put it back in the * freelist. */ AtomicReplaceHead(context->mFreeContextProps, props); } } openal-soft-1.24.2/alc/000077500000000000000000000000001474041540300145705ustar00rootroot00000000000000openal-soft-1.24.2/alc/alc.cpp000066400000000000000000003645351474041540300160530ustar00rootroot00000000000000/** * OpenAL cross platform audio library * Copyright (C) 1999-2007 by authors. * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * Or go to http://www.gnu.org/copyleft/lgpl.html */ #include "config.h" #include "config_backends.h" #include "config_simd.h" #include "version.h" #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "AL/al.h" #include "AL/alc.h" #include "AL/alext.h" #include "AL/efx.h" #include "al/auxeffectslot.h" #include "al/buffer.h" #include "al/debug.h" #include "al/effect.h" #include "al/filter.h" #include "al/source.h" #include "alc/events.h" #include "albit.h" #include "alconfig.h" #include "almalloc.h" #include "alnumbers.h" #include "alnumeric.h" #include "alspan.h" #include "alstring.h" #include "alu.h" #include "atomic.h" #include "context.h" #include "core/ambidefs.h" #include "core/bformatdec.h" #include "core/bs2b.h" #include "core/context.h" #include "core/cpu_caps.h" #include "core/devformat.h" #include "core/device.h" #include "core/effects/base.h" #include "core/effectslot.h" #include "core/filters/nfc.h" #include "core/helpers.h" #include "core/mastering.h" #include "core/fpu_ctrl.h" #include "core/logging.h" #include "core/uhjfilter.h" #include "core/voice.h" #include "core/voice_change.h" #include "device.h" #include "effects/base.h" #include "export_list.h" #include "flexarray.h" #include "fmt/core.h" #include "inprogext.h" #include "intrusive_ptr.h" #include "opthelpers.h" #include "strutils.h" #include "backends/base.h" #include "backends/null.h" #include "backends/loopback.h" #if HAVE_PIPEWIRE #include "backends/pipewire.h" #endif #if HAVE_JACK #include "backends/jack.h" #endif #if HAVE_PULSEAUDIO #include "backends/pulseaudio.h" #endif #if HAVE_ALSA #include "backends/alsa.h" #endif #if HAVE_WASAPI #include "backends/wasapi.h" #endif #if HAVE_COREAUDIO #include "backends/coreaudio.h" #endif #if HAVE_OPENSL #include "backends/opensl.h" #endif #if HAVE_OBOE #include "backends/oboe.h" #endif #if HAVE_SOLARIS #include "backends/solaris.h" #endif #if HAVE_SNDIO #include "backends/sndio.hpp" #endif #if HAVE_OSS #include "backends/oss.h" #endif #if HAVE_DSOUND #include "backends/dsound.h" #endif #if HAVE_WINMM #include "backends/winmm.h" #endif #if HAVE_PORTAUDIO #include "backends/portaudio.hpp" #endif #if HAVE_SDL3 #include "backends/sdl3.h" #endif #if HAVE_SDL2 #include "backends/sdl2.h" #endif #if HAVE_OTHERIO #include "backends/otherio.h" #endif #if HAVE_WAVE #include "backends/wave.h" #endif #if ALSOFT_EAX #include "al/eax/api.h" #include "al/eax/globals.h" #endif /************************************************ * Library initialization ************************************************/ #if defined(_WIN32) && !defined(AL_LIBTYPE_STATIC) BOOL APIENTRY DllMain(HINSTANCE module, DWORD reason, LPVOID /*reserved*/) { switch(reason) { case DLL_PROCESS_ATTACH: /* Pin the DLL so we won't get unloaded until the process terminates */ GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_PIN | GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, reinterpret_cast(module), &module); break; } return TRUE; } #endif namespace { using namespace std::string_view_literals; using std::chrono::seconds; using std::chrono::nanoseconds; using voidp = void*; using float2 = std::array; auto gProcessRunning = true; struct ProcessWatcher { ProcessWatcher() = default; ProcessWatcher(const ProcessWatcher&) = delete; ProcessWatcher& operator=(const ProcessWatcher&) = delete; ~ProcessWatcher() { gProcessRunning = false; } }; ProcessWatcher gProcessWatcher; /************************************************ * Backends ************************************************/ struct BackendInfo { const char *name; BackendFactory& (*getFactory)(); }; std::array BackendList{ #if HAVE_PIPEWIRE BackendInfo{"pipewire", PipeWireBackendFactory::getFactory}, #endif #if HAVE_PULSEAUDIO BackendInfo{"pulse", PulseBackendFactory::getFactory}, #endif #if HAVE_WASAPI BackendInfo{"wasapi", WasapiBackendFactory::getFactory}, #endif #if HAVE_COREAUDIO BackendInfo{"core", CoreAudioBackendFactory::getFactory}, #endif #if HAVE_OBOE BackendInfo{"oboe", OboeBackendFactory::getFactory}, #endif #if HAVE_OPENSL BackendInfo{"opensl", OSLBackendFactory::getFactory}, #endif #if HAVE_ALSA BackendInfo{"alsa", AlsaBackendFactory::getFactory}, #endif #if HAVE_SOLARIS BackendInfo{"solaris", SolarisBackendFactory::getFactory}, #endif #if HAVE_SNDIO BackendInfo{"sndio", SndIOBackendFactory::getFactory}, #endif #if HAVE_OSS BackendInfo{"oss", OSSBackendFactory::getFactory}, #endif #if HAVE_DSOUND BackendInfo{"dsound", DSoundBackendFactory::getFactory}, #endif #if HAVE_WINMM BackendInfo{"winmm", WinMMBackendFactory::getFactory}, #endif #if HAVE_PORTAUDIO BackendInfo{"port", PortBackendFactory::getFactory}, #endif #if HAVE_SDL3 BackendInfo{"sdl3", SDL3BackendFactory::getFactory}, #endif #if HAVE_SDL2 BackendInfo{"sdl2", SDL2BackendFactory::getFactory}, #endif #if HAVE_JACK BackendInfo{"jack", JackBackendFactory::getFactory}, #endif #if HAVE_OTHERIO BackendInfo{"otherio", OtherIOBackendFactory::getFactory}, #endif BackendInfo{"null", NullBackendFactory::getFactory}, #if HAVE_WAVE BackendInfo{"wave", WaveBackendFactory::getFactory}, #endif }; BackendFactory *PlaybackFactory{}; BackendFactory *CaptureFactory{}; [[nodiscard]] constexpr auto GetNoErrorString() noexcept { return "No Error"; } [[nodiscard]] constexpr auto GetInvalidDeviceString() noexcept { return "Invalid Device"; } [[nodiscard]] constexpr auto GetInvalidContextString() noexcept { return "Invalid Context"; } [[nodiscard]] constexpr auto GetInvalidEnumString() noexcept { return "Invalid Enum"; } [[nodiscard]] constexpr auto GetInvalidValueString() noexcept { return "Invalid Value"; } [[nodiscard]] constexpr auto GetOutOfMemoryString() noexcept { return "Out of Memory"; } [[nodiscard]] constexpr auto GetDefaultName() noexcept { return "OpenAL Soft\0"; } #ifdef _WIN32 [[nodiscard]] constexpr auto GetDevicePrefix() noexcept { return "OpenAL Soft on "sv; } #else [[nodiscard]] constexpr auto GetDevicePrefix() noexcept { return std::string_view{}; } #endif /************************************************ * Global variables ************************************************/ /* Enumerated device names */ std::vector alcAllDevicesArray; std::vector alcCaptureDeviceArray; std::string alcAllDevicesList; std::string alcCaptureDeviceList; /* Default is always the first in the list */ std::string alcDefaultAllDevicesSpecifier; std::string alcCaptureDefaultDeviceSpecifier; std::atomic LastNullDeviceError{ALC_NO_ERROR}; /* Flag to trap ALC device errors */ bool TrapALCError{false}; /* One-time configuration init control */ std::once_flag alc_config_once{}; /* Flag to specify if alcSuspendContext/alcProcessContext should defer/process * updates. */ bool SuspendDefers{true}; /* Initial seed for dithering. */ constexpr uint DitherRNGSeed{22222u}; /************************************************ * ALC information ************************************************/ [[nodiscard]] constexpr auto GetNoDeviceExtList() noexcept -> const char* { return "ALC_ENUMERATE_ALL_EXT " "ALC_ENUMERATION_EXT " "ALC_EXT_CAPTURE " "ALC_EXT_direct_context " "ALC_EXT_EFX " "ALC_EXT_thread_local_context " "ALC_SOFT_loopback " "ALC_SOFT_loopback_bformat " "ALC_SOFT_reopen_device " "ALC_SOFT_system_events"; } [[nodiscard]] constexpr auto GetExtensionList() noexcept -> const char* { return "ALC_ENUMERATE_ALL_EXT " "ALC_ENUMERATION_EXT " "ALC_EXT_CAPTURE " "ALC_EXT_debug " "ALC_EXT_DEDICATED " "ALC_EXT_direct_context " "ALC_EXT_disconnect " "ALC_EXT_EFX " "ALC_EXT_thread_local_context " "ALC_SOFT_device_clock " "ALC_SOFT_HRTF " "ALC_SOFT_loopback " "ALC_SOFT_loopback_bformat " "ALC_SOFT_output_limiter " "ALC_SOFT_output_mode " "ALC_SOFT_pause_device " "ALC_SOFT_reopen_device " "ALC_SOFT_system_events"; } constexpr int alcMajorVersion{1}; constexpr int alcMinorVersion{1}; constexpr int alcEFXMajorVersion{1}; constexpr int alcEFXMinorVersion{0}; using DeviceRef = al::intrusive_ptr; /************************************************ * Device lists ************************************************/ std::vector DeviceList; std::vector ContextList; std::recursive_mutex ListLock; void alc_initconfig() { if(auto loglevel = al::getenv("ALSOFT_LOGLEVEL")) { long lvl = strtol(loglevel->c_str(), nullptr, 0); if(lvl >= static_cast(LogLevel::Trace)) gLogLevel = LogLevel::Trace; else if(lvl <= static_cast(LogLevel::Disable)) gLogLevel = LogLevel::Disable; else gLogLevel = static_cast(lvl); } #ifdef _WIN32 if(const auto logfile = al::getenv(L"ALSOFT_LOGFILE")) { FILE *logf{_wfopen(logfile->c_str(), L"wt")}; if(logf) gLogFile = logf; else { auto u8name = wstr_to_utf8(*logfile); ERR("Failed to open log file '{}'", u8name); } } #else if(const auto logfile = al::getenv("ALSOFT_LOGFILE")) { FILE *logf{fopen(logfile->c_str(), "wt")}; if(logf) gLogFile = logf; else ERR("Failed to open log file '{}'", *logfile); } #endif TRACE("Initializing library v{}-{} {}", ALSOFT_VERSION, ALSOFT_GIT_COMMIT_HASH, ALSOFT_GIT_BRANCH); { std::string names; if(std::size(BackendList) < 1) names = "(none)"; else { const al::span infos{BackendList}; names = infos[0].name; for(const auto &backend : infos.subspan<1>()) { names += ", "; names += backend.name; } } TRACE("Supported backends: {}", names); } ReadALConfig(); if(auto suspendmode = al::getenv("__ALSOFT_SUSPEND_CONTEXT")) { if(al::case_compare(*suspendmode, "ignore"sv) == 0) { SuspendDefers = false; TRACE("Selected context suspend behavior, \"ignore\""); } else ERR("Unhandled context suspend behavior setting: \"{}\"", *suspendmode); } int capfilter{0}; #if HAVE_SSE4_1 capfilter |= CPU_CAP_SSE | CPU_CAP_SSE2 | CPU_CAP_SSE3 | CPU_CAP_SSE4_1; #elif HAVE_SSE3 capfilter |= CPU_CAP_SSE | CPU_CAP_SSE2 | CPU_CAP_SSE3; #elif HAVE_SSE2 capfilter |= CPU_CAP_SSE | CPU_CAP_SSE2; #elif HAVE_SSE capfilter |= CPU_CAP_SSE; #endif #if HAVE_NEON capfilter |= CPU_CAP_NEON; #endif if(auto cpuopt = ConfigValueStr({}, {}, "disable-cpu-exts"sv)) { std::string_view cpulist{*cpuopt}; if(al::case_compare(cpulist, "all"sv) == 0) capfilter = 0; else while(!cpulist.empty()) { auto nextpos = std::min(cpulist.find(','), cpulist.size()); auto entry = cpulist.substr(0, nextpos); while(nextpos < cpulist.size() && cpulist[nextpos] == ',') ++nextpos; cpulist.remove_prefix(nextpos); while(!entry.empty() && std::isspace(entry.front())) entry.remove_prefix(1); while(!entry.empty() && std::isspace(entry.back())) entry.remove_suffix(1); if(entry.empty()) continue; if(al::case_compare(entry, "sse"sv) == 0) capfilter &= ~CPU_CAP_SSE; else if(al::case_compare(entry, "sse2"sv) == 0) capfilter &= ~CPU_CAP_SSE2; else if(al::case_compare(entry, "sse3"sv) == 0) capfilter &= ~CPU_CAP_SSE3; else if(al::case_compare(entry, "sse4.1"sv) == 0) capfilter &= ~CPU_CAP_SSE4_1; else if(al::case_compare(entry, "neon"sv) == 0) capfilter &= ~CPU_CAP_NEON; else WARN("Invalid CPU extension \"{}\"", entry); } } if(auto cpuopt = GetCPUInfo()) { if(!cpuopt->mVendor.empty() || !cpuopt->mName.empty()) { TRACE("Vendor ID: \"{}\"", cpuopt->mVendor); TRACE("Name: \"{}\"", cpuopt->mName); } const int caps{cpuopt->mCaps}; TRACE("Extensions:{}{}{}{}{}{}", ((capfilter&CPU_CAP_SSE) ?(caps&CPU_CAP_SSE) ?" +SSE"sv : " -SSE"sv : ""sv), ((capfilter&CPU_CAP_SSE2) ?(caps&CPU_CAP_SSE2) ?" +SSE2"sv : " -SSE2"sv : ""sv), ((capfilter&CPU_CAP_SSE3) ?(caps&CPU_CAP_SSE3) ?" +SSE3"sv : " -SSE3"sv : ""sv), ((capfilter&CPU_CAP_SSE4_1)?(caps&CPU_CAP_SSE4_1)?" +SSE4.1"sv : " -SSE4.1"sv : ""sv), ((capfilter&CPU_CAP_NEON) ?(caps&CPU_CAP_NEON) ?" +NEON"sv : " -NEON"sv : ""sv), (!capfilter) ? " -none-"sv : ""sv); CPUCapFlags = caps & capfilter; } if(auto priopt = ConfigValueInt({}, {}, "rt-prio"sv)) RTPrioLevel = *priopt; if(auto limopt = ConfigValueBool({}, {}, "rt-time-limit"sv)) AllowRTTimeLimit = *limopt; { CompatFlagBitset compatflags{}; auto checkflag = [](const char *envname, const std::string_view optname) -> bool { if(auto optval = al::getenv(envname)) { return al::case_compare(*optval, "true"sv) == 0 || strtol(optval->c_str(), nullptr, 0) == 1; } return GetConfigValueBool({}, "game_compat", optname, false); }; sBufferSubDataCompat = checkflag("__ALSOFT_ENABLE_SUB_DATA_EXT", "enable-sub-data-ext"sv); compatflags.set(CompatFlags::ReverseX, checkflag("__ALSOFT_REVERSE_X", "reverse-x"sv)); compatflags.set(CompatFlags::ReverseY, checkflag("__ALSOFT_REVERSE_Y", "reverse-y"sv)); compatflags.set(CompatFlags::ReverseZ, checkflag("__ALSOFT_REVERSE_Z", "reverse-z"sv)); aluInit(compatflags, ConfigValueFloat({}, "game_compat"sv, "nfc-scale"sv).value_or(1.0f)); } Voice::InitMixer(ConfigValueStr({}, {}, "resampler"sv)); if(auto uhjfiltopt = ConfigValueStr({}, "uhj"sv, "decode-filter"sv)) { if(al::case_compare(*uhjfiltopt, "fir256"sv) == 0) UhjDecodeQuality = UhjQualityType::FIR256; else if(al::case_compare(*uhjfiltopt, "fir512"sv) == 0) UhjDecodeQuality = UhjQualityType::FIR512; else if(al::case_compare(*uhjfiltopt, "iir"sv) == 0) UhjDecodeQuality = UhjQualityType::IIR; else WARN("Unsupported uhj/decode-filter: {}", *uhjfiltopt); } if(auto uhjfiltopt = ConfigValueStr({}, "uhj"sv, "encode-filter"sv)) { if(al::case_compare(*uhjfiltopt, "fir256"sv) == 0) UhjEncodeQuality = UhjQualityType::FIR256; else if(al::case_compare(*uhjfiltopt, "fir512"sv) == 0) UhjEncodeQuality = UhjQualityType::FIR512; else if(al::case_compare(*uhjfiltopt, "iir"sv) == 0) UhjEncodeQuality = UhjQualityType::IIR; else WARN("Unsupported uhj/encode-filter: {}", *uhjfiltopt); } if(auto traperr = al::getenv("ALSOFT_TRAP_ERROR"); traperr && (al::case_compare(*traperr, "true"sv) == 0 || std::strtol(traperr->c_str(), nullptr, 0) == 1)) { TrapALError = true; TrapALCError = true; } else { traperr = al::getenv("ALSOFT_TRAP_AL_ERROR"); if(traperr) TrapALError = al::case_compare(*traperr, "true"sv) == 0 || strtol(traperr->c_str(), nullptr, 0) == 1; else TrapALError = GetConfigValueBool({}, {}, "trap-al-error"sv, false); traperr = al::getenv("ALSOFT_TRAP_ALC_ERROR"); if(traperr) TrapALCError = al::case_compare(*traperr, "true"sv) == 0 || strtol(traperr->c_str(), nullptr, 0) == 1; else TrapALCError = GetConfigValueBool({}, {}, "trap-alc-error"sv, false); } if(auto boostopt = ConfigValueFloat({}, "reverb"sv, "boost"sv)) { const float valf{std::isfinite(*boostopt) ? std::clamp(*boostopt, -24.0f, 24.0f) : 0.0f}; ReverbBoost *= std::pow(10.0f, valf / 20.0f); } auto BackendListEnd = BackendList.end(); auto devopt = al::getenv("ALSOFT_DRIVERS"); if(!devopt) devopt = ConfigValueStr({}, {}, "drivers"sv); if(devopt) { auto backendlist_cur = BackendList.begin(); bool endlist{true}; std::string_view drvlist{*devopt}; while(!drvlist.empty()) { auto nextpos = std::min(drvlist.find(','), drvlist.size()); auto entry = drvlist.substr(0, nextpos); endlist = true; if(nextpos < drvlist.size()) { endlist = false; while(nextpos < drvlist.size() && drvlist[nextpos] == ',') ++nextpos; } drvlist.remove_prefix(nextpos); while(!entry.empty() && std::isspace(entry.front())) entry.remove_prefix(1); const bool delitem{!entry.empty() && entry.front() == '-'}; if(delitem) entry.remove_prefix(1); while(!entry.empty() && std::isspace(entry.back())) entry.remove_suffix(1); if(entry.empty()) continue; #ifdef HAVE_WASAPI /* HACK: For backwards compatibility, convert backend references of * mmdevapi to wasapi. This should eventually be removed. */ if(entry == "mmdevapi"sv) entry = "wasapi"sv; #endif auto find_backend = [entry](const BackendInfo &backend) -> bool { return entry == backend.name; }; auto this_backend = std::find_if(BackendList.begin(), BackendListEnd, find_backend); if(this_backend == BackendListEnd) continue; if(delitem) BackendListEnd = std::move(this_backend+1, BackendListEnd, this_backend); else backendlist_cur = std::rotate(backendlist_cur, this_backend, this_backend+1); } if(endlist) BackendListEnd = backendlist_cur; } else { /* Exclude the null and wave writer backends from being considered by * default. This ensures there will be no available devices if none of * the normal backends are usable, rather than pretending there is a * device but outputs nowhere. */ while(BackendListEnd != BackendList.begin()) { --BackendListEnd; if(BackendListEnd->name == "null"sv) break; } } auto init_backend = [](BackendInfo &backend) -> void { if(PlaybackFactory && CaptureFactory) return; BackendFactory &factory = backend.getFactory(); if(!factory.init()) { WARN("Failed to initialize backend \"{}\"", backend.name); return; } TRACE("Initialized backend \"{}\"", backend.name); if(!PlaybackFactory && factory.querySupport(BackendType::Playback)) { PlaybackFactory = &factory; TRACE("Added \"{}\" for playback", backend.name); } if(!CaptureFactory && factory.querySupport(BackendType::Capture)) { CaptureFactory = &factory; TRACE("Added \"{}\" for capture", backend.name); } }; std::for_each(BackendList.begin(), BackendListEnd, init_backend); LoopbackBackendFactory::getFactory().init(); if(!PlaybackFactory) WARN("No playback backend available!"); if(!CaptureFactory) WARN("No capture backend available!"); if(auto exclopt = ConfigValueStr({}, {}, "excludefx"sv)) { std::string_view exclude{*exclopt}; while(!exclude.empty()) { const auto nextpos = exclude.find(','); const auto entry = exclude.substr(0, nextpos); exclude.remove_prefix((nextpos < exclude.size()) ? nextpos+1 : exclude.size()); std::for_each(gEffectList.cbegin(), gEffectList.cend(), [entry](const EffectList &effectitem) noexcept { if(entry == std::data(effectitem.name)) DisabledEffects.set(effectitem.type); }); } } InitEffect(&ALCcontext::sDefaultEffect); auto defrevopt = al::getenv("ALSOFT_DEFAULT_REVERB"); if(!defrevopt) defrevopt = ConfigValueStr({}, {}, "default-reverb"sv); if(defrevopt) LoadReverbPreset(*defrevopt, &ALCcontext::sDefaultEffect); #if ALSOFT_EAX { if(const auto eax_enable_opt = ConfigValueBool({}, "eax", "enable")) { eax_g_is_enabled = *eax_enable_opt; if(!eax_g_is_enabled) TRACE("EAX disabled by a configuration."); } else eax_g_is_enabled = true; if((DisabledEffects.test(EAXREVERB_EFFECT) || DisabledEffects.test(CHORUS_EFFECT)) && eax_g_is_enabled) { eax_g_is_enabled = false; TRACE("EAX disabled because {} disabled.", (DisabledEffects.test(EAXREVERB_EFFECT) && DisabledEffects.test(CHORUS_EFFECT)) ? "EAXReverb and Chorus are"sv : DisabledEffects.test(EAXREVERB_EFFECT) ? "EAXReverb is"sv : DisabledEffects.test(CHORUS_EFFECT) ? "Chorus is"sv : ""sv); } } #endif // ALSOFT_EAX } inline void InitConfig() { std::call_once(alc_config_once, [](){alc_initconfig();}); } /************************************************ * Device enumeration ************************************************/ void ProbeAllDevicesList() { InitConfig(); std::lock_guard listlock{ListLock}; if(!PlaybackFactory) { decltype(alcAllDevicesArray){}.swap(alcAllDevicesArray); decltype(alcAllDevicesList){}.swap(alcAllDevicesList); } else { alcAllDevicesArray = PlaybackFactory->enumerate(BackendType::Playback); if(const auto prefix = GetDevicePrefix(); !prefix.empty()) std::for_each(alcAllDevicesArray.begin(), alcAllDevicesArray.end(), [prefix](std::string &name) { name.insert(0, prefix); }); decltype(alcAllDevicesList){}.swap(alcAllDevicesList); if(alcAllDevicesArray.empty()) alcAllDevicesList += '\0'; else for(auto &devname : alcAllDevicesArray) alcAllDevicesList.append(devname) += '\0'; } } void ProbeCaptureDeviceList() { InitConfig(); std::lock_guard listlock{ListLock}; if(!CaptureFactory) { decltype(alcCaptureDeviceArray){}.swap(alcCaptureDeviceArray); decltype(alcCaptureDeviceList){}.swap(alcCaptureDeviceList); } else { alcCaptureDeviceArray = CaptureFactory->enumerate(BackendType::Capture); if(const auto prefix = GetDevicePrefix(); !prefix.empty()) std::for_each(alcCaptureDeviceArray.begin(), alcCaptureDeviceArray.end(), [prefix](std::string &name) { name.insert(0, prefix); }); decltype(alcCaptureDeviceList){}.swap(alcCaptureDeviceList); if(alcCaptureDeviceArray.empty()) alcCaptureDeviceList += '\0'; else for(auto &devname : alcCaptureDeviceArray) alcCaptureDeviceList.append(devname) += '\0'; } } al::span SpanFromAttributeList(const ALCint *attribs) noexcept { al::span attrSpan; if(attribs) { const ALCint *attrEnd{attribs}; while(*attrEnd != 0) attrEnd += 2; /* NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) */ attrSpan = {attribs, attrEnd}; } return attrSpan; } struct DevFmtPair { DevFmtChannels chans; DevFmtType type; }; std::optional DecomposeDevFormat(ALenum format) { struct FormatType { ALenum format; DevFmtChannels channels; DevFmtType type; }; static constexpr std::array list{ FormatType{AL_FORMAT_MONO8, DevFmtMono, DevFmtUByte}, FormatType{AL_FORMAT_MONO16, DevFmtMono, DevFmtShort}, FormatType{AL_FORMAT_MONO_I32, DevFmtMono, DevFmtInt}, FormatType{AL_FORMAT_MONO_FLOAT32, DevFmtMono, DevFmtFloat}, FormatType{AL_FORMAT_STEREO8, DevFmtStereo, DevFmtUByte}, FormatType{AL_FORMAT_STEREO16, DevFmtStereo, DevFmtShort}, FormatType{AL_FORMAT_STEREO_I32, DevFmtStereo, DevFmtInt}, FormatType{AL_FORMAT_STEREO_FLOAT32, DevFmtStereo, DevFmtFloat}, FormatType{AL_FORMAT_QUAD8, DevFmtQuad, DevFmtUByte}, FormatType{AL_FORMAT_QUAD16, DevFmtQuad, DevFmtShort}, FormatType{AL_FORMAT_QUAD32, DevFmtQuad, DevFmtFloat}, FormatType{AL_FORMAT_QUAD_I32, DevFmtQuad, DevFmtInt}, FormatType{AL_FORMAT_QUAD_FLOAT32, DevFmtQuad, DevFmtFloat}, FormatType{AL_FORMAT_51CHN8, DevFmtX51, DevFmtUByte}, FormatType{AL_FORMAT_51CHN16, DevFmtX51, DevFmtShort}, FormatType{AL_FORMAT_51CHN32, DevFmtX51, DevFmtFloat}, FormatType{AL_FORMAT_51CHN_I32, DevFmtX51, DevFmtInt}, FormatType{AL_FORMAT_51CHN_FLOAT32, DevFmtX51, DevFmtFloat}, FormatType{AL_FORMAT_61CHN8, DevFmtX61, DevFmtUByte}, FormatType{AL_FORMAT_61CHN16, DevFmtX61, DevFmtShort}, FormatType{AL_FORMAT_61CHN32, DevFmtX61, DevFmtFloat}, FormatType{AL_FORMAT_61CHN_I32, DevFmtX61, DevFmtInt}, FormatType{AL_FORMAT_61CHN_FLOAT32, DevFmtX61, DevFmtFloat}, FormatType{AL_FORMAT_71CHN8, DevFmtX71, DevFmtUByte}, FormatType{AL_FORMAT_71CHN16, DevFmtX71, DevFmtShort}, FormatType{AL_FORMAT_71CHN32, DevFmtX71, DevFmtFloat}, FormatType{AL_FORMAT_71CHN_I32, DevFmtX71, DevFmtInt}, FormatType{AL_FORMAT_71CHN_FLOAT32, DevFmtX71, DevFmtFloat}, }; for(const auto &item : list) { if(item.format == format) return DevFmtPair{item.channels, item.type}; } return std::nullopt; } std::optional DevFmtTypeFromEnum(ALCenum type) { switch(type) { case ALC_BYTE_SOFT: return DevFmtByte; case ALC_UNSIGNED_BYTE_SOFT: return DevFmtUByte; case ALC_SHORT_SOFT: return DevFmtShort; case ALC_UNSIGNED_SHORT_SOFT: return DevFmtUShort; case ALC_INT_SOFT: return DevFmtInt; case ALC_UNSIGNED_INT_SOFT: return DevFmtUInt; case ALC_FLOAT_SOFT: return DevFmtFloat; } WARN("Unsupported format type: {:#04x}", as_unsigned(type)); return std::nullopt; } ALCenum EnumFromDevFmt(DevFmtType type) { switch(type) { case DevFmtByte: return ALC_BYTE_SOFT; case DevFmtUByte: return ALC_UNSIGNED_BYTE_SOFT; case DevFmtShort: return ALC_SHORT_SOFT; case DevFmtUShort: return ALC_UNSIGNED_SHORT_SOFT; case DevFmtInt: return ALC_INT_SOFT; case DevFmtUInt: return ALC_UNSIGNED_INT_SOFT; case DevFmtFloat: return ALC_FLOAT_SOFT; } throw std::runtime_error{fmt::format("Invalid DevFmtType: {}", int{al::to_underlying(type)})}; } std::optional DevFmtChannelsFromEnum(ALCenum channels) { switch(channels) { case ALC_MONO_SOFT: return DevFmtMono; case ALC_STEREO_SOFT: return DevFmtStereo; case ALC_QUAD_SOFT: return DevFmtQuad; case ALC_5POINT1_SOFT: return DevFmtX51; case ALC_6POINT1_SOFT: return DevFmtX61; case ALC_7POINT1_SOFT: return DevFmtX71; case ALC_BFORMAT3D_SOFT: return DevFmtAmbi3D; } WARN("Unsupported format channels: {:#04x}", as_unsigned(channels)); return std::nullopt; } ALCenum EnumFromDevFmt(DevFmtChannels channels) { switch(channels) { case DevFmtMono: return ALC_MONO_SOFT; case DevFmtStereo: return ALC_STEREO_SOFT; case DevFmtQuad: return ALC_QUAD_SOFT; case DevFmtX51: return ALC_5POINT1_SOFT; case DevFmtX61: return ALC_6POINT1_SOFT; case DevFmtX71: return ALC_7POINT1_SOFT; case DevFmtAmbi3D: return ALC_BFORMAT3D_SOFT; /* FIXME: Shouldn't happen. */ case DevFmtX714: case DevFmtX7144: case DevFmtX3D71: break; } throw std::runtime_error{fmt::format("Invalid DevFmtChannels: {}", int{al::to_underlying(channels)})}; } std::optional DevAmbiLayoutFromEnum(ALCenum layout) { switch(layout) { case ALC_FUMA_SOFT: return DevAmbiLayout::FuMa; case ALC_ACN_SOFT: return DevAmbiLayout::ACN; } WARN("Unsupported ambisonic layout: {:#04x}", as_unsigned(layout)); return std::nullopt; } ALCenum EnumFromDevAmbi(DevAmbiLayout layout) { switch(layout) { case DevAmbiLayout::FuMa: return ALC_FUMA_SOFT; case DevAmbiLayout::ACN: return ALC_ACN_SOFT; } throw std::runtime_error{fmt::format("Invalid DevAmbiLayout: {}", int{al::to_underlying(layout)})}; } std::optional DevAmbiScalingFromEnum(ALCenum scaling) { switch(scaling) { case ALC_FUMA_SOFT: return DevAmbiScaling::FuMa; case ALC_SN3D_SOFT: return DevAmbiScaling::SN3D; case ALC_N3D_SOFT: return DevAmbiScaling::N3D; } WARN("Unsupported ambisonic scaling: {:#04x}", as_unsigned(scaling)); return std::nullopt; } ALCenum EnumFromDevAmbi(DevAmbiScaling scaling) { switch(scaling) { case DevAmbiScaling::FuMa: return ALC_FUMA_SOFT; case DevAmbiScaling::SN3D: return ALC_SN3D_SOFT; case DevAmbiScaling::N3D: return ALC_N3D_SOFT; } throw std::runtime_error{fmt::format("Invalid DevAmbiScaling: {}", int{al::to_underlying(scaling)})}; } /* Downmixing channel arrays, to map a device format's missing channels to * existing ones. Based on what PipeWire does, though simplified. */ constexpr float inv_sqrt2f{static_cast(1.0 / al::numbers::sqrt2)}; constexpr std::array FrontStereo3dB{ InputRemixMap::TargetMix{FrontLeft, inv_sqrt2f}, InputRemixMap::TargetMix{FrontRight, inv_sqrt2f} }; constexpr std::array FrontStereo6dB{ InputRemixMap::TargetMix{FrontLeft, 0.5f}, InputRemixMap::TargetMix{FrontRight, 0.5f} }; constexpr std::array SideStereo3dB{ InputRemixMap::TargetMix{SideLeft, inv_sqrt2f}, InputRemixMap::TargetMix{SideRight, inv_sqrt2f} }; constexpr std::array BackStereo3dB{ InputRemixMap::TargetMix{BackLeft, inv_sqrt2f}, InputRemixMap::TargetMix{BackRight, inv_sqrt2f} }; constexpr std::array FrontLeft3dB{InputRemixMap::TargetMix{FrontLeft, inv_sqrt2f}}; constexpr std::array FrontRight3dB{InputRemixMap::TargetMix{FrontRight, inv_sqrt2f}}; constexpr std::array SideLeft0dB{InputRemixMap::TargetMix{SideLeft, 1.0f}}; constexpr std::array SideRight0dB{InputRemixMap::TargetMix{SideRight, 1.0f}}; constexpr std::array BackLeft0dB{InputRemixMap::TargetMix{BackLeft, 1.0f}}; constexpr std::array BackRight0dB{InputRemixMap::TargetMix{BackRight, 1.0f}}; constexpr std::array BackCenter3dB{InputRemixMap::TargetMix{BackCenter, inv_sqrt2f}}; constexpr std::array StereoDownmix{ InputRemixMap{FrontCenter, FrontStereo3dB}, InputRemixMap{SideLeft, FrontLeft3dB}, InputRemixMap{SideRight, FrontRight3dB}, InputRemixMap{BackLeft, FrontLeft3dB}, InputRemixMap{BackRight, FrontRight3dB}, InputRemixMap{BackCenter, FrontStereo6dB}, }; constexpr std::array QuadDownmix{ InputRemixMap{FrontCenter, FrontStereo3dB}, InputRemixMap{SideLeft, BackLeft0dB}, InputRemixMap{SideRight, BackRight0dB}, InputRemixMap{BackCenter, BackStereo3dB}, }; constexpr std::array X51Downmix{ InputRemixMap{BackLeft, SideLeft0dB}, InputRemixMap{BackRight, SideRight0dB}, InputRemixMap{BackCenter, SideStereo3dB}, }; constexpr std::array X61Downmix{ InputRemixMap{BackLeft, BackCenter3dB}, InputRemixMap{BackRight, BackCenter3dB}, }; constexpr std::array X71Downmix{ InputRemixMap{BackCenter, BackStereo3dB}, }; auto CreateDeviceLimiter(const al::Device *device, const float threshold) -> std::unique_ptr { static constexpr float LookAheadTime{0.001f}; static constexpr float HoldTime{0.002f}; static constexpr float PreGainDb{0.0f}; static constexpr float PostGainDb{0.0f}; static constexpr float Ratio{std::numeric_limits::infinity()}; static constexpr float KneeDb{0.0f}; static constexpr float AttackTime{0.02f}; static constexpr float ReleaseTime{0.2f}; const auto flags = Compressor::FlagBits{}.set(Compressor::AutoKnee).set(Compressor::AutoAttack) .set(Compressor::AutoRelease).set(Compressor::AutoPostGain).set(Compressor::AutoDeclip); return Compressor::Create(device->RealOut.Buffer.size(), static_cast(device->mSampleRate), flags, LookAheadTime, HoldTime, PreGainDb, PostGainDb, threshold, Ratio, KneeDb, AttackTime, ReleaseTime); } /** * Updates the device's base clock time with however many samples have been * done. This is used so frequency changes on the device don't cause the time * to jump forward or back. Must not be called while the device is running/ * mixing. */ inline void UpdateClockBase(al::Device *device) { using std::chrono::duration_cast; const auto mixLock = device->getWriteMixLock(); auto clockBaseSec = device->mClockBaseSec.load(std::memory_order_relaxed); auto clockBaseNSec = nanoseconds{device->mClockBaseNSec.load(std::memory_order_relaxed)}; clockBaseNSec += nanoseconds{seconds{device->mSamplesDone.load(std::memory_order_relaxed)}} / device->mSampleRate; clockBaseSec += duration_cast(clockBaseNSec); clockBaseNSec %= seconds{1}; device->mClockBaseSec.store(clockBaseSec, std::memory_order_relaxed); device->mClockBaseNSec.store(duration_cast(clockBaseNSec), std::memory_order_relaxed); device->mSamplesDone.store(0, std::memory_order_relaxed); } /** * Updates device parameters according to the attribute list (caller is * responsible for holding the list lock). */ auto UpdateDeviceParams(al::Device *device, const al::span attrList) -> ALCenum { if(attrList.empty() && device->Type == DeviceType::Loopback) { WARN("Missing attributes for loopback device"); return ALC_INVALID_VALUE; } uint numMono{device->NumMonoSources}; uint numStereo{device->NumStereoSources}; uint numSends{device->NumAuxSends}; std::optional stereomode; std::optional optlimit; std::optional optsrate; std::optional optchans; std::optional opttype; std::optional optlayout; std::optional optscale; uint period_size{DefaultUpdateSize}; uint buffer_size{DefaultUpdateSize * DefaultNumUpdates}; int hrtf_id{-1}; uint aorder{0u}; if(device->Type != DeviceType::Loopback) { /* Get default settings from the user configuration */ if(auto freqopt = device->configValue({}, "frequency")) { optsrate = std::clamp(*freqopt, MinOutputRate, MaxOutputRate); const double scale{static_cast(*optsrate) / double{DefaultOutputRate}}; period_size = static_cast(std::lround(period_size * scale)); } if(auto persizeopt = device->configValue({}, "period_size")) period_size = std::clamp(*persizeopt, 64u, 8192u); if(auto numperopt = device->configValue({}, "periods")) buffer_size = std::clamp(*numperopt, 2u, 16u) * period_size; else buffer_size = period_size * uint{DefaultNumUpdates}; if(auto typeopt = device->configValue({}, "sample-type")) { struct TypeMap { std::string_view name; DevFmtType type; }; constexpr std::array typelist{ TypeMap{"int8"sv, DevFmtByte }, TypeMap{"uint8"sv, DevFmtUByte }, TypeMap{"int16"sv, DevFmtShort }, TypeMap{"uint16"sv, DevFmtUShort}, TypeMap{"int32"sv, DevFmtInt }, TypeMap{"uint32"sv, DevFmtUInt }, TypeMap{"float32"sv, DevFmtFloat }, }; auto iter = std::find_if(typelist.begin(), typelist.end(), [svfmt=std::string_view{*typeopt}](const TypeMap &entry) -> bool { return al::case_compare(entry.name, svfmt) == 0; }); if(iter == typelist.end()) ERR("Unsupported sample-type: {}", *typeopt); else opttype = iter->type; } if(auto chanopt = device->configValue({}, "channels")) { struct ChannelMap { std::string_view name; DevFmtChannels chans; uint8_t order; }; constexpr std::array chanlist{ ChannelMap{"mono"sv, DevFmtMono, 0}, ChannelMap{"stereo"sv, DevFmtStereo, 0}, ChannelMap{"quad"sv, DevFmtQuad, 0}, ChannelMap{"surround51"sv, DevFmtX51, 0}, ChannelMap{"surround61"sv, DevFmtX61, 0}, ChannelMap{"surround71"sv, DevFmtX71, 0}, ChannelMap{"surround714"sv, DevFmtX714, 0}, ChannelMap{"surround7144"sv, DevFmtX7144, 0}, ChannelMap{"surround3d71"sv, DevFmtX3D71, 0}, ChannelMap{"surround51rear"sv, DevFmtX51, 0}, ChannelMap{"ambi1"sv, DevFmtAmbi3D, 1}, ChannelMap{"ambi2"sv, DevFmtAmbi3D, 2}, ChannelMap{"ambi3"sv, DevFmtAmbi3D, 3}, }; auto iter = std::find_if(chanlist.begin(), chanlist.end(), [svfmt=std::string_view{*chanopt}](const ChannelMap &entry) -> bool { return al::case_compare(entry.name, svfmt) == 0; }); if(iter == chanlist.end()) ERR("Unsupported channels: {}", *chanopt); else { optchans = iter->chans; aorder = iter->order; } } if(auto ambiopt = device->configValue({}, "ambi-format"sv)) { if(al::case_compare(*ambiopt, "fuma"sv) == 0) { optlayout = DevAmbiLayout::FuMa; optscale = DevAmbiScaling::FuMa; } else if(al::case_compare(*ambiopt, "acn+fuma"sv) == 0) { optlayout = DevAmbiLayout::ACN; optscale = DevAmbiScaling::FuMa; } else if(al::case_compare(*ambiopt, "ambix"sv) == 0 || al::case_compare(*ambiopt, "acn+sn3d"sv) == 0) { optlayout = DevAmbiLayout::ACN; optscale = DevAmbiScaling::SN3D; } else if(al::case_compare(*ambiopt, "acn+n3d"sv) == 0) { optlayout = DevAmbiLayout::ACN; optscale = DevAmbiScaling::N3D; } else ERR("Unsupported ambi-format: {}", *ambiopt); } if(auto hrtfopt = device->configValue({}, "hrtf"sv)) { WARN("general/hrtf is deprecated, please use stereo-encoding instead"); if(al::case_compare(*hrtfopt, "true"sv) == 0) stereomode = StereoEncoding::Hrtf; else if(al::case_compare(*hrtfopt, "false"sv) == 0) { if(!stereomode || *stereomode == StereoEncoding::Hrtf) stereomode = StereoEncoding::Default; } else if(al::case_compare(*hrtfopt, "auto"sv) != 0) ERR("Unexpected hrtf value: {}", *hrtfopt); } } if(auto encopt = device->configValue({}, "stereo-encoding"sv)) { if(al::case_compare(*encopt, "basic"sv) == 0 || al::case_compare(*encopt, "panpot"sv) == 0) stereomode = StereoEncoding::Basic; else if(al::case_compare(*encopt, "uhj") == 0) stereomode = StereoEncoding::Uhj; else if(al::case_compare(*encopt, "hrtf") == 0) stereomode = StereoEncoding::Hrtf; else ERR("Unexpected stereo-encoding: {}", *encopt); } // Check for app-specified attributes if(!attrList.empty()) { ALenum outmode{ALC_ANY_SOFT}; std::optional opthrtf; int freqAttr{}; #define ATTRIBUTE(a) a: TRACE("{} = {}", #a, attrList[attrIdx + 1]); #define ATTRIBUTE_HEX(a) a: TRACE("{} = {:#x}", #a, as_unsigned(attrList[attrIdx + 1])); for(size_t attrIdx{0};attrIdx < attrList.size();attrIdx+=2) { switch(attrList[attrIdx]) { case ATTRIBUTE_HEX(ALC_FORMAT_CHANNELS_SOFT) if(device->Type == DeviceType::Loopback) optchans = DevFmtChannelsFromEnum(attrList[attrIdx + 1]); break; case ATTRIBUTE_HEX(ALC_FORMAT_TYPE_SOFT) if(device->Type == DeviceType::Loopback) opttype = DevFmtTypeFromEnum(attrList[attrIdx + 1]); break; case ATTRIBUTE(ALC_FREQUENCY) freqAttr = attrList[attrIdx + 1]; break; case ATTRIBUTE_HEX(ALC_AMBISONIC_LAYOUT_SOFT) if(device->Type == DeviceType::Loopback) optlayout = DevAmbiLayoutFromEnum(attrList[attrIdx + 1]); break; case ATTRIBUTE_HEX(ALC_AMBISONIC_SCALING_SOFT) if(device->Type == DeviceType::Loopback) optscale = DevAmbiScalingFromEnum(attrList[attrIdx + 1]); break; case ATTRIBUTE(ALC_AMBISONIC_ORDER_SOFT) if(device->Type == DeviceType::Loopback) aorder = static_cast(attrList[attrIdx + 1]); break; case ATTRIBUTE(ALC_MONO_SOURCES) numMono = static_cast(attrList[attrIdx + 1]); if(numMono > INT_MAX) numMono = 0; break; case ATTRIBUTE(ALC_STEREO_SOURCES) numStereo = static_cast(attrList[attrIdx + 1]); if(numStereo > INT_MAX) numStereo = 0; break; case ATTRIBUTE(ALC_MAX_AUXILIARY_SENDS) numSends = static_cast(attrList[attrIdx + 1]); if(numSends > uint{std::numeric_limits::max()}) numSends = 0; else numSends = std::min(numSends, uint{MaxSendCount}); break; case ATTRIBUTE(ALC_HRTF_SOFT) if(attrList[attrIdx + 1] == ALC_FALSE) opthrtf = false; else if(attrList[attrIdx + 1] == ALC_TRUE) opthrtf = true; else if(attrList[attrIdx + 1] == ALC_DONT_CARE_SOFT) opthrtf = std::nullopt; break; case ATTRIBUTE(ALC_HRTF_ID_SOFT) hrtf_id = attrList[attrIdx + 1]; break; case ATTRIBUTE(ALC_OUTPUT_LIMITER_SOFT) if(attrList[attrIdx + 1] == ALC_FALSE) optlimit = false; else if(attrList[attrIdx + 1] == ALC_TRUE) optlimit = true; else if(attrList[attrIdx + 1] == ALC_DONT_CARE_SOFT) optlimit = std::nullopt; break; case ATTRIBUTE_HEX(ALC_OUTPUT_MODE_SOFT) outmode = attrList[attrIdx + 1]; break; case ATTRIBUTE_HEX(ALC_CONTEXT_FLAGS_EXT) /* Handled in alcCreateContext */ break; case ATTRIBUTE(ALC_SYNC) /* Ignored attribute */ break; default: TRACE("{:#04x} = {} ({:#x})", as_unsigned(attrList[attrIdx]), attrList[attrIdx + 1], as_unsigned(attrList[attrIdx + 1])); break; } } #undef ATTRIBUTE_HEX #undef ATTRIBUTE if(device->Type == DeviceType::Loopback) { if(!optchans || !opttype) return ALC_INVALID_VALUE; if(freqAttr < int{MinOutputRate} || freqAttr > int{MaxOutputRate}) return ALC_INVALID_VALUE; if(*optchans == DevFmtAmbi3D) { if(!optlayout || !optscale) return ALC_INVALID_VALUE; if(aorder < 1 || aorder > MaxAmbiOrder) return ALC_INVALID_VALUE; if((*optlayout == DevAmbiLayout::FuMa || *optscale == DevAmbiScaling::FuMa) && aorder > 3) return ALC_INVALID_VALUE; } else if(*optchans == DevFmtStereo) { if(opthrtf) { if(*opthrtf) stereomode = StereoEncoding::Hrtf; else { if(stereomode.value_or(StereoEncoding::Hrtf) == StereoEncoding::Hrtf) stereomode = StereoEncoding::Default; } } if(outmode == ALC_STEREO_BASIC_SOFT) stereomode = StereoEncoding::Basic; else if(outmode == ALC_STEREO_UHJ_SOFT) stereomode = StereoEncoding::Uhj; else if(outmode == ALC_STEREO_HRTF_SOFT) stereomode = StereoEncoding::Hrtf; } optsrate = static_cast(freqAttr); } else { if(opthrtf) { if(*opthrtf) stereomode = StereoEncoding::Hrtf; else { if(stereomode.value_or(StereoEncoding::Hrtf) == StereoEncoding::Hrtf) stereomode = StereoEncoding::Default; } } if(outmode != ALC_ANY_SOFT) { using OutputMode = al::Device::OutputMode; switch(OutputMode(outmode)) { case OutputMode::Any: break; case OutputMode::Mono: optchans = DevFmtMono; break; case OutputMode::Stereo: optchans = DevFmtStereo; break; case OutputMode::StereoBasic: optchans = DevFmtStereo; stereomode = StereoEncoding::Basic; break; case OutputMode::Uhj2: optchans = DevFmtStereo; stereomode = StereoEncoding::Uhj; break; case OutputMode::Hrtf: optchans = DevFmtStereo; stereomode = StereoEncoding::Hrtf; break; case OutputMode::Quad: optchans = DevFmtQuad; break; case OutputMode::X51: optchans = DevFmtX51; break; case OutputMode::X61: optchans = DevFmtX61; break; case OutputMode::X71: optchans = DevFmtX71; break; } } if(freqAttr) { uint oldrate = optsrate.value_or(DefaultOutputRate); freqAttr = std::clamp(freqAttr, MinOutputRate, MaxOutputRate); const double scale{static_cast(freqAttr) / oldrate}; period_size = static_cast(std::lround(period_size * scale)); buffer_size = static_cast(std::lround(buffer_size * scale)); optsrate = static_cast(freqAttr); } } /* If a context is already running on the device, stop playback so the * device attributes can be updated. */ if(device->mDeviceState == DeviceState::Playing) { device->Backend->stop(); device->mDeviceState = DeviceState::Unprepared; } UpdateClockBase(device); } if(device->mDeviceState == DeviceState::Playing) return ALC_NO_ERROR; device->mDeviceState = DeviceState::Unprepared; device->AvgSpeakerDist = 0.0f; device->mNFCtrlFilter = NfcFilter{}; device->mUhjEncoder = nullptr; device->AmbiDecoder = nullptr; device->Bs2b = nullptr; device->PostProcess = nullptr; device->Limiter = nullptr; device->ChannelDelays = nullptr; std::fill(std::begin(device->HrtfAccumData), std::end(device->HrtfAccumData), float2{}); device->Dry.AmbiMap.fill(BFChannelConfig{}); device->Dry.Buffer = {}; std::fill(std::begin(device->NumChannelsPerOrder), std::end(device->NumChannelsPerOrder), 0u); device->RealOut.RemixMap = {}; device->RealOut.ChannelIndex.fill(InvalidChannelIndex); device->RealOut.Buffer = {}; device->MixBuffer.clear(); device->MixBuffer.shrink_to_fit(); UpdateClockBase(device); device->FixedLatency = nanoseconds::zero(); device->DitherDepth = 0.0f; device->DitherSeed = DitherRNGSeed; device->mHrtfStatus = ALC_HRTF_DISABLED_SOFT; /************************************************************************* * Update device format request */ if(device->Type == DeviceType::Loopback) { device->mSampleRate = *optsrate; device->FmtChans = *optchans; device->FmtType = *opttype; if(device->FmtChans == DevFmtAmbi3D) { device->mAmbiOrder = aorder; device->mAmbiLayout = *optlayout; device->mAmbiScale = *optscale; } device->Flags.set(FrequencyRequest).set(ChannelsRequest).set(SampleTypeRequest); } else { device->FmtType = opttype.value_or(DevFmtTypeDefault); device->FmtChans = optchans.value_or(DevFmtChannelsDefault); device->mAmbiOrder = 0; device->mBufferSize = buffer_size; device->mUpdateSize = period_size; device->mSampleRate = optsrate.value_or(DefaultOutputRate); device->Flags.set(FrequencyRequest, optsrate.has_value()) .set(ChannelsRequest, optchans.has_value()) .set(SampleTypeRequest, opttype.has_value()); if(device->FmtChans == DevFmtAmbi3D) { device->mAmbiOrder = std::clamp(aorder, 1u, uint{MaxAmbiOrder}); device->mAmbiLayout = optlayout.value_or(DevAmbiLayout::Default); device->mAmbiScale = optscale.value_or(DevAmbiScaling::Default); if(device->mAmbiOrder > 3 && (device->mAmbiLayout == DevAmbiLayout::FuMa || device->mAmbiScale == DevAmbiScaling::FuMa)) { ERR("FuMa is incompatible with {}{} order ambisonics (up to 3rd order only)", device->mAmbiOrder, GetCounterSuffix(device->mAmbiOrder)); device->mAmbiOrder = 3; } } } TRACE("Pre-reset: {}{}, {}{}, {}{}hz, {} / {} buffer", device->Flags.test(ChannelsRequest)?"*":"", DevFmtChannelsString(device->FmtChans), device->Flags.test(SampleTypeRequest)?"*":"", DevFmtTypeString(device->FmtType), device->Flags.test(FrequencyRequest)?"*":"", device->mSampleRate, device->mUpdateSize, device->mBufferSize); const uint oldFreq{device->mSampleRate}; const DevFmtChannels oldChans{device->FmtChans}; const DevFmtType oldType{device->FmtType}; try { auto backend = device->Backend.get(); if(!backend->reset()) throw al::backend_exception{al::backend_error::DeviceError, "Device reset failure"}; } catch(std::exception &e) { ERR("Device error: {}", e.what()); device->handleDisconnect("{}", e.what()); return ALC_INVALID_DEVICE; } if(device->FmtChans != oldChans && device->Flags.test(ChannelsRequest)) { ERR("Failed to set {}, got {} instead", DevFmtChannelsString(oldChans), DevFmtChannelsString(device->FmtChans)); device->Flags.reset(ChannelsRequest); } if(device->FmtType != oldType && device->Flags.test(SampleTypeRequest)) { ERR("Failed to set {}, got {} instead", DevFmtTypeString(oldType), DevFmtTypeString(device->FmtType)); device->Flags.reset(SampleTypeRequest); } if(device->mSampleRate != oldFreq && device->Flags.test(FrequencyRequest)) { WARN("Failed to set {}hz, got {}hz instead", oldFreq, device->mSampleRate); device->Flags.reset(FrequencyRequest); } TRACE("Post-reset: {}, {}, {}hz, {} / {} buffer", DevFmtChannelsString(device->FmtChans), DevFmtTypeString(device->FmtType), device->mSampleRate, device->mUpdateSize, device->mBufferSize); if(device->Type != DeviceType::Loopback) { if(auto modeopt = device->configValue({}, "stereo-mode")) { if(al::case_compare(*modeopt, "headphones"sv) == 0) device->Flags.set(DirectEar); else if(al::case_compare(*modeopt, "speakers"sv) == 0) device->Flags.reset(DirectEar); else if(al::case_compare(*modeopt, "auto"sv) != 0) ERR("Unexpected stereo-mode: {}", *modeopt); } } aluInitRenderer(device, hrtf_id, stereomode); /* Calculate the max number of sources, and split them between the mono and * stereo count given the requested number of stereo sources. */ if(auto srcsopt = device->configValue({}, "sources"sv)) { if(*srcsopt <= 0) numMono = 256; else numMono = std::max(*srcsopt, 16u); } else { numMono = std::min(numMono, std::numeric_limits::max()-numStereo); numMono = std::max(numMono+numStereo, 256u); } numStereo = std::min(numStereo, numMono); numMono -= numStereo; device->SourcesMax = numMono + numStereo; device->NumMonoSources = numMono; device->NumStereoSources = numStereo; if(auto sendsopt = device->configValue({}, "sends"sv)) numSends = std::min(numSends, std::clamp(*sendsopt, 0u, uint{MaxSendCount})); device->NumAuxSends = numSends; TRACE("Max sources: {} ({} + {}), effect slots: {}, sends: {}", device->SourcesMax, device->NumMonoSources, device->NumStereoSources, device->AuxiliaryEffectSlotMax, device->NumAuxSends); switch(device->FmtChans) { case DevFmtMono: break; case DevFmtStereo: if(!device->mUhjEncoder) device->RealOut.RemixMap = StereoDownmix; break; case DevFmtQuad: device->RealOut.RemixMap = QuadDownmix; break; case DevFmtX51: device->RealOut.RemixMap = X51Downmix; break; case DevFmtX61: device->RealOut.RemixMap = X61Downmix; break; case DevFmtX71: device->RealOut.RemixMap = X71Downmix; break; case DevFmtX714: device->RealOut.RemixMap = X71Downmix; break; case DevFmtX7144: device->RealOut.RemixMap = X71Downmix; break; case DevFmtX3D71: device->RealOut.RemixMap = X51Downmix; break; case DevFmtAmbi3D: break; } size_t sample_delay{0}; if(auto *encoder{device->mUhjEncoder.get()}) sample_delay += encoder->getDelay(); if(device->getConfigValueBool({}, "dither"sv, true)) { int depth{device->configValue({}, "dither-depth"sv).value_or(0)}; if(depth <= 0) { switch(device->FmtType) { case DevFmtByte: case DevFmtUByte: depth = 8; break; case DevFmtShort: case DevFmtUShort: depth = 16; break; case DevFmtInt: case DevFmtUInt: case DevFmtFloat: break; } } if(depth > 0) { depth = std::clamp(depth, 2, 24); device->DitherDepth = std::pow(2.0f, static_cast(depth-1)); } } if(!(device->DitherDepth > 0.0f)) TRACE("Dithering disabled"); else TRACE("Dithering enabled ({}-bit, {:g})", float2int(std::log2(device->DitherDepth)+0.5f)+1, device->DitherDepth); if(!optlimit) optlimit = device->configValue({}, "output-limiter"); /* If the gain limiter is unset, use the limiter for integer-based output * (where samples must be clamped), and don't for floating-point (which can * take unclamped samples). */ if(!optlimit) { switch(device->FmtType) { case DevFmtByte: case DevFmtUByte: case DevFmtShort: case DevFmtUShort: case DevFmtInt: case DevFmtUInt: optlimit = true; break; case DevFmtFloat: break; } } if(!optlimit.value_or(false)) TRACE("Output limiter disabled"); else { float thrshld{1.0f}; switch(device->FmtType) { case DevFmtByte: case DevFmtUByte: thrshld = 127.0f / 128.0f; break; case DevFmtShort: case DevFmtUShort: thrshld = 32767.0f / 32768.0f; break; case DevFmtInt: case DevFmtUInt: case DevFmtFloat: break; } if(device->DitherDepth > 0.0f) thrshld -= 1.0f / device->DitherDepth; const float thrshld_dB{std::log10(thrshld) * 20.0f}; auto limiter = CreateDeviceLimiter(device, thrshld_dB); sample_delay += limiter->getLookAhead(); device->Limiter = std::move(limiter); TRACE("Output limiter enabled, {:.4f}dB limit", thrshld_dB); } /* Convert the sample delay from samples to nanosamples to nanoseconds. */ sample_delay = std::min(sample_delay, std::numeric_limits::max()); device->FixedLatency += nanoseconds{seconds{sample_delay}} / device->mSampleRate; TRACE("Fixed device latency: {}ns", device->FixedLatency.count()); FPUCtl mixer_mode{}; auto reset_context = [device](ContextBase *ctxbase) { auto *context = dynamic_cast(ctxbase); assert(context != nullptr); if(!context) return; std::unique_lock proplock{context->mPropLock}; std::unique_lock slotlock{context->mEffectSlotLock}; /* Clear out unused effect slot clusters. */ auto slot_cluster_not_in_use = [](ContextBase::EffectSlotCluster &clusterptr) -> bool { return std::none_of(clusterptr->begin(), clusterptr->end(), std::mem_fn(&EffectSlot::InUse)); }; auto slotcluster_end = std::remove_if(context->mEffectSlotClusters.begin(), context->mEffectSlotClusters.end(), slot_cluster_not_in_use); context->mEffectSlotClusters.erase(slotcluster_end, context->mEffectSlotClusters.end()); /* Free all wet buffers. Any in use will be reallocated with an updated * configuration in aluInitEffectPanning. */ auto clear_wetbuffers = [](ContextBase::EffectSlotCluster &clusterptr) { auto clear_buffer = [](EffectSlot &slot) { slot.mWetBuffer.clear(); slot.mWetBuffer.shrink_to_fit(); slot.Wet.Buffer = {}; }; std::for_each(clusterptr->begin(), clusterptr->end(), clear_buffer); }; std::for_each(context->mEffectSlotClusters.begin(), context->mEffectSlotClusters.end(), clear_wetbuffers); if(ALeffectslot *slot{context->mDefaultSlot.get()}) { auto *slotbase = slot->mSlot; aluInitEffectPanning(slotbase, context); if(auto *props = slotbase->Update.exchange(nullptr, std::memory_order_relaxed)) AtomicReplaceHead(context->mFreeEffectSlotProps, props); EffectState *state{slot->Effect.State.get()}; state->mOutTarget = device->Dry.Buffer; state->deviceUpdate(device, slot->Buffer); slot->mPropsDirty = true; } if(EffectSlotArray *curarray{context->mActiveAuxSlots.load(std::memory_order_relaxed)}) std::fill(curarray->begin()+ptrdiff_t(curarray->size()>>1), curarray->end(), nullptr); auto reset_slots = [device,context](EffectSlotSubList &sublist) { uint64_t usemask{~sublist.FreeMask}; while(usemask) { const auto idx = static_cast(al::countr_zero(usemask)); auto &slot = (*sublist.EffectSlots)[idx]; usemask &= ~(1_u64 << idx); auto *slotbase = slot.mSlot; aluInitEffectPanning(slotbase, context); if(auto *props = slotbase->Update.exchange(nullptr, std::memory_order_relaxed)) AtomicReplaceHead(context->mFreeEffectSlotProps, props); EffectState *state{slot.Effect.State.get()}; state->mOutTarget = device->Dry.Buffer; state->deviceUpdate(device, slot.Buffer); slot.mPropsDirty = true; } }; std::for_each(context->mEffectSlotList.begin(), context->mEffectSlotList.end(), reset_slots); /* Clear all effect slot props to let them get allocated again. */ context->mEffectSlotPropClusters.clear(); context->mFreeEffectSlotProps.store(nullptr, std::memory_order_relaxed); slotlock.unlock(); std::unique_lock srclock{context->mSourceLock}; const uint num_sends{device->NumAuxSends}; auto reset_sources = [num_sends](SourceSubList &sublist) { uint64_t usemask{~sublist.FreeMask}; while(usemask) { const auto idx = static_cast(al::countr_zero(usemask)); auto &source = (*sublist.Sources)[idx]; usemask &= ~(1_u64 << idx); auto clear_send = [](ALsource::SendData &send) -> void { if(send.Slot) DecrementRef(send.Slot->ref); send.Slot = nullptr; send.Gain = 1.0f; send.GainHF = 1.0f; send.HFReference = LowPassFreqRef; send.GainLF = 1.0f; send.LFReference = HighPassFreqRef; }; const auto sends = al::span{source.Send}.subspan(num_sends); std::for_each(sends.begin(), sends.end(), clear_send); source.mPropsDirty = true; } }; std::for_each(context->mSourceList.begin(), context->mSourceList.end(), reset_sources); auto reset_voice = [device,num_sends,context](Voice *voice) { /* Clear extraneous property set sends. */ const auto sendparams = al::span{voice->mProps.Send}.subspan(num_sends); std::fill(sendparams.begin(), sendparams.end(), VoiceProps::SendData{}); std::fill(voice->mSend.begin()+num_sends, voice->mSend.end(), Voice::TargetData{}); auto clear_wetparams = [num_sends](Voice::ChannelData &chandata) { const auto wetparams = al::span{chandata.mWetParams}.subspan(num_sends); std::fill(wetparams.begin(), wetparams.end(), SendParams{}); }; std::for_each(voice->mChans.begin(), voice->mChans.end(), clear_wetparams); if(VoicePropsItem *props{voice->mUpdate.exchange(nullptr, std::memory_order_relaxed)}) AtomicReplaceHead(context->mFreeVoiceProps, props); /* Force the voice to stopped if it was stopping. */ Voice::State vstate{Voice::Stopping}; voice->mPlayState.compare_exchange_strong(vstate, Voice::Stopped, std::memory_order_acquire, std::memory_order_acquire); if(voice->mSourceID.load(std::memory_order_relaxed) == 0u) return; voice->prepare(device); }; const auto voicespan = context->getVoicesSpan(); std::for_each(voicespan.begin(), voicespan.end(), reset_voice); /* Clear all voice props to let them get allocated again. */ context->mVoicePropClusters.clear(); context->mFreeVoiceProps.store(nullptr, std::memory_order_relaxed); srclock.unlock(); context->mPropsDirty = false; UpdateContextProps(context); UpdateAllEffectSlotProps(context); UpdateAllSourceProps(context); }; auto ctxspan = al::span{*device->mContexts.load()}; std::for_each(ctxspan.begin(), ctxspan.end(), reset_context); mixer_mode.leave(); device->mDeviceState = DeviceState::Configured; if(!device->Flags.test(DevicePaused)) { try { auto backend = device->Backend.get(); backend->start(); device->mDeviceState = DeviceState::Playing; } catch(al::backend_exception& e) { ERR("{}", e.what()); device->handleDisconnect("{}", e.what()); return ALC_INVALID_DEVICE; } TRACE("Post-start: {}, {}, {}hz, {} / {} buffer", DevFmtChannelsString(device->FmtChans), DevFmtTypeString(device->FmtType), device->mSampleRate, device->mUpdateSize, device->mBufferSize); } return ALC_NO_ERROR; } /** * Updates device parameters as above, and also first clears the disconnected * status, if set. */ auto ResetDeviceParams(al::Device *device, const al::span attrList) -> bool { /* If the device was disconnected, reset it since we're opened anew. */ if(!device->Connected.load(std::memory_order_relaxed)) UNLIKELY { /* Make sure disconnection is finished before continuing on. */ std::ignore = device->waitForMix(); for(ContextBase *ctxbase : *device->mContexts.load(std::memory_order_acquire)) { auto *ctx = dynamic_cast(ctxbase); assert(ctx != nullptr); if(!ctx || !ctx->mStopVoicesOnDisconnect.load(std::memory_order_acquire)) continue; /* Clear any pending voice changes and reallocate voices to get a * clean restart. */ std::lock_guard sourcelock{ctx->mSourceLock}; auto *vchg = ctx->mCurrentVoiceChange.load(std::memory_order_acquire); while(auto *next = vchg->mNext.load(std::memory_order_acquire)) vchg = next; ctx->mCurrentVoiceChange.store(vchg, std::memory_order_release); ctx->mVoicePropClusters.clear(); ctx->mFreeVoiceProps.store(nullptr, std::memory_order_relaxed); ctx->mVoiceClusters.clear(); ctx->allocVoices(std::max(256, ctx->mActiveVoiceCount.load(std::memory_order_relaxed))); } device->Connected.store(true); } ALCenum err{UpdateDeviceParams(device, attrList)}; if(err == ALC_NO_ERROR) LIKELY return ALC_TRUE; alcSetError(device, err); return ALC_FALSE; } /** Checks if the device handle is valid, and returns a new reference if so. */ DeviceRef VerifyDevice(ALCdevice *device) { std::lock_guard listlock{ListLock}; auto iter = std::lower_bound(DeviceList.begin(), DeviceList.end(), device); if(iter != DeviceList.end() && *iter == device) { (*iter)->add_ref(); return DeviceRef{*iter}; } return nullptr; } /** * Checks if the given context is valid, returning a new reference to it if so. */ ContextRef VerifyContext(ALCcontext *context) { std::lock_guard listlock{ListLock}; auto iter = std::lower_bound(ContextList.begin(), ContextList.end(), context); if(iter != ContextList.end() && *iter == context) { (*iter)->add_ref(); return ContextRef{*iter}; } return nullptr; } } // namespace FORCE_ALIGN void ALC_APIENTRY alsoft_set_log_callback(LPALSOFTLOGCALLBACK callback, void *userptr) noexcept { al_set_log_callback(callback, userptr); } /** Returns a new reference to the currently active context for this thread. */ ContextRef GetContextRef() noexcept { ALCcontext *context{ALCcontext::getThreadContext()}; if(context) context->add_ref(); else { while(ALCcontext::sGlobalContextLock.exchange(true, std::memory_order_acquire)) { /* Wait to make sure another thread isn't trying to change the * current context and bring its refcount to 0. */ } context = ALCcontext::sGlobalContext.load(std::memory_order_acquire); if(context) LIKELY context->add_ref(); ALCcontext::sGlobalContextLock.store(false, std::memory_order_release); } return ContextRef{context}; } void alcSetError(al::Device *device, ALCenum errorCode) { WARN("Error generated on device {}, code {:#04x}", voidp{device}, as_unsigned(errorCode)); if(TrapALCError) { #ifdef _WIN32 /* DebugBreak() will cause an exception if there is no debugger */ if(IsDebuggerPresent()) DebugBreak(); #elif defined(SIGTRAP) raise(SIGTRAP); #endif } if(device) device->LastError.store(errorCode); else LastNullDeviceError.store(errorCode); } /************************************************ * Standard ALC functions ************************************************/ ALC_API ALCenum ALC_APIENTRY alcGetError(ALCdevice *device) noexcept { if(!gProcessRunning) return ALC_INVALID_DEVICE; DeviceRef dev{VerifyDevice(device)}; if(dev) return dev->LastError.exchange(ALC_NO_ERROR); return LastNullDeviceError.exchange(ALC_NO_ERROR); } ALC_API void ALC_APIENTRY alcSuspendContext(ALCcontext *context) noexcept { ContextRef ctx{VerifyContext(context)}; if(!ctx) { alcSetError(nullptr, ALC_INVALID_CONTEXT); return; } if(ctx->mContextFlags.test(ContextFlags::DebugBit)) UNLIKELY ctx->debugMessage(DebugSource::API, DebugType::Portability, 0, DebugSeverity::Medium, "alcSuspendContext behavior is not portable -- some implementations suspend all " "rendering, some only defer property changes, and some are completely no-op; consider " "using alcDevicePauseSOFT to suspend all rendering, or alDeferUpdatesSOFT to only " "defer property changes"); if(SuspendDefers) { std::lock_guard proplock{ctx->mPropLock}; ctx->deferUpdates(); } } ALC_API void ALC_APIENTRY alcProcessContext(ALCcontext *context) noexcept { ContextRef ctx{VerifyContext(context)}; if(!ctx) { alcSetError(nullptr, ALC_INVALID_CONTEXT); return; } if(ctx->mContextFlags.test(ContextFlags::DebugBit)) UNLIKELY ctx->debugMessage(DebugSource::API, DebugType::Portability, 1, DebugSeverity::Medium, "alcProcessContext behavior is not portable -- some implementations resume rendering, " "some apply deferred property changes, and some are completely no-op; consider using " "alcDeviceResumeSOFT to resume rendering, or alProcessUpdatesSOFT to apply deferred " "property changes"); if(SuspendDefers) { std::lock_guard proplock{ctx->mPropLock}; ctx->processUpdates(); } } ALC_API auto ALC_APIENTRY alcGetString(ALCdevice *Device, ALCenum param) noexcept -> const ALCchar* { switch(param) { case ALC_NO_ERROR: return GetNoErrorString(); case ALC_INVALID_ENUM: return GetInvalidEnumString(); case ALC_INVALID_VALUE: return GetInvalidValueString(); case ALC_INVALID_DEVICE: return GetInvalidDeviceString(); case ALC_INVALID_CONTEXT: return GetInvalidContextString(); case ALC_OUT_OF_MEMORY: return GetOutOfMemoryString(); case ALC_DEVICE_SPECIFIER: return GetDefaultName(); case ALC_ALL_DEVICES_SPECIFIER: if(DeviceRef dev{VerifyDevice(Device)}) { if(dev->Type == DeviceType::Capture) { alcSetError(dev.get(), ALC_INVALID_ENUM); return nullptr; } if(dev->Type == DeviceType::Loopback) return GetDefaultName(); auto statelock = std::lock_guard{dev->StateLock}; return dev->mDeviceName.c_str(); } ProbeAllDevicesList(); return alcAllDevicesList.c_str(); case ALC_CAPTURE_DEVICE_SPECIFIER: if(DeviceRef dev{VerifyDevice(Device)}) { if(dev->Type != DeviceType::Capture) { alcSetError(dev.get(), ALC_INVALID_ENUM); return nullptr; } auto statelock = std::lock_guard{dev->StateLock}; return dev->mDeviceName.c_str(); } ProbeCaptureDeviceList(); return alcCaptureDeviceList.c_str(); /* Default devices are always first in the list */ case ALC_DEFAULT_DEVICE_SPECIFIER: return GetDefaultName(); case ALC_DEFAULT_ALL_DEVICES_SPECIFIER: if(alcAllDevicesList.empty()) ProbeAllDevicesList(); /* Copy first entry as default. */ if(alcAllDevicesArray.empty()) return GetDefaultName(); alcDefaultAllDevicesSpecifier = alcAllDevicesArray.front(); return alcDefaultAllDevicesSpecifier.c_str(); case ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER: if(alcCaptureDeviceList.empty()) ProbeCaptureDeviceList(); /* Copy first entry as default. */ if(alcCaptureDeviceArray.empty()) return GetDefaultName(); alcCaptureDefaultDeviceSpecifier = alcCaptureDeviceArray.front(); return alcCaptureDefaultDeviceSpecifier.c_str(); case ALC_EXTENSIONS: if(VerifyDevice(Device)) return GetExtensionList(); return GetNoDeviceExtList(); case ALC_HRTF_SPECIFIER_SOFT: if(DeviceRef dev{VerifyDevice(Device)}) { std::lock_guard statelock{dev->StateLock}; return dev->mHrtf ? dev->mHrtfName.c_str() : ""; } alcSetError(nullptr, ALC_INVALID_DEVICE); return nullptr; default: alcSetError(VerifyDevice(Device).get(), ALC_INVALID_ENUM); } return nullptr; } namespace { auto GetIntegerv(al::Device *device, ALCenum param, const al::span values) -> size_t { if(values.empty()) { alcSetError(device, ALC_INVALID_VALUE); return 0; } if(!device) { switch(param) { case ALC_MAJOR_VERSION: values[0] = alcMajorVersion; return 1; case ALC_MINOR_VERSION: values[0] = alcMinorVersion; return 1; case ALC_EFX_MAJOR_VERSION: values[0] = alcEFXMajorVersion; return 1; case ALC_EFX_MINOR_VERSION: values[0] = alcEFXMinorVersion; return 1; case ALC_MAX_AUXILIARY_SENDS: values[0] = MaxSendCount; return 1; case ALC_ATTRIBUTES_SIZE: case ALC_ALL_ATTRIBUTES: case ALC_FREQUENCY: case ALC_REFRESH: case ALC_SYNC: case ALC_MONO_SOURCES: case ALC_STEREO_SOURCES: case ALC_CAPTURE_SAMPLES: case ALC_FORMAT_CHANNELS_SOFT: case ALC_FORMAT_TYPE_SOFT: case ALC_AMBISONIC_LAYOUT_SOFT: case ALC_AMBISONIC_SCALING_SOFT: case ALC_AMBISONIC_ORDER_SOFT: case ALC_MAX_AMBISONIC_ORDER_SOFT: alcSetError(nullptr, ALC_INVALID_DEVICE); return 0; default: alcSetError(nullptr, ALC_INVALID_ENUM); } return 0; } std::lock_guard statelock{device->StateLock}; if(device->Type == DeviceType::Capture) { static constexpr int MaxCaptureAttributes{9}; switch(param) { case ALC_ATTRIBUTES_SIZE: values[0] = MaxCaptureAttributes; return 1; case ALC_ALL_ATTRIBUTES: if(values.size() >= MaxCaptureAttributes) { size_t i{0}; values[i++] = ALC_MAJOR_VERSION; values[i++] = alcMajorVersion; values[i++] = ALC_MINOR_VERSION; values[i++] = alcMinorVersion; values[i++] = ALC_CAPTURE_SAMPLES; values[i++] = static_cast(device->Backend->availableSamples()); values[i++] = ALC_CONNECTED; values[i++] = device->Connected.load(std::memory_order_relaxed); values[i++] = 0; assert(i == MaxCaptureAttributes); return i; } alcSetError(device, ALC_INVALID_VALUE); return 0; case ALC_MAJOR_VERSION: values[0] = alcMajorVersion; return 1; case ALC_MINOR_VERSION: values[0] = alcMinorVersion; return 1; case ALC_CAPTURE_SAMPLES: values[0] = static_cast(device->Backend->availableSamples()); return 1; case ALC_CONNECTED: values[0] = device->Connected.load(std::memory_order_acquire); return 1; default: alcSetError(device, ALC_INVALID_ENUM); } return 0; } /* render device */ auto NumAttrsForDevice = [device]() noexcept -> uint8_t { if(device->Type == DeviceType::Loopback && device->FmtChans == DevFmtAmbi3D) return 37; return 31; }; switch(param) { case ALC_ATTRIBUTES_SIZE: values[0] = NumAttrsForDevice(); return 1; case ALC_ALL_ATTRIBUTES: if(values.size() >= NumAttrsForDevice()) { size_t i{0}; values[i++] = ALC_MAJOR_VERSION; values[i++] = alcMajorVersion; values[i++] = ALC_MINOR_VERSION; values[i++] = alcMinorVersion; values[i++] = ALC_EFX_MAJOR_VERSION; values[i++] = alcEFXMajorVersion; values[i++] = ALC_EFX_MINOR_VERSION; values[i++] = alcEFXMinorVersion; values[i++] = ALC_FREQUENCY; values[i++] = static_cast(device->mSampleRate); if(device->Type != DeviceType::Loopback) { values[i++] = ALC_REFRESH; values[i++] = static_cast(device->mSampleRate / device->mUpdateSize); values[i++] = ALC_SYNC; values[i++] = ALC_FALSE; } else { if(device->FmtChans == DevFmtAmbi3D) { values[i++] = ALC_AMBISONIC_LAYOUT_SOFT; values[i++] = EnumFromDevAmbi(device->mAmbiLayout); values[i++] = ALC_AMBISONIC_SCALING_SOFT; values[i++] = EnumFromDevAmbi(device->mAmbiScale); values[i++] = ALC_AMBISONIC_ORDER_SOFT; values[i++] = static_cast(device->mAmbiOrder); } values[i++] = ALC_FORMAT_CHANNELS_SOFT; values[i++] = EnumFromDevFmt(device->FmtChans); values[i++] = ALC_FORMAT_TYPE_SOFT; values[i++] = EnumFromDevFmt(device->FmtType); } values[i++] = ALC_MONO_SOURCES; values[i++] = static_cast(device->NumMonoSources); values[i++] = ALC_STEREO_SOURCES; values[i++] = static_cast(device->NumStereoSources); values[i++] = ALC_MAX_AUXILIARY_SENDS; values[i++] = static_cast(device->NumAuxSends); values[i++] = ALC_HRTF_SOFT; values[i++] = (device->mHrtf ? ALC_TRUE : ALC_FALSE); values[i++] = ALC_HRTF_STATUS_SOFT; values[i++] = device->mHrtfStatus; values[i++] = ALC_OUTPUT_LIMITER_SOFT; values[i++] = device->Limiter ? ALC_TRUE : ALC_FALSE; values[i++] = ALC_MAX_AMBISONIC_ORDER_SOFT; values[i++] = MaxAmbiOrder; values[i++] = ALC_OUTPUT_MODE_SOFT; values[i++] = static_cast(device->getOutputMode1()); values[i++] = 0; assert(i == NumAttrsForDevice()); return i; } alcSetError(device, ALC_INVALID_VALUE); return 0; case ALC_MAJOR_VERSION: values[0] = alcMajorVersion; return 1; case ALC_MINOR_VERSION: values[0] = alcMinorVersion; return 1; case ALC_EFX_MAJOR_VERSION: values[0] = alcEFXMajorVersion; return 1; case ALC_EFX_MINOR_VERSION: values[0] = alcEFXMinorVersion; return 1; case ALC_FREQUENCY: values[0] = static_cast(device->mSampleRate); return 1; case ALC_REFRESH: if(device->Type == DeviceType::Loopback) { alcSetError(device, ALC_INVALID_DEVICE); return 0; } values[0] = static_cast(device->mSampleRate / device->mUpdateSize); return 1; case ALC_SYNC: if(device->Type == DeviceType::Loopback) { alcSetError(device, ALC_INVALID_DEVICE); return 0; } values[0] = ALC_FALSE; return 1; case ALC_FORMAT_CHANNELS_SOFT: if(device->Type != DeviceType::Loopback) { alcSetError(device, ALC_INVALID_DEVICE); return 0; } values[0] = EnumFromDevFmt(device->FmtChans); return 1; case ALC_FORMAT_TYPE_SOFT: if(device->Type != DeviceType::Loopback) { alcSetError(device, ALC_INVALID_DEVICE); return 0; } values[0] = EnumFromDevFmt(device->FmtType); return 1; case ALC_AMBISONIC_LAYOUT_SOFT: if(device->Type != DeviceType::Loopback || device->FmtChans != DevFmtAmbi3D) { alcSetError(device, ALC_INVALID_DEVICE); return 0; } values[0] = EnumFromDevAmbi(device->mAmbiLayout); return 1; case ALC_AMBISONIC_SCALING_SOFT: if(device->Type != DeviceType::Loopback || device->FmtChans != DevFmtAmbi3D) { alcSetError(device, ALC_INVALID_DEVICE); return 0; } values[0] = EnumFromDevAmbi(device->mAmbiScale); return 1; case ALC_AMBISONIC_ORDER_SOFT: if(device->Type != DeviceType::Loopback || device->FmtChans != DevFmtAmbi3D) { alcSetError(device, ALC_INVALID_DEVICE); return 0; } values[0] = static_cast(device->mAmbiOrder); return 1; case ALC_MONO_SOURCES: values[0] = static_cast(device->NumMonoSources); return 1; case ALC_STEREO_SOURCES: values[0] = static_cast(device->NumStereoSources); return 1; case ALC_MAX_AUXILIARY_SENDS: values[0] = static_cast(device->NumAuxSends); return 1; case ALC_CONNECTED: values[0] = device->Connected.load(std::memory_order_acquire); return 1; case ALC_HRTF_SOFT: values[0] = (device->mHrtf ? ALC_TRUE : ALC_FALSE); return 1; case ALC_HRTF_STATUS_SOFT: values[0] = device->mHrtfStatus; return 1; case ALC_NUM_HRTF_SPECIFIERS_SOFT: device->enumerateHrtfs(); values[0] = static_cast(std::min(device->mHrtfList.size(), size_t{std::numeric_limits::max()})); return 1; case ALC_OUTPUT_LIMITER_SOFT: values[0] = device->Limiter ? ALC_TRUE : ALC_FALSE; return 1; case ALC_MAX_AMBISONIC_ORDER_SOFT: values[0] = MaxAmbiOrder; return 1; case ALC_OUTPUT_MODE_SOFT: values[0] = static_cast(device->getOutputMode1()); return 1; default: alcSetError(device, ALC_INVALID_ENUM); } return 0; } } // namespace ALC_API void ALC_APIENTRY alcGetIntegerv(ALCdevice *device, ALCenum param, ALCsizei size, ALCint *values) noexcept { DeviceRef dev{VerifyDevice(device)}; if(size <= 0 || values == nullptr) alcSetError(dev.get(), ALC_INVALID_VALUE); else GetIntegerv(dev.get(), param, {values, static_cast(size)}); } ALC_API void ALC_APIENTRY alcGetInteger64vSOFT(ALCdevice *device, ALCenum pname, ALCsizei size, ALCint64SOFT *values) noexcept { DeviceRef dev{VerifyDevice(device)}; if(size <= 0 || values == nullptr) { alcSetError(dev.get(), ALC_INVALID_VALUE); return; } const auto valuespan = al::span{values, static_cast(size)}; if(!dev || dev->Type == DeviceType::Capture) { auto ivals = std::vector(valuespan.size()); if(size_t got{GetIntegerv(dev.get(), pname, ivals)}) std::copy_n(ivals.cbegin(), got, valuespan.begin()); return; } /* render device */ auto NumAttrsForDevice = [](al::Device *aldev) noexcept -> size_t { if(aldev->Type == DeviceType::Loopback && aldev->FmtChans == DevFmtAmbi3D) return 41; return 35; }; std::lock_guard statelock{dev->StateLock}; switch(pname) { case ALC_ATTRIBUTES_SIZE: valuespan[0] = static_cast(NumAttrsForDevice(dev.get())); break; case ALC_ALL_ATTRIBUTES: if(valuespan.size() < NumAttrsForDevice(dev.get())) alcSetError(dev.get(), ALC_INVALID_VALUE); else { size_t i{0}; valuespan[i++] = ALC_FREQUENCY; valuespan[i++] = dev->mSampleRate; if(dev->Type != DeviceType::Loopback) { valuespan[i++] = ALC_REFRESH; valuespan[i++] = dev->mSampleRate / dev->mUpdateSize; valuespan[i++] = ALC_SYNC; valuespan[i++] = ALC_FALSE; } else { valuespan[i++] = ALC_FORMAT_CHANNELS_SOFT; valuespan[i++] = EnumFromDevFmt(dev->FmtChans); valuespan[i++] = ALC_FORMAT_TYPE_SOFT; valuespan[i++] = EnumFromDevFmt(dev->FmtType); if(dev->FmtChans == DevFmtAmbi3D) { valuespan[i++] = ALC_AMBISONIC_LAYOUT_SOFT; valuespan[i++] = EnumFromDevAmbi(dev->mAmbiLayout); valuespan[i++] = ALC_AMBISONIC_SCALING_SOFT; valuespan[i++] = EnumFromDevAmbi(dev->mAmbiScale); valuespan[i++] = ALC_AMBISONIC_ORDER_SOFT; valuespan[i++] = dev->mAmbiOrder; } } valuespan[i++] = ALC_MONO_SOURCES; valuespan[i++] = dev->NumMonoSources; valuespan[i++] = ALC_STEREO_SOURCES; valuespan[i++] = dev->NumStereoSources; valuespan[i++] = ALC_MAX_AUXILIARY_SENDS; valuespan[i++] = dev->NumAuxSends; valuespan[i++] = ALC_HRTF_SOFT; valuespan[i++] = (dev->mHrtf ? ALC_TRUE : ALC_FALSE); valuespan[i++] = ALC_HRTF_STATUS_SOFT; valuespan[i++] = dev->mHrtfStatus; valuespan[i++] = ALC_OUTPUT_LIMITER_SOFT; valuespan[i++] = dev->Limiter ? ALC_TRUE : ALC_FALSE; ClockLatency clock{GetClockLatency(dev.get(), dev->Backend.get())}; valuespan[i++] = ALC_DEVICE_CLOCK_SOFT; valuespan[i++] = clock.ClockTime.count(); valuespan[i++] = ALC_DEVICE_LATENCY_SOFT; valuespan[i++] = clock.Latency.count(); valuespan[i++] = ALC_OUTPUT_MODE_SOFT; valuespan[i++] = al::to_underlying(dev->getOutputMode1()); valuespan[i++] = 0; } break; case ALC_DEVICE_CLOCK_SOFT: { uint samplecount, refcount; seconds clocksec; nanoseconds clocknsec; do { refcount = dev->waitForMix(); samplecount = dev->mSamplesDone.load(std::memory_order_relaxed); clocksec = dev->mClockBaseSec.load(std::memory_order_relaxed); clocknsec = dev->mClockBaseNSec.load(std::memory_order_relaxed); std::atomic_thread_fence(std::memory_order_acquire); } while(refcount != dev->mMixCount.load(std::memory_order_relaxed)); valuespan[0] = nanoseconds{clocksec + nanoseconds{clocknsec} + nanoseconds{seconds{samplecount}}/dev->mSampleRate}.count(); } break; case ALC_DEVICE_LATENCY_SOFT: valuespan[0] = GetClockLatency(dev.get(), dev->Backend.get()).Latency.count(); break; case ALC_DEVICE_CLOCK_LATENCY_SOFT: if(size < 2) alcSetError(dev.get(), ALC_INVALID_VALUE); else { ClockLatency clock{GetClockLatency(dev.get(), dev->Backend.get())}; valuespan[0] = clock.ClockTime.count(); valuespan[1] = clock.Latency.count(); } break; default: auto ivals = std::vector(valuespan.size()); if(size_t got{GetIntegerv(dev.get(), pname, ivals)}) std::copy_n(ivals.cbegin(), got, valuespan.begin()); break; } } ALC_API ALCboolean ALC_APIENTRY alcIsExtensionPresent(ALCdevice *device, const ALCchar *extName) noexcept { DeviceRef dev{VerifyDevice(device)}; if(!extName) { alcSetError(dev.get(), ALC_INVALID_VALUE); return ALC_FALSE; } const std::string_view tofind{extName}; const auto extlist = dev ? std::string_view{GetExtensionList()} : std::string_view{GetNoDeviceExtList()}; auto matchpos = extlist.find(tofind); while(matchpos != std::string_view::npos) { const auto endpos = matchpos + tofind.size(); if((matchpos == 0 || std::isspace(extlist[matchpos-1])) && (endpos == extlist.size() || std::isspace(extlist[endpos]))) return ALC_TRUE; matchpos = extlist.find(tofind, matchpos+1); } return ALC_FALSE; } ALCvoid* ALC_APIENTRY alcGetProcAddress2(ALCdevice *device, const ALCchar *funcName) noexcept { return alcGetProcAddress(device, funcName); } ALC_API ALCvoid* ALC_APIENTRY alcGetProcAddress(ALCdevice *device, const ALCchar *funcName) noexcept { if(!funcName) { DeviceRef dev{VerifyDevice(device)}; alcSetError(dev.get(), ALC_INVALID_VALUE); return nullptr; } #if ALSOFT_EAX if(eax_g_is_enabled) { for(const auto &func : eaxFunctions) { if(strcmp(func.funcName, funcName) == 0) return func.address; } } #endif for(const auto &func : alcFunctions) { if(strcmp(func.funcName, funcName) == 0) return func.address; } return nullptr; } ALC_API ALCenum ALC_APIENTRY alcGetEnumValue(ALCdevice *device, const ALCchar *enumName) noexcept { if(!enumName) { DeviceRef dev{VerifyDevice(device)}; alcSetError(dev.get(), ALC_INVALID_VALUE); return 0; } #if ALSOFT_EAX if(eax_g_is_enabled) { for(const auto &enm : eaxEnumerations) { if(strcmp(enm.enumName, enumName) == 0) return enm.value; } } #endif for(const auto &enm : alcEnumerations) { if(strcmp(enm.enumName, enumName) == 0) return enm.value; } return 0; } ALC_API ALCcontext* ALC_APIENTRY alcCreateContext(ALCdevice *device, const ALCint *attrList) noexcept { /* Explicitly hold the list lock while taking the StateLock in case the * device is asynchronously destroyed, to ensure this new context is * properly cleaned up after being made. */ std::unique_lock listlock{ListLock}; DeviceRef dev{VerifyDevice(device)}; if(!dev || dev->Type == DeviceType::Capture || !dev->Connected.load(std::memory_order_relaxed)) { listlock.unlock(); alcSetError(dev.get(), ALC_INVALID_DEVICE); return nullptr; } std::unique_lock statelock{dev->StateLock}; listlock.unlock(); dev->LastError.store(ALC_NO_ERROR); const auto attrSpan = SpanFromAttributeList(attrList); ALCenum err{UpdateDeviceParams(dev.get(), attrSpan)}; if(err != ALC_NO_ERROR) { alcSetError(dev.get(), err); return nullptr; } ContextFlagBitset ctxflags{0}; for(size_t i{0};i < attrSpan.size();i+=2) { if(attrSpan[i] == ALC_CONTEXT_FLAGS_EXT) { ctxflags = static_cast(attrSpan[i+1]); break; } } auto context = ContextRef{new(std::nothrow) ALCcontext{dev, ctxflags}}; if(!context) { alcSetError(dev.get(), ALC_OUT_OF_MEMORY); return nullptr; } context->init(); if(auto volopt = dev->configValue({}, "volume-adjust")) { const float valf{*volopt}; if(!std::isfinite(valf)) ERR("volume-adjust must be finite: {:f}", valf); else { const float db{std::clamp(valf, -24.0f, 24.0f)}; if(db != valf) WARN("volume-adjust clamped: {:f}, range: +/-24", valf); context->mGainBoost = std::pow(10.0f, db/20.0f); TRACE("volume-adjust gain: {:f}", context->mGainBoost); } } { using ContextArray = al::FlexArray; /* Allocate a new context array, which holds 1 more than the current/ * old array. */ auto *oldarray = dev->mContexts.load(); auto newarray = ContextArray::Create(oldarray->size() + 1); /* Copy the current/old context handles to the new array, appending the * new context. */ auto iter = std::copy(oldarray->begin(), oldarray->end(), newarray->begin()); *iter = context.get(); /* Store the new context array in the device. Wait for any current mix * to finish before deleting the old array. */ auto prevarray = dev->mContexts.exchange(std::move(newarray)); std::ignore = dev->waitForMix(); } statelock.unlock(); { listlock.lock(); auto iter = std::lower_bound(ContextList.cbegin(), ContextList.cend(), context.get()); ContextList.emplace(iter, context.get()); listlock.unlock(); } if(ALeffectslot *slot{context->mDefaultSlot.get()}) { ALenum sloterr{slot->initEffect(0, ALCcontext::sDefaultEffect.type, ALCcontext::sDefaultEffect.Props, context.get())}; if(sloterr == AL_NO_ERROR) slot->updateProps(context.get()); else ERR("Failed to initialize the default effect"); } TRACE("Created context {}", voidp{context.get()}); return context.release(); } ALC_API void ALC_APIENTRY alcDestroyContext(ALCcontext *context) noexcept { if(!gProcessRunning) return; std::unique_lock listlock{ListLock}; auto iter = std::lower_bound(ContextList.begin(), ContextList.end(), context); if(iter == ContextList.end() || *iter != context) { listlock.unlock(); alcSetError(nullptr, ALC_INVALID_CONTEXT); return; } /* Hold a reference to this context so it remains valid until the ListLock * is released. */ ContextRef ctx{*iter}; ContextList.erase(iter); auto *Device = ctx->mALDevice.get(); std::lock_guard statelock{Device->StateLock}; ctx->deinit(); } ALC_API auto ALC_APIENTRY alcGetCurrentContext() noexcept -> ALCcontext* { ALCcontext *Context{ALCcontext::getThreadContext()}; if(!Context) Context = ALCcontext::sGlobalContext.load(); return Context; } /** Returns the currently active thread-local context. */ ALC_API auto ALC_APIENTRY alcGetThreadContext() noexcept -> ALCcontext* { return ALCcontext::getThreadContext(); } ALC_API ALCboolean ALC_APIENTRY alcMakeContextCurrent(ALCcontext *context) noexcept { /* context must be valid or nullptr */ ContextRef ctx; if(context) { ctx = VerifyContext(context); if(!ctx) { alcSetError(nullptr, ALC_INVALID_CONTEXT); return ALC_FALSE; } } /* Release this reference (if any) to store it in the GlobalContext * pointer. Take ownership of the reference (if any) that was previously * stored there, and let the reference go. */ while(ALCcontext::sGlobalContextLock.exchange(true, std::memory_order_acquire)) { /* Wait to make sure another thread isn't getting or trying to change * the current context as its refcount is decremented. */ } ctx = ContextRef{ALCcontext::sGlobalContext.exchange(ctx.release())}; ALCcontext::sGlobalContextLock.store(false, std::memory_order_release); /* Take ownership of the thread-local context reference (if any), clearing * the storage to null. */ ctx = ContextRef{ALCcontext::getThreadContext()}; if(ctx) ALCcontext::setThreadContext(nullptr); /* Reset (decrement) the previous thread-local reference. */ return ALC_TRUE; } /** Makes the given context the active context for the current thread. */ ALC_API ALCboolean ALC_APIENTRY alcSetThreadContext(ALCcontext *context) noexcept { /* context must be valid or nullptr */ ContextRef ctx; if(context) { ctx = VerifyContext(context); if(!ctx) { alcSetError(nullptr, ALC_INVALID_CONTEXT); return ALC_FALSE; } } /* context's reference count is already incremented */ ContextRef old{ALCcontext::getThreadContext()}; ALCcontext::setThreadContext(ctx.release()); return ALC_TRUE; } ALC_API ALCdevice* ALC_APIENTRY alcGetContextsDevice(ALCcontext *Context) noexcept { ContextRef ctx{VerifyContext(Context)}; if(!ctx) { alcSetError(nullptr, ALC_INVALID_CONTEXT); return nullptr; } return ctx->mALDevice.get(); } ALC_API ALCdevice* ALC_APIENTRY alcOpenDevice(const ALCchar *deviceName) noexcept { InitConfig(); if(!PlaybackFactory) { alcSetError(nullptr, ALC_INVALID_VALUE); return nullptr; } std::string_view devname{deviceName ? deviceName : ""}; if(!devname.empty()) { TRACE("Opening playback device \"{}\"", devname); if(al::case_compare(devname, GetDefaultName()) == 0 #ifdef _WIN32 /* Some old Windows apps hardcode these expecting OpenAL to use a * specific audio API, even when they're not enumerated. Creative's * router effectively ignores them too. */ || al::case_compare(devname, "DirectSound3D"sv) == 0 || al::case_compare(devname, "DirectSound"sv) == 0 || al::case_compare(devname, "MMSYSTEM"sv) == 0 #endif /* Some old Linux apps hardcode configuration strings that were * supported by the OpenAL SI. We can't really do anything useful * with them, so just ignore. */ || al::starts_with(devname, "'("sv) || al::case_compare(devname, "openal-soft"sv) == 0) devname = {}; else { const auto prefix = GetDevicePrefix(); if(!prefix.empty() && devname.size() > prefix.size() && al::starts_with(devname, prefix)) devname = devname.substr(prefix.size()); } } else TRACE("Opening default playback device"); const uint DefaultSends{ #if ALSOFT_EAX eax_g_is_enabled ? uint{EAX_MAX_FXSLOTS} : #endif // ALSOFT_EAX uint{DefaultSendCount} }; auto device = DeviceRef{new(std::nothrow) al::Device{DeviceType::Playback}}; if(!device) { WARN("Failed to create playback device handle"); alcSetError(nullptr, ALC_OUT_OF_MEMORY); return nullptr; } /* Set output format */ device->FmtChans = DevFmtChannelsDefault; device->FmtType = DevFmtTypeDefault; device->mSampleRate = DefaultOutputRate; device->mUpdateSize = DefaultUpdateSize; device->mBufferSize = DefaultUpdateSize * DefaultNumUpdates; device->SourcesMax = 256; device->NumStereoSources = 1; device->NumMonoSources = device->SourcesMax - device->NumStereoSources; device->AuxiliaryEffectSlotMax = 64; device->NumAuxSends = DefaultSends; try { auto backend = PlaybackFactory->createBackend(device.get(), BackendType::Playback); std::lock_guard listlock{ListLock}; backend->open(devname); device->mDeviceName = std::string{GetDevicePrefix()}+backend->mDeviceName; device->Backend = std::move(backend); } catch(al::backend_exception &e) { WARN("Failed to open playback device: {}", e.what()); alcSetError(nullptr, (e.errorCode() == al::backend_error::OutOfMemory) ? ALC_OUT_OF_MEMORY : ALC_INVALID_VALUE); return nullptr; } auto checkopt = [&device](const char *envname, const std::string_view optname) { if(auto optval = al::getenv(envname)) return optval; return device->configValue("game_compat", optname); }; if(auto overrideopt = checkopt("__ALSOFT_VENDOR_OVERRIDE", "vendor-override"sv)) { device->mVendorOverride = std::move(*overrideopt); TRACE("Overriding vendor string: \"{}\"", device->mVendorOverride); } if(auto overrideopt = checkopt("__ALSOFT_VERSION_OVERRIDE", "version-override"sv)) { device->mVersionOverride = std::move(*overrideopt); TRACE("Overriding version string: \"{}\"", device->mVersionOverride); } if(auto overrideopt = checkopt("__ALSOFT_RENDERER_OVERRIDE", "renderer-override"sv)) { device->mRendererOverride = std::move(*overrideopt); TRACE("Overriding renderer string: \"{}\"", device->mRendererOverride); } { std::lock_guard listlock{ListLock}; auto iter = std::lower_bound(DeviceList.cbegin(), DeviceList.cend(), device.get()); DeviceList.emplace(iter, device.get()); } TRACE("Created device {}, \"{}\"", voidp{device.get()}, device->mDeviceName); return device.release(); } ALC_API ALCboolean ALC_APIENTRY alcCloseDevice(ALCdevice *device) noexcept { if(!gProcessRunning) return ALC_FALSE; std::unique_lock listlock{ListLock}; auto iter = std::lower_bound(DeviceList.begin(), DeviceList.end(), device); if(iter == DeviceList.end() || *iter != device) { alcSetError(nullptr, ALC_INVALID_DEVICE); return ALC_FALSE; } if((*iter)->Type == DeviceType::Capture) { alcSetError(*iter, ALC_INVALID_DEVICE); return ALC_FALSE; } /* Erase the device, and any remaining contexts left on it, from their * respective lists. */ DeviceRef dev{*iter}; DeviceList.erase(iter); std::unique_lock statelock{dev->StateLock}; std::vector orphanctxs; for(ContextBase *ctx : *dev->mContexts.load()) { auto ctxiter = std::lower_bound(ContextList.begin(), ContextList.end(), ctx); if(ctxiter != ContextList.end() && *ctxiter == ctx) { orphanctxs.emplace_back(*ctxiter); ContextList.erase(ctxiter); } } listlock.unlock(); for(ContextRef &context : orphanctxs) { WARN("Releasing orphaned context {}", voidp{context.get()}); context->deinit(); } orphanctxs.clear(); if(dev->mDeviceState == DeviceState::Playing) { dev->Backend->stop(); dev->mDeviceState = DeviceState::Configured; } return ALC_TRUE; } /************************************************ * ALC capture functions ************************************************/ ALC_API ALCdevice* ALC_APIENTRY alcCaptureOpenDevice(const ALCchar *deviceName, ALCuint frequency, ALCenum format, ALCsizei samples) noexcept { InitConfig(); if(!CaptureFactory) { alcSetError(nullptr, ALC_INVALID_VALUE); return nullptr; } if(samples <= 0) { alcSetError(nullptr, ALC_INVALID_VALUE); return nullptr; } std::string_view devname{deviceName ? deviceName : ""}; if(!devname.empty()) { TRACE("Opening capture device \"{}\"", devname); if(al::case_compare(devname, GetDefaultName()) == 0 || al::case_compare(devname, "openal-soft"sv) == 0) devname = {}; else { const auto prefix = GetDevicePrefix(); if(!prefix.empty() && devname.size() > prefix.size() && al::starts_with(devname, prefix)) devname = devname.substr(prefix.size()); } } else TRACE("Opening default capture device"); auto device = DeviceRef{new(std::nothrow) al::Device{DeviceType::Capture}}; if(!device) { WARN("Failed to create capture device handle"); alcSetError(nullptr, ALC_OUT_OF_MEMORY); return nullptr; } auto decompfmt = DecomposeDevFormat(format); if(!decompfmt) { alcSetError(nullptr, ALC_INVALID_ENUM); return nullptr; } device->mSampleRate = frequency; device->FmtChans = decompfmt->chans; device->FmtType = decompfmt->type; device->Flags.set(FrequencyRequest); device->Flags.set(ChannelsRequest); device->Flags.set(SampleTypeRequest); device->mUpdateSize = static_cast(samples); device->mBufferSize = static_cast(samples); TRACE("Capture format: {}, {}, {}hz, {} / {} buffer", DevFmtChannelsString(device->FmtChans), DevFmtTypeString(device->FmtType), device->mSampleRate, device->mUpdateSize, device->mBufferSize); try { auto backend = CaptureFactory->createBackend(device.get(), BackendType::Capture); std::lock_guard listlock{ListLock}; backend->open(devname); device->mDeviceName = std::string{GetDevicePrefix()}+backend->mDeviceName; device->Backend = std::move(backend); } catch(al::backend_exception &e) { WARN("Failed to open capture device: {}", e.what()); alcSetError(nullptr, (e.errorCode() == al::backend_error::OutOfMemory) ? ALC_OUT_OF_MEMORY : ALC_INVALID_VALUE); return nullptr; } { std::lock_guard listlock{ListLock}; auto iter = std::lower_bound(DeviceList.cbegin(), DeviceList.cend(), device.get()); DeviceList.emplace(iter, device.get()); } device->mDeviceState = DeviceState::Configured; TRACE("Created capture device {}, \"{}\"", voidp{device.get()}, device->mDeviceName); return device.release(); } ALC_API ALCboolean ALC_APIENTRY alcCaptureCloseDevice(ALCdevice *device) noexcept { if(!gProcessRunning) return ALC_FALSE; std::unique_lock listlock{ListLock}; auto iter = std::lower_bound(DeviceList.begin(), DeviceList.end(), device); if(iter == DeviceList.end() || *iter != device) { alcSetError(nullptr, ALC_INVALID_DEVICE); return ALC_FALSE; } if((*iter)->Type != DeviceType::Capture) { alcSetError(*iter, ALC_INVALID_DEVICE); return ALC_FALSE; } DeviceRef dev{*iter}; DeviceList.erase(iter); listlock.unlock(); std::lock_guard statelock{dev->StateLock}; if(dev->mDeviceState == DeviceState::Playing) { dev->Backend->stop(); dev->mDeviceState = DeviceState::Configured; } return ALC_TRUE; } ALC_API void ALC_APIENTRY alcCaptureStart(ALCdevice *device) noexcept { DeviceRef dev{VerifyDevice(device)}; if(!dev || dev->Type != DeviceType::Capture) { alcSetError(dev.get(), ALC_INVALID_DEVICE); return; } std::lock_guard statelock{dev->StateLock}; if(!dev->Connected.load(std::memory_order_acquire) || dev->mDeviceState < DeviceState::Configured) alcSetError(dev.get(), ALC_INVALID_DEVICE); else if(dev->mDeviceState != DeviceState::Playing) { try { auto backend = dev->Backend.get(); backend->start(); dev->mDeviceState = DeviceState::Playing; } catch(al::backend_exception& e) { ERR("{}", e.what()); dev->handleDisconnect("{}", e.what()); alcSetError(dev.get(), ALC_INVALID_DEVICE); } } } ALC_API void ALC_APIENTRY alcCaptureStop(ALCdevice *device) noexcept { DeviceRef dev{VerifyDevice(device)}; if(!dev || dev->Type != DeviceType::Capture) alcSetError(dev.get(), ALC_INVALID_DEVICE); else { std::lock_guard statelock{dev->StateLock}; if(dev->mDeviceState == DeviceState::Playing) { dev->Backend->stop(); dev->mDeviceState = DeviceState::Configured; } } } ALC_API void ALC_APIENTRY alcCaptureSamples(ALCdevice *device, ALCvoid *buffer, ALCsizei samples) noexcept { DeviceRef dev{VerifyDevice(device)}; if(!dev || dev->Type != DeviceType::Capture) { alcSetError(dev.get(), ALC_INVALID_DEVICE); return; } if(samples < 0 || (samples > 0 && buffer == nullptr)) { alcSetError(dev.get(), ALC_INVALID_VALUE); return; } if(samples < 1) return; std::lock_guard statelock{dev->StateLock}; BackendBase *backend{dev->Backend.get()}; const auto usamples = static_cast(samples); if(usamples > backend->availableSamples()) { alcSetError(dev.get(), ALC_INVALID_VALUE); return; } backend->captureSamples(static_cast(buffer), usamples); } /************************************************ * ALC loopback functions ************************************************/ /** Open a loopback device, for manual rendering. */ ALC_API ALCdevice* ALC_APIENTRY alcLoopbackOpenDeviceSOFT(const ALCchar *deviceName) noexcept { InitConfig(); /* Make sure the device name, if specified, is us. */ if(deviceName && strcmp(deviceName, GetDefaultName()) != 0) { alcSetError(nullptr, ALC_INVALID_VALUE); return nullptr; } const uint DefaultSends{ #if ALSOFT_EAX eax_g_is_enabled ? uint{EAX_MAX_FXSLOTS} : #endif // ALSOFT_EAX uint{DefaultSendCount} }; auto device = DeviceRef{new(std::nothrow) al::Device{DeviceType::Loopback}}; if(!device) { WARN("Failed to create loopback device handle"); alcSetError(nullptr, ALC_OUT_OF_MEMORY); return nullptr; } device->SourcesMax = 256; device->AuxiliaryEffectSlotMax = 64; device->NumAuxSends = DefaultSends; //Set output format device->mBufferSize = 0; device->mUpdateSize = 0; device->mSampleRate = DefaultOutputRate; device->FmtChans = DevFmtChannelsDefault; device->FmtType = DevFmtTypeDefault; device->NumStereoSources = 1; device->NumMonoSources = device->SourcesMax - device->NumStereoSources; try { auto backend = LoopbackBackendFactory::getFactory().createBackend(device.get(), BackendType::Playback); backend->open("Loopback"); device->mDeviceName = std::string{GetDevicePrefix()}+backend->mDeviceName; device->Backend = std::move(backend); } catch(al::backend_exception &e) { WARN("Failed to open loopback device: {}", e.what()); alcSetError(nullptr, (e.errorCode() == al::backend_error::OutOfMemory) ? ALC_OUT_OF_MEMORY : ALC_INVALID_VALUE); return nullptr; } { std::lock_guard listlock{ListLock}; auto iter = std::lower_bound(DeviceList.cbegin(), DeviceList.cend(), device.get()); DeviceList.emplace(iter, device.get()); } TRACE("Created loopback device {}", voidp{device.get()}); return device.release(); } /** * Determines if the loopback device supports the given format for rendering. */ ALC_API ALCboolean ALC_APIENTRY alcIsRenderFormatSupportedSOFT(ALCdevice *device, ALCsizei freq, ALCenum channels, ALCenum type) noexcept { DeviceRef dev{VerifyDevice(device)}; if(!dev || dev->Type != DeviceType::Loopback) alcSetError(dev.get(), ALC_INVALID_DEVICE); else if(freq <= 0) alcSetError(dev.get(), ALC_INVALID_VALUE); else { if(DevFmtTypeFromEnum(type).has_value() && DevFmtChannelsFromEnum(channels).has_value() && freq >= int{MinOutputRate} && freq <= int{MaxOutputRate}) return ALC_TRUE; } return ALC_FALSE; } /** * Renders some samples into a buffer, using the format last set by the * attributes given to alcCreateContext. */ #if defined(__GNUC__) && defined(__i386__) /* Needed on x86-32 even without SSE codegen, since the mixer may still use SSE * and GCC assumes the stack is aligned (x86-64 ABI guarantees alignment). */ [[gnu::force_align_arg_pointer]] #endif ALC_API void ALC_APIENTRY alcRenderSamplesSOFT(ALCdevice *device, ALCvoid *buffer, ALCsizei samples) noexcept { auto aldev = dynamic_cast(device); if(!aldev || aldev->Type != DeviceType::Loopback) UNLIKELY alcSetError(aldev, ALC_INVALID_DEVICE); else if(samples < 0 || (samples > 0 && buffer == nullptr)) UNLIKELY alcSetError(aldev, ALC_INVALID_VALUE); else aldev->renderSamples(buffer, static_cast(samples), aldev->channelsFromFmt()); } /************************************************ * ALC DSP pause/resume functions ************************************************/ /** Pause the DSP to stop audio processing. */ ALC_API void ALC_APIENTRY alcDevicePauseSOFT(ALCdevice *device) noexcept { DeviceRef dev{VerifyDevice(device)}; if(!dev || dev->Type != DeviceType::Playback) alcSetError(dev.get(), ALC_INVALID_DEVICE); else { std::lock_guard statelock{dev->StateLock}; if(dev->mDeviceState == DeviceState::Playing) { dev->Backend->stop(); dev->mDeviceState = DeviceState::Configured; } dev->Flags.set(DevicePaused); } } /** Resume the DSP to restart audio processing. */ ALC_API void ALC_APIENTRY alcDeviceResumeSOFT(ALCdevice *device) noexcept { DeviceRef dev{VerifyDevice(device)}; if(!dev || dev->Type != DeviceType::Playback) { alcSetError(dev.get(), ALC_INVALID_DEVICE); return; } std::lock_guard statelock{dev->StateLock}; if(!dev->Flags.test(DevicePaused)) return; if(dev->mDeviceState < DeviceState::Configured) { WARN("Cannot resume unconfigured device"); alcSetError(dev.get(), ALC_INVALID_DEVICE); return; } if(!dev->Connected.load()) { WARN("Cannot resume a disconnected device"); alcSetError(dev.get(), ALC_INVALID_DEVICE); return; } dev->Flags.reset(DevicePaused); if(dev->mContexts.load()->empty()) return; try { auto backend = dev->Backend.get(); backend->start(); dev->mDeviceState = DeviceState::Playing; } catch(al::backend_exception& e) { ERR("{}", e.what()); dev->handleDisconnect("{}", e.what()); alcSetError(dev.get(), ALC_INVALID_DEVICE); return; } TRACE("Post-resume: {}, {}, {}hz, {} / {} buffer", DevFmtChannelsString(dev->FmtChans), DevFmtTypeString(dev->FmtType), dev->mSampleRate, dev->mUpdateSize, dev->mBufferSize); } /************************************************ * ALC HRTF functions ************************************************/ /** Gets a string parameter at the given index. */ ALC_API const ALCchar* ALC_APIENTRY alcGetStringiSOFT(ALCdevice *device, ALCenum paramName, ALCsizei index) noexcept { DeviceRef dev{VerifyDevice(device)}; if(!dev || dev->Type == DeviceType::Capture) alcSetError(dev.get(), ALC_INVALID_DEVICE); else switch(paramName) { case ALC_HRTF_SPECIFIER_SOFT: if(index >= 0 && static_cast(index) < dev->mHrtfList.size()) return dev->mHrtfList[static_cast(index)].c_str(); alcSetError(dev.get(), ALC_INVALID_VALUE); break; default: alcSetError(dev.get(), ALC_INVALID_ENUM); break; } return nullptr; } /** Resets the given device output, using the specified attribute list. */ ALC_API ALCboolean ALC_APIENTRY alcResetDeviceSOFT(ALCdevice *device, const ALCint *attribs) noexcept { std::unique_lock listlock{ListLock}; DeviceRef dev{VerifyDevice(device)}; if(!dev || dev->Type == DeviceType::Capture) { listlock.unlock(); alcSetError(dev.get(), ALC_INVALID_DEVICE); return ALC_FALSE; } std::lock_guard statelock{dev->StateLock}; listlock.unlock(); /* Force the backend to stop mixing first since we're resetting. Also reset * the connected state so lost devices can attempt recover. */ if(dev->mDeviceState == DeviceState::Playing) { dev->Backend->stop(); dev->mDeviceState = DeviceState::Configured; } return ResetDeviceParams(dev.get(), SpanFromAttributeList(attribs)) ? ALC_TRUE : ALC_FALSE; } /************************************************ * ALC device reopen functions ************************************************/ /** Reopens the given device output, using the specified name and attribute list. */ FORCE_ALIGN ALCboolean ALC_APIENTRY alcReopenDeviceSOFT(ALCdevice *device, const ALCchar *deviceName, const ALCint *attribs) noexcept { std::unique_lock listlock{ListLock}; DeviceRef dev{VerifyDevice(device)}; if(!dev || dev->Type != DeviceType::Playback) { listlock.unlock(); alcSetError(dev.get(), ALC_INVALID_DEVICE); return ALC_FALSE; } std::lock_guard statelock{dev->StateLock}; std::string_view devname{deviceName ? deviceName : ""}; if(!devname.empty()) { if(devname.length() >= size_t{std::numeric_limits::max()}) { ERR("Device name too long ({} >= {})", devname.length(), std::numeric_limits::max()); alcSetError(dev.get(), ALC_INVALID_VALUE); return ALC_FALSE; } if(al::case_compare(devname, GetDefaultName()) == 0) devname = {}; else { const auto prefix = GetDevicePrefix(); if(!prefix.empty() && devname.size() > prefix.size() && al::starts_with(devname, prefix)) devname = devname.substr(prefix.size()); } } /* Force the backend device to stop first since we're opening another one. */ const bool wasPlaying{dev->mDeviceState == DeviceState::Playing}; if(wasPlaying) { dev->Backend->stop(); dev->mDeviceState = DeviceState::Configured; } BackendPtr newbackend; try { newbackend = PlaybackFactory->createBackend(dev.get(), BackendType::Playback); newbackend->open(devname); } catch(al::backend_exception &e) { listlock.unlock(); newbackend = nullptr; WARN("Failed to reopen playback device: {}", e.what()); alcSetError(dev.get(), (e.errorCode() == al::backend_error::OutOfMemory) ? ALC_OUT_OF_MEMORY : ALC_INVALID_VALUE); if(dev->Connected.load(std::memory_order_relaxed) && wasPlaying) { try { auto backend = dev->Backend.get(); backend->start(); dev->mDeviceState = DeviceState::Playing; } catch(al::backend_exception &be) { ERR("{}", be.what()); dev->handleDisconnect("{}", be.what()); } } return ALC_FALSE; } listlock.unlock(); dev->mDeviceName = std::string{GetDevicePrefix()}+newbackend->mDeviceName; dev->Backend = std::move(newbackend); dev->mDeviceState = DeviceState::Unprepared; TRACE("Reopened device {}, \"{}\"", voidp{dev.get()}, dev->mDeviceName); std::string{}.swap(dev->mVendorOverride); std::string{}.swap(dev->mVersionOverride); std::string{}.swap(dev->mRendererOverride); auto checkopt = [&dev](const char *envname, const std::string_view optname) { if(auto optval = al::getenv(envname)) return optval; return dev->configValue("game_compat", optname); }; if(auto overrideopt = checkopt("__ALSOFT_VENDOR_OVERRIDE", "vendor-override"sv)) { dev->mVendorOverride = std::move(*overrideopt); TRACE("Overriding vendor string: \"{}\"", dev->mVendorOverride); } if(auto overrideopt = checkopt("__ALSOFT_VERSION_OVERRIDE", "version-override"sv)) { dev->mVersionOverride = std::move(*overrideopt); TRACE("Overriding version string: \"{}\"", dev->mVersionOverride); } if(auto overrideopt = checkopt("__ALSOFT_RENDERER_OVERRIDE", "renderer-override"sv)) { dev->mRendererOverride = std::move(*overrideopt); TRACE("Overriding renderer string: \"{}\"", dev->mRendererOverride); } /* Always return true even if resetting fails. It shouldn't fail, but this * is primarily to avoid confusion by the app seeing the function return * false while the device is on the new output anyway. We could try to * restore the old backend if this fails, but the configuration would be * changed with the new backend and would need to be reset again with the * old one, and the provided attributes may not be appropriate or desirable * for the old device. * * In this way, we essentially act as if the function succeeded, but * immediately disconnects following it. */ ResetDeviceParams(dev.get(), SpanFromAttributeList(attribs)); return ALC_TRUE; } /************************************************ * ALC event query functions ************************************************/ FORCE_ALIGN ALCenum ALC_APIENTRY alcEventIsSupportedSOFT(ALCenum eventType, ALCenum deviceType) noexcept { auto etype = alc::GetEventType(eventType); if(!etype) { WARN("Invalid event type: {:#04x}", as_unsigned(eventType)); alcSetError(nullptr, ALC_INVALID_ENUM); return ALC_FALSE; } auto supported = alc::EventSupport::NoSupport; switch(deviceType) { case ALC_PLAYBACK_DEVICE_SOFT: if(PlaybackFactory) supported = PlaybackFactory->queryEventSupport(*etype, BackendType::Playback); return al::to_underlying(supported); case ALC_CAPTURE_DEVICE_SOFT: if(CaptureFactory) supported = CaptureFactory->queryEventSupport(*etype, BackendType::Capture); return al::to_underlying(supported); } WARN("Invalid device type: {:#04x}", as_unsigned(deviceType)); alcSetError(nullptr, ALC_INVALID_ENUM); return ALC_FALSE; } openal-soft-1.24.2/alc/alconfig.cpp000066400000000000000000000417401474041540300170640ustar00rootroot00000000000000/** * OpenAL cross platform audio library * Copyright (C) 1999-2007 by authors. * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * Or go to http://www.gnu.org/copyleft/lgpl.html */ #include "config.h" #include "alconfig.h" #ifdef _WIN32 #include #include #endif #ifdef __APPLE__ #include #endif #include #include #include #include #include #include #include #include #include #include #include #include "almalloc.h" #include "alstring.h" #include "core/helpers.h" #include "core/logging.h" #include "filesystem.h" #include "strutils.h" #if ALSOFT_UWP #include // !!This is important!! #include #include #include using namespace winrt; #endif namespace { using namespace std::string_view_literals; #if defined(_WIN32) && !defined(_GAMING_XBOX) && !ALSOFT_UWP struct CoTaskMemDeleter { void operator()(void *mem) const { CoTaskMemFree(mem); } }; #endif struct ConfigEntry { std::string key; std::string value; }; std::vector ConfOpts; std::string &lstrip(std::string &line) { size_t pos{0}; while(pos < line.length() && std::isspace(line[pos])) ++pos; line.erase(0, pos); return line; } bool readline(std::istream &f, std::string &output) { while(f.good() && f.peek() == '\n') f.ignore(); return std::getline(f, output) && !output.empty(); } std::string expdup(std::string_view str) { std::string output; while(!str.empty()) { if(auto nextpos = str.find('$')) { output += str.substr(0, nextpos); if(nextpos == std::string_view::npos) break; str.remove_prefix(nextpos); } str.remove_prefix(1); if(str.empty()) { output += '$'; break; } if(str.front() == '$') { output += '$'; str.remove_prefix(1); continue; } const bool hasbraces{str.front() == '{'}; if(hasbraces) str.remove_prefix(1); size_t envend{0}; while(envend < str.size() && (std::isalnum(str[envend]) || str[envend] == '_')) ++envend; if(hasbraces && (envend == str.size() || str[envend] != '}')) continue; const std::string envname{str.substr(0, envend)}; if(hasbraces) ++envend; str.remove_prefix(envend); if(auto envval = al::getenv(envname.c_str())) output += *envval; } return output; } void LoadConfigFromFile(std::istream &f) { std::string curSection; std::string buffer; while(readline(f, buffer)) { if(lstrip(buffer).empty()) continue; if(buffer[0] == '[') { auto endpos = buffer.find(']', 1); if(endpos == 1 || endpos == std::string::npos) { ERR(" config parse error: bad line \"{}\"", buffer); continue; } if(buffer[endpos+1] != '\0') { size_t last{endpos+1}; while(last < buffer.size() && std::isspace(buffer[last])) ++last; if(last < buffer.size() && buffer[last] != '#') { ERR(" config parse error: bad line \"{}\"", buffer); continue; } } auto section = std::string_view{buffer}.substr(1, endpos-1); curSection.clear(); if(al::case_compare(section, "general"sv) != 0) { do { auto nextp = section.find('%'); if(nextp == std::string_view::npos) { curSection += section; break; } curSection += section.substr(0, nextp); section.remove_prefix(nextp); if(section.size() > 2 && ((section[1] >= '0' && section[1] <= '9') || (section[1] >= 'a' && section[1] <= 'f') || (section[1] >= 'A' && section[1] <= 'F')) && ((section[2] >= '0' && section[2] <= '9') || (section[2] >= 'a' && section[2] <= 'f') || (section[2] >= 'A' && section[2] <= 'F'))) { int b{0}; if(section[1] >= '0' && section[1] <= '9') b = (section[1]-'0') << 4; else if(section[1] >= 'a' && section[1] <= 'f') b = (section[1]-'a'+0xa) << 4; else if(section[1] >= 'A' && section[1] <= 'F') b = (section[1]-'A'+0x0a) << 4; if(section[2] >= '0' && section[2] <= '9') b |= (section[2]-'0'); else if(section[2] >= 'a' && section[2] <= 'f') b |= (section[2]-'a'+0xa); else if(section[2] >= 'A' && section[2] <= 'F') b |= (section[2]-'A'+0x0a); curSection += static_cast(b); section.remove_prefix(3); } else if(section.size() > 1 && section[1] == '%') { curSection += '%'; section.remove_prefix(2); } else { curSection += '%'; section.remove_prefix(1); } } while(!section.empty()); } continue; } auto cmtpos = std::min(buffer.find('#'), buffer.size()); while(cmtpos > 0 && std::isspace(buffer[cmtpos-1])) --cmtpos; if(!cmtpos) continue; buffer.erase(cmtpos); auto sep = buffer.find('='); if(sep == std::string::npos) { ERR(" config parse error: malformed option line: \"{}\"", buffer); continue; } auto keypart = std::string_view{buffer}.substr(0, sep++); while(!keypart.empty() && std::isspace(keypart.back())) keypart.remove_suffix(1); if(keypart.empty()) { ERR(" config parse error: malformed option line: \"{}\"", buffer); continue; } auto valpart = std::string_view{buffer}.substr(sep); while(!valpart.empty() && std::isspace(valpart.front())) valpart.remove_prefix(1); std::string fullKey; if(!curSection.empty()) { fullKey += curSection; fullKey += '/'; } fullKey += keypart; if(valpart.size() > size_t{std::numeric_limits::max()}) { ERR(" config parse error: value too long in line \"{}\"", buffer); continue; } if(valpart.size() > 1) { if((valpart.front() == '"' && valpart.back() == '"') || (valpart.front() == '\'' && valpart.back() == '\'')) { valpart.remove_prefix(1); valpart.remove_suffix(1); } } TRACE(" setting '{}' = '{}'", fullKey, valpart); /* Check if we already have this option set */ auto find_key = [&fullKey](const ConfigEntry &entry) -> bool { return entry.key == fullKey; }; auto ent = std::find_if(ConfOpts.begin(), ConfOpts.end(), find_key); if(ent != ConfOpts.end()) { if(!valpart.empty()) ent->value = expdup(valpart); else ConfOpts.erase(ent); } else if(!valpart.empty()) ConfOpts.emplace_back(ConfigEntry{std::move(fullKey), expdup(valpart)}); } ConfOpts.shrink_to_fit(); } auto GetConfigValue(const std::string_view devName, const std::string_view blockName, const std::string_view keyName) -> const std::string& { static const auto emptyString = std::string{}; if(keyName.empty()) return emptyString; std::string key; if(!blockName.empty() && al::case_compare(blockName, "general"sv) != 0) { key = blockName; key += '/'; } if(!devName.empty()) { key += devName; key += '/'; } key += keyName; auto iter = std::find_if(ConfOpts.cbegin(), ConfOpts.cend(), [&key](const ConfigEntry &entry) -> bool { return entry.key == key; }); if(iter != ConfOpts.cend()) { TRACE("Found option {} = \"{}\"", key, iter->value); if(!iter->value.empty()) return iter->value; return emptyString; } if(devName.empty()) return emptyString; return GetConfigValue({}, blockName, keyName); } } // namespace #ifdef _WIN32 void ReadALConfig() { fs::path path; #if !defined(_GAMING_XBOX) { #if !ALSOFT_UWP std::unique_ptr bufstore; const HRESULT hr{SHGetKnownFolderPath(FOLDERID_RoamingAppData, KF_FLAG_DONT_UNEXPAND, nullptr, al::out_ptr(bufstore))}; if(SUCCEEDED(hr)) { const std::wstring_view buffer{bufstore.get()}; #else winrt::Windows::Storage::ApplicationDataContainer localSettings = winrt::Windows::Storage::ApplicationData::Current().LocalSettings(); auto bufstore = Windows::Storage::ApplicationData::Current().RoamingFolder().Path(); std::wstring_view buffer{bufstore}; { #endif path = fs::path{buffer}; path /= L"alsoft.ini"; TRACE("Loading config {}...", al::u8_as_char(path.u8string())); if(fs::ifstream f{path}; f.is_open()) LoadConfigFromFile(f); } } #endif path = fs::u8path(GetProcBinary().path); if(!path.empty()) { path /= L"alsoft.ini"; TRACE("Loading config {}...", al::u8_as_char(path.u8string())); if(fs::ifstream f{path}; f.is_open()) LoadConfigFromFile(f); } if(auto confpath = al::getenv(L"ALSOFT_CONF")) { path = *confpath; TRACE("Loading config {}...", al::u8_as_char(path.u8string())); if(fs::ifstream f{path}; f.is_open()) LoadConfigFromFile(f); } } #else void ReadALConfig() { fs::path path{"/etc/openal/alsoft.conf"}; TRACE("Loading config {}...", al::u8_as_char(path.u8string())); if(fs::ifstream f{path}; f.is_open()) LoadConfigFromFile(f); std::string confpaths{al::getenv("XDG_CONFIG_DIRS").value_or("/etc/xdg")}; /* Go through the list in reverse, since "the order of base directories * denotes their importance; the first directory listed is the most * important". Ergo, we need to load the settings from the later dirs * first so that the settings in the earlier dirs override them. */ while(!confpaths.empty()) { auto next = confpaths.rfind(':'); if(next < confpaths.length()) { path = fs::path{std::string_view{confpaths}.substr(next+1)}.lexically_normal(); confpaths.erase(next); } else { path = fs::path{confpaths}.lexically_normal(); confpaths.clear(); } if(!path.is_absolute()) WARN("Ignoring XDG config dir: {}", al::u8_as_char(path.u8string())); else { path /= "alsoft.conf"; TRACE("Loading config {}...", al::u8_as_char(path.u8string())); if(fs::ifstream f{path}; f.is_open()) LoadConfigFromFile(f); } } #ifdef __APPLE__ CFBundleRef mainBundle = CFBundleGetMainBundle(); if(mainBundle) { CFURLRef configURL{CFBundleCopyResourceURL(mainBundle, CFSTR(".alsoftrc"), CFSTR(""), nullptr)}; std::array fileName{}; if(configURL && CFURLGetFileSystemRepresentation(configURL, true, fileName.data(), fileName.size())) { if(std::ifstream f{reinterpret_cast(fileName.data())}; f.is_open()) LoadConfigFromFile(f); } } #endif if(auto homedir = al::getenv("HOME")) { path = *homedir; path /= ".alsoftrc"; TRACE("Loading config {}...", al::u8_as_char(path.u8string())); if(std::ifstream f{path}; f.is_open()) LoadConfigFromFile(f); } if(auto configdir = al::getenv("XDG_CONFIG_HOME")) { path = *configdir; path /= "alsoft.conf"; } else { path.clear(); if(auto homedir = al::getenv("HOME")) { path = *homedir; path /= ".config/alsoft.conf"; } } if(!path.empty()) { TRACE("Loading config {}...", al::u8_as_char(path.u8string())); if(std::ifstream f{path}; f.is_open()) LoadConfigFromFile(f); } path = GetProcBinary().path; if(!path.empty()) { path /= "alsoft.conf"; TRACE("Loading config {}...", al::u8_as_char(path.u8string())); if(std::ifstream f{path}; f.is_open()) LoadConfigFromFile(f); } if(auto confname = al::getenv("ALSOFT_CONF")) { TRACE("Loading config {}...", *confname); if(std::ifstream f{*confname}; f.is_open()) LoadConfigFromFile(f); } } #endif auto ConfigValueStr(const std::string_view devName, const std::string_view blockName, const std::string_view keyName) -> std::optional { if(auto&& val = GetConfigValue(devName, blockName, keyName); !val.empty()) return val; return std::nullopt; } auto ConfigValueInt(const std::string_view devName, const std::string_view blockName, const std::string_view keyName) -> std::optional { if(auto&& val = GetConfigValue(devName, blockName, keyName); !val.empty()) try { return static_cast(std::stol(val, nullptr, 0)); } catch(std::exception&) { WARN("Option is not an int: {} = {}", keyName, val); } return std::nullopt; } auto ConfigValueUInt(const std::string_view devName, const std::string_view blockName, const std::string_view keyName) -> std::optional { if(auto&& val = GetConfigValue(devName, blockName, keyName); !val.empty()) try { return static_cast(std::stoul(val, nullptr, 0)); } catch(std::exception&) { WARN("Option is not an unsigned int: {} = {}", keyName, val); } return std::nullopt; } auto ConfigValueFloat(const std::string_view devName, const std::string_view blockName, const std::string_view keyName) -> std::optional { if(auto&& val = GetConfigValue(devName, blockName, keyName); !val.empty()) try { return std::stof(val); } catch(std::exception&) { WARN("Option is not a float: {} = {}", keyName, val); } return std::nullopt; } auto ConfigValueBool(const std::string_view devName, const std::string_view blockName, const std::string_view keyName) -> std::optional { if(auto&& val = GetConfigValue(devName, blockName, keyName); !val.empty()) try { return al::case_compare(val, "on"sv) == 0 || al::case_compare(val, "yes"sv) == 0 || al::case_compare(val, "true"sv) == 0 || std::stoll(val) != 0; } catch(std::out_of_range&) { /* If out of range, the value is some non-0 (true) value and it doesn't * matter that it's too big or small. */ return true; } catch(std::exception&) { /* If stoll fails to convert for any other reason, it's some other word * that's treated as false. */ return false; } return std::nullopt; } auto GetConfigValueBool(const std::string_view devName, const std::string_view blockName, const std::string_view keyName, bool def) -> bool { if(auto&& val = GetConfigValue(devName, blockName, keyName); !val.empty()) try { return al::case_compare(val, "on"sv) == 0 || al::case_compare(val, "yes"sv) == 0 || al::case_compare(val, "true"sv) == 0 || std::stoll(val) != 0; } catch(std::out_of_range&) { return true; } catch(std::exception&) { return false; } return def; } openal-soft-1.24.2/alc/alconfig.h000066400000000000000000000017441474041540300165310ustar00rootroot00000000000000#ifndef ALCONFIG_H #define ALCONFIG_H #include #include #include void ReadALConfig(); bool GetConfigValueBool(const std::string_view devName, const std::string_view blockName, const std::string_view keyName, bool def); std::optional ConfigValueStr(const std::string_view devName, const std::string_view blockName, const std::string_view keyName); std::optional ConfigValueInt(const std::string_view devName, const std::string_view blockName, const std::string_view keyName); std::optional ConfigValueUInt(const std::string_view devName, const std::string_view blockName, const std::string_view keyName); std::optional ConfigValueFloat(const std::string_view devName, const std::string_view blockName, const std::string_view keyName); std::optional ConfigValueBool(const std::string_view devName, const std::string_view blockName, const std::string_view keyName); #endif /* ALCONFIG_H */ openal-soft-1.24.2/alc/alu.cpp000066400000000000000000002563511474041540300160710ustar00rootroot00000000000000/** * OpenAL cross platform audio library * Copyright (C) 1999-2007 by authors. * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * Or go to http://www.gnu.org/copyleft/lgpl.html */ #include "config.h" #include "config_simd.h" #include "alu.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "almalloc.h" #include "alnumbers.h" #include "alnumeric.h" #include "alsem.h" #include "alspan.h" #include "alstring.h" #include "atomic.h" #include "core/ambidefs.h" #include "core/async_event.h" #include "core/bformatdec.h" #include "core/bs2b.h" #include "core/bsinc_defs.h" #include "core/bsinc_tables.h" #include "core/bufferline.h" #include "core/buffer_storage.h" #include "core/context.h" #include "core/cpu_caps.h" #include "core/cubic_tables.h" #include "core/devformat.h" #include "core/device.h" #include "core/effects/base.h" #include "core/effectslot.h" #include "core/filters/biquad.h" #include "core/filters/nfc.h" #include "core/fpu_ctrl.h" #include "core/hrtf.h" #include "core/mastering.h" #include "core/mixer.h" #include "core/mixer/defs.h" #include "core/mixer/hrtfdefs.h" #include "core/resampler_limits.h" #include "core/storage_formats.h" #include "core/uhjfilter.h" #include "core/voice.h" #include "core/voice_change.h" #include "intrusive_ptr.h" #include "opthelpers.h" #include "ringbuffer.h" #include "strutils.h" #include "vecmat.h" struct CTag; #if HAVE_SSE struct SSETag; #endif #if HAVE_SSE2 struct SSE2Tag; #endif #if HAVE_SSE4_1 struct SSE4Tag; #endif #if HAVE_NEON struct NEONTag; #endif struct PointTag; struct LerpTag; struct CubicTag; struct BSincTag; struct FastBSincTag; static_assert(!(MaxResamplerPadding&1), "MaxResamplerPadding is not a multiple of two"); namespace { using uint = unsigned int; using namespace std::chrono; using namespace std::string_view_literals; float InitConeScale() { float ret{1.0f}; if(auto optval = al::getenv("__ALSOFT_HALF_ANGLE_CONES")) { if(al::case_compare(*optval, "true"sv) == 0 || strtol(optval->c_str(), nullptr, 0) == 1) ret *= 0.5f; } return ret; } /* Cone scalar */ const float ConeScale{InitConeScale()}; /* Localized scalars for mono sources (initialized in aluInit, after * configuration is loaded). */ float XScale{1.0f}; float YScale{1.0f}; float ZScale{1.0f}; /* Source distance scale for NFC filters. */ float NfcScale{1.0f}; using HrtfDirectMixerFunc = void(*)(const FloatBufferSpan LeftOut, const FloatBufferSpan RightOut, const al::span InSamples, const al::span AccumSamples, const al::span TempBuf, const al::span ChanState, const size_t IrSize, const size_t SamplesToDo); HrtfDirectMixerFunc MixDirectHrtf{MixDirectHrtf_}; inline HrtfDirectMixerFunc SelectHrtfMixer() { #if HAVE_NEON if((CPUCapFlags&CPU_CAP_NEON)) return MixDirectHrtf_; #endif #if HAVE_SSE if((CPUCapFlags&CPU_CAP_SSE)) return MixDirectHrtf_; #endif return MixDirectHrtf_; } inline void BsincPrepare(const uint increment, BsincState *state, const BSincTable *table) { size_t si{BSincScaleCount - 1}; float sf{0.0f}; if(increment > MixerFracOne) { sf = MixerFracOne/static_cast(increment) - table->scaleBase; sf = std::max(0.0f, BSincScaleCount*sf*table->scaleRange - 1.0f); si = float2uint(sf); /* The interpolation factor is fit to this diagonally-symmetric curve * to reduce the transition ripple caused by interpolating different * scales of the sinc function. */ sf = 1.0f - std::cos(std::asin(sf - static_cast(si))); } state->sf = sf; state->m = table->m[si]; state->l = (state->m/2) - 1; state->filter = table->Tab.subspan(table->filterOffset[si]); } inline ResamplerFunc SelectResampler(Resampler resampler, uint increment) { switch(resampler) { case Resampler::Point: return Resample_; case Resampler::Linear: #if HAVE_NEON if((CPUCapFlags&CPU_CAP_NEON)) return Resample_; #endif #if HAVE_SSE4_1 if((CPUCapFlags&CPU_CAP_SSE4_1)) return Resample_; #endif #if HAVE_SSE2 if((CPUCapFlags&CPU_CAP_SSE2)) return Resample_; #endif return Resample_; case Resampler::Spline: case Resampler::Gaussian: #if HAVE_NEON if((CPUCapFlags&CPU_CAP_NEON)) return Resample_; #endif #if HAVE_SSE4_1 if((CPUCapFlags&CPU_CAP_SSE4_1)) return Resample_; #endif #if HAVE_SSE2 if((CPUCapFlags&CPU_CAP_SSE2)) return Resample_; #endif #if HAVE_SSE if((CPUCapFlags&CPU_CAP_SSE)) return Resample_; #endif return Resample_; case Resampler::BSinc12: case Resampler::BSinc24: if(increment > MixerFracOne) { #if HAVE_NEON if((CPUCapFlags&CPU_CAP_NEON)) return Resample_; #endif #if HAVE_SSE if((CPUCapFlags&CPU_CAP_SSE)) return Resample_; #endif return Resample_; } /* fall-through */ case Resampler::FastBSinc12: case Resampler::FastBSinc24: #if HAVE_NEON if((CPUCapFlags&CPU_CAP_NEON)) return Resample_; #endif #if HAVE_SSE if((CPUCapFlags&CPU_CAP_SSE)) return Resample_; #endif return Resample_; } return Resample_; } } // namespace void aluInit(CompatFlagBitset flags, const float nfcscale) { MixDirectHrtf = SelectHrtfMixer(); XScale = flags.test(CompatFlags::ReverseX) ? -1.0f : 1.0f; YScale = flags.test(CompatFlags::ReverseY) ? -1.0f : 1.0f; ZScale = flags.test(CompatFlags::ReverseZ) ? -1.0f : 1.0f; NfcScale = std::clamp(nfcscale, 0.0001f, 10000.0f); } ResamplerFunc PrepareResampler(Resampler resampler, uint increment, InterpState *state) { switch(resampler) { case Resampler::Point: case Resampler::Linear: break; case Resampler::Spline: state->emplace(al::span{gSplineFilter.mTable}); break; case Resampler::Gaussian: state->emplace(al::span{gGaussianFilter.mTable}); break; case Resampler::FastBSinc12: case Resampler::BSinc12: BsincPrepare(increment, &state->emplace(), &gBSinc12); break; case Resampler::FastBSinc24: case Resampler::BSinc24: BsincPrepare(increment, &state->emplace(), &gBSinc24); break; } return SelectResampler(resampler, increment); } void DeviceBase::ProcessHrtf(const size_t SamplesToDo) { /* HRTF is stereo output only. */ const size_t lidx{RealOut.ChannelIndex[FrontLeft]}; const size_t ridx{RealOut.ChannelIndex[FrontRight]}; MixDirectHrtf(RealOut.Buffer[lidx], RealOut.Buffer[ridx], Dry.Buffer, HrtfAccumData, mHrtfState->mTemp, mHrtfState->mChannels, mHrtfState->mIrSize, SamplesToDo); } void DeviceBase::ProcessAmbiDec(const size_t SamplesToDo) { AmbiDecoder->process(RealOut.Buffer, Dry.Buffer, SamplesToDo); } void DeviceBase::ProcessAmbiDecStablized(const size_t SamplesToDo) { /* Decode with front image stablization. */ const size_t lidx{RealOut.ChannelIndex[FrontLeft]}; const size_t ridx{RealOut.ChannelIndex[FrontRight]}; const size_t cidx{RealOut.ChannelIndex[FrontCenter]}; AmbiDecoder->processStablize(RealOut.Buffer, Dry.Buffer, lidx, ridx, cidx, SamplesToDo); } void DeviceBase::ProcessUhj(const size_t SamplesToDo) { /* UHJ is stereo output only. */ const size_t lidx{RealOut.ChannelIndex[FrontLeft]}; const size_t ridx{RealOut.ChannelIndex[FrontRight]}; /* Encode to stereo-compatible 2-channel UHJ output. */ mUhjEncoder->encode(RealOut.Buffer[lidx].data(), RealOut.Buffer[ridx].data(), {{Dry.Buffer[0].data(), Dry.Buffer[1].data(), Dry.Buffer[2].data()}}, SamplesToDo); } void DeviceBase::ProcessBs2b(const size_t SamplesToDo) { /* First, decode the ambisonic mix to the "real" output. */ AmbiDecoder->process(RealOut.Buffer, Dry.Buffer, SamplesToDo); /* BS2B is stereo output only. */ const size_t lidx{RealOut.ChannelIndex[FrontLeft]}; const size_t ridx{RealOut.ChannelIndex[FrontRight]}; /* Now apply the BS2B binaural/crossfeed filter. */ Bs2b->cross_feed(al::span{RealOut.Buffer[lidx]}.first(SamplesToDo), al::span{RealOut.Buffer[ridx]}.first(SamplesToDo)); } namespace { /* This RNG method was created based on the math found in opusdec. It's quick, * and starting with a seed value of 22222, is suitable for generating * whitenoise. */ inline uint dither_rng(uint *seed) noexcept { *seed = (*seed * 96314165) + 907633515; return *seed; } /* Ambisonic upsampler function. It's effectively a matrix multiply. It takes * an 'upsampler' and 'rotator' as the input matrices, and creates a matrix * that behaves as if the B-Format input was first decoded to a speaker array * at its input order, encoded back into the higher order mix, then finally * rotated. */ void UpsampleBFormatTransform( const al::span,MaxAmbiChannels> output, const al::span> upsampler, const al::span,MaxAmbiChannels> rotator, size_t ambi_order) { const size_t num_chans{AmbiChannelsFromOrder(ambi_order)}; for(size_t i{0};i < upsampler.size();++i) output[i].fill(0.0f); for(size_t i{0};i < upsampler.size();++i) { for(size_t k{0};k < num_chans;++k) { const float a{upsampler[i][k]}; /* Write the full number of channels. The compiler will have an * easier time optimizing if it has a fixed length. */ std::transform(rotator[k].cbegin(), rotator[k].cend(), output[i].cbegin(), output[i].begin(), [a](float rot, float dst) noexcept { return rot*a + dst; }); } } } constexpr auto GetAmbiScales(AmbiScaling scaletype) noexcept { switch(scaletype) { case AmbiScaling::FuMa: return al::span{AmbiScale::FromFuMa}; case AmbiScaling::SN3D: return al::span{AmbiScale::FromSN3D}; case AmbiScaling::UHJ: return al::span{AmbiScale::FromUHJ}; case AmbiScaling::N3D: break; } return al::span{AmbiScale::FromN3D}; } constexpr auto GetAmbiLayout(AmbiLayout layouttype) noexcept { if(layouttype == AmbiLayout::FuMa) return al::span{AmbiIndex::FromFuMa}; return al::span{AmbiIndex::FromACN}; } constexpr auto GetAmbi2DLayout(AmbiLayout layouttype) noexcept { if(layouttype == AmbiLayout::FuMa) return al::span{AmbiIndex::FromFuMa2D}; return al::span{AmbiIndex::FromACN2D}; } bool CalcContextParams(ContextBase *ctx) { ContextProps *props{ctx->mParams.ContextUpdate.exchange(nullptr, std::memory_order_acq_rel)}; if(!props) return false; const alu::Vector pos{props->Position[0], props->Position[1], props->Position[2], 1.0f}; ctx->mParams.Position = pos; /* AT then UP */ alu::Vector N{props->OrientAt[0], props->OrientAt[1], props->OrientAt[2], 0.0f}; N.normalize(); alu::Vector V{props->OrientUp[0], props->OrientUp[1], props->OrientUp[2], 0.0f}; V.normalize(); /* Build and normalize right-vector */ alu::Vector U{N.cross_product(V)}; U.normalize(); const alu::Matrix rot{ U[0], V[0], -N[0], 0.0, U[1], V[1], -N[1], 0.0, U[2], V[2], -N[2], 0.0, 0.0, 0.0, 0.0, 1.0}; const alu::Vector vel{props->Velocity[0], props->Velocity[1], props->Velocity[2], 0.0}; ctx->mParams.Matrix = rot; ctx->mParams.Velocity = rot * vel; ctx->mParams.Gain = props->Gain * ctx->mGainBoost; ctx->mParams.MetersPerUnit = props->MetersPerUnit #if ALSOFT_EAX * props->DistanceFactor #endif ; ctx->mParams.AirAbsorptionGainHF = props->AirAbsorptionGainHF; ctx->mParams.DopplerFactor = props->DopplerFactor; ctx->mParams.SpeedOfSound = props->SpeedOfSound * props->DopplerVelocity #if ALSOFT_EAX / props->DistanceFactor #endif ; ctx->mParams.SourceDistanceModel = props->SourceDistanceModel; ctx->mParams.mDistanceModel = props->mDistanceModel; AtomicReplaceHead(ctx->mFreeContextProps, props); return true; } bool CalcEffectSlotParams(EffectSlot *slot, EffectSlot **sorted_slots, ContextBase *context) { EffectSlotProps *props{slot->Update.exchange(nullptr, std::memory_order_acq_rel)}; if(!props) return false; /* If the effect slot target changed, clear the first sorted entry to force * a re-sort. */ if(slot->Target != props->Target) *sorted_slots = nullptr; slot->Gain = props->Gain; slot->AuxSendAuto = props->AuxSendAuto; slot->Target = props->Target; slot->EffectType = props->Type; slot->mEffectProps = props->Props; slot->RoomRolloff = 0.0f; slot->DecayTime = 0.0f; slot->DecayLFRatio = 0.0f; slot->DecayHFRatio = 0.0f; slot->DecayHFLimit = false; slot->AirAbsorptionGainHF = 1.0f; if(auto *reverbprops = std::get_if(&props->Props)) { slot->RoomRolloff = reverbprops->RoomRolloffFactor; slot->AirAbsorptionGainHF = reverbprops->AirAbsorptionGainHF; /* If this effect slot's Auxiliary Send Auto is off, don't apply the * automatic send adjustments based on source distance. */ if(slot->AuxSendAuto) { slot->DecayTime = reverbprops->DecayTime; slot->DecayLFRatio = reverbprops->DecayLFRatio; slot->DecayHFRatio = reverbprops->DecayHFRatio; slot->DecayHFLimit = reverbprops->DecayHFLimit; } } EffectState *state{props->State.release()}; EffectState *oldstate{slot->mEffectState.release()}; slot->mEffectState.reset(state); /* Only release the old state if it won't get deleted, since we can't be * deleting/freeing anything in the mixer. */ if(!oldstate->releaseIfNoDelete()) { /* Otherwise, if it would be deleted send it off with a release event. */ RingBuffer *ring{context->mAsyncEvents.get()}; auto evt_vec = ring->getWriteVector(); if(evt_vec[0].len > 0) LIKELY { auto &evt = InitAsyncEvent(evt_vec[0].buf); evt.mEffectState = oldstate; ring->writeAdvance(1); } else { /* If writing the event failed, the queue was probably full. Store * the old state in the property object where it can eventually be * cleaned up sometime later (not ideal, but better than blocking * or leaking). */ props->State.reset(oldstate); } } AtomicReplaceHead(context->mFreeEffectSlotProps, props); const auto output = [slot,context]() -> EffectTarget { if(EffectSlot *target{slot->Target}) return EffectTarget{&target->Wet, nullptr}; DeviceBase *device{context->mDevice}; return EffectTarget{&device->Dry, &device->RealOut}; }(); state->update(context, slot, &slot->mEffectProps, output); return true; } /* Scales the azimuth of the given vector by 3 if it's in front. Effectively * scales +/-30 degrees to +/-90 degrees, leaving > +90 and < -90 alone. */ inline std::array ScaleAzimuthFront3(std::array pos) { if(pos[2] < 0.0f) { /* Normalize the length of the x,z components for a 2D vector of the * azimuth angle. Negate Z since {0,0,-1} is angle 0. */ const float len2d{std::sqrt(pos[0]*pos[0] + pos[2]*pos[2])}; float x{pos[0] / len2d}; float z{-pos[2] / len2d}; /* Z > cos(pi/6) = -30 < azimuth < 30 degrees. */ if(z > 0.866025403785f) { /* Triple the angle represented by x,z. */ x = x*3.0f - x*x*x*4.0f; z = z*z*z*4.0f - z*3.0f; /* Scale the vector back to fit in 3D. */ pos[0] = x * len2d; pos[2] = -z * len2d; } else { /* If azimuth >= 30 degrees, clamp to 90 degrees. */ pos[0] = std::copysign(len2d, pos[0]); pos[2] = 0.0f; } } return pos; } /* Scales the azimuth of the given vector by 1.5 (3/2) if it's in front. */ inline std::array ScaleAzimuthFront3_2(std::array pos) { if(pos[2] < 0.0f) { const float len2d{std::sqrt(pos[0]*pos[0] + pos[2]*pos[2])}; float x{pos[0] / len2d}; float z{-pos[2] / len2d}; /* Z > cos(pi/3) = -60 < azimuth < 60 degrees. */ if(z > 0.5f) { /* Halve the angle represented by x,z. */ x = std::copysign(std::sqrt((1.0f - z) * 0.5f), x); z = std::sqrt((1.0f + z) * 0.5f); /* Triple the angle represented by x,z. */ x = x*3.0f - x*x*x*4.0f; z = z*z*z*4.0f - z*3.0f; /* Scale the vector back to fit in 3D. */ pos[0] = x * len2d; pos[2] = -z * len2d; } else { /* If azimuth >= 60 degrees, clamp to 90 degrees. */ pos[0] = std::copysign(len2d, pos[0]); pos[2] = 0.0f; } } return pos; } /* Begin ambisonic rotation helpers. * * Rotating first-order B-Format just needs a straight-forward X/Y/Z rotation * matrix. Higher orders, however, are more complicated. The method implemented * here is a recursive algorithm (the rotation for first-order is used to help * generate the second-order rotation, which helps generate the third-order * rotation, etc). * * Adapted from * , * provided under the BSD 3-Clause license. * * Copyright (c) 2015, Archontis Politis * Copyright (c) 2019, Christopher Robinson * * The u, v, and w coefficients used for generating higher-order rotations are * precomputed since they're constant. The second-order coefficients are * followed by the third-order coefficients, etc. */ constexpr size_t CalcRotatorSize(size_t l) noexcept { if(l >= 2) return (l*2 + 1)*(l*2 + 1) + CalcRotatorSize(l-1); return 0; } struct RotatorCoeffs { struct CoeffValues { float u, v, w; }; std::array mCoeffs{}; RotatorCoeffs() { auto coeffs = mCoeffs.begin(); for(int l=2;l <= MaxAmbiOrder;++l) { for(int n{-l};n <= l;++n) { for(int m{-l};m <= l;++m) { /* compute u,v,w terms of Eq.8.1 (Table I) * * const bool d{m == 0}; // the delta function d_m0 * const double denom{(std::abs(n) == l) ? * (2*l) * (2*l - 1) : (l*l - n*n)}; * * const int abs_m{std::abs(m)}; * coeffs->u = std::sqrt((l*l - m*m) / denom); * coeffs->v = std::sqrt((l+abs_m-1) * (l+abs_m) / denom) * * (1.0+d) * (1.0 - 2.0*d) * 0.5; * coeffs->w = std::sqrt((l-abs_m-1) * (l-abs_m) / denom) * * (1.0-d) * -0.5; */ const double denom{static_cast((std::abs(n) == l) ? (2*l) * (2*l - 1) : (l*l - n*n))}; if(m == 0) { coeffs->u = static_cast(std::sqrt(l * l / denom)); coeffs->v = static_cast(std::sqrt((l-1) * l / denom) * -1.0); coeffs->w = 0.0f; } else { const int abs_m{std::abs(m)}; coeffs->u = static_cast(std::sqrt((l*l - m*m) / denom)); coeffs->v = static_cast(std::sqrt((l+abs_m-1) * (l+abs_m) / denom) * 0.5); coeffs->w = static_cast(std::sqrt((l-abs_m-1) * (l-abs_m) / denom) * -0.5); } ++coeffs; } } } } }; const RotatorCoeffs RotatorCoeffArray{}; /** * Given the matrix, pre-filled with the (zeroth- and) first-order rotation * coefficients, this fills in the coefficients for the higher orders up to and * including the given order. The matrix is in ACN layout. */ void AmbiRotator(AmbiRotateMatrix &matrix, const int order) { /* Don't do anything for < 2nd order. */ if(order < 2) return; static constexpr auto P = [](const int i, const int l, const int a, const int n, const size_t last_band, const AmbiRotateMatrix &R) { const float ri1{ R[ 1+2][static_cast(i+2_z)]}; const float rim1{R[-1+2][static_cast(i+2_z)]}; const float ri0{ R[ 0+2][static_cast(i+2_z)]}; const size_t y{last_band + static_cast(a+l-1)}; if(n == -l) return ri1*R[last_band][y] + rim1*R[last_band + static_cast(l-1_z)*2][y]; if(n == l) return ri1*R[last_band + static_cast(l-1_z)*2][y] - rim1*R[last_band][y]; return ri0*R[last_band + static_cast(l-1_z+n)][y]; }; static constexpr auto U = [](const int l, const int m, const int n, const size_t last_band, const AmbiRotateMatrix &R) { return P(0, l, m, n, last_band, R); }; static constexpr auto V = [](const int l, const int m, const int n, const size_t last_band, const AmbiRotateMatrix &R) { using namespace al::numbers; if(m > 0) { const bool d{m == 1}; const float p0{P( 1, l, m-1, n, last_band, R)}; const float p1{P(-1, l, -m+1, n, last_band, R)}; return d ? p0*sqrt2_v : (p0 - p1); } const bool d{m == -1}; const float p0{P( 1, l, m+1, n, last_band, R)}; const float p1{P(-1, l, -m-1, n, last_band, R)}; return d ? p1*sqrt2_v : (p0 + p1); }; static constexpr auto W = [](const int l, const int m, const int n, const size_t last_band, const AmbiRotateMatrix &R) { assert(m != 0); if(m > 0) { const float p0{P( 1, l, m+1, n, last_band, R)}; const float p1{P(-1, l, -m-1, n, last_band, R)}; return p0 + p1; } const float p0{P( 1, l, m-1, n, last_band, R)}; const float p1{P(-1, l, -m+1, n, last_band, R)}; return p0 - p1; }; // compute rotation matrix of each subsequent band recursively auto coeffs = RotatorCoeffArray.mCoeffs.cbegin(); size_t band_idx{4}, last_band{1}; for(int l{2};l <= order;++l) { size_t y{band_idx}; for(int n{-l};n <= l;++n,++y) { size_t x{band_idx}; for(int m{-l};m <= l;++m,++x) { float r{0.0f}; // computes Eq.8.1 if(const float u{coeffs->u}; u != 0.0f) r += u * U(l, m, n, last_band, matrix); if(const float v{coeffs->v}; v != 0.0f) r += v * V(l, m, n, last_band, matrix); if(const float w{coeffs->w}; w != 0.0f) r += w * W(l, m, n, last_band, matrix); matrix[y][x] = r; ++coeffs; } } last_band = band_idx; band_idx += static_cast(l)*2_uz + 1; } } /* End ambisonic rotation helpers. */ constexpr float sin30{0.5f}; constexpr float cos30{0.866025403785f}; constexpr float sin45{al::numbers::sqrt2_v*0.5f}; constexpr float cos45{al::numbers::sqrt2_v*0.5f}; constexpr float sin110{ 0.939692620786f}; constexpr float cos110{-0.342020143326f}; struct ChanPosMap { Channel channel; std::array pos; }; struct GainTriplet { float Base, HF, LF; }; void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, const float zpos, const float Distance, const float Spread, const GainTriplet &DryGain, const al::span WetGain, const al::span SendSlots, const VoiceProps *props, const ContextParams &Context, DeviceBase *Device) { static constexpr std::array MonoMap{ ChanPosMap{FrontCenter, std::array{0.0f, 0.0f, -1.0f}} }; static constexpr std::array RearMap{ ChanPosMap{BackLeft, std::array{-sin30, 0.0f, cos30}}, ChanPosMap{BackRight, std::array{ sin30, 0.0f, cos30}}, }; static constexpr std::array QuadMap{ ChanPosMap{FrontLeft, std::array{-sin45, 0.0f, -cos45}}, ChanPosMap{FrontRight, std::array{ sin45, 0.0f, -cos45}}, ChanPosMap{BackLeft, std::array{-sin45, 0.0f, cos45}}, ChanPosMap{BackRight, std::array{ sin45, 0.0f, cos45}}, }; static constexpr std::array X51Map{ ChanPosMap{FrontLeft, std::array{-sin30, 0.0f, -cos30}}, ChanPosMap{FrontRight, std::array{ sin30, 0.0f, -cos30}}, ChanPosMap{FrontCenter, std::array{ 0.0f, 0.0f, -1.0f}}, ChanPosMap{LFE, {}}, ChanPosMap{SideLeft, std::array{-sin110, 0.0f, -cos110}}, ChanPosMap{SideRight, std::array{ sin110, 0.0f, -cos110}}, }; static constexpr std::array X61Map{ ChanPosMap{FrontLeft, std::array{-sin30, 0.0f, -cos30}}, ChanPosMap{FrontRight, std::array{ sin30, 0.0f, -cos30}}, ChanPosMap{FrontCenter, std::array{ 0.0f, 0.0f, -1.0f}}, ChanPosMap{LFE, {}}, ChanPosMap{BackCenter, std::array{ 0.0f, 0.0f, 1.0f}}, ChanPosMap{SideLeft, std::array{-1.0f, 0.0f, 0.0f}}, ChanPosMap{SideRight, std::array{ 1.0f, 0.0f, 0.0f}}, }; static constexpr std::array X71Map{ ChanPosMap{FrontLeft, std::array{-sin30, 0.0f, -cos30}}, ChanPosMap{FrontRight, std::array{ sin30, 0.0f, -cos30}}, ChanPosMap{FrontCenter, std::array{ 0.0f, 0.0f, -1.0f}}, ChanPosMap{LFE, {}}, ChanPosMap{BackLeft, std::array{-sin30, 0.0f, cos30}}, ChanPosMap{BackRight, std::array{ sin30, 0.0f, cos30}}, ChanPosMap{SideLeft, std::array{ -1.0f, 0.0f, 0.0f}}, ChanPosMap{SideRight, std::array{ 1.0f, 0.0f, 0.0f}}, }; std::array StereoMap{ ChanPosMap{FrontLeft, std::array{-sin30, 0.0f, -cos30}}, ChanPosMap{FrontRight, std::array{ sin30, 0.0f, -cos30}}, }; const auto Frequency = static_cast(Device->mSampleRate); const uint NumSends{Device->NumAuxSends}; const size_t num_channels{voice->mChans.size()}; ASSUME(num_channels > 0); for(auto &chandata : voice->mChans) { chandata.mDryParams.Hrtf.Target = HrtfFilter{}; chandata.mDryParams.Gains.Target.fill(0.0f); std::for_each(chandata.mWetParams.begin(), chandata.mWetParams.begin()+NumSends, [](SendParams ¶ms) -> void { params.Gains.Target.fill(0.0f); }); } const auto getChans = [props,&StereoMap](FmtChannels chanfmt) noexcept -> std::pair> { switch(chanfmt) { case FmtMono: /* Mono buffers are never played direct. */ return {DirectMode::Off, al::span{MonoMap}}; case FmtStereo: case FmtMonoDup: if(props->DirectChannels == DirectMode::Off) { for(size_t i{0};i < 2;++i) { /* StereoPan is counter-clockwise in radians. */ const float a{props->StereoPan[i]}; StereoMap[i].pos[0] = -std::sin(a); StereoMap[i].pos[2] = -std::cos(a); } } return {props->DirectChannels, al::span{StereoMap}}; case FmtRear: return {props->DirectChannels, al::span{RearMap}}; case FmtQuad: return {props->DirectChannels, al::span{QuadMap}}; case FmtX51: return {props->DirectChannels, al::span{X51Map}}; case FmtX61: return {props->DirectChannels, al::span{X61Map}}; case FmtX71: return {props->DirectChannels, al::span{X71Map}}; case FmtBFormat2D: case FmtBFormat3D: case FmtUHJ2: case FmtUHJ3: case FmtUHJ4: case FmtSuperStereo: return {DirectMode::Off, {}}; } return {props->DirectChannels, {}}; }; const auto [DirectChannels,chans] = getChans(voice->mFmtChannels); voice->mFlags.reset(VoiceHasHrtf).reset(VoiceHasNfc); if(auto *decoder{voice->mDecoder.get()}) decoder->mWidthControl = std::min(props->EnhWidth, 0.7f); const float lgain{std::min(1.0f-props->Panning, 1.0f)}; const float rgain{std::min(1.0f+props->Panning, 1.0f)}; const float mingain{std::min(lgain, rgain)}; auto SelectChannelGain = [lgain,rgain,mingain](const Channel chan) noexcept { switch(chan) { case FrontLeft: return lgain; case FrontRight: return rgain; case FrontCenter: break; case LFE: break; case BackLeft: return lgain; case BackRight: return rgain; case BackCenter: break; case SideLeft: return lgain; case SideRight: return rgain; case TopCenter: break; case TopFrontLeft: return lgain; case TopFrontCenter: break; case TopFrontRight: return rgain; case TopBackLeft: return lgain; case TopBackCenter: break; case TopBackRight: return rgain; case BottomFrontLeft: return lgain; case BottomFrontRight: return rgain; case BottomBackLeft: return lgain; case BottomBackRight: return rgain; case Aux0: case Aux1: case Aux2: case Aux3: case Aux4: case Aux5: case Aux6: case Aux7: case Aux8: case Aux9: case Aux10: case Aux11: case Aux12: case Aux13: case Aux14: case Aux15: case MaxChannels: break; } return mingain; }; if(IsAmbisonic(voice->mFmtChannels)) { /* Special handling for B-Format and UHJ sources. */ if(Device->AvgSpeakerDist > 0.0f && voice->mFmtChannels != FmtUHJ2 && voice->mFmtChannels != FmtSuperStereo) { if(!(Distance > std::numeric_limits::epsilon())) { /* NOTE: The NFCtrlFilters were created with a w0 of 0, which * is what we want for FOA input. The first channel may have * been previously re-adjusted if panned, so reset it. */ voice->mChans[0].mDryParams.NFCtrlFilter.adjust(0.0f); } else { /* Clamp the distance for really close sources, to prevent * excessive bass. */ const float mdist{std::max(Distance*NfcScale, Device->AvgSpeakerDist/4.0f)}; const float w0{SpeedOfSoundMetersPerSec / (mdist * Frequency)}; /* Only need to adjust the first channel of a B-Format source. */ voice->mChans[0].mDryParams.NFCtrlFilter.adjust(w0); } voice->mFlags.set(VoiceHasNfc); } /* Panning a B-Format sound toward some direction is easy. Just pan the * first (W) channel as a normal mono sound. The angular spread is used * as a directional scalar to blend between full coverage and full * panning. */ const float coverage{!(Distance > std::numeric_limits::epsilon()) ? 1.0f : (al::numbers::inv_pi_v/2.0f * Spread)}; auto calc_coeffs = [xpos,ypos,zpos](RenderMode mode) { if(mode != RenderMode::Pairwise) return CalcDirectionCoeffs(std::array{xpos, ypos, zpos}, 0.0f); const auto pos = ScaleAzimuthFront3_2(std::array{xpos, ypos, zpos}); return CalcDirectionCoeffs(pos, 0.0f); }; const auto scales = GetAmbiScales(voice->mAmbiScaling); auto coeffs = calc_coeffs(Device->mRenderMode); if(!(coverage > 0.0f)) { ComputePanGains(&Device->Dry, coeffs, DryGain.Base*scales[0], voice->mChans[0].mDryParams.Gains.Target); for(uint i{0};i < NumSends;i++) { if(const EffectSlot *Slot{SendSlots[i]}) ComputePanGains(&Slot->Wet, coeffs, WetGain[i].Base*scales[0], voice->mChans[0].mWetParams[i].Gains.Target); } } else { /* Local B-Format sources have their XYZ channels rotated according * to the orientation. */ /* AT then UP */ alu::Vector N{props->OrientAt[0], props->OrientAt[1], props->OrientAt[2], 0.0f}; N.normalize(); alu::Vector V{props->OrientUp[0], props->OrientUp[1], props->OrientUp[2], 0.0f}; V.normalize(); if(!props->HeadRelative) { N = Context.Matrix * N; V = Context.Matrix * V; } /* Build and normalize right-vector */ alu::Vector U{N.cross_product(V)}; U.normalize(); /* Build a rotation matrix. Manually fill the zeroth- and first- * order elements, then construct the rotation for the higher * orders. */ AmbiRotateMatrix &shrot = Device->mAmbiRotateMatrix; shrot.fill(AmbiRotateMatrix::value_type{}); shrot[0][0] = 1.0f; shrot[1][1] = U[0]; shrot[1][2] = -U[1]; shrot[1][3] = U[2]; shrot[2][1] = -V[0]; shrot[2][2] = V[1]; shrot[2][3] = -V[2]; shrot[3][1] = -N[0]; shrot[3][2] = N[1]; shrot[3][3] = -N[2]; AmbiRotator(shrot, static_cast(Device->mAmbiOrder)); /* If the device is higher order than the voice, "upsample" the * matrix. * * NOTE: Starting with second-order, a 2D upsample needs to be * applied with a 2D source and 3D output, even when they're the * same order. This is because higher orders have a height offset * on various channels (i.e. when elevation=0, those height-related * channels should be non-0). */ AmbiRotateMatrix &mixmatrix = Device->mAmbiRotateMatrix2; if(Device->mAmbiOrder > voice->mAmbiOrder || (Device->mAmbiOrder >= 2 && !Device->m2DMixing && Is2DAmbisonic(voice->mFmtChannels))) { if(voice->mAmbiOrder == 1) { const auto upsampler = Is2DAmbisonic(voice->mFmtChannels) ? al::span{AmbiScale::FirstOrder2DUp} : al::span{AmbiScale::FirstOrderUp}; UpsampleBFormatTransform(mixmatrix, upsampler, shrot, Device->mAmbiOrder); } else if(voice->mAmbiOrder == 2) { const auto upsampler = Is2DAmbisonic(voice->mFmtChannels) ? al::span{AmbiScale::SecondOrder2DUp} : al::span{AmbiScale::SecondOrderUp}; UpsampleBFormatTransform(mixmatrix, upsampler, shrot, Device->mAmbiOrder); } else if(voice->mAmbiOrder == 3) { const auto upsampler = Is2DAmbisonic(voice->mFmtChannels) ? al::span{AmbiScale::ThirdOrder2DUp} : al::span{AmbiScale::ThirdOrderUp}; UpsampleBFormatTransform(mixmatrix, upsampler, shrot, Device->mAmbiOrder); } else if(voice->mAmbiOrder == 4) { const auto upsampler = al::span{AmbiScale::FourthOrder2DUp}; UpsampleBFormatTransform(mixmatrix, upsampler, shrot, Device->mAmbiOrder); } else al::unreachable(); } else mixmatrix = shrot; /* Convert the rotation matrix for input ordering and scaling, and * whether input is 2D or 3D. */ const auto index_map = Is2DAmbisonic(voice->mFmtChannels) ? GetAmbi2DLayout(voice->mAmbiLayout).subspan(0) : GetAmbiLayout(voice->mAmbiLayout).subspan(0); /* Scale the panned W signal inversely to coverage (full coverage * means no panned signal), and according to the channel scaling. */ std::for_each(coeffs.begin(), coeffs.end(), [scale=(1.0f-coverage)*scales[0]](float &coeff) noexcept { coeff *= scale; }); for(size_t c{0};c < num_channels;c++) { const size_t acn{index_map[c]}; const float scale{scales[acn] * coverage}; /* For channel 0, combine the B-Format signal (scaled according * to the coverage amount) with the directional pan. For all * other channels, use just the (scaled) B-Format signal. */ std::transform(mixmatrix[acn].cbegin(), mixmatrix[acn].cend(), coeffs.begin(), coeffs.begin(), [scale](const float in, const float coeff) noexcept { return in*scale + coeff; }); ComputePanGains(&Device->Dry, coeffs, DryGain.Base, voice->mChans[c].mDryParams.Gains.Target); for(uint i{0};i < NumSends;i++) { if(const EffectSlot *Slot{SendSlots[i]}) ComputePanGains(&Slot->Wet, coeffs, WetGain[i].Base, voice->mChans[c].mWetParams[i].Gains.Target); } coeffs = std::array{}; } } } else if(DirectChannels != DirectMode::Off && !Device->RealOut.RemixMap.empty()) { /* Direct source channels always play local. Skip the virtual channels * and write inputs to the matching real outputs. */ voice->mDirect.Buffer = Device->RealOut.Buffer; for(size_t c{0};c < num_channels;c++) { const float pangain{SelectChannelGain(chans[c].channel)}; if(uint idx{Device->channelIdxByName(chans[c].channel)}; idx != InvalidChannelIndex) voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain.Base * pangain; else if(DirectChannels == DirectMode::RemixMismatch) { auto match_channel = [channel=chans[c].channel](const InputRemixMap &map) noexcept { return channel == map.channel; }; auto remap = std::find_if(Device->RealOut.RemixMap.cbegin(), Device->RealOut.RemixMap.cend(), match_channel); if(remap != Device->RealOut.RemixMap.cend()) { for(const auto &target : remap->targets) { idx = Device->channelIdxByName(target.channel); if(idx != InvalidChannelIndex) voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain.Base * pangain * target.mix; } } } } /* Auxiliary sends still use normal channel panning since they mix to * B-Format, which can't channel-match. */ for(size_t c{0};c < num_channels;c++) { /* Skip LFE */ if(chans[c].channel == LFE) continue; const float pangain{SelectChannelGain(chans[c].channel)}; const auto coeffs = CalcDirectionCoeffs(chans[c].pos, 0.0f); for(uint i{0};i < NumSends;i++) { if(const EffectSlot *Slot{SendSlots[i]}) ComputePanGains(&Slot->Wet, coeffs, WetGain[i].Base * pangain, voice->mChans[c].mWetParams[i].Gains.Target); } } } else if(Device->mRenderMode == RenderMode::Hrtf) { /* Full HRTF rendering. Skip the virtual channels and render to the * real outputs. */ voice->mDirect.Buffer = Device->RealOut.Buffer; if(Distance > std::numeric_limits::epsilon()) { if(voice->mFmtChannels == FmtMono) { const float src_ev{std::asin(std::clamp(ypos, -1.0f, 1.0f))}; const float src_az{std::atan2(xpos, -zpos)}; Device->mHrtf->getCoeffs(src_ev, src_az, Distance*NfcScale, Spread, voice->mChans[0].mDryParams.Hrtf.Target.Coeffs, voice->mChans[0].mDryParams.Hrtf.Target.Delay); voice->mChans[0].mDryParams.Hrtf.Target.Gain = DryGain.Base; const auto coeffs = CalcDirectionCoeffs(std::array{xpos, ypos, zpos}, Spread); for(uint i{0};i < NumSends;i++) { if(const EffectSlot *Slot{SendSlots[i]}) ComputePanGains(&Slot->Wet, coeffs, WetGain[i].Base, voice->mChans[0].mWetParams[i].Gains.Target); } } else for(size_t c{0};c < num_channels;c++) { /* Skip LFE */ if(chans[c].channel == LFE) continue; const float pangain{SelectChannelGain(chans[c].channel)}; /* Warp the channel position toward the source position as the * source spread decreases. With no spread, all channels are at * the source position, at full spread (pi*2), each channel is * left unchanged. */ const float a{1.0f - (al::numbers::inv_pi_v/2.0f)*Spread}; std::array pos{ lerpf(chans[c].pos[0], xpos, a), lerpf(chans[c].pos[1], ypos, a), lerpf(chans[c].pos[2], zpos, a)}; const float len{std::sqrt(pos[0]*pos[0] + pos[1]*pos[1] + pos[2]*pos[2])}; if(len < 1.0f) { pos[0] /= len; pos[1] /= len; pos[2] /= len; } const float ev{std::asin(std::clamp(pos[1], -1.0f, 1.0f))}; const float az{std::atan2(pos[0], -pos[2])}; Device->mHrtf->getCoeffs(ev, az, Distance*NfcScale, 0.0f, voice->mChans[c].mDryParams.Hrtf.Target.Coeffs, voice->mChans[c].mDryParams.Hrtf.Target.Delay); voice->mChans[c].mDryParams.Hrtf.Target.Gain = DryGain.Base * pangain; const auto coeffs = CalcDirectionCoeffs(pos, 0.0f); for(uint i{0};i < NumSends;i++) { if(const EffectSlot *Slot{SendSlots[i]}) ComputePanGains(&Slot->Wet, coeffs, WetGain[i].Base * pangain, voice->mChans[c].mWetParams[i].Gains.Target); } } } else { /* With no distance, spread is only meaningful for mono sources * where it can be 0 or full (non-mono sources are always full * spread here). */ const float spread{Spread * float(voice->mFmtChannels == FmtMono)}; /* Local sources on HRTF play with each channel panned to its * relative location around the listener, providing "virtual * speaker" responses. */ for(size_t c{0};c < num_channels;c++) { /* Skip LFE */ if(chans[c].channel == LFE) continue; const float pangain{SelectChannelGain(chans[c].channel)}; /* Get the HRIR coefficients and delays for this channel * position. */ const float ev{std::asin(chans[c].pos[1])}; const float az{std::atan2(chans[c].pos[0], -chans[c].pos[2])}; Device->mHrtf->getCoeffs(ev, az, std::numeric_limits::infinity(), spread, voice->mChans[c].mDryParams.Hrtf.Target.Coeffs, voice->mChans[c].mDryParams.Hrtf.Target.Delay); voice->mChans[c].mDryParams.Hrtf.Target.Gain = DryGain.Base * pangain; /* Normal panning for auxiliary sends. */ const auto coeffs = CalcDirectionCoeffs(chans[c].pos, spread); for(uint i{0};i < NumSends;i++) { if(const EffectSlot *Slot{SendSlots[i]}) ComputePanGains(&Slot->Wet, coeffs, WetGain[i].Base * pangain, voice->mChans[c].mWetParams[i].Gains.Target); } } } voice->mFlags.set(VoiceHasHrtf); } else { /* Non-HRTF rendering. Use normal panning to the output. */ if(Distance > std::numeric_limits::epsilon()) { /* Calculate NFC filter coefficient if needed. */ if(Device->AvgSpeakerDist > 0.0f) { /* Clamp the distance for really close sources, to prevent * excessive bass. */ const float mdist{std::max(Distance*NfcScale, Device->AvgSpeakerDist/4.0f)}; const float w0{SpeedOfSoundMetersPerSec / (mdist * Frequency)}; /* Adjust NFC filters. */ for(size_t c{0};c < num_channels;c++) voice->mChans[c].mDryParams.NFCtrlFilter.adjust(w0); voice->mFlags.set(VoiceHasNfc); } if(voice->mFmtChannels == FmtMono) { auto calc_coeffs = [xpos,ypos,zpos,Spread](RenderMode mode) { if(mode != RenderMode::Pairwise) return CalcDirectionCoeffs(std::array{xpos, ypos, zpos}, Spread); const auto pos = ScaleAzimuthFront3_2(std::array{xpos, ypos, zpos}); return CalcDirectionCoeffs(pos, Spread); }; const auto coeffs = calc_coeffs(Device->mRenderMode); ComputePanGains(&Device->Dry, coeffs, DryGain.Base, voice->mChans[0].mDryParams.Gains.Target); for(uint i{0};i < NumSends;i++) { if(const EffectSlot *Slot{SendSlots[i]}) ComputePanGains(&Slot->Wet, coeffs, WetGain[i].Base, voice->mChans[0].mWetParams[i].Gains.Target); } } else for(size_t c{0};c < num_channels;c++) { const auto pangain = SelectChannelGain(chans[c].channel); /* Special-case LFE */ if(chans[c].channel == LFE) { if(Device->Dry.Buffer.data() == Device->RealOut.Buffer.data()) { const auto idx = uint{Device->channelIdxByName(chans[c].channel)}; if(idx != InvalidChannelIndex) voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain.Base * pangain; } continue; } /* Warp the channel position toward the source position as the * spread decreases. With no spread, all channels are at the * source position, at full spread (pi*2), each channel * position is left unchanged. */ const auto a = 1.0f - (al::numbers::inv_pi_v/2.0f)*Spread; auto pos = std::array{ lerpf(chans[c].pos[0], xpos, a), lerpf(chans[c].pos[1], ypos, a), lerpf(chans[c].pos[2], zpos, a)}; const auto len = std::sqrt(pos[0]*pos[0] + pos[1]*pos[1] + pos[2]*pos[2]); if(len < 1.0f) { pos[0] /= len; pos[1] /= len; pos[2] /= len; } if(Device->mRenderMode == RenderMode::Pairwise) pos = ScaleAzimuthFront3(pos); const auto coeffs = CalcDirectionCoeffs(pos, 0.0f); ComputePanGains(&Device->Dry, coeffs, DryGain.Base * pangain, voice->mChans[c].mDryParams.Gains.Target); for(uint i{0};i < NumSends;i++) { if(const EffectSlot *Slot{SendSlots[i]}) ComputePanGains(&Slot->Wet, coeffs, WetGain[i].Base * pangain, voice->mChans[c].mWetParams[i].Gains.Target); } } } else { if(Device->AvgSpeakerDist > 0.0f) { /* If the source distance is 0, simulate a plane-wave by using * infinite distance, which results in a w0 of 0. */ static constexpr float w0{0.0f}; for(size_t c{0};c < num_channels;c++) voice->mChans[c].mDryParams.NFCtrlFilter.adjust(w0); voice->mFlags.set(VoiceHasNfc); } /* With no distance, spread is only meaningful for mono sources * where it can be 0 or full (non-mono sources are always full * spread here). */ const float spread{Spread * float(voice->mFmtChannels == FmtMono)}; for(size_t c{0};c < num_channels;c++) { const float pangain{SelectChannelGain(chans[c].channel)}; /* Special-case LFE */ if(chans[c].channel == LFE) { if(Device->Dry.Buffer.data() == Device->RealOut.Buffer.data()) { const uint idx{Device->channelIdxByName(chans[c].channel)}; if(idx != InvalidChannelIndex) voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain.Base * pangain; } continue; } const auto coeffs = CalcDirectionCoeffs((Device->mRenderMode==RenderMode::Pairwise) ? ScaleAzimuthFront3(chans[c].pos) : chans[c].pos, spread); ComputePanGains(&Device->Dry, coeffs, DryGain.Base * pangain, voice->mChans[c].mDryParams.Gains.Target); for(uint i{0};i < NumSends;i++) { if(const EffectSlot *Slot{SendSlots[i]}) ComputePanGains(&Slot->Wet, coeffs, WetGain[i].Base * pangain, voice->mChans[c].mWetParams[i].Gains.Target); } } } } { const float hfNorm{props->Direct.HFReference / Frequency}; const float lfNorm{props->Direct.LFReference / Frequency}; voice->mDirect.FilterType = AF_None; if(DryGain.HF != 1.0f) voice->mDirect.FilterType |= AF_LowPass; if(DryGain.LF != 1.0f) voice->mDirect.FilterType |= AF_HighPass; auto &lowpass = voice->mChans[0].mDryParams.LowPass; auto &highpass = voice->mChans[0].mDryParams.HighPass; lowpass.setParamsFromSlope(BiquadType::HighShelf, hfNorm, DryGain.HF, 1.0f); highpass.setParamsFromSlope(BiquadType::LowShelf, lfNorm, DryGain.LF, 1.0f); for(size_t c{1};c < num_channels;c++) { voice->mChans[c].mDryParams.LowPass.copyParamsFrom(lowpass); voice->mChans[c].mDryParams.HighPass.copyParamsFrom(highpass); } } for(uint i{0};i < NumSends;i++) { const float hfNorm{props->Send[i].HFReference / Frequency}; const float lfNorm{props->Send[i].LFReference / Frequency}; voice->mSend[i].FilterType = AF_None; if(WetGain[i].HF != 1.0f) voice->mSend[i].FilterType |= AF_LowPass; if(WetGain[i].LF != 1.0f) voice->mSend[i].FilterType |= AF_HighPass; auto &lowpass = voice->mChans[0].mWetParams[i].LowPass; auto &highpass = voice->mChans[0].mWetParams[i].HighPass; lowpass.setParamsFromSlope(BiquadType::HighShelf, hfNorm, WetGain[i].HF, 1.0f); highpass.setParamsFromSlope(BiquadType::LowShelf, lfNorm, WetGain[i].LF, 1.0f); for(size_t c{1};c < num_channels;c++) { voice->mChans[c].mWetParams[i].LowPass.copyParamsFrom(lowpass); voice->mChans[c].mWetParams[i].HighPass.copyParamsFrom(highpass); } } } void CalcNonAttnSourceParams(Voice *voice, const VoiceProps *props, const ContextBase *context) { DeviceBase *Device{context->mDevice}; std::array SendSlots{}; voice->mDirect.Buffer = Device->Dry.Buffer; for(uint i{0};i < Device->NumAuxSends;i++) { SendSlots[i] = props->Send[i].Slot; if(!SendSlots[i] || SendSlots[i]->EffectType == EffectSlotType::None) { SendSlots[i] = nullptr; voice->mSend[i].Buffer = {}; } else voice->mSend[i].Buffer = SendSlots[i]->Wet.Buffer; } /* Calculate the stepping value */ const auto Pitch = static_cast(voice->mFrequency) / static_cast(Device->mSampleRate) * props->Pitch; if(Pitch > float{MaxPitch}) voice->mStep = MaxPitch<mStep = std::max(fastf2u(Pitch * MixerFracOne), 1u); voice->mResampler = PrepareResampler(props->mResampler, voice->mStep, &voice->mResampleState); /* Calculate gains */ GainTriplet DryGain{}; DryGain.Base = std::min(std::clamp(props->Gain, props->MinGain, props->MaxGain) * props->Direct.Gain * context->mParams.Gain, GainMixMax); DryGain.HF = props->Direct.GainHF; DryGain.LF = props->Direct.GainLF; std::array WetGain{}; for(uint i{0};i < Device->NumAuxSends;i++) { WetGain[i].Base = std::min(std::clamp(props->Gain, props->MinGain, props->MaxGain) * props->Send[i].Gain * context->mParams.Gain, GainMixMax); WetGain[i].HF = props->Send[i].GainHF; WetGain[i].LF = props->Send[i].GainLF; } CalcPanningAndFilters(voice, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, DryGain, WetGain, SendSlots, props, context->mParams, Device); } void CalcAttnSourceParams(Voice *voice, const VoiceProps *props, const ContextBase *context) { DeviceBase *Device{context->mDevice}; const uint NumSends{Device->NumAuxSends}; /* Set mixing buffers and get send parameters. */ voice->mDirect.Buffer = Device->Dry.Buffer; std::array SendSlots{}; std::array RoomRolloff{}; for(uint i{0};i < NumSends;i++) { SendSlots[i] = props->Send[i].Slot; if(!SendSlots[i] || SendSlots[i]->EffectType == EffectSlotType::None) { SendSlots[i] = nullptr; voice->mSend[i].Buffer = {}; } else { /* NOTE: Contrary to the EFX docs, the effect's room rolloff factor * applies to the selected distance model along with the source's * room rolloff factor, not necessarily the inverse distance model. */ RoomRolloff[i] = props->RoomRolloffFactor + SendSlots[i]->RoomRolloff; voice->mSend[i].Buffer = SendSlots[i]->Wet.Buffer; } } /* Transform source to listener space (convert to head relative) */ alu::Vector Position{props->Position[0], props->Position[1], props->Position[2], 1.0f}; alu::Vector Velocity{props->Velocity[0], props->Velocity[1], props->Velocity[2], 0.0f}; alu::Vector Direction{props->Direction[0], props->Direction[1], props->Direction[2], 0.0f}; if(!props->HeadRelative) { /* Transform source vectors */ Position = context->mParams.Matrix * (Position - context->mParams.Position); Velocity = context->mParams.Matrix * Velocity; Direction = context->mParams.Matrix * Direction; } else { /* Offset the source velocity to be relative of the listener velocity */ Velocity += context->mParams.Velocity; } const bool directional{Direction.normalize() > 0.0f}; alu::Vector ToSource{Position[0], Position[1], Position[2], 0.0f}; const float Distance{ToSource.normalize()}; /* Calculate distance attenuation */ float ClampedDist{Distance}; float DryGainBase{props->Gain}; std::array WetGainBase{}; WetGainBase.fill(props->Gain); float DryAttnBase{1.0f}; switch(context->mParams.SourceDistanceModel ? props->mDistanceModel : context->mParams.mDistanceModel) { case DistanceModel::InverseClamped: if(props->MaxDistance < props->RefDistance) break; ClampedDist = std::clamp(ClampedDist, props->RefDistance, props->MaxDistance); /*fall-through*/ case DistanceModel::Inverse: if(props->RefDistance > 0.0f) { float dist{lerpf(props->RefDistance, ClampedDist, props->RolloffFactor)}; if(dist > 0.0f) { DryAttnBase = props->RefDistance / dist; DryGainBase *= DryAttnBase; } for(size_t i{0};i < NumSends;++i) { dist = lerpf(props->RefDistance, ClampedDist, RoomRolloff[i]); if(dist > 0.0f) WetGainBase[i] *= props->RefDistance / dist; } } break; case DistanceModel::LinearClamped: if(props->MaxDistance < props->RefDistance) break; ClampedDist = std::clamp(ClampedDist, props->RefDistance, props->MaxDistance); /*fall-through*/ case DistanceModel::Linear: if(props->MaxDistance != props->RefDistance) { float attn{(ClampedDist-props->RefDistance) / (props->MaxDistance-props->RefDistance) * props->RolloffFactor}; DryAttnBase = std::max(1.0f - attn, 0.0f); DryGainBase *= DryAttnBase; for(size_t i{0};i < NumSends;++i) { attn = (ClampedDist-props->RefDistance) / (props->MaxDistance-props->RefDistance) * RoomRolloff[i]; WetGainBase[i] *= std::max(1.0f - attn, 0.0f); } } break; case DistanceModel::ExponentClamped: if(props->MaxDistance < props->RefDistance) break; ClampedDist = std::clamp(ClampedDist, props->RefDistance, props->MaxDistance); /*fall-through*/ case DistanceModel::Exponent: if(ClampedDist > 0.0f && props->RefDistance > 0.0f) { const float dist_ratio{ClampedDist/props->RefDistance}; DryAttnBase = std::pow(dist_ratio, -props->RolloffFactor); DryGainBase *= DryAttnBase; for(size_t i{0};i < NumSends;++i) WetGainBase[i] *= std::pow(dist_ratio, -RoomRolloff[i]); } break; case DistanceModel::Disable: break; } /* Calculate directional soundcones */ float ConeHF{1.0f}, WetCone{1.0f}, WetConeHF{1.0f}; if(directional && props->InnerAngle < 360.0f) { static constexpr float Rad2Deg{static_cast(180.0 / al::numbers::pi)}; const float Angle{Rad2Deg*2.0f * std::acos(-Direction.dot_product(ToSource)) * ConeScale}; float ConeGain{1.0f}; if(Angle >= props->OuterAngle) { ConeGain = props->OuterGain; if(props->DryGainHFAuto) ConeHF = props->OuterGainHF; } else if(Angle >= props->InnerAngle) { const float scale{(Angle-props->InnerAngle) / (props->OuterAngle-props->InnerAngle)}; ConeGain = lerpf(1.0f, props->OuterGain, scale); if(props->DryGainHFAuto) ConeHF = lerpf(1.0f, props->OuterGainHF, scale); } DryGainBase *= ConeGain; if(props->WetGainAuto) WetCone = ConeGain; if(props->WetGainHFAuto) WetConeHF = ConeHF; } /* Apply gain and frequency filters */ GainTriplet DryGain{}; DryGainBase = std::clamp(DryGainBase, props->MinGain, props->MaxGain) * context->mParams.Gain; DryGain.Base = std::min(DryGainBase * props->Direct.Gain, GainMixMax); DryGain.HF = ConeHF * props->Direct.GainHF; DryGain.LF = props->Direct.GainLF; std::array WetGain{}; for(uint i{0};i < NumSends;i++) { const auto gain = std::clamp(WetGainBase[i]*WetCone, props->MinGain, props->MaxGain) * context->mParams.Gain; WetGain[i].Base = std::min(gain * props->Send[i].Gain, GainMixMax); WetGain[i].HF = WetConeHF * props->Send[i].GainHF; WetGain[i].LF = props->Send[i].GainLF; } /* Distance-based air absorption and initial send decay. */ if(Distance > props->RefDistance) LIKELY { /* FIXME: In keeping with EAX, the base air absorption gain should be * taken from the reverb property in the "primary fx slot" when it has * a reverb effect and the environment flag set, and be applied to the * direct path and all environment sends, rather than each path using * the air absorption gain associated with the given slot's effect. At * this point in the mixer, and even in EFX itself, there's no concept * of a "primary fx slot" so it's unclear which effect slot should be * checked. * * The HF reference is also intended to be handled the same way, but * again, there's no concept of a "primary fx slot" here and no way to * know which effect slot to look at for the reference frequency. */ const auto distance_units = float{(Distance-props->RefDistance) * props->RolloffFactor}; const auto distance_meters = float{distance_units * context->mParams.MetersPerUnit}; const auto absorb = float{distance_meters * props->AirAbsorptionFactor}; if(absorb > std::numeric_limits::epsilon()) DryGain.HF *= std::pow(context->mParams.AirAbsorptionGainHF, absorb); /* If the source's Auxiliary Send Filter Gain Auto is off, no extra * adjustment is applied to the send gains. */ for(uint i{props->WetGainAuto ? 0u : NumSends};i < NumSends;++i) { if(!SendSlots[i] || !(SendSlots[i]->DecayTime > 0.0f)) continue; if(SendSlots[i]->AirAbsorptionGainHF < 1.0f && absorb > std::numeric_limits::epsilon()) WetGain[i].HF *= std::pow(SendSlots[i]->AirAbsorptionGainHF, absorb); const float DecayDistance{SendSlots[i]->DecayTime * SpeedOfSoundMetersPerSec}; /* Apply a decay-time transformation to the wet path, based on the * source distance. The initial decay of the reverb effect is * calculated and applied to the wet path. * * FIXME: This is very likely not correct. It more likely should * work by calculating a rolloff dynamically based on the reverb * parameters (and source distance?) and add it to the room rolloff * with the reverb and source rolloff parameters. */ const float baseAttn{DryAttnBase}; const float fact{distance_meters / DecayDistance}; const float gain{std::pow(ReverbDecayGain, fact)*(1.0f-baseAttn) + baseAttn}; WetGain[i].Base *= gain; } } /* Initial source pitch */ float Pitch{props->Pitch}; /* Calculate velocity-based doppler effect */ float DopplerFactor{props->DopplerFactor * context->mParams.DopplerFactor}; if(DopplerFactor > 0.0f) { const alu::Vector &lvelocity = context->mParams.Velocity; float vss{Velocity.dot_product(ToSource) * -DopplerFactor}; float vls{lvelocity.dot_product(ToSource) * -DopplerFactor}; const float SpeedOfSound{context->mParams.SpeedOfSound}; if(!(vls < SpeedOfSound)) { /* Listener moving away from the source at the speed of sound. * Sound waves can't catch it. */ Pitch = 0.0f; } else if(!(vss < SpeedOfSound)) { /* Source moving toward the listener at the speed of sound. Sound * waves bunch up to extreme frequencies. */ Pitch = std::numeric_limits::infinity(); } else { /* Source and listener movement is nominal. Calculate the proper * doppler shift. */ Pitch *= (SpeedOfSound-vls) / (SpeedOfSound-vss); } } /* Adjust pitch based on the buffer and output frequencies, and calculate * fixed-point stepping value. */ Pitch *= static_cast(voice->mFrequency) / static_cast(Device->mSampleRate); if(Pitch > float{MaxPitch}) voice->mStep = MaxPitch<mStep = std::max(fastf2u(Pitch * MixerFracOne), 1u); voice->mResampler = PrepareResampler(props->mResampler, voice->mStep, &voice->mResampleState); float spread{0.0f}; if(props->Radius > Distance) spread = al::numbers::pi_v*2.0f - Distance/props->Radius*al::numbers::pi_v; else if(Distance > 0.0f) spread = std::asin(props->Radius/Distance) * 2.0f; CalcPanningAndFilters(voice, ToSource[0]*XScale, ToSource[1]*YScale, ToSource[2]*ZScale, Distance, spread, DryGain, WetGain, SendSlots, props, context->mParams, Device); } void CalcSourceParams(Voice *voice, ContextBase *context, bool force) { VoicePropsItem *props{voice->mUpdate.exchange(nullptr, std::memory_order_acq_rel)}; if(!props && !force) return; if(props) { voice->mProps = static_cast(*props); AtomicReplaceHead(context->mFreeVoiceProps, props); } if((voice->mProps.DirectChannels != DirectMode::Off && voice->mFmtChannels != FmtMono && !IsAmbisonic(voice->mFmtChannels)) || voice->mProps.mSpatializeMode == SpatializeMode::Off || (voice->mProps.mSpatializeMode==SpatializeMode::Auto && voice->mFmtChannels != FmtMono)) CalcNonAttnSourceParams(voice, &voice->mProps, context); else CalcAttnSourceParams(voice, &voice->mProps, context); } void SendSourceStateEvent(ContextBase *context, uint id, VChangeState state) { RingBuffer *ring{context->mAsyncEvents.get()}; auto evt_vec = ring->getWriteVector(); if(evt_vec[0].len < 1) return; auto &evt = InitAsyncEvent(evt_vec[0].buf); evt.mId = id; switch(state) { case VChangeState::Reset: evt.mState = AsyncSrcState::Reset; break; case VChangeState::Stop: evt.mState = AsyncSrcState::Stop; break; case VChangeState::Play: evt.mState = AsyncSrcState::Play; break; case VChangeState::Pause: evt.mState = AsyncSrcState::Pause; break; /* Shouldn't happen. */ case VChangeState::Restart: al::unreachable(); } ring->writeAdvance(1); } void ProcessVoiceChanges(ContextBase *ctx) { VoiceChange *cur{ctx->mCurrentVoiceChange.load(std::memory_order_acquire)}; VoiceChange *next{cur->mNext.load(std::memory_order_acquire)}; if(!next) return; const auto enabledevt = ctx->mEnabledEvts.load(std::memory_order_acquire); do { cur = next; bool sendevt{false}; if(cur->mState == VChangeState::Reset || cur->mState == VChangeState::Stop) { if(Voice *voice{cur->mVoice}) { voice->mCurrentBuffer.store(nullptr, std::memory_order_relaxed); voice->mLoopBuffer.store(nullptr, std::memory_order_relaxed); /* A source ID indicates the voice was playing or paused, which * gets a reset/stop event. */ sendevt = voice->mSourceID.exchange(0u, std::memory_order_relaxed) != 0u; Voice::State oldvstate{Voice::Playing}; voice->mPlayState.compare_exchange_strong(oldvstate, Voice::Stopping, std::memory_order_relaxed, std::memory_order_acquire); voice->mPendingChange.store(false, std::memory_order_release); } /* Reset state change events are always sent, even if the voice is * already stopped or even if there is no voice. */ sendevt |= (cur->mState == VChangeState::Reset); } else if(cur->mState == VChangeState::Pause) { Voice *voice{cur->mVoice}; Voice::State oldvstate{Voice::Playing}; sendevt = voice->mPlayState.compare_exchange_strong(oldvstate, Voice::Stopping, std::memory_order_release, std::memory_order_acquire); } else if(cur->mState == VChangeState::Play) { /* NOTE: When playing a voice, sending a source state change event * depends if there's an old voice to stop and if that stop is * successful. If there is no old voice, a playing event is always * sent. If there is an old voice, an event is sent only if the * voice is already stopped. */ if(Voice *oldvoice{cur->mOldVoice}) { oldvoice->mCurrentBuffer.store(nullptr, std::memory_order_relaxed); oldvoice->mLoopBuffer.store(nullptr, std::memory_order_relaxed); oldvoice->mSourceID.store(0u, std::memory_order_relaxed); Voice::State oldvstate{Voice::Playing}; sendevt = !oldvoice->mPlayState.compare_exchange_strong(oldvstate, Voice::Stopping, std::memory_order_relaxed, std::memory_order_acquire); oldvoice->mPendingChange.store(false, std::memory_order_release); } else sendevt = true; Voice *voice{cur->mVoice}; voice->mPlayState.store(Voice::Playing, std::memory_order_release); } else if(cur->mState == VChangeState::Restart) { /* Restarting a voice never sends a source change event. */ Voice *oldvoice{cur->mOldVoice}; oldvoice->mCurrentBuffer.store(nullptr, std::memory_order_relaxed); oldvoice->mLoopBuffer.store(nullptr, std::memory_order_relaxed); /* If there's no sourceID, the old voice finished so don't start * the new one at its new offset. */ if(oldvoice->mSourceID.exchange(0u, std::memory_order_relaxed) != 0u) { /* Otherwise, set the voice to stopping if it's not already (it * might already be, if paused), and play the new voice as * appropriate. */ Voice::State oldvstate{Voice::Playing}; oldvoice->mPlayState.compare_exchange_strong(oldvstate, Voice::Stopping, std::memory_order_relaxed, std::memory_order_acquire); Voice *voice{cur->mVoice}; voice->mPlayState.store((oldvstate == Voice::Playing) ? Voice::Playing : Voice::Stopped, std::memory_order_release); } oldvoice->mPendingChange.store(false, std::memory_order_release); } if(sendevt && enabledevt.test(al::to_underlying(AsyncEnableBits::SourceState))) SendSourceStateEvent(ctx, cur->mSourceID, cur->mState); next = cur->mNext.load(std::memory_order_acquire); } while(next); ctx->mCurrentVoiceChange.store(cur, std::memory_order_release); } void ProcessParamUpdates(ContextBase *ctx, const al::span slots, const al::span sorted_slots, const al::span voices) { ProcessVoiceChanges(ctx); IncrementRef(ctx->mUpdateCount); if(!ctx->mHoldUpdates.load(std::memory_order_acquire)) LIKELY { bool force{CalcContextParams(ctx)}; auto sorted_slot_base = al::to_address(sorted_slots.begin()); for(EffectSlot *slot : slots) force |= CalcEffectSlotParams(slot, sorted_slot_base, ctx); for(Voice *voice : voices) { /* Only update voices that have a source. */ if(voice->mSourceID.load(std::memory_order_relaxed) != 0) CalcSourceParams(voice, ctx, force); } } IncrementRef(ctx->mUpdateCount); } void ProcessContexts(DeviceBase *device, const uint SamplesToDo) { ASSUME(SamplesToDo > 0); const auto curtime = device->getClockTime(); auto proc_context = [SamplesToDo,curtime](ContextBase *ctx) { const auto auxslotspan = al::span{*ctx->mActiveAuxSlots.load(std::memory_order_acquire)}; const auto auxslots = auxslotspan.first(auxslotspan.size()>>1); const auto sorted_slots = auxslotspan.last(auxslotspan.size()>>1); const auto voices = ctx->getVoicesSpanAcquired(); /* Process pending property updates for objects on the context. */ ProcessParamUpdates(ctx, auxslots, sorted_slots, voices); /* Clear auxiliary effect slot mixing buffers. */ auto clear_wetbuffers = [](EffectSlot *slot) { auto clear_buffer = [](const FloatBufferSpan buffer) { std::fill(buffer.begin(), buffer.end(), 0.0f); }; std::for_each(slot->Wet.Buffer.begin(), slot->Wet.Buffer.end(), clear_buffer); }; std::for_each(auxslots.begin(), auxslots.end(), clear_wetbuffers); /* Process voices that have a playing source. */ auto proc_voice = [ctx,curtime,SamplesToDo](Voice *voice) { const Voice::State vstate{voice->mPlayState.load(std::memory_order_acquire)}; if(vstate != Voice::Stopped && vstate != Voice::Pending) voice->mix(vstate, ctx, curtime, SamplesToDo); }; std::for_each(voices.begin(), voices.end(), proc_voice); /* Process effects. */ if(!auxslots.empty()) { /* Sort the slots into extra storage, so that effect slots come * before their effect slot target (or their targets' target). Skip * sorting if it has already been done. */ if(!sorted_slots[0]) { /* First, copy the slots to the sorted list and partition them, * so that all slots without a target slot go to the end. */ auto has_target = [](const EffectSlot *slot) noexcept -> bool { return slot->Target != nullptr; }; auto split_point = std::partition_copy(auxslots.rbegin(), auxslots.rend(), sorted_slots.begin(), sorted_slots.rbegin(), has_target).first; /* There must be at least one slot without a slot target. */ assert(split_point != sorted_slots.end()); /* Starting from the back of the sorted list, continue * partitioning the front of the list given each target until * all targets are accounted for. This ensures all slots * without a target go last, all slots directly targeting those * last slots go second-to-last, all slots directly targeting * those second-last slots go third-to-last, etc. */ auto next_target = sorted_slots.end(); while(std::distance(sorted_slots.begin(), split_point) > 1) { /* This shouldn't happen, but if there's unsorted slots * left that don't target any sorted slots, they can't * contribute to the output, so leave them. */ if(next_target == split_point) UNLIKELY break; --next_target; auto not_next = [next_target](const EffectSlot *slot) noexcept -> bool { return slot->Target != *next_target; }; split_point = std::partition(sorted_slots.begin(), split_point, not_next); } } auto proc_slot = [SamplesToDo](const EffectSlot *slot) { EffectState *state{slot->mEffectState.get()}; state->process(SamplesToDo, slot->Wet.Buffer, state->mOutTarget); }; std::for_each(sorted_slots.begin(), sorted_slots.end(), proc_slot); } /* Signal the event handler if there are any events to read. */ if(RingBuffer *ring{ctx->mAsyncEvents.get()}; ring->readSpace() > 0) ctx->mEventSem.post(); }; const auto contexts = al::span{*device->mContexts.load(std::memory_order_acquire)}; std::for_each(contexts.begin(), contexts.end(), proc_context); } void ApplyDistanceComp(const al::span Samples, const size_t SamplesToDo, const al::span chandata) { ASSUME(SamplesToDo > 0); auto distcomp = chandata.begin(); for(auto &chanbuffer : Samples) { const float gain{distcomp->Gain}; auto distbuf = al::span{al::assume_aligned<16>(distcomp->Buffer.data()), distcomp->Buffer.size()}; ++distcomp; const size_t base{distbuf.size()}; if(base < 1) continue; const auto inout = al::span{al::assume_aligned<16>(chanbuffer.data()), SamplesToDo}; if(SamplesToDo >= base) LIKELY { auto delay_end = std::rotate(inout.begin(), inout.end()-ptrdiff_t(base), inout.end()); std::swap_ranges(inout.begin(), delay_end, distbuf.begin()); } else { auto delay_start = std::swap_ranges(inout.begin(), inout.end(), distbuf.begin()); std::rotate(distbuf.begin(), delay_start, distbuf.begin()+ptrdiff_t(base)); } std::transform(inout.begin(), inout.end(), inout.begin(), [gain](float s) { return s*gain; }); } } void ApplyDither(const al::span Samples, uint *dither_seed, const float quant_scale, const size_t SamplesToDo) { static constexpr double invRNGRange{1.0 / std::numeric_limits::max()}; ASSUME(SamplesToDo > 0); /* Dithering. Generate whitenoise (uniform distribution of random values * between -1 and +1) and add it to the sample values, after scaling up to * the desired quantization depth and before rounding. */ const float invscale{1.0f / quant_scale}; uint seed{*dither_seed}; auto dither_sample = [&seed,invscale,quant_scale](const float sample) noexcept -> float { float val{sample * quant_scale}; uint rng0{dither_rng(&seed)}; uint rng1{dither_rng(&seed)}; val += static_cast(rng0*invRNGRange - rng1*invRNGRange); return fast_roundf(val) * invscale; }; for(FloatBufferLine &inout : Samples) std::transform(inout.begin(), inout.begin()+SamplesToDo, inout.begin(), dither_sample); *dither_seed = seed; } /* Base template left undefined. Should be marked =delete, but Clang 3.8.1 * chokes on that given the inline specializations. */ template inline T SampleConv(float) noexcept; template<> inline float SampleConv(float val) noexcept { return val; } template<> inline int32_t SampleConv(float val) noexcept { /* Floats have a 23-bit mantissa, plus an implied 1 bit and a sign bit. * This means a normalized float has at most 25 bits of signed precision. * When scaling and clamping for a signed 32-bit integer, these following * values are the best a float can give. */ return fastf2i(std::clamp(val*2147483648.0f, -2147483648.0f, 2147483520.0f)); } template<> inline int16_t SampleConv(float val) noexcept { return static_cast(fastf2i(std::clamp(val*32768.0f, -32768.0f, 32767.0f))); } template<> inline int8_t SampleConv(float val) noexcept { return static_cast(fastf2i(std::clamp(val*128.0f, -128.0f, 127.0f))); } /* Define unsigned output variations. */ template<> inline uint32_t SampleConv(float val) noexcept { return static_cast(SampleConv(val)) + 2147483648u; } template<> inline uint16_t SampleConv(float val) noexcept { return static_cast(SampleConv(val) + 32768); } template<> inline uint8_t SampleConv(float val) noexcept { return static_cast(SampleConv(val) + 128); } template void Write(const al::span InBuffer, void *OutBuffer, const size_t Offset, const size_t SamplesToDo, const size_t FrameStep) { ASSUME(FrameStep > 0); ASSUME(SamplesToDo > 0); /* Some Clang versions don't like calling subspan on an rvalue here. */ const auto output_ = al::span{static_cast(OutBuffer), (Offset+SamplesToDo)*FrameStep}; const auto output = output_.subspan(Offset*FrameStep); size_t c{0}; for(const FloatBufferLine &inbuf : InBuffer) { auto out = output.begin(); auto conv_sample = [FrameStep,c,&out](const float s) noexcept { out[c] = SampleConv(s); out += ptrdiff_t(FrameStep); }; std::for_each_n(inbuf.cbegin(), SamplesToDo, conv_sample); ++c; } if(const size_t extra{FrameStep - c}) { const auto silence = SampleConv(0.0f); for(size_t i{0};i < SamplesToDo;++i) std::fill_n(&output[i*FrameStep + c], extra, silence); } } template void Write(const al::span InBuffer, al::span OutBuffers, const size_t Offset, const size_t SamplesToDo) { ASSUME(SamplesToDo > 0); auto srcbuf = InBuffer.cbegin(); for(auto *dstbuf : OutBuffers) { const auto src = al::span{*srcbuf}.first(SamplesToDo); /* Some Clang versions don't like calling subspan on an rvalue here. */ const auto dst_ = al::span{static_cast(dstbuf), Offset+SamplesToDo}; const auto dst = dst_.subspan(Offset); std::transform(src.cbegin(), src.end(), dst.begin(), SampleConv); ++srcbuf; } } } // namespace uint DeviceBase::renderSamples(const uint numSamples) { const uint samplesToDo{std::min(numSamples, uint{BufferLineSize})}; /* Clear main mixing buffers. */ for(FloatBufferLine &buffer : MixBuffer) buffer.fill(0.0f); { const auto mixLock = getWriteMixLock(); /* Process and mix each context's sources and effects. */ ProcessContexts(this, samplesToDo); /* Every second's worth of samples is converted and added to clock base * so that large sample counts don't overflow during conversion. This * also guarantees a stable conversion. */ auto samplesDone = mSamplesDone.load(std::memory_order_relaxed) + samplesToDo; auto clockBaseSec = mClockBaseSec.load(std::memory_order_relaxed) + seconds32{samplesDone/mSampleRate}; mSamplesDone.store(samplesDone%mSampleRate, std::memory_order_relaxed); mClockBaseSec.store(clockBaseSec, std::memory_order_relaxed); } /* Apply any needed post-process for finalizing the Dry mix to the RealOut * (Ambisonic decode, UHJ encode, etc). */ postProcess(samplesToDo); /* Apply compression, limiting sample amplitude if needed or desired. */ if(Limiter) Limiter->process(samplesToDo, RealOut.Buffer); /* Apply delays and attenuation for mismatched speaker distances. */ if(ChannelDelays) ApplyDistanceComp(RealOut.Buffer, samplesToDo, ChannelDelays->mChannels); /* Apply dithering. The compressor should have left enough headroom for the * dither noise to not saturate. */ if(DitherDepth > 0.0f) ApplyDither(RealOut.Buffer, &DitherSeed, DitherDepth, samplesToDo); return samplesToDo; } void DeviceBase::renderSamples(const al::span outBuffers, const uint numSamples) { FPUCtl mixer_mode{}; uint total{0}; while(const uint todo{numSamples - total}) { const uint samplesToDo{renderSamples(todo)}; switch(FmtType) { #define HANDLE_WRITE(T) case T: \ Write>(RealOut.Buffer, outBuffers, total, samplesToDo); break; HANDLE_WRITE(DevFmtByte) HANDLE_WRITE(DevFmtUByte) HANDLE_WRITE(DevFmtShort) HANDLE_WRITE(DevFmtUShort) HANDLE_WRITE(DevFmtInt) HANDLE_WRITE(DevFmtUInt) HANDLE_WRITE(DevFmtFloat) } #undef HANDLE_WRITE total += samplesToDo; } } void DeviceBase::renderSamples(void *outBuffer, const uint numSamples, const size_t frameStep) { FPUCtl mixer_mode{}; uint total{0}; while(const uint todo{numSamples - total}) { const uint samplesToDo{renderSamples(todo)}; if(outBuffer) LIKELY { /* Finally, interleave and convert samples, writing to the device's * output buffer. */ switch(FmtType) { #define HANDLE_WRITE(T) case T: \ Write>(RealOut.Buffer, outBuffer, total, samplesToDo, frameStep); break; HANDLE_WRITE(DevFmtByte) HANDLE_WRITE(DevFmtUByte) HANDLE_WRITE(DevFmtShort) HANDLE_WRITE(DevFmtUShort) HANDLE_WRITE(DevFmtInt) HANDLE_WRITE(DevFmtUInt) HANDLE_WRITE(DevFmtFloat) #undef HANDLE_WRITE } } total += samplesToDo; } } void DeviceBase::doDisconnect(std::string msg) { const auto mixLock = getWriteMixLock(); if(Connected.exchange(false, std::memory_order_acq_rel)) { AsyncEvent evt{std::in_place_type}; auto &disconnect = std::get(evt); disconnect.msg = std::move(msg); for(ContextBase *ctx : *mContexts.load()) { RingBuffer *ring{ctx->mAsyncEvents.get()}; auto evt_data = ring->getWriteVector()[0]; if(evt_data.len > 0) { al::construct_at(reinterpret_cast(evt_data.buf), evt); ring->writeAdvance(1); ctx->mEventSem.post(); } if(!ctx->mStopVoicesOnDisconnect.load()) { ProcessVoiceChanges(ctx); continue; } auto voicelist = ctx->getVoicesSpanAcquired(); auto stop_voice = [](Voice *voice) -> void { voice->mCurrentBuffer.store(nullptr, std::memory_order_relaxed); voice->mLoopBuffer.store(nullptr, std::memory_order_relaxed); voice->mSourceID.store(0u, std::memory_order_relaxed); voice->mPlayState.store(Voice::Stopped, std::memory_order_release); }; std::for_each(voicelist.begin(), voicelist.end(), stop_voice); } } } openal-soft-1.24.2/alc/alu.h000066400000000000000000000014221474041540300155210ustar00rootroot00000000000000#ifndef ALU_H #define ALU_H #include #include #include struct ALCcontext; struct ALCdevice; struct EffectSlot; enum class StereoEncoding : std::uint8_t; namespace al { struct Device; } // namespace al constexpr float GainMixMax{1000.0f}; /* +60dB */ enum CompatFlags : std::uint8_t { ReverseX, ReverseY, ReverseZ, Count }; using CompatFlagBitset = std::bitset; void aluInit(CompatFlagBitset flags, const float nfcscale); /* aluInitRenderer * * Set up the appropriate panning method and mixing method given the device * properties. */ void aluInitRenderer(al::Device *device, int hrtf_id, std::optional stereomode); void aluInitEffectPanning(EffectSlot *slot, ALCcontext *context); #endif openal-soft-1.24.2/alc/backends/000077500000000000000000000000001474041540300163425ustar00rootroot00000000000000openal-soft-1.24.2/alc/backends/alsa.cpp000066400000000000000000001353771474041540300200060ustar00rootroot00000000000000/** * OpenAL cross platform audio library * Copyright (C) 1999-2007 by authors. * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * Or go to http://www.gnu.org/copyleft/lgpl.html */ #include "config.h" #include "alsa.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "alc/alconfig.h" #include "almalloc.h" #include "alnumeric.h" #include "althrd_setname.h" #include "core/device.h" #include "core/helpers.h" #include "core/logging.h" #include "dynload.h" #include "fmt/core.h" #include "ringbuffer.h" #include namespace { using namespace std::string_view_literals; [[nodiscard]] constexpr auto GetDefaultName() noexcept { return "ALSA Default"sv; } #if HAVE_DYNLOAD #define ALSA_FUNCS(MAGIC) \ MAGIC(snd_strerror); \ MAGIC(snd_pcm_open); \ MAGIC(snd_pcm_close); \ MAGIC(snd_pcm_nonblock); \ MAGIC(snd_pcm_frames_to_bytes); \ MAGIC(snd_pcm_bytes_to_frames); \ MAGIC(snd_pcm_hw_params_malloc); \ MAGIC(snd_pcm_hw_params_free); \ MAGIC(snd_pcm_hw_params_any); \ MAGIC(snd_pcm_hw_params_current); \ MAGIC(snd_pcm_hw_params_get_access); \ MAGIC(snd_pcm_hw_params_get_buffer_size); \ MAGIC(snd_pcm_hw_params_get_buffer_time_min); \ MAGIC(snd_pcm_hw_params_get_buffer_time_max); \ MAGIC(snd_pcm_hw_params_get_channels); \ MAGIC(snd_pcm_hw_params_get_period_size); \ MAGIC(snd_pcm_hw_params_get_period_time_max); \ MAGIC(snd_pcm_hw_params_get_period_time_min); \ MAGIC(snd_pcm_hw_params_get_periods); \ MAGIC(snd_pcm_hw_params_set_access); \ MAGIC(snd_pcm_hw_params_set_buffer_size_min); \ MAGIC(snd_pcm_hw_params_set_buffer_size_near); \ MAGIC(snd_pcm_hw_params_set_buffer_time_near); \ MAGIC(snd_pcm_hw_params_set_channels); \ MAGIC(snd_pcm_hw_params_set_channels_near); \ MAGIC(snd_pcm_hw_params_set_format); \ MAGIC(snd_pcm_hw_params_set_period_time_near); \ MAGIC(snd_pcm_hw_params_set_period_size_near); \ MAGIC(snd_pcm_hw_params_set_periods_near); \ MAGIC(snd_pcm_hw_params_set_rate_near); \ MAGIC(snd_pcm_hw_params_set_rate); \ MAGIC(snd_pcm_hw_params_set_rate_resample); \ MAGIC(snd_pcm_hw_params_test_format); \ MAGIC(snd_pcm_hw_params_test_channels); \ MAGIC(snd_pcm_hw_params); \ MAGIC(snd_pcm_sw_params); \ MAGIC(snd_pcm_sw_params_current); \ MAGIC(snd_pcm_sw_params_free); \ MAGIC(snd_pcm_sw_params_malloc); \ MAGIC(snd_pcm_sw_params_set_avail_min); \ MAGIC(snd_pcm_sw_params_set_stop_threshold); \ MAGIC(snd_pcm_prepare); \ MAGIC(snd_pcm_start); \ MAGIC(snd_pcm_resume); \ MAGIC(snd_pcm_reset); \ MAGIC(snd_pcm_wait); \ MAGIC(snd_pcm_delay); \ MAGIC(snd_pcm_state); \ MAGIC(snd_pcm_avail_update); \ MAGIC(snd_pcm_mmap_begin); \ MAGIC(snd_pcm_mmap_commit); \ MAGIC(snd_pcm_readi); \ MAGIC(snd_pcm_writei); \ MAGIC(snd_pcm_drain); \ MAGIC(snd_pcm_drop); \ MAGIC(snd_pcm_recover); \ MAGIC(snd_pcm_info_malloc); \ MAGIC(snd_pcm_info_free); \ MAGIC(snd_pcm_info_set_device); \ MAGIC(snd_pcm_info_set_subdevice); \ MAGIC(snd_pcm_info_set_stream); \ MAGIC(snd_pcm_info_get_name); \ MAGIC(snd_ctl_pcm_next_device); \ MAGIC(snd_ctl_pcm_info); \ MAGIC(snd_ctl_open); \ MAGIC(snd_ctl_close); \ MAGIC(snd_ctl_card_info_malloc); \ MAGIC(snd_ctl_card_info_free); \ MAGIC(snd_ctl_card_info); \ MAGIC(snd_ctl_card_info_get_name); \ MAGIC(snd_ctl_card_info_get_id); \ MAGIC(snd_card_next); \ MAGIC(snd_config_update_free_global) void *alsa_handle; #define MAKE_FUNC(f) decltype(f) * p##f ALSA_FUNCS(MAKE_FUNC); #undef MAKE_FUNC #ifndef IN_IDE_PARSER #define snd_strerror psnd_strerror #define snd_pcm_open psnd_pcm_open #define snd_pcm_close psnd_pcm_close #define snd_pcm_nonblock psnd_pcm_nonblock #define snd_pcm_frames_to_bytes psnd_pcm_frames_to_bytes #define snd_pcm_bytes_to_frames psnd_pcm_bytes_to_frames #define snd_pcm_hw_params_malloc psnd_pcm_hw_params_malloc #define snd_pcm_hw_params_free psnd_pcm_hw_params_free #define snd_pcm_hw_params_any psnd_pcm_hw_params_any #define snd_pcm_hw_params_current psnd_pcm_hw_params_current #define snd_pcm_hw_params_set_access psnd_pcm_hw_params_set_access #define snd_pcm_hw_params_set_format psnd_pcm_hw_params_set_format #define snd_pcm_hw_params_set_channels psnd_pcm_hw_params_set_channels #define snd_pcm_hw_params_set_channels_near psnd_pcm_hw_params_set_channels_near #define snd_pcm_hw_params_set_periods_near psnd_pcm_hw_params_set_periods_near #define snd_pcm_hw_params_set_rate_near psnd_pcm_hw_params_set_rate_near #define snd_pcm_hw_params_set_rate psnd_pcm_hw_params_set_rate #define snd_pcm_hw_params_set_rate_resample psnd_pcm_hw_params_set_rate_resample #define snd_pcm_hw_params_set_buffer_time_near psnd_pcm_hw_params_set_buffer_time_near #define snd_pcm_hw_params_set_period_time_near psnd_pcm_hw_params_set_period_time_near #define snd_pcm_hw_params_set_buffer_size_near psnd_pcm_hw_params_set_buffer_size_near #define snd_pcm_hw_params_set_period_size_near psnd_pcm_hw_params_set_period_size_near #define snd_pcm_hw_params_set_buffer_size_min psnd_pcm_hw_params_set_buffer_size_min #define snd_pcm_hw_params_get_buffer_time_min psnd_pcm_hw_params_get_buffer_time_min #define snd_pcm_hw_params_get_buffer_time_max psnd_pcm_hw_params_get_buffer_time_max #define snd_pcm_hw_params_get_period_time_min psnd_pcm_hw_params_get_period_time_min #define snd_pcm_hw_params_get_period_time_max psnd_pcm_hw_params_get_period_time_max #define snd_pcm_hw_params_get_buffer_size psnd_pcm_hw_params_get_buffer_size #define snd_pcm_hw_params_get_period_size psnd_pcm_hw_params_get_period_size #define snd_pcm_hw_params_get_access psnd_pcm_hw_params_get_access #define snd_pcm_hw_params_get_periods psnd_pcm_hw_params_get_periods #define snd_pcm_hw_params_get_channels psnd_pcm_hw_params_get_channels #define snd_pcm_hw_params_test_format psnd_pcm_hw_params_test_format #define snd_pcm_hw_params_test_channels psnd_pcm_hw_params_test_channels #define snd_pcm_hw_params psnd_pcm_hw_params #define snd_pcm_sw_params_malloc psnd_pcm_sw_params_malloc #define snd_pcm_sw_params_current psnd_pcm_sw_params_current #define snd_pcm_sw_params_set_avail_min psnd_pcm_sw_params_set_avail_min #define snd_pcm_sw_params_set_stop_threshold psnd_pcm_sw_params_set_stop_threshold #define snd_pcm_sw_params psnd_pcm_sw_params #define snd_pcm_sw_params_free psnd_pcm_sw_params_free #define snd_pcm_prepare psnd_pcm_prepare #define snd_pcm_start psnd_pcm_start #define snd_pcm_resume psnd_pcm_resume #define snd_pcm_reset psnd_pcm_reset #define snd_pcm_wait psnd_pcm_wait #define snd_pcm_delay psnd_pcm_delay #define snd_pcm_state psnd_pcm_state #define snd_pcm_avail_update psnd_pcm_avail_update #define snd_pcm_mmap_begin psnd_pcm_mmap_begin #define snd_pcm_mmap_commit psnd_pcm_mmap_commit #define snd_pcm_readi psnd_pcm_readi #define snd_pcm_writei psnd_pcm_writei #define snd_pcm_drain psnd_pcm_drain #define snd_pcm_drop psnd_pcm_drop #define snd_pcm_recover psnd_pcm_recover #define snd_pcm_info_malloc psnd_pcm_info_malloc #define snd_pcm_info_free psnd_pcm_info_free #define snd_pcm_info_set_device psnd_pcm_info_set_device #define snd_pcm_info_set_subdevice psnd_pcm_info_set_subdevice #define snd_pcm_info_set_stream psnd_pcm_info_set_stream #define snd_pcm_info_get_name psnd_pcm_info_get_name #define snd_ctl_pcm_next_device psnd_ctl_pcm_next_device #define snd_ctl_pcm_info psnd_ctl_pcm_info #define snd_ctl_open psnd_ctl_open #define snd_ctl_close psnd_ctl_close #define snd_ctl_card_info_malloc psnd_ctl_card_info_malloc #define snd_ctl_card_info_free psnd_ctl_card_info_free #define snd_ctl_card_info psnd_ctl_card_info #define snd_ctl_card_info_get_name psnd_ctl_card_info_get_name #define snd_ctl_card_info_get_id psnd_ctl_card_info_get_id #define snd_card_next psnd_card_next #define snd_config_update_free_global psnd_config_update_free_global #endif #endif struct HwParamsDeleter { void operator()(snd_pcm_hw_params_t *ptr) { snd_pcm_hw_params_free(ptr); } }; using HwParamsPtr = std::unique_ptr; HwParamsPtr CreateHwParams() { snd_pcm_hw_params_t *hp{}; snd_pcm_hw_params_malloc(&hp); return HwParamsPtr{hp}; } struct SwParamsDeleter { void operator()(snd_pcm_sw_params_t *ptr) { snd_pcm_sw_params_free(ptr); } }; using SwParamsPtr = std::unique_ptr; SwParamsPtr CreateSwParams() { snd_pcm_sw_params_t *sp{}; snd_pcm_sw_params_malloc(&sp); return SwParamsPtr{sp}; } struct DevMap { std::string name; std::string device_name; template DevMap(T&& name_, U&& devname) : name{std::forward(name_)}, device_name{std::forward(devname)} { } }; std::vector PlaybackDevices; std::vector CaptureDevices; std::string_view prefix_name(snd_pcm_stream_t stream) noexcept { if(stream == SND_PCM_STREAM_PLAYBACK) return "device-prefix"sv; return "capture-prefix"sv; } struct SndCtlCardInfo { snd_ctl_card_info_t *mInfo{}; SndCtlCardInfo() { snd_ctl_card_info_malloc(&mInfo); } ~SndCtlCardInfo() { if(mInfo) snd_ctl_card_info_free(mInfo); } SndCtlCardInfo(const SndCtlCardInfo&) = delete; SndCtlCardInfo& operator=(const SndCtlCardInfo&) = delete; [[nodiscard]] operator snd_ctl_card_info_t*() const noexcept { return mInfo; } /* NOLINT(google-explicit-constructor) */ }; struct SndPcmInfo { snd_pcm_info_t *mInfo{}; SndPcmInfo() { snd_pcm_info_malloc(&mInfo); } ~SndPcmInfo() { if(mInfo) snd_pcm_info_free(mInfo); } SndPcmInfo(const SndPcmInfo&) = delete; SndPcmInfo& operator=(const SndPcmInfo&) = delete; [[nodiscard]] operator snd_pcm_info_t*() const noexcept { return mInfo; } /* NOLINT(google-explicit-constructor) */ }; struct SndCtl { snd_ctl_t *mHandle{}; SndCtl() = default; ~SndCtl() { if(mHandle) snd_ctl_close(mHandle); } SndCtl(const SndCtl&) = delete; SndCtl& operator=(const SndCtl&) = delete; [[nodiscard]] auto open(const char *name, int mode) { return snd_ctl_open(&mHandle, name, mode); } [[nodiscard]] operator snd_ctl_t*() const noexcept { return mHandle; } /* NOLINT(google-explicit-constructor) */ }; std::vector probe_devices(snd_pcm_stream_t stream) { std::vector devlist; SndCtlCardInfo info; SndPcmInfo pcminfo; auto defname = ConfigValueStr({}, "alsa"sv, (stream == SND_PCM_STREAM_PLAYBACK) ? "device"sv : "capture"sv); devlist.emplace_back(GetDefaultName(), defname ? std::string_view{*defname} : "default"sv); if(auto customdevs = ConfigValueStr({}, "alsa"sv, (stream == SND_PCM_STREAM_PLAYBACK) ? "custom-devices"sv : "custom-captures"sv)) { size_t curpos{customdevs->find_first_not_of(';')}; while(curpos < customdevs->length()) { size_t nextpos{customdevs->find(';', curpos+1)}; const size_t seppos{customdevs->find('=', curpos)}; if(seppos == curpos || seppos >= nextpos) { const auto spec = std::string_view{*customdevs}.substr(curpos, nextpos-curpos); ERR("Invalid ALSA device specification \"{}\"", spec); } else { const std::string_view strview{*customdevs}; const auto &entry = devlist.emplace_back(strview.substr(curpos, seppos-curpos), strview.substr(seppos+1, nextpos-seppos-1)); TRACE("Got device \"{}\", \"{}\"", entry.name, entry.device_name); } if(nextpos < customdevs->length()) nextpos = customdevs->find_first_not_of(';', nextpos+1); curpos = nextpos; } } const std::string main_prefix{ConfigValueStr({}, "alsa"sv, prefix_name(stream)) .value_or("plughw:")}; int card{-1}; int err{snd_card_next(&card)}; for(;err >= 0 && card >= 0;err = snd_card_next(&card)) { std::string name{"hw:" + std::to_string(card)}; SndCtl handle; err = handle.open(name.c_str(), 0); if(err < 0) { ERR("control open (hw:{}): {}", card, snd_strerror(err)); continue; } err = snd_ctl_card_info(handle, info); if(err < 0) { ERR("control hardware info (hw:{}): {}", card, snd_strerror(err)); continue; } const char *cardname{snd_ctl_card_info_get_name(info)}; const char *cardid{snd_ctl_card_info_get_id(info)}; name = prefix_name(stream); name += '-'; name += cardid; const std::string card_prefix{ConfigValueStr({}, "alsa"sv, name).value_or(main_prefix)}; int dev{-1}; while(true) { if(snd_ctl_pcm_next_device(handle, &dev) < 0) ERR("snd_ctl_pcm_next_device failed"); if(dev < 0) break; snd_pcm_info_set_device(pcminfo, static_cast(dev)); snd_pcm_info_set_subdevice(pcminfo, 0); snd_pcm_info_set_stream(pcminfo, stream); err = snd_ctl_pcm_info(handle, pcminfo); if(err < 0) { if(err != -ENOENT) ERR("control digital audio info (hw:{}): {}", card, snd_strerror(err)); continue; } /* "prefix-cardid-dev" */ name = fmt::format("{}-{}-{}", prefix_name(stream), cardid, dev); const auto device_prefix = std::string{ConfigValueStr({}, "alsa"sv, name) .value_or(card_prefix)}; /* "CardName, PcmName (CARD=cardid,DEV=dev)" */ name = fmt::format("{}, {} (CARD={},DEV={})", cardname, snd_pcm_info_get_name(pcminfo), cardid, dev); /* "devprefixCARD=cardid,DEV=dev" */ auto device = fmt::format("{}CARD={},DEV={}", device_prefix, cardid, dev); const auto &entry = devlist.emplace_back(std::move(name), std::move(device)); TRACE("Got device \"{}\", \"{}\"", entry.name, entry.device_name); } } if(err < 0) ERR("snd_card_next failed: {}", snd_strerror(err)); return devlist; } int verify_state(snd_pcm_t *handle) { snd_pcm_state_t state{snd_pcm_state(handle)}; switch(state) { case SND_PCM_STATE_OPEN: case SND_PCM_STATE_SETUP: case SND_PCM_STATE_PREPARED: case SND_PCM_STATE_RUNNING: case SND_PCM_STATE_DRAINING: case SND_PCM_STATE_PAUSED: /* All Okay */ break; case SND_PCM_STATE_XRUN: if(int err{snd_pcm_recover(handle, -EPIPE, 1)}; err < 0) return err; break; case SND_PCM_STATE_SUSPENDED: if(int err{snd_pcm_recover(handle, -ESTRPIPE, 1)}; err < 0) return err; break; case SND_PCM_STATE_DISCONNECTED: return -ENODEV; /* ALSA headers have made this enum public, leaving us in a bind: use * the enum despite being private and internal to the libasound, or * ignore when an enum value isn't handled. We can't rely on it being * declared either, since older headers don't have it and it could be * removed in the future. We can't even really rely on its value, since * being private/internal means it's subject to change, but this is the * best we can do. */ case 1024 /*SND_PCM_STATE_PRIVATE1*/: assert(state != 1024); } return state; } struct AlsaPlayback final : public BackendBase { explicit AlsaPlayback(DeviceBase *device) noexcept : BackendBase{device} { } ~AlsaPlayback() override; int mixerProc(); int mixerNoMMapProc(); void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; ClockLatency getClockLatency() override; snd_pcm_t *mPcmHandle{nullptr}; std::mutex mMutex; uint mFrameStep{}; std::vector mBuffer; std::atomic mKillNow{true}; std::thread mThread; }; AlsaPlayback::~AlsaPlayback() { if(mPcmHandle) snd_pcm_close(mPcmHandle); mPcmHandle = nullptr; } int AlsaPlayback::mixerProc() { SetRTPriority(); althrd_setname(GetMixerThreadName()); const snd_pcm_uframes_t update_size{mDevice->mUpdateSize}; const snd_pcm_uframes_t buffer_size{mDevice->mBufferSize}; while(!mKillNow.load(std::memory_order_acquire)) { int state{verify_state(mPcmHandle)}; if(state < 0) { ERR("Invalid state detected: {}", snd_strerror(state)); mDevice->handleDisconnect("Bad state: {}", snd_strerror(state)); break; } snd_pcm_sframes_t avails{snd_pcm_avail_update(mPcmHandle)}; if(avails < 0) { ERR("available update failed: {}", snd_strerror(static_cast(avails))); continue; } snd_pcm_uframes_t avail{static_cast(avails)}; if(avail > buffer_size) { WARN("available samples exceeds the buffer size"); snd_pcm_reset(mPcmHandle); continue; } // make sure there's frames to process if(avail < update_size) { if(state != SND_PCM_STATE_RUNNING) { int err{snd_pcm_start(mPcmHandle)}; if(err < 0) { ERR("start failed: {}", snd_strerror(err)); continue; } } if(snd_pcm_wait(mPcmHandle, 1000) == 0) ERR("Wait timeout... buffer size too low?"); continue; } avail -= avail%update_size; // it is possible that contiguous areas are smaller, thus we use a loop std::lock_guard dlock{mMutex}; while(avail > 0) { snd_pcm_uframes_t frames{avail}; const snd_pcm_channel_area_t *areas{}; snd_pcm_uframes_t offset{}; int err{snd_pcm_mmap_begin(mPcmHandle, &areas, &offset, &frames)}; if(err < 0) { ERR("mmap begin error: {}", snd_strerror(err)); break; } /* NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) */ char *WritePtr{static_cast(areas->addr) + (offset * areas->step / 8)}; mDevice->renderSamples(WritePtr, static_cast(frames), mFrameStep); snd_pcm_sframes_t commitres{snd_pcm_mmap_commit(mPcmHandle, offset, frames)}; if(commitres < 0 || static_cast(commitres) != frames) { ERR("mmap commit error: {}", snd_strerror(commitres >= 0 ? -EPIPE : static_cast(commitres))); break; } avail -= frames; } } return 0; } int AlsaPlayback::mixerNoMMapProc() { SetRTPriority(); althrd_setname(GetMixerThreadName()); const snd_pcm_uframes_t update_size{mDevice->mUpdateSize}; const snd_pcm_uframes_t buffer_size{mDevice->mBufferSize}; while(!mKillNow.load(std::memory_order_acquire)) { int state{verify_state(mPcmHandle)}; if(state < 0) { ERR("Invalid state detected: {}", snd_strerror(state)); mDevice->handleDisconnect("Bad state: {}", snd_strerror(state)); break; } snd_pcm_sframes_t avail{snd_pcm_avail_update(mPcmHandle)}; if(avail < 0) { ERR("available update failed: {}", snd_strerror(static_cast(avail))); continue; } if(static_cast(avail) > buffer_size) { WARN("available samples exceeds the buffer size"); snd_pcm_reset(mPcmHandle); continue; } if(static_cast(avail) < update_size) { if(state != SND_PCM_STATE_RUNNING) { int err{snd_pcm_start(mPcmHandle)}; if(err < 0) { ERR("start failed: {}", snd_strerror(err)); continue; } } if(snd_pcm_wait(mPcmHandle, 1000) == 0) ERR("Wait timeout... buffer size too low?"); continue; } auto WritePtr = mBuffer.begin(); avail = snd_pcm_bytes_to_frames(mPcmHandle, static_cast(mBuffer.size())); std::lock_guard dlock{mMutex}; mDevice->renderSamples(al::to_address(WritePtr), static_cast(avail), mFrameStep); while(avail > 0) { snd_pcm_sframes_t ret{snd_pcm_writei(mPcmHandle, al::to_address(WritePtr), static_cast(avail))}; switch(ret) { case -EAGAIN: continue; #if ESTRPIPE != EPIPE case -ESTRPIPE: #endif case -EPIPE: case -EINTR: ret = snd_pcm_recover(mPcmHandle, static_cast(ret), 1); if(ret < 0) avail = 0; break; default: if(ret >= 0) { WritePtr += snd_pcm_frames_to_bytes(mPcmHandle, ret); avail -= ret; } break; } if(ret < 0) { ret = snd_pcm_prepare(mPcmHandle); if(ret < 0) break; } } } return 0; } void AlsaPlayback::open(std::string_view name) { std::string driver{"default"}; if(!name.empty()) { if(PlaybackDevices.empty()) PlaybackDevices = probe_devices(SND_PCM_STREAM_PLAYBACK); auto iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(), [name](const DevMap &entry) -> bool { return entry.name == name; }); if(iter == PlaybackDevices.cend()) throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found", name}; driver = iter->device_name; } else { name = GetDefaultName(); if(auto driveropt = ConfigValueStr({}, "alsa"sv, "device"sv)) driver = std::move(driveropt).value(); } TRACE("Opening device \"{}\"", driver); snd_pcm_t *pcmHandle{}; int err{snd_pcm_open(&pcmHandle, driver.c_str(), SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)}; if(err < 0) throw al::backend_exception{al::backend_error::NoDevice, "Could not open ALSA device \"{}\"", driver}; if(mPcmHandle) snd_pcm_close(mPcmHandle); mPcmHandle = pcmHandle; /* Free alsa's global config tree. Otherwise valgrind reports a ton of leaks. */ snd_config_update_free_global(); mDeviceName = name; } bool AlsaPlayback::reset() { snd_pcm_format_t format{SND_PCM_FORMAT_UNKNOWN}; switch(mDevice->FmtType) { case DevFmtByte: format = SND_PCM_FORMAT_S8; break; case DevFmtUByte: format = SND_PCM_FORMAT_U8; break; case DevFmtShort: format = SND_PCM_FORMAT_S16; break; case DevFmtUShort: format = SND_PCM_FORMAT_U16; break; case DevFmtInt: format = SND_PCM_FORMAT_S32; break; case DevFmtUInt: format = SND_PCM_FORMAT_U32; break; case DevFmtFloat: format = SND_PCM_FORMAT_FLOAT; break; } bool allowmmap{GetConfigValueBool(mDevice->mDeviceName, "alsa"sv, "mmap"sv, true)}; uint periodLen{static_cast(mDevice->mUpdateSize * 1000000_u64 / mDevice->mSampleRate)}; uint bufferLen{static_cast(mDevice->mBufferSize * 1000000_u64 / mDevice->mSampleRate)}; uint rate{mDevice->mSampleRate}; HwParamsPtr hp{CreateHwParams()}; #define CHECK(x) do { \ if(int err{x}; err < 0) \ throw al::backend_exception{al::backend_error::DeviceError, #x " failed: {}", \ snd_strerror(err)}; \ } while(0) CHECK(snd_pcm_hw_params_any(mPcmHandle, hp.get())); /* set interleaved access */ if(!allowmmap || snd_pcm_hw_params_set_access(mPcmHandle, hp.get(), SND_PCM_ACCESS_MMAP_INTERLEAVED) < 0) { /* No mmap */ CHECK(snd_pcm_hw_params_set_access(mPcmHandle, hp.get(), SND_PCM_ACCESS_RW_INTERLEAVED)); } /* test and set format (implicitly sets sample bits) */ if(snd_pcm_hw_params_test_format(mPcmHandle, hp.get(), format) < 0) { struct FormatMap { snd_pcm_format_t format; DevFmtType fmttype; }; static constexpr std::array formatlist{ FormatMap{SND_PCM_FORMAT_FLOAT, DevFmtFloat }, FormatMap{SND_PCM_FORMAT_S32, DevFmtInt }, FormatMap{SND_PCM_FORMAT_U32, DevFmtUInt }, FormatMap{SND_PCM_FORMAT_S16, DevFmtShort }, FormatMap{SND_PCM_FORMAT_U16, DevFmtUShort}, FormatMap{SND_PCM_FORMAT_S8, DevFmtByte }, FormatMap{SND_PCM_FORMAT_U8, DevFmtUByte }, }; for(const auto &fmt : formatlist) { format = fmt.format; if(snd_pcm_hw_params_test_format(mPcmHandle, hp.get(), format) >= 0) { mDevice->FmtType = fmt.fmttype; break; } } } CHECK(snd_pcm_hw_params_set_format(mPcmHandle, hp.get(), format)); /* set channels (implicitly sets frame bits) */ if(snd_pcm_hw_params_set_channels(mPcmHandle, hp.get(), mDevice->channelsFromFmt()) < 0) { uint numchans{2u}; CHECK(snd_pcm_hw_params_set_channels_near(mPcmHandle, hp.get(), &numchans)); if(numchans < 1) throw al::backend_exception{al::backend_error::DeviceError, "Got 0 device channels"}; if(numchans == 1) mDevice->FmtChans = DevFmtMono; else mDevice->FmtChans = DevFmtStereo; } /* set rate (implicitly constrains period/buffer parameters) */ if(!GetConfigValueBool(mDevice->mDeviceName, "alsa", "allow-resampler", false) || !mDevice->Flags.test(FrequencyRequest)) { if(snd_pcm_hw_params_set_rate_resample(mPcmHandle, hp.get(), 0) < 0) WARN("Failed to disable ALSA resampler"); } else if(snd_pcm_hw_params_set_rate_resample(mPcmHandle, hp.get(), 1) < 0) WARN("Failed to enable ALSA resampler"); CHECK(snd_pcm_hw_params_set_rate_near(mPcmHandle, hp.get(), &rate, nullptr)); /* set period time (implicitly constrains period/buffer parameters) */ if(int err{snd_pcm_hw_params_set_period_time_near(mPcmHandle, hp.get(), &periodLen, nullptr)}; err < 0) ERR("snd_pcm_hw_params_set_period_time_near failed: {}", snd_strerror(err)); /* set buffer time (implicitly sets buffer size/bytes/time and period size/bytes) */ if(int err{snd_pcm_hw_params_set_buffer_time_near(mPcmHandle, hp.get(), &bufferLen, nullptr)}; err < 0) ERR("snd_pcm_hw_params_set_buffer_time_near failed: {}", snd_strerror(err)); /* install and prepare hardware configuration */ CHECK(snd_pcm_hw_params(mPcmHandle, hp.get())); /* retrieve configuration info */ snd_pcm_uframes_t periodSizeInFrames{}; snd_pcm_uframes_t bufferSizeInFrames{}; snd_pcm_access_t access{}; CHECK(snd_pcm_hw_params_get_access(hp.get(), &access)); CHECK(snd_pcm_hw_params_get_period_size(hp.get(), &periodSizeInFrames, nullptr)); CHECK(snd_pcm_hw_params_get_buffer_size(hp.get(), &bufferSizeInFrames)); CHECK(snd_pcm_hw_params_get_channels(hp.get(), &mFrameStep)); hp = nullptr; SwParamsPtr sp{CreateSwParams()}; CHECK(snd_pcm_sw_params_current(mPcmHandle, sp.get())); CHECK(snd_pcm_sw_params_set_avail_min(mPcmHandle, sp.get(), periodSizeInFrames)); CHECK(snd_pcm_sw_params_set_stop_threshold(mPcmHandle, sp.get(), bufferSizeInFrames)); CHECK(snd_pcm_sw_params(mPcmHandle, sp.get())); #undef CHECK sp = nullptr; mDevice->mBufferSize = static_cast(bufferSizeInFrames); mDevice->mUpdateSize = static_cast(periodSizeInFrames); mDevice->mSampleRate = rate; setDefaultChannelOrder(); return true; } void AlsaPlayback::start() { snd_pcm_access_t access{}; HwParamsPtr hp{CreateHwParams()}; #define CHECK(x) do { \ if(int err{x}; err < 0) \ throw al::backend_exception{al::backend_error::DeviceError, #x " failed: {}", \ snd_strerror(err)}; \ } while(0) CHECK(snd_pcm_hw_params_current(mPcmHandle, hp.get())); /* retrieve configuration info */ CHECK(snd_pcm_hw_params_get_access(hp.get(), &access)); hp = nullptr; int (AlsaPlayback::*thread_func)(){}; if(access == SND_PCM_ACCESS_RW_INTERLEAVED) { auto datalen = snd_pcm_frames_to_bytes(mPcmHandle, mDevice->mUpdateSize); mBuffer.resize(static_cast(datalen)); thread_func = &AlsaPlayback::mixerNoMMapProc; } else { CHECK(snd_pcm_prepare(mPcmHandle)); thread_func = &AlsaPlayback::mixerProc; } #undef CHECK try { mKillNow.store(false, std::memory_order_release); mThread = std::thread{thread_func, this}; } catch(std::exception& e) { throw al::backend_exception{al::backend_error::DeviceError, "Failed to start mixing thread: {}", e.what()}; } } void AlsaPlayback::stop() { if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable()) return; mThread.join(); mBuffer.clear(); int err{snd_pcm_drop(mPcmHandle)}; if(err < 0) ERR("snd_pcm_drop failed: {}", snd_strerror(err)); } ClockLatency AlsaPlayback::getClockLatency() { std::lock_guard dlock{mMutex}; ClockLatency ret{}; ret.ClockTime = mDevice->getClockTime(); snd_pcm_sframes_t delay{}; int err{snd_pcm_delay(mPcmHandle, &delay)}; if(err < 0) { ERR("Failed to get pcm delay: {}", snd_strerror(err)); delay = 0; } ret.Latency = std::chrono::seconds{std::max(0, delay)}; ret.Latency /= mDevice->mSampleRate; return ret; } struct AlsaCapture final : public BackendBase { explicit AlsaCapture(DeviceBase *device) noexcept : BackendBase{device} { } ~AlsaCapture() override; void open(std::string_view name) override; void start() override; void stop() override; void captureSamples(std::byte *buffer, uint samples) override; uint availableSamples() override; ClockLatency getClockLatency() override; snd_pcm_t *mPcmHandle{nullptr}; std::vector mBuffer; bool mDoCapture{false}; RingBufferPtr mRing{nullptr}; snd_pcm_sframes_t mLastAvail{0}; }; AlsaCapture::~AlsaCapture() { if(mPcmHandle) snd_pcm_close(mPcmHandle); mPcmHandle = nullptr; } void AlsaCapture::open(std::string_view name) { std::string driver{"default"}; if(!name.empty()) { if(CaptureDevices.empty()) CaptureDevices = probe_devices(SND_PCM_STREAM_CAPTURE); auto iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(), [name](const DevMap &entry) -> bool { return entry.name == name; }); if(iter == CaptureDevices.cend()) throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found", name}; driver = iter->device_name; } else { name = GetDefaultName(); if(auto driveropt = ConfigValueStr({}, "alsa"sv, "capture"sv)) driver = std::move(driveropt).value(); } TRACE("Opening device \"{}\"", driver); if(int err{snd_pcm_open(&mPcmHandle, driver.c_str(), SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK)}; err < 0) throw al::backend_exception{al::backend_error::NoDevice, "Could not open ALSA device \"{}\"", driver}; /* Free alsa's global config tree. Otherwise valgrind reports a ton of leaks. */ snd_config_update_free_global(); snd_pcm_format_t format{SND_PCM_FORMAT_UNKNOWN}; switch(mDevice->FmtType) { case DevFmtByte: format = SND_PCM_FORMAT_S8; break; case DevFmtUByte: format = SND_PCM_FORMAT_U8; break; case DevFmtShort: format = SND_PCM_FORMAT_S16; break; case DevFmtUShort: format = SND_PCM_FORMAT_U16; break; case DevFmtInt: format = SND_PCM_FORMAT_S32; break; case DevFmtUInt: format = SND_PCM_FORMAT_U32; break; case DevFmtFloat: format = SND_PCM_FORMAT_FLOAT; break; } snd_pcm_uframes_t bufferSizeInFrames{std::max(mDevice->mBufferSize, 100u*mDevice->mSampleRate/1000u)}; snd_pcm_uframes_t periodSizeInFrames{std::min(mDevice->mBufferSize, 25u*mDevice->mSampleRate/1000u)}; bool needring{false}; HwParamsPtr hp{CreateHwParams()}; #define CHECK(x) do { \ if(int err{x}; err < 0) \ throw al::backend_exception{al::backend_error::DeviceError, #x " failed: {}", \ snd_strerror(err)}; \ } while(0) CHECK(snd_pcm_hw_params_any(mPcmHandle, hp.get())); /* set interleaved access */ CHECK(snd_pcm_hw_params_set_access(mPcmHandle, hp.get(), SND_PCM_ACCESS_RW_INTERLEAVED)); /* set format (implicitly sets sample bits) */ CHECK(snd_pcm_hw_params_set_format(mPcmHandle, hp.get(), format)); /* set channels (implicitly sets frame bits) */ CHECK(snd_pcm_hw_params_set_channels(mPcmHandle, hp.get(), mDevice->channelsFromFmt())); /* set rate (implicitly constrains period/buffer parameters) */ CHECK(snd_pcm_hw_params_set_rate(mPcmHandle, hp.get(), mDevice->mSampleRate, 0)); /* set buffer size in frame units (implicitly sets period size/bytes/time and buffer time/bytes) */ if(snd_pcm_hw_params_set_buffer_size_min(mPcmHandle, hp.get(), &bufferSizeInFrames) < 0) { TRACE("Buffer too large, using intermediate ring buffer"); needring = true; CHECK(snd_pcm_hw_params_set_buffer_size_near(mPcmHandle, hp.get(), &bufferSizeInFrames)); } /* set buffer size in frame units (implicitly sets period size/bytes/time and buffer time/bytes) */ CHECK(snd_pcm_hw_params_set_period_size_near(mPcmHandle, hp.get(), &periodSizeInFrames, nullptr)); /* install and prepare hardware configuration */ CHECK(snd_pcm_hw_params(mPcmHandle, hp.get())); /* retrieve configuration info */ CHECK(snd_pcm_hw_params_get_period_size(hp.get(), &periodSizeInFrames, nullptr)); #undef CHECK hp = nullptr; if(needring) mRing = RingBuffer::Create(mDevice->mBufferSize, mDevice->frameSizeFromFmt(), false); mDeviceName = name; } void AlsaCapture::start() { if(int err{snd_pcm_prepare(mPcmHandle)}; err < 0) throw al::backend_exception{al::backend_error::DeviceError, "snd_pcm_prepare failed: {}", snd_strerror(err)}; if(int err{snd_pcm_start(mPcmHandle)}; err < 0) throw al::backend_exception{al::backend_error::DeviceError, "snd_pcm_start failed: {}", snd_strerror(err)}; mDoCapture = true; } void AlsaCapture::stop() { /* OpenAL requires access to unread audio after stopping, but ALSA's * snd_pcm_drain is unreliable and snd_pcm_drop drops it. Capture what's * available now so it'll be available later after the drop. */ uint avail{availableSamples()}; if(!mRing && avail > 0) { /* The ring buffer implicitly captures when checking availability. * Direct access needs to explicitly capture it into temp storage. */ auto temp = std::vector( static_cast(snd_pcm_frames_to_bytes(mPcmHandle, avail))); captureSamples(temp.data(), avail); mBuffer = std::move(temp); } if(int err{snd_pcm_drop(mPcmHandle)}; err < 0) ERR("snd_pcm_drop failed: {}", snd_strerror(err)); mDoCapture = false; } void AlsaCapture::captureSamples(std::byte *buffer, uint samples) { if(mRing) { std::ignore = mRing->read(buffer, samples); return; } const auto outspan = al::span{buffer, static_cast(snd_pcm_frames_to_bytes(mPcmHandle, samples))}; auto outiter = outspan.begin(); mLastAvail -= samples; while(mDevice->Connected.load(std::memory_order_acquire) && samples > 0) { snd_pcm_sframes_t amt{0}; if(!mBuffer.empty()) { /* First get any data stored from the last stop */ amt = snd_pcm_bytes_to_frames(mPcmHandle, static_cast(mBuffer.size())); if(static_cast(amt) > samples) amt = samples; amt = snd_pcm_frames_to_bytes(mPcmHandle, amt); std::copy_n(mBuffer.begin(), amt, outiter); mBuffer.erase(mBuffer.begin(), mBuffer.begin()+amt); amt = snd_pcm_bytes_to_frames(mPcmHandle, amt); } else if(mDoCapture) amt = snd_pcm_readi(mPcmHandle, al::to_address(outiter), samples); if(amt < 0) { ERR("read error: {}", snd_strerror(static_cast(amt))); if(amt == -EAGAIN) continue; amt = snd_pcm_recover(mPcmHandle, static_cast(amt), 1); if(amt >= 0) { amt = snd_pcm_start(mPcmHandle); if(amt >= 0) amt = snd_pcm_avail_update(mPcmHandle); } if(amt < 0) { const char *err{snd_strerror(static_cast(amt))}; ERR("restore error: {}", err); mDevice->handleDisconnect("Capture recovery failure: {}", err); break; } /* If the amount available is less than what's asked, we lost it * during recovery. So just give silence instead. */ if(static_cast(amt) < samples) break; continue; } outiter += amt; samples -= static_cast(amt); } if(samples > 0) std::fill_n(outiter, snd_pcm_frames_to_bytes(mPcmHandle, samples), std::byte((mDevice->FmtType == DevFmtUByte) ? 0x80 : 0)); } uint AlsaCapture::availableSamples() { snd_pcm_sframes_t avail{0}; if(mDevice->Connected.load(std::memory_order_acquire) && mDoCapture) avail = snd_pcm_avail_update(mPcmHandle); if(avail < 0) { ERR("snd_pcm_avail_update failed: {}", snd_strerror(static_cast(avail))); avail = snd_pcm_recover(mPcmHandle, static_cast(avail), 1); if(avail >= 0) { if(mDoCapture) avail = snd_pcm_start(mPcmHandle); if(avail >= 0) avail = snd_pcm_avail_update(mPcmHandle); } if(avail < 0) { const char *err{snd_strerror(static_cast(avail))}; ERR("restore error: {}", err); mDevice->handleDisconnect("Capture recovery failure: {}", err); } } if(!mRing) { avail = std::max(avail, 0); avail += snd_pcm_bytes_to_frames(mPcmHandle, static_cast(mBuffer.size())); mLastAvail = std::max(mLastAvail, avail); return static_cast(mLastAvail); } while(avail > 0) { auto vec = mRing->getWriteVector(); if(vec[0].len == 0) break; snd_pcm_sframes_t amt{std::min(static_cast(vec[0].len), avail)}; amt = snd_pcm_readi(mPcmHandle, vec[0].buf, static_cast(amt)); if(amt < 0) { ERR("read error: {}", snd_strerror(static_cast(amt))); if(amt == -EAGAIN) continue; amt = snd_pcm_recover(mPcmHandle, static_cast(amt), 1); if(amt >= 0) { if(mDoCapture) amt = snd_pcm_start(mPcmHandle); if(amt >= 0) amt = snd_pcm_avail_update(mPcmHandle); } if(amt < 0) { const char *err{snd_strerror(static_cast(amt))}; ERR("restore error: {}", err); mDevice->handleDisconnect("Capture recovery failure: {}", err); break; } avail = amt; continue; } mRing->writeAdvance(static_cast(amt)); avail -= amt; } return static_cast(mRing->readSpace()); } ClockLatency AlsaCapture::getClockLatency() { ClockLatency ret{}; ret.ClockTime = mDevice->getClockTime(); snd_pcm_sframes_t delay{}; int err{snd_pcm_delay(mPcmHandle, &delay)}; if(err < 0) { ERR("Failed to get pcm delay: {}", snd_strerror(err)); delay = 0; } ret.Latency = std::chrono::seconds{std::max(0, delay)}; ret.Latency /= mDevice->mSampleRate; return ret; } } // namespace bool AlsaBackendFactory::init() { #if HAVE_DYNLOAD if(!alsa_handle) { alsa_handle = LoadLib("libasound.so.2"); if(!alsa_handle) { WARN("Failed to load {}", "libasound.so.2"); return false; } std::string missing_funcs; #define LOAD_FUNC(f) do { \ p##f = reinterpret_cast(GetSymbol(alsa_handle, #f)); \ if(p##f == nullptr) missing_funcs += "\n" #f; \ } while(0) ALSA_FUNCS(LOAD_FUNC); #undef LOAD_FUNC if(!missing_funcs.empty()) { WARN("Missing expected functions:{}", missing_funcs); CloseLib(alsa_handle); alsa_handle = nullptr; return false; } } #endif return true; } bool AlsaBackendFactory::querySupport(BackendType type) { return (type == BackendType::Playback || type == BackendType::Capture); } auto AlsaBackendFactory::enumerate(BackendType type) -> std::vector { std::vector outnames; auto add_device = [&outnames](const DevMap &entry) -> void { outnames.emplace_back(entry.name); }; switch(type) { case BackendType::Playback: PlaybackDevices = probe_devices(SND_PCM_STREAM_PLAYBACK); outnames.reserve(PlaybackDevices.size()); std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device); break; case BackendType::Capture: CaptureDevices = probe_devices(SND_PCM_STREAM_CAPTURE); outnames.reserve(CaptureDevices.size()); std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device); break; } return outnames; } BackendPtr AlsaBackendFactory::createBackend(DeviceBase *device, BackendType type) { if(type == BackendType::Playback) return BackendPtr{new AlsaPlayback{device}}; if(type == BackendType::Capture) return BackendPtr{new AlsaCapture{device}}; return nullptr; } BackendFactory &AlsaBackendFactory::getFactory() { static AlsaBackendFactory factory{}; return factory; } openal-soft-1.24.2/alc/backends/alsa.h000066400000000000000000000007141474041540300174350ustar00rootroot00000000000000#ifndef BACKENDS_ALSA_H #define BACKENDS_ALSA_H #include "base.h" struct AlsaBackendFactory final : public BackendFactory { public: auto init() -> bool final; auto querySupport(BackendType type) -> bool final; auto enumerate(BackendType type) -> std::vector final; auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_ALSA_H */ openal-soft-1.24.2/alc/backends/base.cpp000066400000000000000000000213441474041540300177640ustar00rootroot00000000000000 #include "config.h" #include "base.h" #include #include #include #include "core/devformat.h" namespace al { auto backend_exception::make_string(fmt::string_view fmt, fmt::format_args args) -> std::string { return fmt::vformat(fmt, std::move(args)); } backend_exception::~backend_exception() = default; } // namespace al bool BackendBase::reset() { throw al::backend_exception{al::backend_error::DeviceError, "Invalid BackendBase call"}; } void BackendBase::captureSamples(std::byte*, uint) { } uint BackendBase::availableSamples() { return 0; } ClockLatency BackendBase::getClockLatency() { ClockLatency ret{}; uint refcount; do { refcount = mDevice->waitForMix(); ret.ClockTime = mDevice->getClockTime(); std::atomic_thread_fence(std::memory_order_acquire); } while(refcount != mDevice->mMixCount.load(std::memory_order_relaxed)); /* NOTE: The device will generally have about all but one periods filled at * any given time during playback. Without a more accurate measurement from * the output, this is an okay approximation. */ ret.Latency = std::chrono::seconds{mDevice->mBufferSize - mDevice->mUpdateSize}; ret.Latency /= mDevice->mSampleRate; return ret; } void BackendBase::setDefaultWFXChannelOrder() const { mDevice->RealOut.ChannelIndex.fill(InvalidChannelIndex); switch(mDevice->FmtChans) { case DevFmtMono: mDevice->RealOut.ChannelIndex[FrontCenter] = 0; break; case DevFmtStereo: mDevice->RealOut.ChannelIndex[FrontLeft] = 0; mDevice->RealOut.ChannelIndex[FrontRight] = 1; break; case DevFmtQuad: mDevice->RealOut.ChannelIndex[FrontLeft] = 0; mDevice->RealOut.ChannelIndex[FrontRight] = 1; mDevice->RealOut.ChannelIndex[BackLeft] = 2; mDevice->RealOut.ChannelIndex[BackRight] = 3; break; case DevFmtX51: mDevice->RealOut.ChannelIndex[FrontLeft] = 0; mDevice->RealOut.ChannelIndex[FrontRight] = 1; mDevice->RealOut.ChannelIndex[FrontCenter] = 2; mDevice->RealOut.ChannelIndex[LFE] = 3; mDevice->RealOut.ChannelIndex[SideLeft] = 4; mDevice->RealOut.ChannelIndex[SideRight] = 5; break; case DevFmtX61: mDevice->RealOut.ChannelIndex[FrontLeft] = 0; mDevice->RealOut.ChannelIndex[FrontRight] = 1; mDevice->RealOut.ChannelIndex[FrontCenter] = 2; mDevice->RealOut.ChannelIndex[LFE] = 3; mDevice->RealOut.ChannelIndex[BackCenter] = 4; mDevice->RealOut.ChannelIndex[SideLeft] = 5; mDevice->RealOut.ChannelIndex[SideRight] = 6; break; case DevFmtX71: mDevice->RealOut.ChannelIndex[FrontLeft] = 0; mDevice->RealOut.ChannelIndex[FrontRight] = 1; mDevice->RealOut.ChannelIndex[FrontCenter] = 2; mDevice->RealOut.ChannelIndex[LFE] = 3; mDevice->RealOut.ChannelIndex[BackLeft] = 4; mDevice->RealOut.ChannelIndex[BackRight] = 5; mDevice->RealOut.ChannelIndex[SideLeft] = 6; mDevice->RealOut.ChannelIndex[SideRight] = 7; break; case DevFmtX714: mDevice->RealOut.ChannelIndex[FrontLeft] = 0; mDevice->RealOut.ChannelIndex[FrontRight] = 1; mDevice->RealOut.ChannelIndex[FrontCenter] = 2; mDevice->RealOut.ChannelIndex[LFE] = 3; mDevice->RealOut.ChannelIndex[BackLeft] = 4; mDevice->RealOut.ChannelIndex[BackRight] = 5; mDevice->RealOut.ChannelIndex[SideLeft] = 6; mDevice->RealOut.ChannelIndex[SideRight] = 7; mDevice->RealOut.ChannelIndex[TopFrontLeft] = 8; mDevice->RealOut.ChannelIndex[TopFrontRight] = 9; mDevice->RealOut.ChannelIndex[TopBackLeft] = 10; mDevice->RealOut.ChannelIndex[TopBackRight] = 11; break; case DevFmtX7144: mDevice->RealOut.ChannelIndex[FrontLeft] = 0; mDevice->RealOut.ChannelIndex[FrontRight] = 1; mDevice->RealOut.ChannelIndex[FrontCenter] = 2; mDevice->RealOut.ChannelIndex[LFE] = 3; mDevice->RealOut.ChannelIndex[BackLeft] = 4; mDevice->RealOut.ChannelIndex[BackRight] = 5; mDevice->RealOut.ChannelIndex[SideLeft] = 6; mDevice->RealOut.ChannelIndex[SideRight] = 7; mDevice->RealOut.ChannelIndex[TopFrontLeft] = 8; mDevice->RealOut.ChannelIndex[TopFrontRight] = 9; mDevice->RealOut.ChannelIndex[TopBackLeft] = 10; mDevice->RealOut.ChannelIndex[TopBackRight] = 11; mDevice->RealOut.ChannelIndex[BottomFrontLeft] = 12; mDevice->RealOut.ChannelIndex[BottomFrontRight] = 13; mDevice->RealOut.ChannelIndex[BottomBackLeft] = 14; mDevice->RealOut.ChannelIndex[BottomBackRight] = 15; break; case DevFmtX3D71: mDevice->RealOut.ChannelIndex[FrontLeft] = 0; mDevice->RealOut.ChannelIndex[FrontRight] = 1; mDevice->RealOut.ChannelIndex[FrontCenter] = 2; mDevice->RealOut.ChannelIndex[LFE] = 3; mDevice->RealOut.ChannelIndex[Aux0] = 4; mDevice->RealOut.ChannelIndex[Aux1] = 5; mDevice->RealOut.ChannelIndex[SideLeft] = 6; mDevice->RealOut.ChannelIndex[SideRight] = 7; break; case DevFmtAmbi3D: break; } } void BackendBase::setDefaultChannelOrder() const { mDevice->RealOut.ChannelIndex.fill(InvalidChannelIndex); switch(mDevice->FmtChans) { case DevFmtX51: mDevice->RealOut.ChannelIndex[FrontLeft] = 0; mDevice->RealOut.ChannelIndex[FrontRight] = 1; mDevice->RealOut.ChannelIndex[SideLeft] = 2; mDevice->RealOut.ChannelIndex[SideRight] = 3; mDevice->RealOut.ChannelIndex[FrontCenter] = 4; mDevice->RealOut.ChannelIndex[LFE] = 5; return; case DevFmtX71: mDevice->RealOut.ChannelIndex[FrontLeft] = 0; mDevice->RealOut.ChannelIndex[FrontRight] = 1; mDevice->RealOut.ChannelIndex[BackLeft] = 2; mDevice->RealOut.ChannelIndex[BackRight] = 3; mDevice->RealOut.ChannelIndex[FrontCenter] = 4; mDevice->RealOut.ChannelIndex[LFE] = 5; mDevice->RealOut.ChannelIndex[SideLeft] = 6; mDevice->RealOut.ChannelIndex[SideRight] = 7; return; case DevFmtX714: mDevice->RealOut.ChannelIndex[FrontLeft] = 0; mDevice->RealOut.ChannelIndex[FrontRight] = 1; mDevice->RealOut.ChannelIndex[BackLeft] = 2; mDevice->RealOut.ChannelIndex[BackRight] = 3; mDevice->RealOut.ChannelIndex[FrontCenter] = 4; mDevice->RealOut.ChannelIndex[LFE] = 5; mDevice->RealOut.ChannelIndex[SideLeft] = 6; mDevice->RealOut.ChannelIndex[SideRight] = 7; mDevice->RealOut.ChannelIndex[TopFrontLeft] = 8; mDevice->RealOut.ChannelIndex[TopFrontRight] = 9; mDevice->RealOut.ChannelIndex[TopBackLeft] = 10; mDevice->RealOut.ChannelIndex[TopBackRight] = 11; break; case DevFmtX7144: mDevice->RealOut.ChannelIndex[FrontLeft] = 0; mDevice->RealOut.ChannelIndex[FrontRight] = 1; mDevice->RealOut.ChannelIndex[BackLeft] = 2; mDevice->RealOut.ChannelIndex[BackRight] = 3; mDevice->RealOut.ChannelIndex[FrontCenter] = 4; mDevice->RealOut.ChannelIndex[LFE] = 5; mDevice->RealOut.ChannelIndex[SideLeft] = 6; mDevice->RealOut.ChannelIndex[SideRight] = 7; mDevice->RealOut.ChannelIndex[TopFrontLeft] = 8; mDevice->RealOut.ChannelIndex[TopFrontRight] = 9; mDevice->RealOut.ChannelIndex[TopBackLeft] = 10; mDevice->RealOut.ChannelIndex[TopBackRight] = 11; mDevice->RealOut.ChannelIndex[BottomFrontLeft] = 12; mDevice->RealOut.ChannelIndex[BottomFrontRight] = 13; mDevice->RealOut.ChannelIndex[BottomBackLeft] = 14; mDevice->RealOut.ChannelIndex[BottomBackRight] = 15; break; case DevFmtX3D71: mDevice->RealOut.ChannelIndex[FrontLeft] = 0; mDevice->RealOut.ChannelIndex[FrontRight] = 1; mDevice->RealOut.ChannelIndex[Aux0] = 2; mDevice->RealOut.ChannelIndex[Aux1] = 3; mDevice->RealOut.ChannelIndex[FrontCenter] = 4; mDevice->RealOut.ChannelIndex[LFE] = 5; mDevice->RealOut.ChannelIndex[SideLeft] = 6; mDevice->RealOut.ChannelIndex[SideRight] = 7; return; /* Same as WFX order */ case DevFmtMono: case DevFmtStereo: case DevFmtQuad: case DevFmtX61: case DevFmtAmbi3D: setDefaultWFXChannelOrder(); break; } } openal-soft-1.24.2/alc/backends/base.h000066400000000000000000000065101474041540300174270ustar00rootroot00000000000000#ifndef ALC_BACKENDS_BASE_H #define ALC_BACKENDS_BASE_H #include #include #include #include #include #include #include #include "alc/events.h" #include "core/device.h" #include "core/except.h" #include "fmt/core.h" using uint = unsigned int; struct ClockLatency { std::chrono::nanoseconds ClockTime; std::chrono::nanoseconds Latency; }; struct BackendBase { virtual void open(std::string_view name) = 0; virtual bool reset(); virtual void start() = 0; virtual void stop() = 0; virtual void captureSamples(std::byte *buffer, uint samples); virtual uint availableSamples(); virtual ClockLatency getClockLatency(); DeviceBase *const mDevice; std::string mDeviceName; BackendBase() = delete; BackendBase(const BackendBase&) = delete; BackendBase(BackendBase&&) = delete; explicit BackendBase(DeviceBase *device) noexcept : mDevice{device} { } virtual ~BackendBase() = default; void operator=(const BackendBase&) = delete; void operator=(BackendBase&&) = delete; protected: /** Sets the default channel order used by most non-WaveFormatEx-based APIs. */ void setDefaultChannelOrder() const; /** Sets the default channel order used by WaveFormatEx. */ void setDefaultWFXChannelOrder() const; }; using BackendPtr = std::unique_ptr; enum class BackendType { Playback, Capture }; /* Helper to get the device latency from the backend, including any fixed * latency from post-processing. */ inline ClockLatency GetClockLatency(DeviceBase *device, BackendBase *backend) { ClockLatency ret{backend->getClockLatency()}; ret.Latency += device->FixedLatency; return ret; } struct BackendFactory { BackendFactory() = default; BackendFactory(const BackendFactory&) = delete; BackendFactory(BackendFactory&&) = delete; virtual ~BackendFactory() = default; void operator=(const BackendFactory&) = delete; void operator=(BackendFactory&&) = delete; virtual auto init() -> bool = 0; virtual auto querySupport(BackendType type) -> bool = 0; virtual auto queryEventSupport(alc::EventType, BackendType) -> alc::EventSupport { return alc::EventSupport::NoSupport; } virtual auto enumerate(BackendType type) -> std::vector = 0; virtual auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr = 0; }; namespace al { enum class backend_error { NoDevice, DeviceError, OutOfMemory }; class backend_exception final : public base_exception { backend_error mErrorCode; static auto make_string(fmt::string_view fmt, fmt::format_args args) -> std::string; public: template backend_exception(backend_error code, fmt::format_string fmt, Args&& ...args) : base_exception{make_string(fmt, fmt::make_format_args(args...))}, mErrorCode{code} { } backend_exception(const backend_exception&) = default; backend_exception(backend_exception&&) = default; ~backend_exception() override; backend_exception& operator=(const backend_exception&) = default; backend_exception& operator=(backend_exception&&) = default; [[nodiscard]] auto errorCode() const noexcept -> backend_error { return mErrorCode; } }; } // namespace al #endif /* ALC_BACKENDS_BASE_H */ openal-soft-1.24.2/alc/backends/coreaudio.cpp000066400000000000000000001163051474041540300210260ustar00rootroot00000000000000/** * OpenAL cross platform audio library * Copyright (C) 1999-2007 by authors. * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * Or go to http://www.gnu.org/copyleft/lgpl.html */ #include "config.h" #include "coreaudio.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "alnumeric.h" #include "alstring.h" #include "core/converter.h" #include "core/device.h" #include "core/logging.h" #include "fmt/core.h" #include "ringbuffer.h" #include #include #if TARGET_OS_IOS || TARGET_OS_TV #define CAN_ENUMERATE 0 #else #include #define CAN_ENUMERATE 1 #endif namespace { constexpr auto OutputElement = 0; constexpr auto InputElement = 1; // These following arrays should always be defined in ascending AudioChannelLabel value order constexpr std::array MonoChanMap { kAudioChannelLabel_Mono }; constexpr std::array StereoChanMap { kAudioChannelLabel_Left, kAudioChannelLabel_Right}; constexpr std::array QuadChanMap { kAudioChannelLabel_Left, kAudioChannelLabel_Right, kAudioChannelLabel_LeftSurround, kAudioChannelLabel_RightSurround }; constexpr std::array X51ChanMap { kAudioChannelLabel_Left, kAudioChannelLabel_Right, kAudioChannelLabel_Center, kAudioChannelLabel_LFEScreen, kAudioChannelLabel_LeftSurround, kAudioChannelLabel_RightSurround }; constexpr std::array X51RearChanMap { kAudioChannelLabel_Left, kAudioChannelLabel_Right, kAudioChannelLabel_Center, kAudioChannelLabel_LFEScreen, kAudioChannelLabel_RearSurroundRight, kAudioChannelLabel_RearSurroundLeft }; constexpr std::array X61ChanMap { kAudioChannelLabel_Left, kAudioChannelLabel_Right, kAudioChannelLabel_Center, kAudioChannelLabel_LFEScreen, kAudioChannelLabel_CenterSurround, kAudioChannelLabel_RearSurroundRight, kAudioChannelLabel_RearSurroundLeft }; constexpr std::array X71ChanMap { kAudioChannelLabel_Left, kAudioChannelLabel_Right, kAudioChannelLabel_Center, kAudioChannelLabel_LFEScreen, kAudioChannelLabel_LeftSurround, kAudioChannelLabel_RightSurround, kAudioChannelLabel_LeftCenter, kAudioChannelLabel_RightCenter }; struct FourCCPrinter { char mString[sizeof(UInt32) + 1]{}; explicit constexpr FourCCPrinter(UInt32 code) noexcept { for(size_t i{0};i < sizeof(UInt32);++i) { const auto ch = static_cast(code & 0xff); /* If this breaks early it'll leave the first byte null, to get * read as a 0-length string. */ if(ch <= 0x1f || ch >= 0x7f) break; mString[sizeof(UInt32)-1-i] = ch; code >>= 8; } } explicit constexpr FourCCPrinter(OSStatus code) noexcept : FourCCPrinter{static_cast(code)} { } constexpr const char *c_str() const noexcept { return mString; } }; #if CAN_ENUMERATE struct DeviceEntry { AudioDeviceID mId; std::string mName; }; std::vector PlaybackList; std::vector CaptureList; OSStatus GetHwProperty(AudioHardwarePropertyID propId, UInt32 dataSize, void *propData) { const AudioObjectPropertyAddress addr{propId, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster}; return AudioObjectGetPropertyData(kAudioObjectSystemObject, &addr, 0, nullptr, &dataSize, propData); } OSStatus GetHwPropertySize(AudioHardwarePropertyID propId, UInt32 *outSize) { const AudioObjectPropertyAddress addr{propId, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster}; return AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &addr, 0, nullptr, outSize); } OSStatus GetDevProperty(AudioDeviceID devId, AudioDevicePropertyID propId, bool isCapture, UInt32 elem, UInt32 dataSize, void *propData) { static const AudioObjectPropertyScope scopes[2]{kAudioDevicePropertyScopeOutput, kAudioDevicePropertyScopeInput}; const AudioObjectPropertyAddress addr{propId, scopes[isCapture], elem}; return AudioObjectGetPropertyData(devId, &addr, 0, nullptr, &dataSize, propData); } OSStatus GetDevPropertySize(AudioDeviceID devId, AudioDevicePropertyID inPropertyID, bool isCapture, UInt32 elem, UInt32 *outSize) { static const AudioObjectPropertyScope scopes[2]{kAudioDevicePropertyScopeOutput, kAudioDevicePropertyScopeInput}; const AudioObjectPropertyAddress addr{inPropertyID, scopes[isCapture], elem}; return AudioObjectGetPropertyDataSize(devId, &addr, 0, nullptr, outSize); } std::string GetDeviceName(AudioDeviceID devId) { std::string devname; CFStringRef nameRef; /* Try to get the device name as a CFString, for Unicode name support. */ OSStatus err{GetDevProperty(devId, kAudioDevicePropertyDeviceNameCFString, false, 0, sizeof(nameRef), &nameRef)}; if(err == noErr) { const CFIndex propSize{CFStringGetMaximumSizeForEncoding(CFStringGetLength(nameRef), kCFStringEncodingUTF8)}; devname.resize(static_cast(propSize)+1, '\0'); CFStringGetCString(nameRef, &devname[0], propSize+1, kCFStringEncodingUTF8); CFRelease(nameRef); } else { /* If that failed, just get the C string. Hopefully there's nothing bad * with this. */ UInt32 propSize{}; if(GetDevPropertySize(devId, kAudioDevicePropertyDeviceName, false, 0, &propSize)) return devname; devname.resize(propSize+1, '\0'); if(GetDevProperty(devId, kAudioDevicePropertyDeviceName, false, 0, propSize, &devname[0])) { devname.clear(); return devname; } } /* Clear extraneous nul chars that may have been written with the name * string, and return it. */ while(!devname.empty() && !devname.back()) devname.pop_back(); return devname; } auto GetDeviceChannelCount(AudioDeviceID devId, bool isCapture) -> UInt32 { auto propSize = UInt32{}; auto err = GetDevPropertySize(devId, kAudioDevicePropertyStreamConfiguration, isCapture, 0, &propSize); if(err) { ERR("kAudioDevicePropertyStreamConfiguration size query failed: '{}' ({})", FourCCPrinter{err}.c_str(), err); return 0; } auto buflist_data = std::make_unique(propSize); auto *buflist = reinterpret_cast(buflist_data.get()); err = GetDevProperty(devId, kAudioDevicePropertyStreamConfiguration, isCapture, 0, propSize, buflist); if(err) { ERR("kAudioDevicePropertyStreamConfiguration query failed: '{}' ({})", FourCCPrinter{err}.c_str(), err); return 0; } auto numChannels = UInt32{0}; for(size_t i{0};i < buflist->mNumberBuffers;++i) numChannels += buflist->mBuffers[i].mNumberChannels; return numChannels; } void EnumerateDevices(std::vector &list, bool isCapture) { UInt32 propSize{}; if(auto err = GetHwPropertySize(kAudioHardwarePropertyDevices, &propSize)) { ERR("Failed to get device list size: {}", err); return; } auto devIds = std::vector(propSize/sizeof(AudioDeviceID), kAudioDeviceUnknown); if(auto err = GetHwProperty(kAudioHardwarePropertyDevices, propSize, devIds.data())) { ERR("Failed to get device list: '{}' ({})", FourCCPrinter{err}.c_str(), err); return; } std::vector newdevs; newdevs.reserve(devIds.size()); AudioDeviceID defaultId{kAudioDeviceUnknown}; GetHwProperty(isCapture ? kAudioHardwarePropertyDefaultInputDevice : kAudioHardwarePropertyDefaultOutputDevice, sizeof(defaultId), &defaultId); if(defaultId != kAudioDeviceUnknown) { newdevs.emplace_back(DeviceEntry{defaultId, GetDeviceName(defaultId)}); const auto &entry = newdevs.back(); TRACE("Got device: {} = ID {}", entry.mName, entry.mId); } for(const AudioDeviceID devId : devIds) { if(devId == kAudioDeviceUnknown) continue; auto match_devid = [devId](const DeviceEntry &entry) noexcept -> bool { return entry.mId == devId; }; auto match = std::find_if(newdevs.cbegin(), newdevs.cend(), match_devid); if(match != newdevs.cend()) continue; auto numChannels = GetDeviceChannelCount(devId, isCapture); if(numChannels > 0) { newdevs.emplace_back(DeviceEntry{devId, GetDeviceName(devId)}); const auto &entry = newdevs.back(); TRACE("Got device: {} = ID {}", entry.mName, entry.mId); } } if(newdevs.size() > 1) { /* Rename entries that have matching names, by appending '#2', '#3', * etc, as needed. */ for(auto curitem = newdevs.begin()+1;curitem != newdevs.end();++curitem) { auto check_match = [curitem](const DeviceEntry &entry) -> bool { return entry.mName == curitem->mName; }; if(std::find_if(newdevs.begin(), curitem, check_match) != curitem) { auto name = std::string{curitem->mName}; auto count = 1_uz; auto check_name = [&name](const DeviceEntry &entry) -> bool { return entry.mName == name; }; do { name = fmt::format("{} #{}", curitem->mName, ++count); } while(std::find_if(newdevs.begin(), curitem, check_name) != curitem); curitem->mName = std::move(name); } } } newdevs.shrink_to_fit(); newdevs.swap(list); } struct DeviceHelper { DeviceHelper() { AudioObjectPropertyAddress addr{kAudioHardwarePropertyDefaultOutputDevice, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster}; OSStatus status = AudioObjectAddPropertyListener(kAudioObjectSystemObject, &addr, DeviceListenerProc, nil); if (status != noErr) ERR("AudioObjectAddPropertyListener fail: {}", status); } ~DeviceHelper() { AudioObjectPropertyAddress addr{kAudioHardwarePropertyDefaultOutputDevice, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster}; OSStatus status = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &addr, DeviceListenerProc, nil); if (status != noErr) ERR("AudioObjectRemovePropertyListener fail: {}", status); } static OSStatus DeviceListenerProc(AudioObjectID /*inObjectID*/, UInt32 inNumberAddresses, const AudioObjectPropertyAddress *inAddresses, void* /*inClientData*/) { for(UInt32 i = 0; i < inNumberAddresses; ++i) { switch(inAddresses[i].mSelector) { case kAudioHardwarePropertyDefaultOutputDevice: case kAudioHardwarePropertyDefaultSystemOutputDevice: alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Playback, "Default playback device changed: "+std::to_string(inAddresses[i].mSelector)); break; case kAudioHardwarePropertyDefaultInputDevice: alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Capture, "Default capture device changed: "+std::to_string(inAddresses[i].mSelector)); break; } } return noErr; } }; static std::optional sDeviceHelper; #else static constexpr char ca_device[] = "CoreAudio Default"; #endif struct CoreAudioPlayback final : public BackendBase { explicit CoreAudioPlayback(DeviceBase *device) noexcept : BackendBase{device} { } ~CoreAudioPlayback() override; OSStatus MixerProc(AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData) noexcept; void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; AudioUnit mAudioUnit{}; uint mFrameSize{0u}; AudioStreamBasicDescription mFormat{}; // This is the OpenAL format as a CoreAudio ASBD }; CoreAudioPlayback::~CoreAudioPlayback() { AudioUnitUninitialize(mAudioUnit); AudioComponentInstanceDispose(mAudioUnit); } OSStatus CoreAudioPlayback::MixerProc(AudioUnitRenderActionFlags*, const AudioTimeStamp*, UInt32, UInt32, AudioBufferList *ioData) noexcept { for(size_t i{0};i < ioData->mNumberBuffers;++i) { auto &buffer = ioData->mBuffers[i]; mDevice->renderSamples(buffer.mData, buffer.mDataByteSize/mFrameSize, buffer.mNumberChannels); } return noErr; } void CoreAudioPlayback::open(std::string_view name) { #if CAN_ENUMERATE AudioDeviceID audioDevice{kAudioDeviceUnknown}; if(name.empty()) GetHwProperty(kAudioHardwarePropertyDefaultOutputDevice, sizeof(audioDevice), &audioDevice); else { if(PlaybackList.empty()) EnumerateDevices(PlaybackList, false); auto find_name = [name](const DeviceEntry &entry) -> bool { return entry.mName == name; }; auto devmatch = std::find_if(PlaybackList.cbegin(), PlaybackList.cend(), find_name); if(devmatch == PlaybackList.cend()) throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found", name}; audioDevice = devmatch->mId; } #else if(name.empty()) name = ca_device; else if(name != ca_device) throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found", name}; #endif /* open the default output unit */ AudioComponentDescription desc{}; desc.componentType = kAudioUnitType_Output; #if CAN_ENUMERATE desc.componentSubType = (audioDevice == kAudioDeviceUnknown) ? kAudioUnitSubType_DefaultOutput : kAudioUnitSubType_HALOutput; #else desc.componentSubType = kAudioUnitSubType_RemoteIO; #endif desc.componentManufacturer = kAudioUnitManufacturer_Apple; desc.componentFlags = 0; desc.componentFlagsMask = 0; AudioComponent comp{AudioComponentFindNext(NULL, &desc)}; if(comp == nullptr) throw al::backend_exception{al::backend_error::NoDevice, "Could not find audio component"}; AudioUnit audioUnit{}; OSStatus err{AudioComponentInstanceNew(comp, &audioUnit)}; if(err != noErr) throw al::backend_exception{al::backend_error::NoDevice, "Could not create component instance: '{}' ({})", FourCCPrinter{err}.c_str(), err}; #if CAN_ENUMERATE if(audioDevice != kAudioDeviceUnknown) AudioUnitSetProperty(audioUnit, kAudioOutputUnitProperty_CurrentDevice, kAudioUnitScope_Global, OutputElement, &audioDevice, sizeof(AudioDeviceID)); #endif err = AudioUnitInitialize(audioUnit); if(err != noErr) throw al::backend_exception{al::backend_error::DeviceError, "Could not initialize audio unit: '{}' ({})", FourCCPrinter{err}.c_str(), err}; /* WARNING: I don't know if "valid" audio unit values are guaranteed to be * non-0. If not, this logic is broken. */ if(mAudioUnit) { AudioUnitUninitialize(mAudioUnit); AudioComponentInstanceDispose(mAudioUnit); } mAudioUnit = audioUnit; #if CAN_ENUMERATE if(!name.empty()) mDeviceName = name; else { UInt32 propSize{sizeof(audioDevice)}; audioDevice = kAudioDeviceUnknown; AudioUnitGetProperty(audioUnit, kAudioOutputUnitProperty_CurrentDevice, kAudioUnitScope_Global, OutputElement, &audioDevice, &propSize); std::string devname{GetDeviceName(audioDevice)}; if(!devname.empty()) mDeviceName = std::move(devname); else mDeviceName = "Unknown Device Name"; } if(audioDevice != kAudioDeviceUnknown) { UInt32 type{}; err = GetDevProperty(audioDevice, kAudioDevicePropertyDataSource, false, kAudioObjectPropertyElementMaster, sizeof(type), &type); if(err != noErr) WARN("Failed to get audio device type: '{}' ({})", FourCCPrinter{err}.c_str(), err); else { TRACE("Got device type '{}'", FourCCPrinter{type}.c_str()); mDevice->Flags.set(DirectEar, (type == kIOAudioOutputPortSubTypeHeadphones)); } } #else mDeviceName = name; #endif } bool CoreAudioPlayback::reset() { OSStatus err{AudioUnitUninitialize(mAudioUnit)}; if(err != noErr) ERR("AudioUnitUninitialize failed: '{}' ({})", FourCCPrinter{err}.c_str(), err); /* retrieve default output unit's properties (output side) */ AudioStreamBasicDescription streamFormat{}; UInt32 size{sizeof(streamFormat)}; err = AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, OutputElement, &streamFormat, &size); if(err != noErr || size != sizeof(streamFormat)) { ERR("AudioUnitGetProperty(StreamFormat) failed: '{}' ({})", FourCCPrinter{err}.c_str(), err); return false; } /* Use the sample rate from the output unit's current parameters, but reset * everything else. */ if(mDevice->mSampleRate != streamFormat.mSampleRate) { mDevice->mBufferSize = static_cast(mDevice->mBufferSize*streamFormat.mSampleRate/ mDevice->mSampleRate + 0.5); mDevice->mSampleRate = static_cast(streamFormat.mSampleRate); } struct ChannelMap { DevFmtChannels fmt; al::span map; bool is_51rear; }; static constexpr std::array chanmaps{{ { DevFmtX71, X71ChanMap, false }, { DevFmtX61, X61ChanMap, false }, { DevFmtX51, X51ChanMap, false }, { DevFmtX51, X51RearChanMap, true }, { DevFmtQuad, QuadChanMap, false }, { DevFmtStereo, StereoChanMap, false }, { DevFmtMono, MonoChanMap, false } }}; if(!mDevice->Flags.test(ChannelsRequest)) { auto propSize = UInt32{}; auto writable = Boolean{}; err = AudioUnitGetPropertyInfo(mAudioUnit, kAudioUnitProperty_AudioChannelLayout, kAudioUnitScope_Output, OutputElement, &propSize, &writable); if(err == noErr) { auto layout_data = std::make_unique(propSize); auto *layout = reinterpret_cast(layout_data.get()); err = AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_AudioChannelLayout, kAudioUnitScope_Output, OutputElement, layout, &propSize); if(err == noErr) { auto descs = al::span{std::data(layout->mChannelDescriptions), layout->mNumberChannelDescriptions}; auto labels = std::vector(descs.size()); std::transform(descs.begin(), descs.end(), labels.begin(), std::mem_fn(&AudioChannelDescription::mChannelLabel)); sort(labels.begin(), labels.end()); auto check_labels = [&labels](const ChannelMap &chanmap) -> bool { return std::includes(labels.begin(), labels.end(), chanmap.map.begin(), chanmap.map.end()); }; auto chaniter = std::find_if(chanmaps.cbegin(), chanmaps.cend(), check_labels); if(chaniter != chanmaps.cend()) mDevice->FmtChans = chaniter->fmt; } } } /* TODO: Also set kAudioUnitProperty_AudioChannelLayout according to the AL * device's channel configuration. */ streamFormat.mChannelsPerFrame = mDevice->channelsFromFmt(); streamFormat.mFramesPerPacket = 1; streamFormat.mFormatFlags = kAudioFormatFlagsNativeEndian | kLinearPCMFormatFlagIsPacked; streamFormat.mFormatID = kAudioFormatLinearPCM; switch(mDevice->FmtType) { case DevFmtUByte: mDevice->FmtType = DevFmtByte; /* fall-through */ case DevFmtByte: streamFormat.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger; streamFormat.mBitsPerChannel = 8; break; case DevFmtUShort: mDevice->FmtType = DevFmtShort; /* fall-through */ case DevFmtShort: streamFormat.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger; streamFormat.mBitsPerChannel = 16; break; case DevFmtUInt: mDevice->FmtType = DevFmtInt; /* fall-through */ case DevFmtInt: streamFormat.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger; streamFormat.mBitsPerChannel = 32; break; case DevFmtFloat: streamFormat.mFormatFlags |= kLinearPCMFormatFlagIsFloat; streamFormat.mBitsPerChannel = 32; break; } streamFormat.mBytesPerFrame = streamFormat.mChannelsPerFrame*streamFormat.mBitsPerChannel/8; streamFormat.mBytesPerPacket = streamFormat.mBytesPerFrame*streamFormat.mFramesPerPacket; err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, OutputElement, &streamFormat, sizeof(streamFormat)); if(err != noErr) { ERR("AudioUnitSetProperty(StreamFormat) failed: '{}' ({})", FourCCPrinter{err}.c_str(), err); return false; } setDefaultWFXChannelOrder(); /* setup callback */ mFrameSize = mDevice->frameSizeFromFmt(); AURenderCallbackStruct input{}; input.inputProc = [](void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData) noexcept { return static_cast(inRefCon)->MixerProc(ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, ioData); }; input.inputProcRefCon = this; err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, OutputElement, &input, sizeof(AURenderCallbackStruct)); if(err != noErr) { ERR("AudioUnitSetProperty(SetRenderCallback) failed: '{}' ({})", FourCCPrinter{err}.c_str(), err); return false; } /* init the default audio unit... */ err = AudioUnitInitialize(mAudioUnit); if(err != noErr) { ERR("AudioUnitInitialize failed: '{}' ({})", FourCCPrinter{err}.c_str(), err); return false; } return true; } void CoreAudioPlayback::start() { const OSStatus err{AudioOutputUnitStart(mAudioUnit)}; if(err != noErr) throw al::backend_exception{al::backend_error::DeviceError, "AudioOutputUnitStart failed: '{}' ({})", FourCCPrinter{err}.c_str(), err}; } void CoreAudioPlayback::stop() { OSStatus err{AudioOutputUnitStop(mAudioUnit)}; if(err != noErr) ERR("AudioOutputUnitStop failed: '{}' ({})", FourCCPrinter{err}.c_str(), err); } struct CoreAudioCapture final : public BackendBase { explicit CoreAudioCapture(DeviceBase *device) noexcept : BackendBase{device} { } ~CoreAudioCapture() override; OSStatus RecordProc(AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData) noexcept; void open(std::string_view name) override; void start() override; void stop() override; void captureSamples(std::byte *buffer, uint samples) override; uint availableSamples() override; AudioUnit mAudioUnit{0}; uint mFrameSize{0u}; AudioStreamBasicDescription mFormat{}; // This is the OpenAL format as a CoreAudio ASBD SampleConverterPtr mConverter; std::vector mCaptureData; RingBufferPtr mRing{nullptr}; }; CoreAudioCapture::~CoreAudioCapture() { if(mAudioUnit) AudioComponentInstanceDispose(mAudioUnit); mAudioUnit = 0; } OSStatus CoreAudioCapture::RecordProc(AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList*) noexcept { union { std::byte buf[std::max(sizeof(AudioBufferList), offsetof(AudioBufferList, mBuffers[1]))]; AudioBufferList list; } audiobuf{}; audiobuf.list.mNumberBuffers = 1; audiobuf.list.mBuffers[0].mNumberChannels = mFormat.mChannelsPerFrame; audiobuf.list.mBuffers[0].mData = mCaptureData.data(); audiobuf.list.mBuffers[0].mDataByteSize = static_cast(mCaptureData.size()); OSStatus err{AudioUnitRender(mAudioUnit, ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, &audiobuf.list)}; if(err != noErr) { ERR("AudioUnitRender capture error: '{}' ({})", FourCCPrinter{err}.c_str(), err); return err; } std::ignore = mRing->write(mCaptureData.data(), inNumberFrames); return noErr; } void CoreAudioCapture::open(std::string_view name) { #if CAN_ENUMERATE AudioDeviceID audioDevice{kAudioDeviceUnknown}; if(name.empty()) GetHwProperty(kAudioHardwarePropertyDefaultInputDevice, sizeof(audioDevice), &audioDevice); else { if(CaptureList.empty()) EnumerateDevices(CaptureList, true); auto find_name = [name](const DeviceEntry &entry) -> bool { return entry.mName == name; }; auto devmatch = std::find_if(CaptureList.cbegin(), CaptureList.cend(), find_name); if(devmatch == CaptureList.cend()) throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found", name}; audioDevice = devmatch->mId; } #else if(name.empty()) name = ca_device; else if(name != ca_device) throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found", name}; #endif AudioComponentDescription desc{}; desc.componentType = kAudioUnitType_Output; #if CAN_ENUMERATE desc.componentSubType = (audioDevice == kAudioDeviceUnknown) ? kAudioUnitSubType_DefaultOutput : kAudioUnitSubType_HALOutput; #else desc.componentSubType = kAudioUnitSubType_RemoteIO; #endif desc.componentManufacturer = kAudioUnitManufacturer_Apple; desc.componentFlags = 0; desc.componentFlagsMask = 0; // Search for component with given description AudioComponent comp{AudioComponentFindNext(NULL, &desc)}; if(comp == NULL) throw al::backend_exception{al::backend_error::NoDevice, "Could not find audio component"}; // Open the component OSStatus err{AudioComponentInstanceNew(comp, &mAudioUnit)}; if(err != noErr) throw al::backend_exception{al::backend_error::NoDevice, "Could not create component instance: '{}' ({})", FourCCPrinter{err}.c_str(), err}; // Turn off AudioUnit output UInt32 enableIO{0}; err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, OutputElement, &enableIO, sizeof(enableIO)); if(err != noErr) throw al::backend_exception{al::backend_error::DeviceError, "Could not disable audio unit output property: '{}' ({})", FourCCPrinter{err}.c_str(), err}; // Turn on AudioUnit input enableIO = 1; err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, InputElement, &enableIO, sizeof(enableIO)); if(err != noErr) throw al::backend_exception{al::backend_error::DeviceError, "Could not enable audio unit input property: '{}' ({})", FourCCPrinter{err}.c_str(), err}; #if CAN_ENUMERATE if(audioDevice != kAudioDeviceUnknown) AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_CurrentDevice, kAudioUnitScope_Global, InputElement, &audioDevice, sizeof(AudioDeviceID)); #endif // set capture callback AURenderCallbackStruct input{}; input.inputProc = [](void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData) noexcept { return static_cast(inRefCon)->RecordProc(ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, ioData); }; input.inputProcRefCon = this; err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_SetInputCallback, kAudioUnitScope_Global, InputElement, &input, sizeof(AURenderCallbackStruct)); if(err != noErr) throw al::backend_exception{al::backend_error::DeviceError, "Could not set capture callback: '{}' ({})", FourCCPrinter{err}.c_str(), err}; // Disable buffer allocation for capture UInt32 flag{0}; err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_ShouldAllocateBuffer, kAudioUnitScope_Output, InputElement, &flag, sizeof(flag)); if(err != noErr) throw al::backend_exception{al::backend_error::DeviceError, "Could not disable buffer allocation property: '{}' ({})", FourCCPrinter{err}.c_str(), err}; // Initialize the device err = AudioUnitInitialize(mAudioUnit); if(err != noErr) throw al::backend_exception{al::backend_error::DeviceError, "Could not initialize audio unit: '{}' ({})", FourCCPrinter{err}.c_str(), err}; // Get the hardware format AudioStreamBasicDescription hardwareFormat{}; UInt32 propertySize{sizeof(hardwareFormat)}; err = AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, InputElement, &hardwareFormat, &propertySize); if(err != noErr || propertySize != sizeof(hardwareFormat)) throw al::backend_exception{al::backend_error::DeviceError, "Could not get input format: '{}' ({})", FourCCPrinter{err}.c_str(), err}; // Set up the requested format description AudioStreamBasicDescription requestedFormat{}; switch(mDevice->FmtType) { case DevFmtByte: requestedFormat.mBitsPerChannel = 8; requestedFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked; break; case DevFmtUByte: requestedFormat.mBitsPerChannel = 8; requestedFormat.mFormatFlags = kAudioFormatFlagIsPacked; break; case DevFmtShort: requestedFormat.mBitsPerChannel = 16; requestedFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked; break; case DevFmtUShort: requestedFormat.mBitsPerChannel = 16; requestedFormat.mFormatFlags = kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked; break; case DevFmtInt: requestedFormat.mBitsPerChannel = 32; requestedFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked; break; case DevFmtUInt: requestedFormat.mBitsPerChannel = 32; requestedFormat.mFormatFlags = kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked; break; case DevFmtFloat: requestedFormat.mBitsPerChannel = 32; requestedFormat.mFormatFlags = kLinearPCMFormatFlagIsFloat | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked; break; } switch(mDevice->FmtChans) { case DevFmtMono: requestedFormat.mChannelsPerFrame = 1; break; case DevFmtStereo: requestedFormat.mChannelsPerFrame = 2; break; case DevFmtQuad: case DevFmtX51: case DevFmtX61: case DevFmtX71: case DevFmtX714: case DevFmtX7144: case DevFmtX3D71: case DevFmtAmbi3D: throw al::backend_exception{al::backend_error::DeviceError, "{} not supported", DevFmtChannelsString(mDevice->FmtChans)}; } requestedFormat.mBytesPerFrame = requestedFormat.mChannelsPerFrame * requestedFormat.mBitsPerChannel / 8; requestedFormat.mBytesPerPacket = requestedFormat.mBytesPerFrame; requestedFormat.mSampleRate = mDevice->mSampleRate; requestedFormat.mFormatID = kAudioFormatLinearPCM; requestedFormat.mReserved = 0; requestedFormat.mFramesPerPacket = 1; // save requested format description for later use mFormat = requestedFormat; mFrameSize = mDevice->frameSizeFromFmt(); // Use intermediate format for sample rate conversion (outputFormat) // Set sample rate to the same as hardware for resampling later AudioStreamBasicDescription outputFormat{requestedFormat}; outputFormat.mSampleRate = hardwareFormat.mSampleRate; // The output format should be the requested format, but using the hardware sample rate // This is because the AudioUnit will automatically scale other properties, except for sample rate err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, InputElement, &outputFormat, sizeof(outputFormat)); if(err != noErr) throw al::backend_exception{al::backend_error::DeviceError, "Could not set input format: '{}' ({})", FourCCPrinter{err}.c_str(), err}; /* Calculate the minimum AudioUnit output format frame count for the pre- * conversion ring buffer. Ensure at least 100ms for the total buffer. */ double srateScale{outputFormat.mSampleRate / mDevice->mSampleRate}; auto FrameCount64 = std::max(static_cast(std::ceil(mDevice->mBufferSize*srateScale)), static_cast(outputFormat.mSampleRate)/10_u64); FrameCount64 += MaxResamplerPadding; if(FrameCount64 > std::numeric_limits::max()) throw al::backend_exception{al::backend_error::DeviceError, "Calculated frame count is too large: {}", FrameCount64}; UInt32 outputFrameCount{}; propertySize = sizeof(outputFrameCount); err = AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global, OutputElement, &outputFrameCount, &propertySize); if(err != noErr || propertySize != sizeof(outputFrameCount)) throw al::backend_exception{al::backend_error::DeviceError, "Could not get input frame count: '{}' ({})", FourCCPrinter{err}.c_str(), err}; mCaptureData.resize(outputFrameCount * mFrameSize); outputFrameCount = static_cast(std::max(uint64_t{outputFrameCount}, FrameCount64)); mRing = RingBuffer::Create(outputFrameCount, mFrameSize, false); /* Set up sample converter if needed */ if(outputFormat.mSampleRate != mDevice->mSampleRate) mConverter = SampleConverter::Create(mDevice->FmtType, mDevice->FmtType, mFormat.mChannelsPerFrame, static_cast(hardwareFormat.mSampleRate), mDevice->mSampleRate, Resampler::FastBSinc24); #if CAN_ENUMERATE if(!name.empty()) mDeviceName = name; else { UInt32 propSize{sizeof(audioDevice)}; audioDevice = kAudioDeviceUnknown; AudioUnitGetProperty(mAudioUnit, kAudioOutputUnitProperty_CurrentDevice, kAudioUnitScope_Global, InputElement, &audioDevice, &propSize); std::string devname{GetDeviceName(audioDevice)}; if(!devname.empty()) mDeviceName = std::move(devname); else mDeviceName = "Unknown Device Name"; } #else mDeviceName = name; #endif } void CoreAudioCapture::start() { OSStatus err{AudioOutputUnitStart(mAudioUnit)}; if(err != noErr) throw al::backend_exception{al::backend_error::DeviceError, "AudioOutputUnitStart failed: '{}' ({})", FourCCPrinter{err}.c_str(), err}; } void CoreAudioCapture::stop() { OSStatus err{AudioOutputUnitStop(mAudioUnit)}; if(err != noErr) ERR("AudioOutputUnitStop failed: '{}' ({})", FourCCPrinter{err}.c_str(), err); } void CoreAudioCapture::captureSamples(std::byte *buffer, uint samples) { if(!mConverter) { std::ignore = mRing->read(buffer, samples); return; } auto rec_vec = mRing->getReadVector(); const void *src0{rec_vec[0].buf}; auto src0len = static_cast(rec_vec[0].len); uint got{mConverter->convert(&src0, &src0len, buffer, samples)}; size_t total_read{rec_vec[0].len - src0len}; if(got < samples && !src0len && rec_vec[1].len > 0) { const void *src1{rec_vec[1].buf}; auto src1len = static_cast(rec_vec[1].len); got += mConverter->convert(&src1, &src1len, buffer + got*mFrameSize, samples-got); total_read += rec_vec[1].len - src1len; } mRing->readAdvance(total_read); } uint CoreAudioCapture::availableSamples() { if(!mConverter) return static_cast(mRing->readSpace()); return mConverter->availableOut(static_cast(mRing->readSpace())); } } // namespace BackendFactory &CoreAudioBackendFactory::getFactory() { static CoreAudioBackendFactory factory{}; return factory; } bool CoreAudioBackendFactory::init() { #if CAN_ENUMERATE sDeviceHelper.emplace(); #endif return true; } bool CoreAudioBackendFactory::querySupport(BackendType type) { return type == BackendType::Playback || type == BackendType::Capture; } auto CoreAudioBackendFactory::enumerate(BackendType type) -> std::vector { std::vector outnames; #if CAN_ENUMERATE auto append_name = [&outnames](const DeviceEntry &entry) -> void { outnames.emplace_back(entry.mName); }; switch(type) { case BackendType::Playback: EnumerateDevices(PlaybackList, false); outnames.reserve(PlaybackList.size()); std::for_each(PlaybackList.cbegin(), PlaybackList.cend(), append_name); break; case BackendType::Capture: EnumerateDevices(CaptureList, true); outnames.reserve(CaptureList.size()); std::for_each(CaptureList.cbegin(), CaptureList.cend(), append_name); break; } #else switch(type) { case BackendType::Playback: case BackendType::Capture: outnames.emplace_back(ca_device); break; } #endif return outnames; } BackendPtr CoreAudioBackendFactory::createBackend(DeviceBase *device, BackendType type) { if(type == BackendType::Playback) return BackendPtr{new CoreAudioPlayback{device}}; if(type == BackendType::Capture) return BackendPtr{new CoreAudioCapture{device}}; return nullptr; } alc::EventSupport CoreAudioBackendFactory::queryEventSupport(alc::EventType eventType, BackendType) { switch(eventType) { case alc::EventType::DefaultDeviceChanged: return alc::EventSupport::FullSupport; case alc::EventType::DeviceAdded: case alc::EventType::DeviceRemoved: case alc::EventType::Count: break; } return alc::EventSupport::NoSupport; } openal-soft-1.24.2/alc/backends/coreaudio.h000066400000000000000000000011041474041540300204610ustar00rootroot00000000000000#ifndef BACKENDS_COREAUDIO_H #define BACKENDS_COREAUDIO_H #include "base.h" struct CoreAudioBackendFactory final : public BackendFactory { public: auto init() -> bool final; auto querySupport(BackendType type) -> bool final; auto queryEventSupport(alc::EventType eventType, BackendType type) -> alc::EventSupport final; auto enumerate(BackendType type) -> std::vector final; auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_COREAUDIO_H */ openal-soft-1.24.2/alc/backends/dsound.cpp000066400000000000000000000670161474041540300203540ustar00rootroot00000000000000/** * OpenAL cross platform audio library * Copyright (C) 1999-2007 by authors. * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * Or go to http://www.gnu.org/copyleft/lgpl.html */ #include "config.h" #include "dsound.h" #define WIN32_LEAN_AND_MEAN #include #include #include #ifndef _WAVEFORMATEXTENSIBLE_ #include #include #endif #include #include #include #include #include #include #include #include #include #include "alnumeric.h" #include "alspan.h" #include "althrd_setname.h" #include "comptr.h" #include "core/device.h" #include "core/helpers.h" #include "core/logging.h" #include "dynload.h" #include "fmt/core.h" #include "ringbuffer.h" #include "strutils.h" /* MinGW-w64 needs this for some unknown reason now. */ using LPCWAVEFORMATEX = const WAVEFORMATEX*; #include #ifndef DSSPEAKER_5POINT1 # define DSSPEAKER_5POINT1 0x00000006 #endif #ifndef DSSPEAKER_5POINT1_BACK # define DSSPEAKER_5POINT1_BACK 0x00000006 #endif #ifndef DSSPEAKER_7POINT1 # define DSSPEAKER_7POINT1 0x00000007 #endif #ifndef DSSPEAKER_7POINT1_SURROUND # define DSSPEAKER_7POINT1_SURROUND 0x00000008 #endif #ifndef DSSPEAKER_5POINT1_SURROUND # define DSSPEAKER_5POINT1_SURROUND 0x00000009 #endif /* Some headers seem to define these as macros for __uuidof, which is annoying * since some headers don't declare them at all. Hopefully the ifdef is enough * to tell if they need to be declared. */ #ifndef KSDATAFORMAT_SUBTYPE_PCM DEFINE_GUID(KSDATAFORMAT_SUBTYPE_PCM, 0x00000001, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71); #endif #ifndef KSDATAFORMAT_SUBTYPE_IEEE_FLOAT DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, 0x00000003, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71); #endif namespace { #if HAVE_DYNLOAD void *ds_handle; HRESULT (WINAPI *pDirectSoundCreate)(const GUID *pcGuidDevice, IDirectSound **ppDS, IUnknown *pUnkOuter); HRESULT (WINAPI *pDirectSoundEnumerateW)(LPDSENUMCALLBACKW pDSEnumCallback, void *pContext); HRESULT (WINAPI *pDirectSoundCaptureCreate)(const GUID *pcGuidDevice, IDirectSoundCapture **ppDSC, IUnknown *pUnkOuter); HRESULT (WINAPI *pDirectSoundCaptureEnumerateW)(LPDSENUMCALLBACKW pDSEnumCallback, void *pContext); #ifndef IN_IDE_PARSER #define DirectSoundCreate pDirectSoundCreate #define DirectSoundEnumerateW pDirectSoundEnumerateW #define DirectSoundCaptureCreate pDirectSoundCaptureCreate #define DirectSoundCaptureEnumerateW pDirectSoundCaptureEnumerateW #endif #endif #define MONO SPEAKER_FRONT_CENTER #define STEREO (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT) #define QUAD (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT) #define X5DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT) #define X5DOT1REAR (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT) #define X6DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_CENTER|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT) #define X7DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT) #define X7DOT1DOT4 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT|SPEAKER_TOP_FRONT_LEFT|SPEAKER_TOP_FRONT_RIGHT|SPEAKER_TOP_BACK_LEFT|SPEAKER_TOP_BACK_RIGHT) #define MAX_UPDATES 128 struct DevMap { std::string name; GUID guid; template DevMap(T0&& name_, T1&& guid_) : name{std::forward(name_)}, guid{std::forward(guid_)} { } }; std::vector PlaybackDevices; std::vector CaptureDevices; bool checkName(const al::span list, const std::string &name) { auto match_name = [&name](const DevMap &entry) -> bool { return entry.name == name; }; return std::find_if(list.cbegin(), list.cend(), match_name) != list.cend(); } BOOL CALLBACK DSoundEnumDevices(GUID *guid, const WCHAR *desc, const WCHAR*, void *data) noexcept { if(!guid) return TRUE; auto& devices = *static_cast*>(data); const auto basename = wstr_to_utf8(desc); auto count = 1; auto newname = basename; while(checkName(devices, newname)) newname = fmt::format("{} #{}", basename, ++count); const DevMap &newentry = devices.emplace_back(std::move(newname), *guid); OLECHAR *guidstr{nullptr}; HRESULT hr{StringFromCLSID(*guid, &guidstr)}; if(SUCCEEDED(hr)) { TRACE("Got device \"{}\", GUID \"{}\"", newentry.name, wstr_to_utf8(guidstr)); CoTaskMemFree(guidstr); } return TRUE; } struct DSoundPlayback final : public BackendBase { explicit DSoundPlayback(DeviceBase *device) noexcept : BackendBase{device} { } ~DSoundPlayback() override; int mixerProc(); void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; ComPtr mDS; ComPtr mPrimaryBuffer; ComPtr mBuffer; ComPtr mNotifies; HANDLE mNotifyEvent{nullptr}; std::atomic mKillNow{true}; std::thread mThread; }; DSoundPlayback::~DSoundPlayback() { mNotifies = nullptr; mBuffer = nullptr; mPrimaryBuffer = nullptr; mDS = nullptr; if(mNotifyEvent) CloseHandle(mNotifyEvent); mNotifyEvent = nullptr; } FORCE_ALIGN int DSoundPlayback::mixerProc() { SetRTPriority(); althrd_setname(GetMixerThreadName()); DSBCAPS DSBCaps{}; DSBCaps.dwSize = sizeof(DSBCaps); HRESULT err{mBuffer->GetCaps(&DSBCaps)}; if(FAILED(err)) { ERR("Failed to get buffer caps: {:#x}", as_unsigned(err)); mDevice->handleDisconnect("Failure retrieving playback buffer info: {:#x}", as_unsigned(err)); return 1; } const size_t FrameStep{mDevice->channelsFromFmt()}; uint FrameSize{mDevice->frameSizeFromFmt()}; DWORD FragSize{mDevice->mUpdateSize * FrameSize}; bool Playing{false}; DWORD LastCursor{0u}; mBuffer->GetCurrentPosition(&LastCursor, nullptr); while(!mKillNow.load(std::memory_order_acquire) && mDevice->Connected.load(std::memory_order_acquire)) { // Get current play cursor DWORD PlayCursor; mBuffer->GetCurrentPosition(&PlayCursor, nullptr); DWORD avail = (PlayCursor-LastCursor+DSBCaps.dwBufferBytes) % DSBCaps.dwBufferBytes; if(avail < FragSize) { if(!Playing) { err = mBuffer->Play(0, 0, DSBPLAY_LOOPING); if(FAILED(err)) { ERR("Failed to play buffer: {:#x}", as_unsigned(err)); mDevice->handleDisconnect("Failure starting playback: {:#x}", as_unsigned(err)); return 1; } Playing = true; } avail = WaitForSingleObjectEx(mNotifyEvent, 2000, FALSE); if(avail != WAIT_OBJECT_0) ERR("WaitForSingleObjectEx error: {:#x}", avail); continue; } avail -= avail%FragSize; // Lock output buffer void *WritePtr1, *WritePtr2; DWORD WriteCnt1{0u}, WriteCnt2{0u}; err = mBuffer->Lock(LastCursor, avail, &WritePtr1, &WriteCnt1, &WritePtr2, &WriteCnt2, 0); // If the buffer is lost, restore it and lock if(err == DSERR_BUFFERLOST) { WARN("Buffer lost, restoring..."); err = mBuffer->Restore(); if(SUCCEEDED(err)) { Playing = false; LastCursor = 0; err = mBuffer->Lock(0, DSBCaps.dwBufferBytes, &WritePtr1, &WriteCnt1, &WritePtr2, &WriteCnt2, 0); } } if(FAILED(err)) { ERR("Buffer lock error: {:#x}", as_unsigned(err)); mDevice->handleDisconnect("Failed to lock output buffer: {:#x}", as_unsigned(err)); return 1; } mDevice->renderSamples(WritePtr1, WriteCnt1/FrameSize, FrameStep); if(WriteCnt2 > 0) mDevice->renderSamples(WritePtr2, WriteCnt2/FrameSize, FrameStep); mBuffer->Unlock(WritePtr1, WriteCnt1, WritePtr2, WriteCnt2); // Update old write cursor location LastCursor += WriteCnt1+WriteCnt2; LastCursor %= DSBCaps.dwBufferBytes; } return 0; } void DSoundPlayback::open(std::string_view name) { HRESULT hr; if(PlaybackDevices.empty()) { /* Initialize COM to prevent name truncation */ ComWrapper com{}; hr = DirectSoundEnumerateW(DSoundEnumDevices, &PlaybackDevices); if(FAILED(hr)) ERR("Error enumerating DirectSound devices: {:#x}", as_unsigned(hr)); } const GUID *guid{nullptr}; if(name.empty() && !PlaybackDevices.empty()) { name = PlaybackDevices[0].name; guid = &PlaybackDevices[0].guid; } else { auto iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(), [name](const DevMap &entry) -> bool { return entry.name == name; }); if(iter == PlaybackDevices.cend()) { GUID id{}; hr = CLSIDFromString(utf8_to_wstr(name).c_str(), &id); if(SUCCEEDED(hr)) iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(), [&id](const DevMap &entry) -> bool { return entry.guid == id; }); if(iter == PlaybackDevices.cend()) throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found", name}; } guid = &iter->guid; } hr = DS_OK; if(!mNotifyEvent) { mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr); if(!mNotifyEvent) hr = E_FAIL; } //DirectSound Init code ComPtr ds; if(SUCCEEDED(hr)) hr = DirectSoundCreate(guid, al::out_ptr(ds), nullptr); if(SUCCEEDED(hr)) hr = ds->SetCooperativeLevel(GetForegroundWindow(), DSSCL_PRIORITY); if(FAILED(hr)) throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: {:#x}", as_unsigned(hr)}; mNotifies = nullptr; mBuffer = nullptr; mPrimaryBuffer = nullptr; mDS = std::move(ds); mDeviceName = name; } bool DSoundPlayback::reset() { mNotifies = nullptr; mBuffer = nullptr; mPrimaryBuffer = nullptr; switch(mDevice->FmtType) { case DevFmtByte: mDevice->FmtType = DevFmtUByte; break; case DevFmtFloat: if(mDevice->Flags.test(SampleTypeRequest)) break; /* fall-through */ case DevFmtUShort: mDevice->FmtType = DevFmtShort; break; case DevFmtUInt: mDevice->FmtType = DevFmtInt; break; case DevFmtUByte: case DevFmtShort: case DevFmtInt: break; } WAVEFORMATEXTENSIBLE OutputType{}; DWORD speakers{}; HRESULT hr{mDS->GetSpeakerConfig(&speakers)}; if(FAILED(hr)) throw al::backend_exception{al::backend_error::DeviceError, "Failed to get speaker config: {:#x}", as_unsigned(hr)}; speakers = DSSPEAKER_CONFIG(speakers); if(!mDevice->Flags.test(ChannelsRequest)) { if(speakers == DSSPEAKER_MONO) mDevice->FmtChans = DevFmtMono; else if(speakers == DSSPEAKER_STEREO || speakers == DSSPEAKER_HEADPHONE) mDevice->FmtChans = DevFmtStereo; else if(speakers == DSSPEAKER_QUAD) mDevice->FmtChans = DevFmtQuad; else if(speakers == DSSPEAKER_5POINT1_SURROUND || speakers == DSSPEAKER_5POINT1_BACK) mDevice->FmtChans = DevFmtX51; else if(speakers == DSSPEAKER_7POINT1 || speakers == DSSPEAKER_7POINT1_SURROUND) mDevice->FmtChans = DevFmtX71; else ERR("Unknown system speaker config: {:#x}", speakers); } mDevice->Flags.set(DirectEar, (speakers == DSSPEAKER_HEADPHONE)); const bool isRear51{speakers == DSSPEAKER_5POINT1_BACK}; switch(mDevice->FmtChans) { case DevFmtMono: OutputType.dwChannelMask = MONO; break; case DevFmtAmbi3D: mDevice->FmtChans = DevFmtStereo; /* fall-through */ case DevFmtStereo: OutputType.dwChannelMask = STEREO; break; case DevFmtQuad: OutputType.dwChannelMask = QUAD; break; case DevFmtX51: OutputType.dwChannelMask = isRear51 ? X5DOT1REAR : X5DOT1; break; case DevFmtX61: OutputType.dwChannelMask = X6DOT1; break; case DevFmtX71: OutputType.dwChannelMask = X7DOT1; break; case DevFmtX7144: mDevice->FmtChans = DevFmtX714; /* fall-through */ case DevFmtX714: OutputType.dwChannelMask = X7DOT1DOT4; break; case DevFmtX3D71: OutputType.dwChannelMask = X7DOT1; break; } do { hr = S_OK; OutputType.Format.wFormatTag = WAVE_FORMAT_PCM; OutputType.Format.nChannels = static_cast(mDevice->channelsFromFmt()); OutputType.Format.wBitsPerSample = static_cast(mDevice->bytesFromFmt() * 8); OutputType.Format.nBlockAlign = static_cast(OutputType.Format.nChannels * OutputType.Format.wBitsPerSample / 8); OutputType.Format.nSamplesPerSec = mDevice->mSampleRate; OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec * OutputType.Format.nBlockAlign; OutputType.Format.cbSize = 0; if(OutputType.Format.nChannels > 2 || mDevice->FmtType == DevFmtFloat) { OutputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) */ OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample; OutputType.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX); if(mDevice->FmtType == DevFmtFloat) OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; else OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; mPrimaryBuffer = nullptr; } else { if(SUCCEEDED(hr) && !mPrimaryBuffer) { DSBUFFERDESC DSBDescription{}; DSBDescription.dwSize = sizeof(DSBDescription); DSBDescription.dwFlags = DSBCAPS_PRIMARYBUFFER; hr = mDS->CreateSoundBuffer(&DSBDescription, al::out_ptr(mPrimaryBuffer), nullptr); } if(SUCCEEDED(hr)) hr = mPrimaryBuffer->SetFormat(&OutputType.Format); } if(FAILED(hr)) break; uint num_updates{mDevice->mBufferSize / mDevice->mUpdateSize}; if(num_updates > MAX_UPDATES) num_updates = MAX_UPDATES; mDevice->mBufferSize = mDevice->mUpdateSize * num_updates; DSBUFFERDESC DSBDescription{}; DSBDescription.dwSize = sizeof(DSBDescription); DSBDescription.dwFlags = DSBCAPS_CTRLPOSITIONNOTIFY | DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_GLOBALFOCUS; DSBDescription.dwBufferBytes = mDevice->mBufferSize * OutputType.Format.nBlockAlign; DSBDescription.lpwfxFormat = &OutputType.Format; hr = mDS->CreateSoundBuffer(&DSBDescription, al::out_ptr(mBuffer), nullptr); if(SUCCEEDED(hr) || mDevice->FmtType != DevFmtFloat) break; mDevice->FmtType = DevFmtShort; } while(FAILED(hr)); if(SUCCEEDED(hr)) { hr = mBuffer->QueryInterface(IID_IDirectSoundNotify, al::out_ptr(mNotifies)); if(SUCCEEDED(hr)) { uint num_updates{mDevice->mBufferSize / mDevice->mUpdateSize}; assert(num_updates <= MAX_UPDATES); std::array nots{}; for(uint i{0};i < num_updates;++i) { nots[i].dwOffset = i * mDevice->mUpdateSize * OutputType.Format.nBlockAlign; nots[i].hEventNotify = mNotifyEvent; } if(mNotifies->SetNotificationPositions(num_updates, nots.data()) != DS_OK) hr = E_FAIL; } } if(FAILED(hr)) { mNotifies = nullptr; mBuffer = nullptr; mPrimaryBuffer = nullptr; return false; } ResetEvent(mNotifyEvent); setDefaultWFXChannelOrder(); return true; } void DSoundPlayback::start() { try { mKillNow.store(false, std::memory_order_release); mThread = std::thread{&DSoundPlayback::mixerProc, this}; } catch(std::exception& e) { throw al::backend_exception{al::backend_error::DeviceError, "Failed to start mixing thread: {}", e.what()}; } } void DSoundPlayback::stop() { if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable()) return; mThread.join(); mBuffer->Stop(); } struct DSoundCapture final : public BackendBase { explicit DSoundCapture(DeviceBase *device) noexcept : BackendBase{device} { } ~DSoundCapture() override; void open(std::string_view name) override; void start() override; void stop() override; void captureSamples(std::byte *buffer, uint samples) override; uint availableSamples() override; ComPtr mDSC; ComPtr mDSCbuffer; DWORD mBufferBytes{0u}; DWORD mCursor{0u}; RingBufferPtr mRing; }; DSoundCapture::~DSoundCapture() { if(mDSCbuffer) { mDSCbuffer->Stop(); mDSCbuffer = nullptr; } mDSC = nullptr; } void DSoundCapture::open(std::string_view name) { HRESULT hr; if(CaptureDevices.empty()) { /* Initialize COM to prevent name truncation */ ComWrapper com{}; hr = DirectSoundCaptureEnumerateW(DSoundEnumDevices, &CaptureDevices); if(FAILED(hr)) ERR("Error enumerating DirectSound devices: {:#x}", as_unsigned(hr)); } const GUID *guid{nullptr}; if(name.empty() && !CaptureDevices.empty()) { name = CaptureDevices[0].name; guid = &CaptureDevices[0].guid; } else { auto iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(), [name](const DevMap &entry) -> bool { return entry.name == name; }); if(iter == CaptureDevices.cend()) { GUID id{}; hr = CLSIDFromString(utf8_to_wstr(name).c_str(), &id); if(SUCCEEDED(hr)) iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(), [&id](const DevMap &entry) -> bool { return entry.guid == id; }); if(iter == CaptureDevices.cend()) throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found", name}; } guid = &iter->guid; } switch(mDevice->FmtType) { case DevFmtByte: case DevFmtUShort: case DevFmtUInt: WARN("{} capture samples not supported", DevFmtTypeString(mDevice->FmtType)); throw al::backend_exception{al::backend_error::DeviceError, "{} capture samples not supported", DevFmtTypeString(mDevice->FmtType)}; case DevFmtUByte: case DevFmtShort: case DevFmtInt: case DevFmtFloat: break; } WAVEFORMATEXTENSIBLE InputType{}; switch(mDevice->FmtChans) { case DevFmtMono: InputType.dwChannelMask = MONO; break; case DevFmtStereo: InputType.dwChannelMask = STEREO; break; case DevFmtQuad: InputType.dwChannelMask = QUAD; break; case DevFmtX51: InputType.dwChannelMask = X5DOT1; break; case DevFmtX61: InputType.dwChannelMask = X6DOT1; break; case DevFmtX71: InputType.dwChannelMask = X7DOT1; break; case DevFmtX714: InputType.dwChannelMask = X7DOT1DOT4; break; case DevFmtX7144: case DevFmtX3D71: case DevFmtAmbi3D: WARN("{} capture not supported", DevFmtChannelsString(mDevice->FmtChans)); throw al::backend_exception{al::backend_error::DeviceError, "{} capture not supported", DevFmtChannelsString(mDevice->FmtChans)}; } InputType.Format.wFormatTag = WAVE_FORMAT_PCM; InputType.Format.nChannels = static_cast(mDevice->channelsFromFmt()); InputType.Format.wBitsPerSample = static_cast(mDevice->bytesFromFmt() * 8); InputType.Format.nBlockAlign = static_cast(InputType.Format.nChannels * InputType.Format.wBitsPerSample / 8); InputType.Format.nSamplesPerSec = mDevice->mSampleRate; InputType.Format.nAvgBytesPerSec = InputType.Format.nSamplesPerSec * InputType.Format.nBlockAlign; InputType.Format.cbSize = 0; /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) */ InputType.Samples.wValidBitsPerSample = InputType.Format.wBitsPerSample; if(mDevice->FmtType == DevFmtFloat) InputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; else InputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; if(InputType.Format.nChannels > 2 || mDevice->FmtType == DevFmtFloat) { InputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; InputType.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX); } const uint samples{std::max(mDevice->mBufferSize, mDevice->mSampleRate/10u)}; DSCBUFFERDESC DSCBDescription{}; DSCBDescription.dwSize = sizeof(DSCBDescription); DSCBDescription.dwFlags = 0; DSCBDescription.dwBufferBytes = samples * InputType.Format.nBlockAlign; DSCBDescription.lpwfxFormat = &InputType.Format; //DirectSoundCapture Init code hr = DirectSoundCaptureCreate(guid, al::out_ptr(mDSC), nullptr); if(SUCCEEDED(hr)) mDSC->CreateCaptureBuffer(&DSCBDescription, al::out_ptr(mDSCbuffer), nullptr); if(SUCCEEDED(hr)) mRing = RingBuffer::Create(mDevice->mBufferSize, InputType.Format.nBlockAlign, false); if(FAILED(hr)) { mRing = nullptr; mDSCbuffer = nullptr; mDSC = nullptr; throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: {:#x}", as_unsigned(hr)}; } mBufferBytes = DSCBDescription.dwBufferBytes; setDefaultWFXChannelOrder(); mDeviceName = name; } void DSoundCapture::start() { const HRESULT hr{mDSCbuffer->Start(DSCBSTART_LOOPING)}; if(FAILED(hr)) throw al::backend_exception{al::backend_error::DeviceError, "Failure starting capture: {:#x}", as_unsigned(hr)}; } void DSoundCapture::stop() { HRESULT hr{mDSCbuffer->Stop()}; if(FAILED(hr)) { ERR("stop failed: {:#x}", as_unsigned(hr)); mDevice->handleDisconnect("Failure stopping capture: {:#x}", as_unsigned(hr)); } } void DSoundCapture::captureSamples(std::byte *buffer, uint samples) { std::ignore = mRing->read(buffer, samples); } uint DSoundCapture::availableSamples() { if(!mDevice->Connected.load(std::memory_order_acquire)) return static_cast(mRing->readSpace()); const uint FrameSize{mDevice->frameSizeFromFmt()}; const DWORD BufferBytes{mBufferBytes}; const DWORD LastCursor{mCursor}; DWORD ReadCursor{}; void *ReadPtr1{}, *ReadPtr2{}; DWORD ReadCnt1{}, ReadCnt2{}; HRESULT hr{mDSCbuffer->GetCurrentPosition(nullptr, &ReadCursor)}; if(SUCCEEDED(hr)) { const DWORD NumBytes{(BufferBytes+ReadCursor-LastCursor) % BufferBytes}; if(!NumBytes) return static_cast(mRing->readSpace()); hr = mDSCbuffer->Lock(LastCursor, NumBytes, &ReadPtr1, &ReadCnt1, &ReadPtr2, &ReadCnt2, 0); } if(SUCCEEDED(hr)) { std::ignore = mRing->write(ReadPtr1, ReadCnt1/FrameSize); if(ReadPtr2 != nullptr && ReadCnt2 > 0) std::ignore = mRing->write(ReadPtr2, ReadCnt2/FrameSize); hr = mDSCbuffer->Unlock(ReadPtr1, ReadCnt1, ReadPtr2, ReadCnt2); mCursor = ReadCursor; } if(FAILED(hr)) { ERR("update failed: {:#x}", as_unsigned(hr)); mDevice->handleDisconnect("Failure retrieving capture data: {:#x}", as_unsigned(hr)); } return static_cast(mRing->readSpace()); } } // namespace BackendFactory &DSoundBackendFactory::getFactory() { static DSoundBackendFactory factory{}; return factory; } bool DSoundBackendFactory::init() { #if HAVE_DYNLOAD if(!ds_handle) { ds_handle = LoadLib("dsound.dll"); if(!ds_handle) { ERR("Failed to load dsound.dll"); return false; } #define LOAD_FUNC(f) do { \ p##f = reinterpret_cast(GetSymbol(ds_handle, #f)); \ if(!p##f) \ { \ CloseLib(ds_handle); \ ds_handle = nullptr; \ return false; \ } \ } while(0) LOAD_FUNC(DirectSoundCreate); LOAD_FUNC(DirectSoundEnumerateW); LOAD_FUNC(DirectSoundCaptureCreate); LOAD_FUNC(DirectSoundCaptureEnumerateW); #undef LOAD_FUNC } #endif return true; } bool DSoundBackendFactory::querySupport(BackendType type) { return (type == BackendType::Playback || type == BackendType::Capture); } auto DSoundBackendFactory::enumerate(BackendType type) -> std::vector { std::vector outnames; auto add_device = [&outnames](const DevMap &entry) -> void { outnames.emplace_back(entry.name); }; /* Initialize COM to prevent name truncation */ ComWrapper com{}; switch(type) { case BackendType::Playback: PlaybackDevices.clear(); if(HRESULT hr{DirectSoundEnumerateW(DSoundEnumDevices, &PlaybackDevices)}; FAILED(hr)) ERR("Error enumerating DirectSound playback devices: {:#x}", as_unsigned(hr)); outnames.reserve(PlaybackDevices.size()); std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device); break; case BackendType::Capture: CaptureDevices.clear(); if(HRESULT hr{DirectSoundCaptureEnumerateW(DSoundEnumDevices, &CaptureDevices)};FAILED(hr)) ERR("Error enumerating DirectSound capture devices: {:#x}", as_unsigned(hr)); outnames.reserve(CaptureDevices.size()); std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device); break; } return outnames; } BackendPtr DSoundBackendFactory::createBackend(DeviceBase *device, BackendType type) { if(type == BackendType::Playback) return BackendPtr{new DSoundPlayback{device}}; if(type == BackendType::Capture) return BackendPtr{new DSoundCapture{device}}; return nullptr; } openal-soft-1.24.2/alc/backends/dsound.h000066400000000000000000000007241474041540300200120ustar00rootroot00000000000000#ifndef BACKENDS_DSOUND_H #define BACKENDS_DSOUND_H #include "base.h" struct DSoundBackendFactory final : public BackendFactory { public: auto init() -> bool final; auto querySupport(BackendType type) -> bool final; auto enumerate(BackendType type) -> std::vector final; auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_DSOUND_H */ openal-soft-1.24.2/alc/backends/jack.cpp000066400000000000000000000601401474041540300177570ustar00rootroot00000000000000/** * OpenAL cross platform audio library * Copyright (C) 1999-2007 by authors. * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * Or go to http://www.gnu.org/copyleft/lgpl.html */ #include "config.h" #include "jack.h" #include #include #include #include #include #include #include #include #include "alc/alconfig.h" #include "alnumeric.h" #include "alsem.h" #include "althrd_setname.h" #include "core/device.h" #include "core/helpers.h" #include "core/logging.h" #include "dynload.h" #include "fmt/format.h" #include "ringbuffer.h" #include #include namespace { using namespace std::string_view_literals; #if HAVE_DYNLOAD #define JACK_FUNCS(MAGIC) \ MAGIC(jack_client_open); \ MAGIC(jack_client_close); \ MAGIC(jack_client_name_size); \ MAGIC(jack_get_client_name); \ MAGIC(jack_connect); \ MAGIC(jack_activate); \ MAGIC(jack_deactivate); \ MAGIC(jack_port_register); \ MAGIC(jack_port_unregister); \ MAGIC(jack_port_get_buffer); \ MAGIC(jack_port_name); \ MAGIC(jack_get_ports); \ MAGIC(jack_free); \ MAGIC(jack_get_sample_rate); \ MAGIC(jack_set_error_function); \ MAGIC(jack_set_process_callback); \ MAGIC(jack_set_buffer_size_callback); \ MAGIC(jack_set_buffer_size); \ MAGIC(jack_get_buffer_size); void *jack_handle; #define MAKE_FUNC(f) decltype(f) * p##f JACK_FUNCS(MAKE_FUNC) decltype(jack_error_callback) * pjack_error_callback; #undef MAKE_FUNC #ifndef IN_IDE_PARSER #define jack_client_open pjack_client_open #define jack_client_close pjack_client_close #define jack_client_name_size pjack_client_name_size #define jack_get_client_name pjack_get_client_name #define jack_connect pjack_connect #define jack_activate pjack_activate #define jack_deactivate pjack_deactivate #define jack_port_register pjack_port_register #define jack_port_unregister pjack_port_unregister #define jack_port_get_buffer pjack_port_get_buffer #define jack_port_name pjack_port_name #define jack_get_ports pjack_get_ports #define jack_free pjack_free #define jack_get_sample_rate pjack_get_sample_rate #define jack_set_error_function pjack_set_error_function #define jack_set_process_callback pjack_set_process_callback #define jack_set_buffer_size_callback pjack_set_buffer_size_callback #define jack_set_buffer_size pjack_set_buffer_size #define jack_get_buffer_size pjack_get_buffer_size #define jack_error_callback (*pjack_error_callback) #endif #endif jack_options_t ClientOptions = JackNullOption; bool jack_load() { #if HAVE_DYNLOAD if(!jack_handle) { #if defined(_WIN64) #define JACKLIB "libjack64.dll" #elif defined(_WIN32) #define JACKLIB "libjack.dll" #else #define JACKLIB "libjack.so.0" #endif jack_handle = LoadLib(JACKLIB); if(!jack_handle) { WARN("Failed to load {}", JACKLIB); return false; } std::string missing_funcs; #define LOAD_FUNC(f) do { \ p##f = reinterpret_cast(GetSymbol(jack_handle, #f)); \ if(p##f == nullptr) missing_funcs += "\n" #f; \ } while(0) JACK_FUNCS(LOAD_FUNC); #undef LOAD_FUNC /* Optional symbols. These don't exist in all versions of JACK. */ #define LOAD_SYM(f) p##f = reinterpret_cast(GetSymbol(jack_handle, #f)) LOAD_SYM(jack_error_callback); #undef LOAD_SYM if(!missing_funcs.empty()) { WARN("Missing expected functions:{}", missing_funcs); CloseLib(jack_handle); jack_handle = nullptr; return false; } } #endif return true; } struct JackDeleter { void operator()(void *ptr) { jack_free(ptr); } }; using JackPortsPtr = std::unique_ptr; /* NOLINT(*-avoid-c-arrays) */ struct DeviceEntry { std::string mName; std::string mPattern; DeviceEntry() = default; DeviceEntry(const DeviceEntry&) = default; DeviceEntry(DeviceEntry&&) = default; template DeviceEntry(T&& name, U&& pattern) : mName{std::forward(name)}, mPattern{std::forward(pattern)} { } ~DeviceEntry(); DeviceEntry& operator=(const DeviceEntry&) = default; DeviceEntry& operator=(DeviceEntry&&) = default; }; DeviceEntry::~DeviceEntry() = default; std::vector PlaybackList; void EnumerateDevices(jack_client_t *client, std::vector &list) { std::remove_reference_t{}.swap(list); if(JackPortsPtr ports{jack_get_ports(client, nullptr, JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput)}) { for(size_t i{0};ports[i];++i) { const std::string_view portname{ports[i]}; const size_t seppos{portname.find(':')}; if(seppos == 0 || seppos >= portname.size()) continue; const auto portdev = portname.substr(0, seppos); auto check_name = [portdev](const DeviceEntry &entry) -> bool { return entry.mName == portdev; }; if(std::find_if(list.cbegin(), list.cend(), check_name) != list.cend()) continue; const auto &entry = list.emplace_back(portdev, fmt::format("{}:", portdev)); TRACE("Got device: {} = {}", entry.mName, entry.mPattern); } /* There are ports but couldn't get device names from them. Add a * generic entry. */ if(ports[0] && list.empty()) { WARN("No device names found in available ports, adding a generic name."); list.emplace_back("JACK"sv, ""sv); } } if(auto listopt = ConfigValueStr({}, "jack", "custom-devices")) { for(size_t strpos{0};strpos < listopt->size();) { size_t nextpos{listopt->find(';', strpos)}; size_t seppos{listopt->find('=', strpos)}; if(seppos >= nextpos || seppos == strpos) { const auto entry = std::string_view{*listopt}.substr(strpos, nextpos-strpos); ERR("Invalid device entry: \"{}\"", entry); if(nextpos != std::string::npos) ++nextpos; strpos = nextpos; continue; } const auto name = std::string_view{*listopt}.substr(strpos, seppos-strpos); const auto pattern = std::string_view{*listopt}.substr(seppos+1, std::min(nextpos, listopt->size())-(seppos+1)); /* Check if this custom pattern already exists in the list. */ auto check_pattern = [pattern](const DeviceEntry &entry) -> bool { return entry.mPattern == pattern; }; auto itemmatch = std::find_if(list.begin(), list.end(), check_pattern); if(itemmatch != list.end()) { /* If so, replace the name with this custom one. */ itemmatch->mName = name; TRACE("Customized device name: {} = {}", itemmatch->mName, itemmatch->mPattern); } else { /* Otherwise, add a new device entry. */ const auto &entry = list.emplace_back(name, pattern); TRACE("Got custom device: {} = {}", entry.mName, entry.mPattern); } if(nextpos != std::string::npos) ++nextpos; strpos = nextpos; } } if(list.size() > 1) { /* Rename entries that have matching names, by appending '#2', '#3', * etc, as needed. */ for(auto curitem = list.begin()+1;curitem != list.end();++curitem) { auto check_match = [curitem](const DeviceEntry &entry) -> bool { return entry.mName == curitem->mName; }; if(std::find_if(list.begin(), curitem, check_match) != curitem) { std::string name{curitem->mName}; size_t count{1}; auto check_name = [&name](const DeviceEntry &entry) -> bool { return entry.mName == name; }; do { name = curitem->mName; name += " #"; name += std::to_string(++count); } while(std::find_if(list.begin(), curitem, check_name) != curitem); curitem->mName = std::move(name); } } } } struct JackPlayback final : public BackendBase { explicit JackPlayback(DeviceBase *device) noexcept : BackendBase{device} { } ~JackPlayback() override; int processRt(jack_nframes_t numframes) noexcept; static int processRtC(jack_nframes_t numframes, void *arg) noexcept { return static_cast(arg)->processRt(numframes); } int process(jack_nframes_t numframes) noexcept; static int processC(jack_nframes_t numframes, void *arg) noexcept { return static_cast(arg)->process(numframes); } int mixerProc(); void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; ClockLatency getClockLatency() override; std::string mPortPattern; jack_client_t *mClient{nullptr}; std::array mPort{}; std::mutex mMutex; std::atomic mPlaying{false}; bool mRTMixing{false}; RingBufferPtr mRing; al::semaphore mSem; std::atomic mKillNow{true}; std::thread mThread; }; JackPlayback::~JackPlayback() { if(!mClient) return; auto unregister_port = [this](jack_port_t *port) -> void { if(port) jack_port_unregister(mClient, port); }; std::for_each(mPort.begin(), mPort.end(), unregister_port); mPort.fill(nullptr); jack_client_close(mClient); mClient = nullptr; } int JackPlayback::processRt(jack_nframes_t numframes) noexcept { auto outptrs = std::array{}; auto numchans = size_t{0}; for(auto port : mPort) { if(!port || numchans == mDevice->RealOut.Buffer.size()) break; outptrs[numchans++] = jack_port_get_buffer(port, numframes); } const auto dst = al::span{outptrs}.first(numchans); if(mPlaying.load(std::memory_order_acquire)) LIKELY mDevice->renderSamples(dst, static_cast(numframes)); else { std::for_each(dst.begin(), dst.end(), [numframes](void *outbuf) -> void { std::fill_n(static_cast(outbuf), numframes, 0.0f); }); } return 0; } int JackPlayback::process(jack_nframes_t numframes) noexcept { std::array,MaxOutputChannels> out; size_t numchans{0}; for(auto port : mPort) { if(!port) break; out[numchans++] = {static_cast(jack_port_get_buffer(port, numframes)), numframes}; } size_t total{0}; if(mPlaying.load(std::memory_order_acquire)) LIKELY { auto data = mRing->getReadVector(); const auto update_size = size_t{mDevice->mUpdateSize}; const auto outlen = size_t{numframes / update_size}; const auto len1 = size_t{std::min(data[0].len/update_size, outlen)}; const auto len2 = size_t{std::min(data[1].len/update_size, outlen-len1)}; auto src = al::span{reinterpret_cast(data[0].buf), update_size*len1*numchans}; for(size_t i{0};i < len1;++i) { for(size_t c{0};c < numchans;++c) { const auto iter = std::copy_n(src.begin(), update_size, out[c].begin()); out[c] = {iter, out[c].end()}; src = src.subspan(update_size); } total += update_size; } src = al::span{reinterpret_cast(data[1].buf), update_size*len2*numchans}; for(size_t i{0};i < len2;++i) { for(size_t c{0};c < numchans;++c) { const auto iter = std::copy_n(src.begin(), update_size, out[c].begin()); out[c] = {iter, out[c].end()}; src = src.subspan(update_size); } total += update_size; } mRing->readAdvance(total); mSem.post(); } if(numframes > total) { auto clear_buf = [](const al::span outbuf) -> void { std::fill(outbuf.begin(), outbuf.end(), 0.0f); }; std::for_each(out.begin(), out.begin()+numchans, clear_buf); } return 0; } int JackPlayback::mixerProc() { SetRTPriority(); althrd_setname(GetMixerThreadName()); const auto update_size = uint{mDevice->mUpdateSize}; const auto num_channels = size_t{mDevice->channelsFromFmt()}; auto outptrs = std::vector(num_channels); while(!mKillNow.load(std::memory_order_acquire) && mDevice->Connected.load(std::memory_order_acquire)) { if(mRing->writeSpace() < update_size) { mSem.wait(); continue; } auto data = mRing->getWriteVector(); const auto len1 = size_t{data[0].len / update_size}; const auto len2 = size_t{data[1].len / update_size}; std::lock_guard dlock{mMutex}; auto buffer = al::span{reinterpret_cast(data[0].buf), data[0].len*num_channels}; auto bufiter = buffer.begin(); for(size_t i{0};i < len1;++i) { std::generate_n(outptrs.begin(), outptrs.size(), [&bufiter,update_size] { auto ret = al::to_address(bufiter); bufiter += ptrdiff_t(update_size); return ret; }); mDevice->renderSamples(outptrs, update_size); } if(len2 > 0) { buffer = al::span{reinterpret_cast(data[1].buf), data[1].len*num_channels}; bufiter = buffer.begin(); for(size_t i{0};i < len2;++i) { std::generate_n(outptrs.begin(), outptrs.size(), [&bufiter,update_size] { auto ret = al::to_address(bufiter); bufiter += ptrdiff_t(update_size); return ret; }); mDevice->renderSamples(outptrs, update_size); } } mRing->writeAdvance((len1+len2) * update_size); } return 0; } void JackPlayback::open(std::string_view name) { if(!mClient) { const PathNamePair &binname = GetProcBinary(); const char *client_name{binname.fname.empty() ? "alsoft" : binname.fname.c_str()}; jack_status_t status{}; mClient = jack_client_open(client_name, ClientOptions, &status, nullptr); if(mClient == nullptr) throw al::backend_exception{al::backend_error::DeviceError, "Failed to open client connection: {:#02x}", as_unsigned(al::to_underlying(status))}; if((status&JackServerStarted)) TRACE("JACK server started"); if((status&JackNameNotUnique)) { client_name = jack_get_client_name(mClient); TRACE("Client name not unique, got '{}' instead", client_name); } } if(PlaybackList.empty()) EnumerateDevices(mClient, PlaybackList); if(name.empty() && !PlaybackList.empty()) { name = PlaybackList[0].mName; mPortPattern = PlaybackList[0].mPattern; } else { auto check_name = [name](const DeviceEntry &entry) -> bool { return entry.mName == name; }; auto iter = std::find_if(PlaybackList.cbegin(), PlaybackList.cend(), check_name); if(iter == PlaybackList.cend()) throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found", name}; mPortPattern = iter->mPattern; } mDeviceName = name; } bool JackPlayback::reset() { auto unregister_port = [this](jack_port_t *port) -> void { if(port) jack_port_unregister(mClient, port); }; std::for_each(mPort.begin(), mPort.end(), unregister_port); mPort.fill(nullptr); mRTMixing = GetConfigValueBool(mDevice->mDeviceName, "jack", "rt-mix", true); jack_set_process_callback(mClient, mRTMixing ? &JackPlayback::processRtC : &JackPlayback::processC, this); /* Ignore the requested buffer metrics and just keep one JACK-sized buffer * ready for when requested. */ mDevice->mSampleRate = jack_get_sample_rate(mClient); mDevice->mUpdateSize = jack_get_buffer_size(mClient); if(mRTMixing) { /* Assume only two periods when directly mixing. Should try to query * the total port latency when connected. */ mDevice->mBufferSize = mDevice->mUpdateSize * 2; } else { const auto devname = std::string_view{mDevice->mDeviceName}; auto bufsize = ConfigValueUInt(devname, "jack", "buffer-size") .value_or(mDevice->mUpdateSize); bufsize = std::max(NextPowerOf2(bufsize), mDevice->mUpdateSize); mDevice->mBufferSize = bufsize + mDevice->mUpdateSize; } /* Force 32-bit float output. */ mDevice->FmtType = DevFmtFloat; int port_num{0}; auto ports = al::span{mPort}.first(mDevice->channelsFromFmt()); auto bad_port = ports.begin(); while(bad_port != ports.end()) { std::string name{"channel_" + std::to_string(++port_num)}; *bad_port = jack_port_register(mClient, name.c_str(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput | JackPortIsTerminal, 0); if(!*bad_port) break; ++bad_port; } if(bad_port != ports.end()) { ERR("Failed to register enough JACK ports for {} output", DevFmtChannelsString(mDevice->FmtChans)); if(bad_port == ports.begin()) return false; if(bad_port == ports.begin()+1) mDevice->FmtChans = DevFmtMono; else { const auto ports_end = ports.begin()+2; while(bad_port != ports_end) { jack_port_unregister(mClient, *(--bad_port)); *bad_port = nullptr; } mDevice->FmtChans = DevFmtStereo; } } setDefaultChannelOrder(); return true; } void JackPlayback::start() { if(jack_activate(mClient)) throw al::backend_exception{al::backend_error::DeviceError, "Failed to activate client"}; const auto devname = std::string_view{mDevice->mDeviceName}; if(ConfigValueBool(devname, "jack", "connect-ports").value_or(true)) { JackPortsPtr pnames{jack_get_ports(mClient, mPortPattern.c_str(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput)}; if(!pnames) { jack_deactivate(mClient); throw al::backend_exception{al::backend_error::DeviceError, "No playback ports found"}; } for(size_t i{0};i < std::size(mPort) && mPort[i];++i) { if(!pnames[i]) { ERR("No physical playback port for \"{}\"", jack_port_name(mPort[i])); break; } if(jack_connect(mClient, jack_port_name(mPort[i]), pnames[i])) ERR("Failed to connect output port \"{}\" to \"{}\"", jack_port_name(mPort[i]), pnames[i]); } } /* Reconfigure buffer metrics in case the server changed it since the reset * (it won't change again after jack_activate), then allocate the ring * buffer with the appropriate size. */ mDevice->mSampleRate = jack_get_sample_rate(mClient); mDevice->mUpdateSize = jack_get_buffer_size(mClient); mDevice->mBufferSize = mDevice->mUpdateSize * 2; mRing = nullptr; if(mRTMixing) mPlaying.store(true, std::memory_order_release); else { uint bufsize{ConfigValueUInt(devname, "jack", "buffer-size") .value_or(mDevice->mUpdateSize)}; bufsize = std::max(NextPowerOf2(bufsize), mDevice->mUpdateSize); mDevice->mBufferSize = bufsize + mDevice->mUpdateSize; mRing = RingBuffer::Create(bufsize, mDevice->frameSizeFromFmt(), true); try { mPlaying.store(true, std::memory_order_release); mKillNow.store(false, std::memory_order_release); mThread = std::thread{&JackPlayback::mixerProc, this}; } catch(std::exception& e) { jack_deactivate(mClient); mPlaying.store(false, std::memory_order_release); throw al::backend_exception{al::backend_error::DeviceError, "Failed to start mixing thread: {}", e.what()}; } } } void JackPlayback::stop() { if(mPlaying.load(std::memory_order_acquire)) { mKillNow.store(true, std::memory_order_release); if(mThread.joinable()) { mSem.post(); mThread.join(); } jack_deactivate(mClient); mPlaying.store(false, std::memory_order_release); } } ClockLatency JackPlayback::getClockLatency() { std::lock_guard dlock{mMutex}; ClockLatency ret{}; ret.ClockTime = mDevice->getClockTime(); ret.Latency = std::chrono::seconds{mRing ? mRing->readSpace() : mDevice->mUpdateSize}; ret.Latency /= mDevice->mSampleRate; return ret; } void jack_msg_handler(const char *message) { WARN("{}", message); } } // namespace bool JackBackendFactory::init() { if(!jack_load()) return false; if(!GetConfigValueBool({}, "jack", "spawn-server", false)) ClientOptions = static_cast(ClientOptions | JackNoStartServer); const PathNamePair &binname = GetProcBinary(); const char *client_name{binname.fname.empty() ? "alsoft" : binname.fname.c_str()}; void (*old_error_cb)(const char*){&jack_error_callback ? jack_error_callback : nullptr}; jack_set_error_function(jack_msg_handler); jack_status_t status{}; jack_client_t *client{jack_client_open(client_name, ClientOptions, &status, nullptr)}; jack_set_error_function(old_error_cb); if(!client) { WARN("jack_client_open() failed, {:#02x}", as_unsigned(al::to_underlying(status))); if((status&JackServerFailed) && !(ClientOptions&JackNoStartServer)) ERR("Unable to connect to JACK server"); return false; } jack_client_close(client); return true; } bool JackBackendFactory::querySupport(BackendType type) { return (type == BackendType::Playback); } auto JackBackendFactory::enumerate(BackendType type) -> std::vector { std::vector outnames; auto append_name = [&outnames](const DeviceEntry &entry) -> void { outnames.emplace_back(entry.mName); }; const PathNamePair &binname = GetProcBinary(); const char *client_name{binname.fname.empty() ? "alsoft" : binname.fname.c_str()}; jack_status_t status{}; switch(type) { case BackendType::Playback: if(jack_client_t *client{jack_client_open(client_name, ClientOptions, &status, nullptr)}) { EnumerateDevices(client, PlaybackList); jack_client_close(client); } else WARN("jack_client_open() failed, {:#02x}", as_unsigned(al::to_underlying(status))); outnames.reserve(PlaybackList.size()); std::for_each(PlaybackList.cbegin(), PlaybackList.cend(), append_name); break; case BackendType::Capture: break; } return outnames; } BackendPtr JackBackendFactory::createBackend(DeviceBase *device, BackendType type) { if(type == BackendType::Playback) return BackendPtr{new JackPlayback{device}}; return nullptr; } BackendFactory &JackBackendFactory::getFactory() { static JackBackendFactory factory{}; return factory; } openal-soft-1.24.2/alc/backends/jack.h000066400000000000000000000007141474041540300174250ustar00rootroot00000000000000#ifndef BACKENDS_JACK_H #define BACKENDS_JACK_H #include "base.h" struct JackBackendFactory final : public BackendFactory { public: auto init() -> bool final; auto querySupport(BackendType type) -> bool final; auto enumerate(BackendType type) -> std::vector final; auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_JACK_H */ openal-soft-1.24.2/alc/backends/loopback.cpp000066400000000000000000000036661474041540300206530ustar00rootroot00000000000000/** * OpenAL cross platform audio library * Copyright (C) 2011 by Chris Robinson * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * Or go to http://www.gnu.org/copyleft/lgpl.html */ #include "config.h" #include "loopback.h" #include "core/device.h" namespace { struct LoopbackBackend final : public BackendBase { explicit LoopbackBackend(DeviceBase *device) noexcept : BackendBase{device} { } void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; }; void LoopbackBackend::open(std::string_view name) { mDeviceName = name; } bool LoopbackBackend::reset() { setDefaultWFXChannelOrder(); return true; } void LoopbackBackend::start() { } void LoopbackBackend::stop() { } } // namespace bool LoopbackBackendFactory::init() { return true; } bool LoopbackBackendFactory::querySupport(BackendType) { return true; } auto LoopbackBackendFactory::enumerate(BackendType) -> std::vector { return {}; } BackendPtr LoopbackBackendFactory::createBackend(DeviceBase *device, BackendType) { return BackendPtr{new LoopbackBackend{device}}; } BackendFactory &LoopbackBackendFactory::getFactory() { static LoopbackBackendFactory factory{}; return factory; } openal-soft-1.24.2/alc/backends/loopback.h000066400000000000000000000007341474041540300203110ustar00rootroot00000000000000#ifndef BACKENDS_LOOPBACK_H #define BACKENDS_LOOPBACK_H #include "base.h" struct LoopbackBackendFactory final : public BackendFactory { public: auto init() -> bool final; auto querySupport(BackendType type) -> bool final; auto enumerate(BackendType type) -> std::vector final; auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_LOOPBACK_H */ openal-soft-1.24.2/alc/backends/null.cpp000066400000000000000000000112621474041540300200220ustar00rootroot00000000000000/** * OpenAL cross platform audio library * Copyright (C) 2010 by Chris Robinson * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * Or go to http://www.gnu.org/copyleft/lgpl.html */ #include "config.h" #include "null.h" #include #include #include #include #include #include #include "althrd_setname.h" #include "core/device.h" #include "core/helpers.h" namespace { using std::chrono::seconds; using std::chrono::milliseconds; using std::chrono::nanoseconds; using namespace std::string_view_literals; [[nodiscard]] constexpr auto GetDeviceName() noexcept { return "No Output"sv; } struct NullBackend final : public BackendBase { explicit NullBackend(DeviceBase *device) noexcept : BackendBase{device} { } int mixerProc(); void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; std::atomic mKillNow{true}; std::thread mThread; }; int NullBackend::mixerProc() { const milliseconds restTime{mDevice->mUpdateSize*1000/mDevice->mSampleRate / 2}; SetRTPriority(); althrd_setname(GetMixerThreadName()); int64_t done{0}; auto start = std::chrono::steady_clock::now(); while(!mKillNow.load(std::memory_order_acquire) && mDevice->Connected.load(std::memory_order_acquire)) { auto now = std::chrono::steady_clock::now(); /* This converts from nanoseconds to nanosamples, then to samples. */ const auto avail = int64_t{std::chrono::duration_cast((now-start) * mDevice->mSampleRate).count()}; if(avail-done < mDevice->mUpdateSize) { std::this_thread::sleep_for(restTime); continue; } while(avail-done >= mDevice->mUpdateSize) { mDevice->renderSamples(nullptr, mDevice->mUpdateSize, 0u); done += mDevice->mUpdateSize; } /* For every completed second, increment the start time and reduce the * samples done. This prevents the difference between the start time * and current time from growing too large, while maintaining the * correct number of samples to render. */ if(done >= mDevice->mSampleRate) { seconds s{done/mDevice->mSampleRate}; start += s; done -= mDevice->mSampleRate*s.count(); } } return 0; } void NullBackend::open(std::string_view name) { if(name.empty()) name = GetDeviceName(); else if(name != GetDeviceName()) throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found", name}; mDeviceName = name; } bool NullBackend::reset() { setDefaultWFXChannelOrder(); return true; } void NullBackend::start() { try { mKillNow.store(false, std::memory_order_release); mThread = std::thread{&NullBackend::mixerProc, this}; } catch(std::exception& e) { throw al::backend_exception{al::backend_error::DeviceError, "Failed to start mixing thread: {}", e.what()}; } } void NullBackend::stop() { if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable()) return; mThread.join(); } } // namespace bool NullBackendFactory::init() { return true; } bool NullBackendFactory::querySupport(BackendType type) { return (type == BackendType::Playback); } auto NullBackendFactory::enumerate(BackendType type) -> std::vector { switch(type) { case BackendType::Playback: /* Include null char. */ return std::vector{std::string{GetDeviceName()}}; case BackendType::Capture: break; } return {}; } BackendPtr NullBackendFactory::createBackend(DeviceBase *device, BackendType type) { if(type == BackendType::Playback) return BackendPtr{new NullBackend{device}}; return nullptr; } BackendFactory &NullBackendFactory::getFactory() { static NullBackendFactory factory{}; return factory; } openal-soft-1.24.2/alc/backends/null.h000066400000000000000000000007141474041540300174670ustar00rootroot00000000000000#ifndef BACKENDS_NULL_H #define BACKENDS_NULL_H #include "base.h" struct NullBackendFactory final : public BackendFactory { public: auto init() -> bool final; auto querySupport(BackendType type) -> bool final; auto enumerate(BackendType type) -> std::vector final; auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_NULL_H */ openal-soft-1.24.2/alc/backends/oboe.cpp000066400000000000000000000304601474041540300177750ustar00rootroot00000000000000 #include "config.h" #include "oboe.h" #include #include #include #include "alnumeric.h" #include "alstring.h" #include "core/device.h" #include "core/logging.h" #include "ringbuffer.h" #include "oboe/Oboe.h" namespace { using namespace std::string_view_literals; [[nodiscard]] constexpr auto GetDeviceName() noexcept { return "Oboe Default"sv; } struct OboePlayback final : public BackendBase, public oboe::AudioStreamCallback { explicit OboePlayback(DeviceBase *device) : BackendBase{device} { } oboe::ManagedStream mStream; oboe::DataCallbackResult onAudioReady(oboe::AudioStream *oboeStream, void *audioData, int32_t numFrames) override; void onErrorAfterClose(oboe::AudioStream* /* audioStream */, oboe::Result /* error */) override; void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; }; oboe::DataCallbackResult OboePlayback::onAudioReady(oboe::AudioStream *oboeStream, void *audioData, int32_t numFrames) { assert(numFrames > 0); const int32_t numChannels{oboeStream->getChannelCount()}; mDevice->renderSamples(audioData, static_cast(numFrames), static_cast(numChannels)); return oboe::DataCallbackResult::Continue; } void OboePlayback::onErrorAfterClose(oboe::AudioStream*, oboe::Result error) { if(error == oboe::Result::ErrorDisconnected) mDevice->handleDisconnect("Oboe AudioStream was disconnected: {}", oboe::convertToText(error)); TRACE("Error was {}", oboe::convertToText(error)); } void OboePlayback::open(std::string_view name) { if(name.empty()) name = GetDeviceName(); else if(name != GetDeviceName()) throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found", name}; /* Open a basic output stream, just to ensure it can work. */ oboe::ManagedStream stream; oboe::Result result{oboe::AudioStreamBuilder{}.setDirection(oboe::Direction::Output) ->setPerformanceMode(oboe::PerformanceMode::LowLatency) ->openManagedStream(stream)}; if(result != oboe::Result::OK) throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: {}", oboe::convertToText(result)}; mDeviceName = name; } bool OboePlayback::reset() { oboe::AudioStreamBuilder builder; builder.setDirection(oboe::Direction::Output); builder.setPerformanceMode(oboe::PerformanceMode::LowLatency); builder.setUsage(oboe::Usage::Game); /* Don't let Oboe convert. We should be able to handle anything it gives * back. */ builder.setSampleRateConversionQuality(oboe::SampleRateConversionQuality::None); builder.setChannelConversionAllowed(false); builder.setFormatConversionAllowed(false); builder.setCallback(this); if(mDevice->Flags.test(FrequencyRequest)) { builder.setSampleRateConversionQuality(oboe::SampleRateConversionQuality::High); builder.setSampleRate(static_cast(mDevice->mSampleRate)); } if(mDevice->Flags.test(ChannelsRequest)) { /* Only use mono or stereo at user request. There's no telling what * other counts may be inferred as. */ builder.setChannelCount((mDevice->FmtChans==DevFmtMono) ? oboe::ChannelCount::Mono : (mDevice->FmtChans==DevFmtStereo) ? oboe::ChannelCount::Stereo : oboe::ChannelCount::Unspecified); } if(mDevice->Flags.test(SampleTypeRequest)) { oboe::AudioFormat format{oboe::AudioFormat::Unspecified}; switch(mDevice->FmtType) { case DevFmtByte: case DevFmtUByte: case DevFmtShort: case DevFmtUShort: format = oboe::AudioFormat::I16; break; case DevFmtInt: case DevFmtUInt: #if OBOE_VERSION_MAJOR > 1 || (OBOE_VERSION_MAJOR == 1 && OBOE_VERSION_MINOR >= 6) format = oboe::AudioFormat::I32; break; #endif case DevFmtFloat: format = oboe::AudioFormat::Float; break; } builder.setFormat(format); } oboe::Result result{builder.openManagedStream(mStream)}; /* If the format failed, try asking for the defaults. */ while(result == oboe::Result::ErrorInvalidFormat) { if(builder.getFormat() != oboe::AudioFormat::Unspecified) builder.setFormat(oboe::AudioFormat::Unspecified); else if(builder.getSampleRate() != oboe::kUnspecified) builder.setSampleRate(oboe::kUnspecified); else if(builder.getChannelCount() != oboe::ChannelCount::Unspecified) builder.setChannelCount(oboe::ChannelCount::Unspecified); else break; result = builder.openManagedStream(mStream); } if(result != oboe::Result::OK) throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: {}", oboe::convertToText(result)}; mStream->setBufferSizeInFrames(std::min(static_cast(mDevice->mBufferSize), mStream->getBufferCapacityInFrames())); TRACE("Got stream with properties:\n{}", oboe::convertToText(mStream.get())); if(static_cast(mStream->getChannelCount()) != mDevice->channelsFromFmt()) { if(mStream->getChannelCount() >= 2) mDevice->FmtChans = DevFmtStereo; else if(mStream->getChannelCount() == 1) mDevice->FmtChans = DevFmtMono; else throw al::backend_exception{al::backend_error::DeviceError, "Got unhandled channel count: {}", mStream->getChannelCount()}; } setDefaultWFXChannelOrder(); switch(mStream->getFormat()) { case oboe::AudioFormat::I16: mDevice->FmtType = DevFmtShort; break; case oboe::AudioFormat::Float: mDevice->FmtType = DevFmtFloat; break; #if OBOE_VERSION_MAJOR > 1 || (OBOE_VERSION_MAJOR == 1 && OBOE_VERSION_MINOR >= 6) case oboe::AudioFormat::I32: mDevice->FmtType = DevFmtInt; break; case oboe::AudioFormat::I24: #endif #if OBOE_VERSION_MAJOR > 1 || (OBOE_VERSION_MAJOR == 1 && OBOE_VERSION_MINOR >= 8) case oboe::AudioFormat::IEC61937: #endif case oboe::AudioFormat::Unspecified: case oboe::AudioFormat::Invalid: throw al::backend_exception{al::backend_error::DeviceError, "Got unhandled sample type: {}", oboe::convertToText(mStream->getFormat())}; } mDevice->mSampleRate = static_cast(mStream->getSampleRate()); /* Ensure the period size is no less than 10ms. It's possible for FramesPerCallback to be 0 * indicating variable updates, but OpenAL should have a reasonable minimum update size set. * FramesPerBurst may not necessarily be correct, but hopefully it can act as a minimum * update size. */ mDevice->mUpdateSize = std::max(mDevice->mSampleRate/100u, static_cast(mStream->getFramesPerBurst())); mDevice->mBufferSize = std::max(mDevice->mUpdateSize*2u, static_cast(mStream->getBufferSizeInFrames())); return true; } void OboePlayback::start() { const oboe::Result result{mStream->start()}; if(result != oboe::Result::OK) throw al::backend_exception{al::backend_error::DeviceError, "Failed to start stream: {}", oboe::convertToText(result)}; } void OboePlayback::stop() { oboe::Result result{mStream->stop()}; if(result != oboe::Result::OK) ERR("Failed to stop stream: {}", oboe::convertToText(result)); } struct OboeCapture final : public BackendBase, public oboe::AudioStreamCallback { explicit OboeCapture(DeviceBase *device) : BackendBase{device} { } oboe::ManagedStream mStream; RingBufferPtr mRing{nullptr}; oboe::DataCallbackResult onAudioReady(oboe::AudioStream *oboeStream, void *audioData, int32_t numFrames) override; void open(std::string_view name) override; void start() override; void stop() override; void captureSamples(std::byte *buffer, uint samples) override; uint availableSamples() override; }; oboe::DataCallbackResult OboeCapture::onAudioReady(oboe::AudioStream*, void *audioData, int32_t numFrames) { std::ignore = mRing->write(audioData, static_cast(numFrames)); return oboe::DataCallbackResult::Continue; } void OboeCapture::open(std::string_view name) { if(name.empty()) name = GetDeviceName(); else if(name != GetDeviceName()) throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found", name}; oboe::AudioStreamBuilder builder; builder.setDirection(oboe::Direction::Input) ->setPerformanceMode(oboe::PerformanceMode::LowLatency) ->setSampleRateConversionQuality(oboe::SampleRateConversionQuality::High) ->setChannelConversionAllowed(true) ->setFormatConversionAllowed(true) ->setSampleRate(static_cast(mDevice->mSampleRate)) ->setCallback(this); /* Only use mono or stereo at user request. There's no telling what * other counts may be inferred as. */ switch(mDevice->FmtChans) { case DevFmtMono: builder.setChannelCount(oboe::ChannelCount::Mono); break; case DevFmtStereo: builder.setChannelCount(oboe::ChannelCount::Stereo); break; case DevFmtQuad: case DevFmtX51: case DevFmtX61: case DevFmtX71: case DevFmtX714: case DevFmtX7144: case DevFmtX3D71: case DevFmtAmbi3D: throw al::backend_exception{al::backend_error::DeviceError, "{} capture not supported", DevFmtChannelsString(mDevice->FmtChans)}; } /* FIXME: This really should support UByte, but Oboe doesn't. We'll need to * convert. */ switch(mDevice->FmtType) { case DevFmtShort: builder.setFormat(oboe::AudioFormat::I16); break; case DevFmtFloat: builder.setFormat(oboe::AudioFormat::Float); break; case DevFmtInt: #if OBOE_VERSION_MAJOR > 1 || (OBOE_VERSION_MAJOR == 1 && OBOE_VERSION_MINOR >= 6) builder.setFormat(oboe::AudioFormat::I32); break; #endif case DevFmtByte: case DevFmtUByte: case DevFmtUShort: case DevFmtUInt: throw al::backend_exception{al::backend_error::DeviceError, "{} capture samples not supported", DevFmtTypeString(mDevice->FmtType)}; } oboe::Result result{builder.openManagedStream(mStream)}; if(result != oboe::Result::OK) throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: {}", oboe::convertToText(result)}; TRACE("Got stream with properties:\n{}", oboe::convertToText(mStream.get())); /* Ensure a minimum ringbuffer size of 100ms. */ mRing = RingBuffer::Create(std::max(mDevice->mBufferSize, mDevice->mSampleRate/10u), static_cast(mStream->getBytesPerFrame()), false); mDeviceName = name; } void OboeCapture::start() { const oboe::Result result{mStream->start()}; if(result != oboe::Result::OK) throw al::backend_exception{al::backend_error::DeviceError, "Failed to start stream: {}", oboe::convertToText(result)}; } void OboeCapture::stop() { const oboe::Result result{mStream->stop()}; if(result != oboe::Result::OK) ERR("Failed to stop stream: {}", oboe::convertToText(result)); } uint OboeCapture::availableSamples() { return static_cast(mRing->readSpace()); } void OboeCapture::captureSamples(std::byte *buffer, uint samples) { std::ignore = mRing->read(buffer, samples); } } // namespace bool OboeBackendFactory::init() { return true; } bool OboeBackendFactory::querySupport(BackendType type) { return type == BackendType::Playback || type == BackendType::Capture; } auto OboeBackendFactory::enumerate(BackendType type) -> std::vector { switch(type) { case BackendType::Playback: case BackendType::Capture: return std::vector{std::string{GetDeviceName()}}; } return {}; } BackendPtr OboeBackendFactory::createBackend(DeviceBase *device, BackendType type) { if(type == BackendType::Playback) return BackendPtr{new OboePlayback{device}}; if(type == BackendType::Capture) return BackendPtr{new OboeCapture{device}}; return BackendPtr{}; } BackendFactory &OboeBackendFactory::getFactory() { static OboeBackendFactory factory{}; return factory; } openal-soft-1.24.2/alc/backends/oboe.h000066400000000000000000000007141474041540300174410ustar00rootroot00000000000000#ifndef BACKENDS_OBOE_H #define BACKENDS_OBOE_H #include "base.h" struct OboeBackendFactory final : public BackendFactory { public: auto init() -> bool final; auto querySupport(BackendType type) -> bool final; auto enumerate(BackendType type) -> std::vector final; auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_OBOE_H */ openal-soft-1.24.2/alc/backends/opensl.cpp000066400000000000000000001011511474041540300203450ustar00rootroot00000000000000/* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* This is an OpenAL backend for Android using the native audio APIs based on * OpenSL ES 1.0.1. It is based on source code for the native-audio sample app * bundled with NDK. */ #include "config.h" #include "opensl.h" #include #include #include #include #include #include #include #include #include "albit.h" #include "alnumeric.h" #include "alsem.h" #include "alstring.h" #include "althrd_setname.h" #include "core/device.h" #include "core/helpers.h" #include "core/logging.h" #include "dynload.h" #include "opthelpers.h" #include "ringbuffer.h" #include #include #include namespace { using namespace std::string_view_literals; #if HAVE_DYNLOAD #define SLES_SYMBOLS(MAGIC) \ MAGIC(slCreateEngine); \ MAGIC(SL_IID_ANDROIDCONFIGURATION); \ MAGIC(SL_IID_ANDROIDSIMPLEBUFFERQUEUE); \ MAGIC(SL_IID_ENGINE); \ MAGIC(SL_IID_PLAY); \ MAGIC(SL_IID_RECORD); void *sles_handle; #define MAKE_SYMBOL(f) decltype(f) * p##f SLES_SYMBOLS(MAKE_SYMBOL) #undef MAKE_SYMBOL #ifndef IN_IDE_PARSER #define slCreateEngine (*pslCreateEngine) #define SL_IID_ANDROIDCONFIGURATION (*pSL_IID_ANDROIDCONFIGURATION) #define SL_IID_ANDROIDSIMPLEBUFFERQUEUE (*pSL_IID_ANDROIDSIMPLEBUFFERQUEUE) #define SL_IID_ENGINE (*pSL_IID_ENGINE) #define SL_IID_PLAY (*pSL_IID_PLAY) #define SL_IID_RECORD (*pSL_IID_RECORD) #endif #endif /* Helper macros */ #define EXTRACT_VCALL_ARGS(...) __VA_ARGS__)) #define VCALL(obj, func) ((*(obj))->func((obj), EXTRACT_VCALL_ARGS #define VCALL0(obj, func) ((*(obj))->func((obj) EXTRACT_VCALL_ARGS [[nodiscard]] constexpr auto GetDeviceName() noexcept { return "OpenSL"sv; } [[nodiscard]] constexpr SLuint32 GetChannelMask(DevFmtChannels chans) noexcept { switch(chans) { case DevFmtMono: return SL_SPEAKER_FRONT_CENTER; case DevFmtStereo: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT; case DevFmtQuad: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT | SL_SPEAKER_BACK_LEFT | SL_SPEAKER_BACK_RIGHT; case DevFmtX51: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT | SL_SPEAKER_FRONT_CENTER | SL_SPEAKER_LOW_FREQUENCY | SL_SPEAKER_SIDE_LEFT | SL_SPEAKER_SIDE_RIGHT; case DevFmtX61: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT | SL_SPEAKER_FRONT_CENTER | SL_SPEAKER_LOW_FREQUENCY | SL_SPEAKER_BACK_CENTER | SL_SPEAKER_SIDE_LEFT | SL_SPEAKER_SIDE_RIGHT; case DevFmtX71: case DevFmtX3D71: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT | SL_SPEAKER_FRONT_CENTER | SL_SPEAKER_LOW_FREQUENCY | SL_SPEAKER_BACK_LEFT | SL_SPEAKER_BACK_RIGHT | SL_SPEAKER_SIDE_LEFT | SL_SPEAKER_SIDE_RIGHT; case DevFmtX714: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT | SL_SPEAKER_FRONT_CENTER | SL_SPEAKER_LOW_FREQUENCY | SL_SPEAKER_BACK_LEFT | SL_SPEAKER_BACK_RIGHT | SL_SPEAKER_SIDE_LEFT | SL_SPEAKER_SIDE_RIGHT | SL_SPEAKER_TOP_FRONT_LEFT | SL_SPEAKER_TOP_FRONT_RIGHT | SL_SPEAKER_TOP_BACK_LEFT | SL_SPEAKER_TOP_BACK_RIGHT; case DevFmtX7144: case DevFmtAmbi3D: break; } return 0; } #ifdef SL_ANDROID_DATAFORMAT_PCM_EX constexpr SLuint32 GetTypeRepresentation(DevFmtType type) noexcept { switch(type) { case DevFmtUByte: case DevFmtUShort: case DevFmtUInt: return SL_ANDROID_PCM_REPRESENTATION_UNSIGNED_INT; case DevFmtByte: case DevFmtShort: case DevFmtInt: return SL_ANDROID_PCM_REPRESENTATION_SIGNED_INT; case DevFmtFloat: return SL_ANDROID_PCM_REPRESENTATION_FLOAT; } return 0; } #endif constexpr SLuint32 GetByteOrderEndianness() noexcept { if(al::endian::native == al::endian::little) return SL_BYTEORDER_LITTLEENDIAN; return SL_BYTEORDER_BIGENDIAN; } constexpr const char *res_str(SLresult result) noexcept { switch(result) { case SL_RESULT_SUCCESS: return "Success"; case SL_RESULT_PRECONDITIONS_VIOLATED: return "Preconditions violated"; case SL_RESULT_PARAMETER_INVALID: return "Parameter invalid"; case SL_RESULT_MEMORY_FAILURE: return "Memory failure"; case SL_RESULT_RESOURCE_ERROR: return "Resource error"; case SL_RESULT_RESOURCE_LOST: return "Resource lost"; case SL_RESULT_IO_ERROR: return "I/O error"; case SL_RESULT_BUFFER_INSUFFICIENT: return "Buffer insufficient"; case SL_RESULT_CONTENT_CORRUPTED: return "Content corrupted"; case SL_RESULT_CONTENT_UNSUPPORTED: return "Content unsupported"; case SL_RESULT_CONTENT_NOT_FOUND: return "Content not found"; case SL_RESULT_PERMISSION_DENIED: return "Permission denied"; case SL_RESULT_FEATURE_UNSUPPORTED: return "Feature unsupported"; case SL_RESULT_INTERNAL_ERROR: return "Internal error"; case SL_RESULT_UNKNOWN_ERROR: return "Unknown error"; case SL_RESULT_OPERATION_ABORTED: return "Operation aborted"; case SL_RESULT_CONTROL_LOST: return "Control lost"; #ifdef SL_RESULT_READONLY case SL_RESULT_READONLY: return "ReadOnly"; #endif #ifdef SL_RESULT_ENGINEOPTION_UNSUPPORTED case SL_RESULT_ENGINEOPTION_UNSUPPORTED: return "Engine option unsupported"; #endif #ifdef SL_RESULT_SOURCE_SINK_INCOMPATIBLE case SL_RESULT_SOURCE_SINK_INCOMPATIBLE: return "Source/Sink incompatible"; #endif } return "Unknown error code"; } inline void PrintErr(SLresult res, const char *str) { if(res != SL_RESULT_SUCCESS) UNLIKELY ERR("{}: {}", str, res_str(res)); } struct OpenSLPlayback final : public BackendBase { explicit OpenSLPlayback(DeviceBase *device) noexcept : BackendBase{device} { } ~OpenSLPlayback() override; void process(SLAndroidSimpleBufferQueueItf bq) noexcept; int mixerProc(); void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; ClockLatency getClockLatency() override; /* engine interfaces */ SLObjectItf mEngineObj{nullptr}; SLEngineItf mEngine{nullptr}; /* output mix interfaces */ SLObjectItf mOutputMix{nullptr}; /* buffer queue player interfaces */ SLObjectItf mBufferQueueObj{nullptr}; RingBufferPtr mRing{nullptr}; al::semaphore mSem; std::mutex mMutex; uint mFrameSize{0}; std::atomic mKillNow{true}; std::thread mThread; }; OpenSLPlayback::~OpenSLPlayback() { if(mBufferQueueObj) VCALL0(mBufferQueueObj,Destroy)(); mBufferQueueObj = nullptr; if(mOutputMix) VCALL0(mOutputMix,Destroy)(); mOutputMix = nullptr; if(mEngineObj) VCALL0(mEngineObj,Destroy)(); mEngineObj = nullptr; mEngine = nullptr; } /* this callback handler is called every time a buffer finishes playing */ void OpenSLPlayback::process(SLAndroidSimpleBufferQueueItf) noexcept { /* A note on the ringbuffer usage: The buffer queue seems to hold on to the * pointer passed to the Enqueue method, rather than copying the audio. * Consequently, the ringbuffer contains the audio that is currently queued * and waiting to play. This process() callback is called when a buffer is * finished, so we simply move the read pointer up to indicate the space is * available for writing again, and wake up the mixer thread to mix and * queue more audio. */ mRing->readAdvance(1); mSem.post(); } int OpenSLPlayback::mixerProc() { SetRTPriority(); althrd_setname(GetMixerThreadName()); SLPlayItf player; SLAndroidSimpleBufferQueueItf bufferQueue; SLresult result{VCALL(mBufferQueueObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &bufferQueue)}; PrintErr(result, "bufferQueue->GetInterface SL_IID_ANDROIDSIMPLEBUFFERQUEUE"); if(SL_RESULT_SUCCESS == result) { result = VCALL(mBufferQueueObj,GetInterface)(SL_IID_PLAY, &player); PrintErr(result, "bufferQueue->GetInterface SL_IID_PLAY"); } const size_t frame_step{mDevice->channelsFromFmt()}; if(SL_RESULT_SUCCESS != result) mDevice->handleDisconnect("Failed to get playback buffer: {:#08x}", result); while(SL_RESULT_SUCCESS == result && !mKillNow.load(std::memory_order_acquire) && mDevice->Connected.load(std::memory_order_acquire)) { if(mRing->writeSpace() == 0) { SLuint32 state{0}; result = VCALL(player,GetPlayState)(&state); PrintErr(result, "player->GetPlayState"); if(SL_RESULT_SUCCESS == result && state != SL_PLAYSTATE_PLAYING) { result = VCALL(player,SetPlayState)(SL_PLAYSTATE_PLAYING); PrintErr(result, "player->SetPlayState"); } if(SL_RESULT_SUCCESS != result) { mDevice->handleDisconnect("Failed to start playback: {:#08x}", result); break; } if(mRing->writeSpace() == 0) { mSem.wait(); continue; } } std::unique_lock dlock{mMutex}; auto data = mRing->getWriteVector(); mDevice->renderSamples(data[0].buf, static_cast(data[0].len)*mDevice->mUpdateSize, frame_step); if(data[1].len > 0) mDevice->renderSamples(data[1].buf, static_cast(data[1].len)*mDevice->mUpdateSize, frame_step); const auto todo = size_t{data[0].len + data[1].len}; mRing->writeAdvance(todo); dlock.unlock(); for(size_t i{0};i < todo;i++) { if(!data[0].len) { data[0] = data[1]; data[1].buf = nullptr; data[1].len = 0; } result = VCALL(bufferQueue,Enqueue)(data[0].buf, mDevice->mUpdateSize*mFrameSize); PrintErr(result, "bufferQueue->Enqueue"); if(SL_RESULT_SUCCESS != result) { mDevice->handleDisconnect("Failed to queue audio: {:#08x}", result); break; } data[0].len--; data[0].buf += mDevice->mUpdateSize*mFrameSize; } } return 0; } void OpenSLPlayback::open(std::string_view name) { if(name.empty()) name = GetDeviceName(); else if(name != GetDeviceName()) throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found", name}; /* There's only one device, so if it's already open, there's nothing to do. */ if(mEngineObj) return; // create engine SLresult result{slCreateEngine(&mEngineObj, 0, nullptr, 0, nullptr, nullptr)}; PrintErr(result, "slCreateEngine"); if(SL_RESULT_SUCCESS == result) { result = VCALL(mEngineObj,Realize)(SL_BOOLEAN_FALSE); PrintErr(result, "engine->Realize"); } if(SL_RESULT_SUCCESS == result) { result = VCALL(mEngineObj,GetInterface)(SL_IID_ENGINE, &mEngine); PrintErr(result, "engine->GetInterface"); } if(SL_RESULT_SUCCESS == result) { result = VCALL(mEngine,CreateOutputMix)(&mOutputMix, 0, nullptr, nullptr); PrintErr(result, "engine->CreateOutputMix"); } if(SL_RESULT_SUCCESS == result) { result = VCALL(mOutputMix,Realize)(SL_BOOLEAN_FALSE); PrintErr(result, "outputMix->Realize"); } if(SL_RESULT_SUCCESS != result) { if(mOutputMix) VCALL0(mOutputMix,Destroy)(); mOutputMix = nullptr; if(mEngineObj) VCALL0(mEngineObj,Destroy)(); mEngineObj = nullptr; mEngine = nullptr; throw al::backend_exception{al::backend_error::DeviceError, "Failed to initialize OpenSL device: {:#08x}", result}; } mDeviceName = name; } bool OpenSLPlayback::reset() { SLresult result; if(mBufferQueueObj) VCALL0(mBufferQueueObj,Destroy)(); mBufferQueueObj = nullptr; mRing = nullptr; mDevice->FmtChans = DevFmtStereo; mDevice->FmtType = DevFmtShort; setDefaultWFXChannelOrder(); mFrameSize = mDevice->frameSizeFromFmt(); const std::array ids{{ SL_IID_ANDROIDSIMPLEBUFFERQUEUE, SL_IID_ANDROIDCONFIGURATION }}; const std::array reqs{{ SL_BOOLEAN_TRUE, SL_BOOLEAN_FALSE }}; SLDataLocator_OutputMix loc_outmix{}; loc_outmix.locatorType = SL_DATALOCATOR_OUTPUTMIX; loc_outmix.outputMix = mOutputMix; SLDataSink audioSnk{}; audioSnk.pLocator = &loc_outmix; audioSnk.pFormat = nullptr; SLDataLocator_AndroidSimpleBufferQueue loc_bufq{}; loc_bufq.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE; loc_bufq.numBuffers = mDevice->mBufferSize / mDevice->mUpdateSize; SLDataSource audioSrc{}; #ifdef SL_ANDROID_DATAFORMAT_PCM_EX SLAndroidDataFormat_PCM_EX format_pcm_ex{}; format_pcm_ex.formatType = SL_ANDROID_DATAFORMAT_PCM_EX; format_pcm_ex.numChannels = mDevice->channelsFromFmt(); format_pcm_ex.sampleRate = mDevice->mSampleRate * 1000; format_pcm_ex.bitsPerSample = mDevice->bytesFromFmt() * 8; format_pcm_ex.containerSize = format_pcm_ex.bitsPerSample; format_pcm_ex.channelMask = GetChannelMask(mDevice->FmtChans); format_pcm_ex.endianness = GetByteOrderEndianness(); format_pcm_ex.representation = GetTypeRepresentation(mDevice->FmtType); audioSrc.pLocator = &loc_bufq; audioSrc.pFormat = &format_pcm_ex; result = VCALL(mEngine,CreateAudioPlayer)(&mBufferQueueObj, &audioSrc, &audioSnk, ids.size(), ids.data(), reqs.data()); if(SL_RESULT_SUCCESS != result) #endif { /* Alter sample type according to what SLDataFormat_PCM can support. */ switch(mDevice->FmtType) { case DevFmtByte: mDevice->FmtType = DevFmtUByte; break; case DevFmtUInt: mDevice->FmtType = DevFmtInt; break; case DevFmtFloat: case DevFmtUShort: mDevice->FmtType = DevFmtShort; break; case DevFmtUByte: case DevFmtShort: case DevFmtInt: break; } SLDataFormat_PCM format_pcm{}; format_pcm.formatType = SL_DATAFORMAT_PCM; format_pcm.numChannels = mDevice->channelsFromFmt(); format_pcm.samplesPerSec = mDevice->mSampleRate * 1000; format_pcm.bitsPerSample = mDevice->bytesFromFmt() * 8; format_pcm.containerSize = format_pcm.bitsPerSample; format_pcm.channelMask = GetChannelMask(mDevice->FmtChans); format_pcm.endianness = GetByteOrderEndianness(); audioSrc.pLocator = &loc_bufq; audioSrc.pFormat = &format_pcm; result = VCALL(mEngine,CreateAudioPlayer)(&mBufferQueueObj, &audioSrc, &audioSnk, ids.size(), ids.data(), reqs.data()); PrintErr(result, "engine->CreateAudioPlayer"); } if(SL_RESULT_SUCCESS == result) { /* Set the stream type to "media" (games, music, etc), if possible. */ SLAndroidConfigurationItf config; result = VCALL(mBufferQueueObj,GetInterface)(SL_IID_ANDROIDCONFIGURATION, &config); PrintErr(result, "bufferQueue->GetInterface SL_IID_ANDROIDCONFIGURATION"); if(SL_RESULT_SUCCESS == result) { SLint32 streamType = SL_ANDROID_STREAM_MEDIA; result = VCALL(config,SetConfiguration)(SL_ANDROID_KEY_STREAM_TYPE, &streamType, sizeof(streamType)); PrintErr(result, "config->SetConfiguration"); } /* Clear any error since this was optional. */ result = SL_RESULT_SUCCESS; } if(SL_RESULT_SUCCESS == result) { result = VCALL(mBufferQueueObj,Realize)(SL_BOOLEAN_FALSE); PrintErr(result, "bufferQueue->Realize"); } if(SL_RESULT_SUCCESS == result) { const uint num_updates{mDevice->mBufferSize / mDevice->mUpdateSize}; mRing = RingBuffer::Create(num_updates, mFrameSize*mDevice->mUpdateSize, true); } if(SL_RESULT_SUCCESS != result) { if(mBufferQueueObj) VCALL0(mBufferQueueObj,Destroy)(); mBufferQueueObj = nullptr; return false; } return true; } void OpenSLPlayback::start() { mRing->reset(); SLAndroidSimpleBufferQueueItf bufferQueue; SLresult result{VCALL(mBufferQueueObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &bufferQueue)}; PrintErr(result, "bufferQueue->GetInterface"); if(SL_RESULT_SUCCESS == result) { result = VCALL(bufferQueue,RegisterCallback)( [](SLAndroidSimpleBufferQueueItf bq, void *context) noexcept { static_cast(context)->process(bq); }, this); PrintErr(result, "bufferQueue->RegisterCallback"); } if(SL_RESULT_SUCCESS != result) throw al::backend_exception{al::backend_error::DeviceError, "Failed to register callback: {:#08x}", result}; try { mKillNow.store(false, std::memory_order_release); mThread = std::thread(&OpenSLPlayback::mixerProc, this); } catch(std::exception& e) { throw al::backend_exception{al::backend_error::DeviceError, "Failed to start mixing thread: {}", e.what()}; } } void OpenSLPlayback::stop() { if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable()) return; mSem.post(); mThread.join(); SLPlayItf player; SLresult result{VCALL(mBufferQueueObj,GetInterface)(SL_IID_PLAY, &player)}; PrintErr(result, "bufferQueue->GetInterface"); if(SL_RESULT_SUCCESS == result) { result = VCALL(player,SetPlayState)(SL_PLAYSTATE_STOPPED); PrintErr(result, "player->SetPlayState"); } SLAndroidSimpleBufferQueueItf bufferQueue; result = VCALL(mBufferQueueObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &bufferQueue); PrintErr(result, "bufferQueue->GetInterface"); if(SL_RESULT_SUCCESS == result) { result = VCALL0(bufferQueue,Clear)(); PrintErr(result, "bufferQueue->Clear"); } if(SL_RESULT_SUCCESS == result) { result = VCALL(bufferQueue,RegisterCallback)(nullptr, nullptr); PrintErr(result, "bufferQueue->RegisterCallback"); } if(SL_RESULT_SUCCESS == result) { SLAndroidSimpleBufferQueueState state; do { std::this_thread::yield(); result = VCALL(bufferQueue,GetState)(&state); } while(SL_RESULT_SUCCESS == result && state.count > 0); PrintErr(result, "bufferQueue->GetState"); mRing->reset(); } } ClockLatency OpenSLPlayback::getClockLatency() { ClockLatency ret; std::lock_guard dlock{mMutex}; ret.ClockTime = mDevice->getClockTime(); ret.Latency = std::chrono::seconds{mRing->readSpace() * mDevice->mUpdateSize}; ret.Latency /= mDevice->mSampleRate; return ret; } struct OpenSLCapture final : public BackendBase { explicit OpenSLCapture(DeviceBase *device) noexcept : BackendBase{device} { } ~OpenSLCapture() override; void process(SLAndroidSimpleBufferQueueItf bq) noexcept; void open(std::string_view name) override; void start() override; void stop() override; void captureSamples(std::byte *buffer, uint samples) override; uint availableSamples() override; /* engine interfaces */ SLObjectItf mEngineObj{nullptr}; SLEngineItf mEngine; /* recording interfaces */ SLObjectItf mRecordObj{nullptr}; RingBufferPtr mRing{nullptr}; uint mSplOffset{0u}; uint mFrameSize{0}; }; OpenSLCapture::~OpenSLCapture() { if(mRecordObj) VCALL0(mRecordObj,Destroy)(); mRecordObj = nullptr; if(mEngineObj) VCALL0(mEngineObj,Destroy)(); mEngineObj = nullptr; mEngine = nullptr; } void OpenSLCapture::process(SLAndroidSimpleBufferQueueItf) noexcept { /* A new chunk has been written into the ring buffer, advance it. */ mRing->writeAdvance(1); } void OpenSLCapture::open(std::string_view name) { if(name.empty()) name = GetDeviceName(); else if(name != GetDeviceName()) throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found", name}; SLresult result{slCreateEngine(&mEngineObj, 0, nullptr, 0, nullptr, nullptr)}; PrintErr(result, "slCreateEngine"); if(SL_RESULT_SUCCESS == result) { result = VCALL(mEngineObj,Realize)(SL_BOOLEAN_FALSE); PrintErr(result, "engine->Realize"); } if(SL_RESULT_SUCCESS == result) { result = VCALL(mEngineObj,GetInterface)(SL_IID_ENGINE, &mEngine); PrintErr(result, "engine->GetInterface"); } if(SL_RESULT_SUCCESS == result) { mFrameSize = mDevice->frameSizeFromFmt(); /* Ensure the total length is at least 100ms */ uint length{std::max(mDevice->mBufferSize, mDevice->mSampleRate/10u)}; /* Ensure the per-chunk length is at least 10ms, and no more than 50ms. */ uint update_len{std::clamp(mDevice->mBufferSize/3u, mDevice->mSampleRate/100u, mDevice->mSampleRate/100u*5u)}; uint num_updates{(length+update_len-1) / update_len}; mRing = RingBuffer::Create(num_updates, update_len*mFrameSize, false); mDevice->mUpdateSize = update_len; mDevice->mBufferSize = static_cast(mRing->writeSpace() * update_len); } if(SL_RESULT_SUCCESS == result) { const std::array ids{{ SL_IID_ANDROIDSIMPLEBUFFERQUEUE, SL_IID_ANDROIDCONFIGURATION }}; const std::array reqs{{ SL_BOOLEAN_TRUE, SL_BOOLEAN_FALSE }}; SLDataLocator_IODevice loc_dev{}; loc_dev.locatorType = SL_DATALOCATOR_IODEVICE; loc_dev.deviceType = SL_IODEVICE_AUDIOINPUT; loc_dev.deviceID = SL_DEFAULTDEVICEID_AUDIOINPUT; loc_dev.device = nullptr; SLDataSource audioSrc{}; audioSrc.pLocator = &loc_dev; audioSrc.pFormat = nullptr; SLDataLocator_AndroidSimpleBufferQueue loc_bq{}; loc_bq.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE; loc_bq.numBuffers = mDevice->mBufferSize / mDevice->mUpdateSize; SLDataSink audioSnk{}; #ifdef SL_ANDROID_DATAFORMAT_PCM_EX SLAndroidDataFormat_PCM_EX format_pcm_ex{}; format_pcm_ex.formatType = SL_ANDROID_DATAFORMAT_PCM_EX; format_pcm_ex.numChannels = mDevice->channelsFromFmt(); format_pcm_ex.sampleRate = mDevice->mSampleRate * 1000; format_pcm_ex.bitsPerSample = mDevice->bytesFromFmt() * 8; format_pcm_ex.containerSize = format_pcm_ex.bitsPerSample; format_pcm_ex.channelMask = GetChannelMask(mDevice->FmtChans); format_pcm_ex.endianness = GetByteOrderEndianness(); format_pcm_ex.representation = GetTypeRepresentation(mDevice->FmtType); audioSnk.pLocator = &loc_bq; audioSnk.pFormat = &format_pcm_ex; result = VCALL(mEngine,CreateAudioRecorder)(&mRecordObj, &audioSrc, &audioSnk, ids.size(), ids.data(), reqs.data()); if(SL_RESULT_SUCCESS != result) #endif { /* Fallback to SLDataFormat_PCM only if it supports the desired * sample type. */ if(mDevice->FmtType == DevFmtUByte || mDevice->FmtType == DevFmtShort || mDevice->FmtType == DevFmtInt) { SLDataFormat_PCM format_pcm{}; format_pcm.formatType = SL_DATAFORMAT_PCM; format_pcm.numChannels = mDevice->channelsFromFmt(); format_pcm.samplesPerSec = mDevice->mSampleRate * 1000; format_pcm.bitsPerSample = mDevice->bytesFromFmt() * 8; format_pcm.containerSize = format_pcm.bitsPerSample; format_pcm.channelMask = GetChannelMask(mDevice->FmtChans); format_pcm.endianness = GetByteOrderEndianness(); audioSnk.pLocator = &loc_bq; audioSnk.pFormat = &format_pcm; result = VCALL(mEngine,CreateAudioRecorder)(&mRecordObj, &audioSrc, &audioSnk, ids.size(), ids.data(), reqs.data()); } PrintErr(result, "engine->CreateAudioRecorder"); } } if(SL_RESULT_SUCCESS == result) { /* Set the record preset to "generic", if possible. */ SLAndroidConfigurationItf config; result = VCALL(mRecordObj,GetInterface)(SL_IID_ANDROIDCONFIGURATION, &config); PrintErr(result, "recordObj->GetInterface SL_IID_ANDROIDCONFIGURATION"); if(SL_RESULT_SUCCESS == result) { SLuint32 preset = SL_ANDROID_RECORDING_PRESET_GENERIC; result = VCALL(config,SetConfiguration)(SL_ANDROID_KEY_RECORDING_PRESET, &preset, sizeof(preset)); PrintErr(result, "config->SetConfiguration"); } /* Clear any error since this was optional. */ result = SL_RESULT_SUCCESS; } if(SL_RESULT_SUCCESS == result) { result = VCALL(mRecordObj,Realize)(SL_BOOLEAN_FALSE); PrintErr(result, "recordObj->Realize"); } SLAndroidSimpleBufferQueueItf bufferQueue; if(SL_RESULT_SUCCESS == result) { result = VCALL(mRecordObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &bufferQueue); PrintErr(result, "recordObj->GetInterface"); } if(SL_RESULT_SUCCESS == result) { result = VCALL(bufferQueue,RegisterCallback)( [](SLAndroidSimpleBufferQueueItf bq, void *context) noexcept { static_cast(context)->process(bq); }, this); PrintErr(result, "bufferQueue->RegisterCallback"); } if(SL_RESULT_SUCCESS == result) { const uint chunk_size{mDevice->mUpdateSize * mFrameSize}; const auto silence = (mDevice->FmtType == DevFmtUByte) ? std::byte{0x80} : std::byte{0}; auto data = mRing->getWriteVector(); std::fill_n(data[0].buf, data[0].len*chunk_size, silence); std::fill_n(data[1].buf, data[1].len*chunk_size, silence); for(size_t i{0u};i < data[0].len && SL_RESULT_SUCCESS == result;i++) { result = VCALL(bufferQueue,Enqueue)(data[0].buf + chunk_size*i, chunk_size); PrintErr(result, "bufferQueue->Enqueue"); } for(size_t i{0u};i < data[1].len && SL_RESULT_SUCCESS == result;i++) { result = VCALL(bufferQueue,Enqueue)(data[1].buf + chunk_size*i, chunk_size); PrintErr(result, "bufferQueue->Enqueue"); } } if(SL_RESULT_SUCCESS != result) { if(mRecordObj) VCALL0(mRecordObj,Destroy)(); mRecordObj = nullptr; if(mEngineObj) VCALL0(mEngineObj,Destroy)(); mEngineObj = nullptr; mEngine = nullptr; throw al::backend_exception{al::backend_error::DeviceError, "Failed to initialize OpenSL device: {:#08x}", result}; } mDeviceName = name; } void OpenSLCapture::start() { SLRecordItf record; SLresult result{VCALL(mRecordObj,GetInterface)(SL_IID_RECORD, &record)}; PrintErr(result, "recordObj->GetInterface"); if(SL_RESULT_SUCCESS == result) { result = VCALL(record,SetRecordState)(SL_RECORDSTATE_RECORDING); PrintErr(result, "record->SetRecordState"); } if(SL_RESULT_SUCCESS != result) throw al::backend_exception{al::backend_error::DeviceError, "Failed to start capture: {:#08x}", result}; } void OpenSLCapture::stop() { SLRecordItf record; SLresult result{VCALL(mRecordObj,GetInterface)(SL_IID_RECORD, &record)}; PrintErr(result, "recordObj->GetInterface"); if(SL_RESULT_SUCCESS == result) { result = VCALL(record,SetRecordState)(SL_RECORDSTATE_PAUSED); PrintErr(result, "record->SetRecordState"); } } void OpenSLCapture::captureSamples(std::byte *buffer, uint samples) { const uint update_size{mDevice->mUpdateSize}; const uint chunk_size{update_size * mFrameSize}; /* Read the desired samples from the ring buffer then advance its read * pointer. */ size_t adv_count{0}; auto rdata = mRing->getReadVector(); for(uint i{0};i < samples;) { const uint rem{std::min(samples - i, update_size - mSplOffset)}; std::copy_n(rdata[0].buf + mSplOffset*size_t{mFrameSize}, rem*size_t{mFrameSize}, buffer + i*size_t{mFrameSize}); mSplOffset += rem; if(mSplOffset == update_size) { /* Finished a chunk, reset the offset and advance the read pointer. */ mSplOffset = 0; ++adv_count; rdata[0].len -= 1; if(!rdata[0].len) rdata[0] = rdata[1]; else rdata[0].buf += chunk_size; } i += rem; } SLAndroidSimpleBufferQueueItf bufferQueue{}; if(mDevice->Connected.load(std::memory_order_acquire)) LIKELY { const SLresult result{VCALL(mRecordObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &bufferQueue)}; PrintErr(result, "recordObj->GetInterface"); if(SL_RESULT_SUCCESS != result) UNLIKELY { mDevice->handleDisconnect("Failed to get capture buffer queue: {:#08x}", result); bufferQueue = nullptr; } } if(!bufferQueue || adv_count == 0) return; /* For each buffer chunk that was fully read, queue another writable buffer * chunk to keep the OpenSL queue full. This is rather convoluted, as a * result of the ring buffer holding more elements than are writable at a * given time. The end of the write vector increments when the read pointer * advances, which will "expose" a previously unwritable element. So for * every element that we've finished reading, we queue that many elements * from the end of the write vector. */ mRing->readAdvance(adv_count); SLresult result{SL_RESULT_SUCCESS}; auto wdata = mRing->getWriteVector(); if(adv_count > wdata[1].len) LIKELY { auto len1 = std::min(wdata[0].len, adv_count-wdata[1].len); auto buf1 = wdata[0].buf + chunk_size*(wdata[0].len-len1); for(size_t i{0u};i < len1 && SL_RESULT_SUCCESS == result;i++) { result = VCALL(bufferQueue,Enqueue)(buf1 + chunk_size*i, chunk_size); PrintErr(result, "bufferQueue->Enqueue"); } } if(wdata[1].len > 0) { auto len2 = std::min(wdata[1].len, adv_count); auto buf2 = wdata[1].buf + chunk_size*(wdata[1].len-len2); for(size_t i{0u};i < len2 && SL_RESULT_SUCCESS == result;i++) { result = VCALL(bufferQueue,Enqueue)(buf2 + chunk_size*i, chunk_size); PrintErr(result, "bufferQueue->Enqueue"); } } } uint OpenSLCapture::availableSamples() { return static_cast(mRing->readSpace()*mDevice->mUpdateSize - mSplOffset); } } // namespace bool OSLBackendFactory::init() { #if HAVE_DYNLOAD if(!sles_handle) { #define SLES_LIBNAME "libOpenSLES.so" sles_handle = LoadLib(SLES_LIBNAME); if(!sles_handle) { WARN("Failed to load {}", SLES_LIBNAME); return false; } std::string missing_syms; #define LOAD_SYMBOL(f) do { \ p##f = reinterpret_cast(GetSymbol(sles_handle, #f)); \ if(p##f == nullptr) missing_syms += "\n" #f; \ } while(0) SLES_SYMBOLS(LOAD_SYMBOL); #undef LOAD_SYMBOL if(!missing_syms.empty()) { WARN("Missing expected symbols:{}", missing_syms); CloseLib(sles_handle); sles_handle = nullptr; return false; } } #endif return true; } bool OSLBackendFactory::querySupport(BackendType type) { return (type == BackendType::Playback || type == BackendType::Capture); } auto OSLBackendFactory::enumerate(BackendType type) -> std::vector { switch(type) { case BackendType::Playback: case BackendType::Capture: return std::vector{std::string{GetDeviceName()}}; } return {}; } BackendPtr OSLBackendFactory::createBackend(DeviceBase *device, BackendType type) { if(type == BackendType::Playback) return BackendPtr{new OpenSLPlayback{device}}; if(type == BackendType::Capture) return BackendPtr{new OpenSLCapture{device}}; return nullptr; } BackendFactory &OSLBackendFactory::getFactory() { static OSLBackendFactory factory{}; return factory; } openal-soft-1.24.2/alc/backends/opensl.h000066400000000000000000000007101474041540300200110ustar00rootroot00000000000000#ifndef BACKENDS_OSL_H #define BACKENDS_OSL_H #include "base.h" struct OSLBackendFactory final : public BackendFactory { public: auto init() -> bool final; auto querySupport(BackendType type) -> bool final; auto enumerate(BackendType type) -> std::vector final; auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_OSL_H */ openal-soft-1.24.2/alc/backends/oss.cpp000066400000000000000000000513411474041540300176560ustar00rootroot00000000000000/** * OpenAL cross platform audio library * Copyright (C) 1999-2007 by authors. * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * Or go to http://www.gnu.org/copyleft/lgpl.html */ #include "config.h" #include "oss.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "alc/alconfig.h" #include "alnumeric.h" #include "althrd_setname.h" #include "core/device.h" #include "core/helpers.h" #include "core/logging.h" #include "fmt/core.h" #include "ringbuffer.h" #include /* * The OSS documentation talks about SOUND_MIXER_READ, but the header * only contains MIXER_READ. Play safe. Same for WRITE. */ #ifndef SOUND_MIXER_READ #define SOUND_MIXER_READ MIXER_READ #endif #ifndef SOUND_MIXER_WRITE #define SOUND_MIXER_WRITE MIXER_WRITE #endif #if defined(SOUND_VERSION) && (SOUND_VERSION < 0x040000) #define ALC_OSS_COMPAT #endif #ifndef SNDCTL_AUDIOINFO #define ALC_OSS_COMPAT #endif /* * FreeBSD strongly discourages the use of specific devices, * such as those returned in oss_audioinfo.devnode */ #ifdef __FreeBSD__ #define ALC_OSS_DEVNODE_TRUC #endif namespace { using namespace std::string_literals; using namespace std::string_view_literals; [[nodiscard]] constexpr auto GetDefaultName() noexcept { return "OSS Default"sv; } std::string DefaultPlayback{"/dev/dsp"s}; std::string DefaultCapture{"/dev/dsp"s}; struct DevMap { std::string name; std::string device_name; template DevMap(T&& name_, U&& devname_) : name{std::forward(name_)}, device_name{std::forward(devname_)} { } }; std::vector PlaybackDevices; std::vector CaptureDevices; #ifdef ALC_OSS_COMPAT #define DSP_CAP_OUTPUT 0x00020000 #define DSP_CAP_INPUT 0x00010000 void ALCossListPopulate(std::vector &devlist, int type) { devlist.emplace_back(GetDefaultName(), (type==DSP_CAP_INPUT) ? DefaultCapture : DefaultPlayback); } #else class FileHandle { int mFd{-1}; public: FileHandle() = default; FileHandle(const FileHandle&) = delete; FileHandle& operator=(const FileHandle&) = delete; ~FileHandle() { if(mFd != -1) ::close(mFd); } template [[nodiscard]] auto open(const char *fname, Args&& ...args) -> bool { close(); mFd = ::open(fname, std::forward(args)...); return mFd != -1; } void close() { if(mFd != -1) ::close(mFd); mFd = -1; } [[nodiscard]] auto get() const noexcept -> int { return mFd; } }; void ALCossListAppend(std::vector &list, std::string_view handle, std::string_view path) { #ifdef ALC_OSS_DEVNODE_TRUC for(size_t i{0};i < path.size();++i) { if(path[i] == '.' && handle.size() >= path.size() - i) { const size_t hoffset{handle.size() + i - path.size()}; if(strncmp(path.data() + i, handle.data() + hoffset, path.size() - i) == 0) handle = handle.substr(0, hoffset); path = path.substr(0, i); } } #endif if(handle.empty()) handle = path; auto match_devname = [path](const DevMap &entry) -> bool { return entry.device_name == path; }; if(std::find_if(list.cbegin(), list.cend(), match_devname) != list.cend()) return; auto checkName = [&list](const std::string_view name) -> bool { auto match_name = [name](const DevMap &entry) -> bool { return entry.name == name; }; return std::find_if(list.cbegin(), list.cend(), match_name) != list.cend(); }; auto count = 1; auto newname = std::string{handle}; while(checkName(newname)) newname = fmt::format("{} #{}", handle, ++count); const auto &entry = list.emplace_back(std::move(newname), path); TRACE("Got device \"{}\", \"{}\"", entry.name, entry.device_name); } void ALCossListPopulate(std::vector &devlist, int type_flag) { oss_sysinfo si{}; FileHandle file; if(!file.open("/dev/mixer", O_RDONLY)) { TRACE("Could not open /dev/mixer: {}", std::generic_category().message(errno)); goto done; } if(ioctl(file.get(), SNDCTL_SYSINFO, &si) == -1) { TRACE("SNDCTL_SYSINFO failed: {}", std::generic_category().message(errno)); goto done; } for(int i{0};i < si.numaudios;i++) { oss_audioinfo ai{}; ai.dev = i; if(ioctl(file.get(), SNDCTL_AUDIOINFO, &ai) == -1) { ERR("SNDCTL_AUDIOINFO ({}) failed: {}", i, std::generic_category().message(errno)); continue; } if(!(ai.caps&type_flag) || ai.devnode[0] == '\0') continue; std::string_view handle; if(ai.handle[0] != '\0') handle = {ai.handle, strnlen(ai.handle, sizeof(ai.handle))}; else handle = {ai.name, strnlen(ai.name, sizeof(ai.name))}; const std::string_view devnode{ai.devnode, strnlen(ai.devnode, sizeof(ai.devnode))}; ALCossListAppend(devlist, handle, devnode); } done: file.close(); const char *defdev{((type_flag==DSP_CAP_INPUT) ? DefaultCapture : DefaultPlayback).c_str()}; auto iter = std::find_if(devlist.cbegin(), devlist.cend(), [defdev](const DevMap &entry) -> bool { return entry.device_name == defdev; } ); if(iter == devlist.cend()) devlist.insert(devlist.begin(), DevMap{GetDefaultName(), defdev}); else { DevMap entry{std::move(*iter)}; devlist.erase(iter); devlist.insert(devlist.begin(), std::move(entry)); } devlist.shrink_to_fit(); } #endif uint log2i(uint x) { uint y{0}; while(x > 1) { x >>= 1; y++; } return y; } struct OSSPlayback final : public BackendBase { explicit OSSPlayback(DeviceBase *device) noexcept : BackendBase{device} { } ~OSSPlayback() override; int mixerProc(); void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; int mFd{-1}; std::vector mMixData; std::atomic mKillNow{true}; std::thread mThread; }; OSSPlayback::~OSSPlayback() { if(mFd != -1) ::close(mFd); mFd = -1; } int OSSPlayback::mixerProc() { SetRTPriority(); althrd_setname(GetMixerThreadName()); const size_t frame_step{mDevice->channelsFromFmt()}; const size_t frame_size{mDevice->frameSizeFromFmt()}; while(!mKillNow.load(std::memory_order_acquire) && mDevice->Connected.load(std::memory_order_acquire)) { pollfd pollitem{}; pollitem.fd = mFd; pollitem.events = POLLOUT; if(int pret{poll(&pollitem, 1, 1000)}; pret < 0) { if(errno == EINTR || errno == EAGAIN) continue; const auto errstr = std::generic_category().message(errno); ERR("poll failed: {}", errstr); mDevice->handleDisconnect("Failed waiting for playback buffer: {}", errstr); break; } else if(pret == 0) /* NOLINT(*-else-after-return) 'pret' is local to the if/else blocks */ { WARN("poll timeout"); continue; } al::span write_buf{mMixData}; mDevice->renderSamples(write_buf.data(), static_cast(write_buf.size()/frame_size), frame_step); while(!write_buf.empty() && !mKillNow.load(std::memory_order_acquire)) { ssize_t wrote{write(mFd, write_buf.data(), write_buf.size())}; if(wrote < 0) { if(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) continue; const auto errstr = std::generic_category().message(errno); ERR("write failed: {}", errstr); mDevice->handleDisconnect("Failed writing playback samples: {}", errstr); break; } write_buf = write_buf.subspan(static_cast(wrote)); } } return 0; } void OSSPlayback::open(std::string_view name) { const char *devname{DefaultPlayback.c_str()}; if(name.empty()) name = GetDefaultName(); else { if(PlaybackDevices.empty()) ALCossListPopulate(PlaybackDevices, DSP_CAP_OUTPUT); auto iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(), [&name](const DevMap &entry) -> bool { return entry.name == name; } ); if(iter == PlaybackDevices.cend()) throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found", name}; devname = iter->device_name.c_str(); } const auto fd = ::open(devname, O_WRONLY); /* NOLINT(cppcoreguidelines-pro-type-vararg) */ if(fd == -1) throw al::backend_exception{al::backend_error::NoDevice, "Could not open {}: {}", devname, std::generic_category().message(errno)}; if(mFd != -1) ::close(mFd); mFd = fd; mDeviceName = name; } bool OSSPlayback::reset() { int ossFormat{}; switch(mDevice->FmtType) { case DevFmtByte: ossFormat = AFMT_S8; break; case DevFmtUByte: ossFormat = AFMT_U8; break; case DevFmtUShort: case DevFmtInt: case DevFmtUInt: case DevFmtFloat: mDevice->FmtType = DevFmtShort; /* fall-through */ case DevFmtShort: ossFormat = AFMT_S16_NE; break; } uint periods{mDevice->mBufferSize / mDevice->mUpdateSize}; uint numChannels{mDevice->channelsFromFmt()}; uint ossSpeed{mDevice->mSampleRate}; uint frameSize{numChannels * mDevice->bytesFromFmt()}; /* According to the OSS spec, 16 bytes (log2(16)) is the minimum. */ uint log2FragmentSize{std::max(log2i(mDevice->mUpdateSize*frameSize), 4u)}; uint numFragmentsLogSize{(periods << 16) | log2FragmentSize}; audio_buf_info info{}; #define CHECKERR(func) if((func) < 0) \ throw al::backend_exception{al::backend_error::DeviceError, #func " failed: {}", \ std::generic_category().message(errno)}; /* Don't fail if SETFRAGMENT fails. We can handle just about anything * that's reported back via GETOSPACE */ /* NOLINTBEGIN(cppcoreguidelines-pro-type-vararg) */ ioctl(mFd, SNDCTL_DSP_SETFRAGMENT, &numFragmentsLogSize); CHECKERR(ioctl(mFd, SNDCTL_DSP_SETFMT, &ossFormat)); CHECKERR(ioctl(mFd, SNDCTL_DSP_CHANNELS, &numChannels)); CHECKERR(ioctl(mFd, SNDCTL_DSP_SPEED, &ossSpeed)); CHECKERR(ioctl(mFd, SNDCTL_DSP_GETOSPACE, &info)); /* NOLINTEND(cppcoreguidelines-pro-type-vararg) */ #undef CHECKERR if(mDevice->channelsFromFmt() != numChannels) { ERR("Failed to set {}, got {} channels instead", DevFmtChannelsString(mDevice->FmtChans), numChannels); return false; } if(!((ossFormat == AFMT_S8 && mDevice->FmtType == DevFmtByte) || (ossFormat == AFMT_U8 && mDevice->FmtType == DevFmtUByte) || (ossFormat == AFMT_S16_NE && mDevice->FmtType == DevFmtShort))) { ERR("Failed to set {} samples, got OSS format {:#x}", DevFmtTypeString(mDevice->FmtType), as_unsigned(ossFormat)); return false; } mDevice->mSampleRate = ossSpeed; mDevice->mUpdateSize = static_cast(info.fragsize) / frameSize; mDevice->mBufferSize = static_cast(info.fragments) * mDevice->mUpdateSize; setDefaultChannelOrder(); mMixData.resize(size_t{mDevice->mUpdateSize} * mDevice->frameSizeFromFmt()); return true; } void OSSPlayback::start() { try { mKillNow.store(false, std::memory_order_release); mThread = std::thread{&OSSPlayback::mixerProc, this}; } catch(std::exception& e) { throw al::backend_exception{al::backend_error::DeviceError, "Failed to start mixing thread: {}", e.what()}; } } void OSSPlayback::stop() { if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable()) return; mThread.join(); if(ioctl(mFd, SNDCTL_DSP_RESET) != 0) /* NOLINT(cppcoreguidelines-pro-type-vararg) */ ERR("Error resetting device: {}", std::generic_category().message(errno)); } struct OSScapture final : public BackendBase { explicit OSScapture(DeviceBase *device) noexcept : BackendBase{device} { } ~OSScapture() override; int recordProc(); void open(std::string_view name) override; void start() override; void stop() override; void captureSamples(std::byte *buffer, uint samples) override; uint availableSamples() override; int mFd{-1}; RingBufferPtr mRing{nullptr}; std::atomic mKillNow{true}; std::thread mThread; }; OSScapture::~OSScapture() { if(mFd != -1) close(mFd); mFd = -1; } int OSScapture::recordProc() { SetRTPriority(); althrd_setname(GetRecordThreadName()); const size_t frame_size{mDevice->frameSizeFromFmt()}; while(!mKillNow.load(std::memory_order_acquire)) { pollfd pollitem{}; pollitem.fd = mFd; pollitem.events = POLLIN; if(int pret{poll(&pollitem, 1, 1000)}; pret < 0) { if(errno == EINTR || errno == EAGAIN) continue; const auto errstr = std::generic_category().message(errno); ERR("poll failed: {}", errstr); mDevice->handleDisconnect("Failed to check capture samples: {}", errstr); break; } else if(pret == 0) /* NOLINT(*-else-after-return) 'pret' is local to the if/else blocks */ { WARN("poll timeout"); continue; } auto vec = mRing->getWriteVector(); if(vec[0].len > 0) { ssize_t amt{read(mFd, vec[0].buf, vec[0].len*frame_size)}; if(amt < 0) { const auto errstr = std::generic_category().message(errno); ERR("read failed: {}", errstr); mDevice->handleDisconnect("Failed reading capture samples: {}", errstr); break; } mRing->writeAdvance(static_cast(amt)/frame_size); } } return 0; } void OSScapture::open(std::string_view name) { const char *devname{DefaultCapture.c_str()}; if(name.empty()) name = GetDefaultName(); else { if(CaptureDevices.empty()) ALCossListPopulate(CaptureDevices, DSP_CAP_INPUT); auto iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(), [&name](const DevMap &entry) -> bool { return entry.name == name; } ); if(iter == CaptureDevices.cend()) throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found", name}; devname = iter->device_name.c_str(); } mFd = ::open(devname, O_RDONLY); /* NOLINT(cppcoreguidelines-pro-type-vararg) */ if(mFd == -1) throw al::backend_exception{al::backend_error::NoDevice, "Could not open {}: {}", devname, std::generic_category().message(errno)}; int ossFormat{}; switch(mDevice->FmtType) { case DevFmtByte: ossFormat = AFMT_S8; break; case DevFmtUByte: ossFormat = AFMT_U8; break; case DevFmtShort: ossFormat = AFMT_S16_NE; break; case DevFmtUShort: case DevFmtInt: case DevFmtUInt: case DevFmtFloat: throw al::backend_exception{al::backend_error::DeviceError, "{} capture samples not supported", DevFmtTypeString(mDevice->FmtType)}; } uint periods{4}; uint numChannels{mDevice->channelsFromFmt()}; uint frameSize{numChannels * mDevice->bytesFromFmt()}; uint ossSpeed{mDevice->mSampleRate}; /* according to the OSS spec, 16 bytes are the minimum */ uint log2FragmentSize{std::max(log2i(mDevice->mBufferSize * frameSize / periods), 4u)}; uint numFragmentsLogSize{(periods << 16) | log2FragmentSize}; audio_buf_info info{}; #define CHECKERR(func) if((func) < 0) { \ throw al::backend_exception{al::backend_error::DeviceError, #func " failed: {}", \ std::generic_category().message(errno)}; \ } /* NOLINTBEGIN(cppcoreguidelines-pro-type-vararg) */ CHECKERR(ioctl(mFd, SNDCTL_DSP_SETFRAGMENT, &numFragmentsLogSize)); CHECKERR(ioctl(mFd, SNDCTL_DSP_SETFMT, &ossFormat)); CHECKERR(ioctl(mFd, SNDCTL_DSP_CHANNELS, &numChannels)); CHECKERR(ioctl(mFd, SNDCTL_DSP_SPEED, &ossSpeed)); CHECKERR(ioctl(mFd, SNDCTL_DSP_GETISPACE, &info)); /* NOLINTEND(cppcoreguidelines-pro-type-vararg) */ #undef CHECKERR if(mDevice->channelsFromFmt() != numChannels) throw al::backend_exception{al::backend_error::DeviceError, "Failed to set {}, got {} channels instead", DevFmtChannelsString(mDevice->FmtChans), numChannels}; if(!((ossFormat == AFMT_S8 && mDevice->FmtType == DevFmtByte) || (ossFormat == AFMT_U8 && mDevice->FmtType == DevFmtUByte) || (ossFormat == AFMT_S16_NE && mDevice->FmtType == DevFmtShort))) throw al::backend_exception{al::backend_error::DeviceError, "Failed to set {} samples, got OSS format {:#x}", DevFmtTypeString(mDevice->FmtType), as_unsigned(ossFormat)}; mRing = RingBuffer::Create(mDevice->mBufferSize, frameSize, false); mDeviceName = name; } void OSScapture::start() { try { mKillNow.store(false, std::memory_order_release); mThread = std::thread{&OSScapture::recordProc, this}; } catch(std::exception& e) { throw al::backend_exception{al::backend_error::DeviceError, "Failed to start recording thread: {}", e.what()}; } } void OSScapture::stop() { if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable()) return; mThread.join(); if(ioctl(mFd, SNDCTL_DSP_RESET) != 0) /* NOLINT(cppcoreguidelines-pro-type-vararg) */ ERR("Error resetting device: {}", std::generic_category().message(errno)); } void OSScapture::captureSamples(std::byte *buffer, uint samples) { std::ignore = mRing->read(buffer, samples); } uint OSScapture::availableSamples() { return static_cast(mRing->readSpace()); } } // namespace BackendFactory &OSSBackendFactory::getFactory() { static OSSBackendFactory factory{}; return factory; } bool OSSBackendFactory::init() { if(auto devopt = ConfigValueStr({}, "oss", "device")) DefaultPlayback = std::move(*devopt); if(auto capopt = ConfigValueStr({}, "oss", "capture")) DefaultCapture = std::move(*capopt); return true; } bool OSSBackendFactory::querySupport(BackendType type) { return (type == BackendType::Playback || type == BackendType::Capture); } auto OSSBackendFactory::enumerate(BackendType type) -> std::vector { std::vector outnames; auto add_device = [&outnames](const DevMap &entry) -> void { if(struct stat buf{}; stat(entry.device_name.c_str(), &buf) == 0) outnames.emplace_back(entry.name); }; switch(type) { case BackendType::Playback: PlaybackDevices.clear(); ALCossListPopulate(PlaybackDevices, DSP_CAP_OUTPUT); outnames.reserve(PlaybackDevices.size()); std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device); break; case BackendType::Capture: CaptureDevices.clear(); ALCossListPopulate(CaptureDevices, DSP_CAP_INPUT); outnames.reserve(CaptureDevices.size()); std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device); break; } return outnames; } BackendPtr OSSBackendFactory::createBackend(DeviceBase *device, BackendType type) { if(type == BackendType::Playback) return BackendPtr{new OSSPlayback{device}}; if(type == BackendType::Capture) return BackendPtr{new OSScapture{device}}; return nullptr; } openal-soft-1.24.2/alc/backends/oss.h000066400000000000000000000007101474041540300173150ustar00rootroot00000000000000#ifndef BACKENDS_OSS_H #define BACKENDS_OSS_H #include "base.h" struct OSSBackendFactory final : public BackendFactory { public: auto init() -> bool final; auto querySupport(BackendType type) -> bool final; auto enumerate(BackendType type) -> std::vector final; auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_OSS_H */ openal-soft-1.24.2/alc/backends/otherio.cpp000066400000000000000000000472471474041540300205350ustar00rootroot00000000000000/** * OpenAL cross platform audio library * Copyright (C) 2024 by authors. * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * Or go to http://www.gnu.org/copyleft/lgpl.html */ #include "config.h" #include "otherio.h" #define WIN32_LEAN_AND_MEAN #include #include #include #include #include #include #include #include #include #include #include #include #ifndef _WAVEFORMATEXTENSIBLE_ #include #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include "albit.h" #include "alnumeric.h" #include "althrd_setname.h" #include "comptr.h" #include "core/converter.h" #include "core/device.h" #include "core/helpers.h" #include "core/logging.h" #include "strutils.h" /* A custom C++ interface that should be capable of interoperating with ASIO * drivers. */ enum class ORIOError : LONG { Okay = 0, Success = 0x3f4847a0, NotPresent = -1000, HWMalfunction, InvalidParameter, InvalidMode, SPNotAdvancing, NoClock, NoMemory, }; /* A 64-bit integer or double, which has the most significant 32-bit word first. */ struct ORIO64Bit { uint32_t hi; uint32_t lo; template auto as() const -> T = delete; }; template<> [[nodiscard]] auto ORIO64Bit::as() const -> uint64_t { return (uint64_t{hi}<<32) | lo; } template<> [[nodiscard]] auto ORIO64Bit::as() const -> int64_t { return static_cast(as()); } template<> [[nodiscard]] auto ORIO64Bit::as() const -> double { return al::bit_cast(as()); } enum class ORIOSampleType : LONG { Int16BE = 0, Int24BE = 1, Int32BE = 2, Float32BE = 3, Float64BE = 4, Int32BE16 = 8, Int32BE18 = 9, Int32BE20 = 10, Int32BE24 = 11, Int16LE = 16, Int24LE = 17, Int32LE = 18, Float32LE = 19, Float64LE = 20, Int32LE16 = 24, Int32LE18 = 25, Int32LE20 = 26, Int32LE24 = 27, DSDInt8LSB1 = 32, DSDInt8MSB1 = 33, DSDInt8 = 40, }; struct ORIOClockSource { LONG mIndex; LONG mAssocChannel; LONG mAssocGroup; LONG mIsCurrent; std::array mName; }; struct ORIOChannelInfo { LONG mChannel; LONG mIsInput; LONG mIsActive; LONG mGroup; ORIOSampleType mSampleType; std::array mName; }; struct ORIOBufferInfo { LONG mIsInput; LONG mChannelNum; std::array mBuffers; }; struct ORIOTime { struct TimeInfo { double mSpeed; ORIO64Bit mSystemTime; ORIO64Bit mSamplePosition; double mSampleRate; ULONG mFlags; std::array mReserved; }; struct TimeCode { double mSpeed; ORIO64Bit mTimeCodeSamples; ULONG mFlags; std::array mFuture; }; std::array mReserved; TimeInfo mTimeInfo; TimeCode mTimeCode; }; #ifdef _WIN64 #define ORIO_CALLBACK CALLBACK #else #define ORIO_CALLBACK #endif struct ORIOCallbacks { void (ORIO_CALLBACK*BufferSwitch)(LONG bufferIndex, LONG directProcess) noexcept; void (ORIO_CALLBACK*SampleRateDidChange)(double srate) noexcept; auto (ORIO_CALLBACK*Message)(LONG selector, LONG value, void *message, double *opt) noexcept -> LONG; auto (ORIO_CALLBACK*BufferSwitchTimeInfo)(ORIOTime *timeInfo, LONG bufferIndex, LONG directProcess) noexcept -> ORIOTime*; }; /* COM interfaces don't include a virtual destructor in their pure-virtual * classes, and we can't add one without breaking ABI. */ #ifdef __GNUC__ _Pragma("GCC diagnostic push") _Pragma("GCC diagnostic ignored \"-Wnon-virtual-dtor\"") #endif /* NOLINTNEXTLINE(cppcoreguidelines-virtual-class-destructor) */ struct ORIOiface : public IUnknown { STDMETHOD_(LONG, Init)(void *sysHandle) = 0; /* A fixed-length span should be passed exactly the same as one pointer. * This ensures an appropriately-sized buffer for the driver. */ STDMETHOD_(void, GetDriverName)(al::span name) = 0; STDMETHOD_(LONG, GetDriverVersion)() = 0; STDMETHOD_(void, GetErrorMessage)(al::span message) = 0; STDMETHOD_(ORIOError, Start)() = 0; STDMETHOD_(ORIOError, Stop)() = 0; STDMETHOD_(ORIOError, GetChannels)(LONG *numInput, LONG *numOutput) = 0; STDMETHOD_(ORIOError, GetLatencies)(LONG *inputLatency, LONG *outputLatency) = 0; STDMETHOD_(ORIOError, GetBufferSize)(LONG *minSize, LONG *maxSize, LONG *preferredSize, LONG *granularity) = 0; STDMETHOD_(ORIOError, CanSampleRate)(double srate) = 0; STDMETHOD_(ORIOError, GetSampleRate)(double *srate) = 0; STDMETHOD_(ORIOError, SetSampleRate)(double srate) = 0; STDMETHOD_(ORIOError, GetClockSources)(ORIOClockSource *clocks, LONG *numSources) = 0; STDMETHOD_(ORIOError, SetClockSource)(LONG index) = 0; STDMETHOD_(ORIOError, GetSamplePosition)(ORIO64Bit *splPos, ORIO64Bit *tstampNS) = 0; STDMETHOD_(ORIOError, GetChannelInfo)(ORIOChannelInfo *info) = 0; STDMETHOD_(ORIOError, CreateBuffers)(ORIOBufferInfo *infos, LONG numInfos, LONG bufferSize, ORIOCallbacks *callbacks) = 0; STDMETHOD_(ORIOError, DisposeBuffers)() = 0; STDMETHOD_(ORIOError, ControlPanel)() = 0; STDMETHOD_(ORIOError, Future)(LONG selector, void *opt) = 0; STDMETHOD_(ORIOError, OutputReady)() = 0; ORIOiface() = default; ORIOiface(const ORIOiface&) = delete; auto operator=(const ORIOiface&) -> ORIOiface& = delete; ~ORIOiface() = delete; }; #ifdef __GNUC__ _Pragma("GCC diagnostic pop") #endif namespace { using namespace std::string_view_literals; using std::chrono::nanoseconds; using std::chrono::milliseconds; using std::chrono::seconds; struct DeviceEntry { std::string mDrvName; CLSID mDrvGuid{}; }; std::vector gDeviceList; struct KeyCloser { void operator()(HKEY key) { RegCloseKey(key); } }; using KeyPtr = std::unique_ptr,KeyCloser>; [[nodiscard]] auto PopulateDeviceList() -> HRESULT { auto regbase = KeyPtr{}; auto res = RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"Software\\ASIO", 0, KEY_READ, al::out_ptr(regbase)); if(res != ERROR_SUCCESS) { ERR("Error opening HKLM\\Software\\ASIO: {}", res); return E_NOINTERFACE; } auto numkeys = DWORD{}; auto maxkeylen = DWORD{}; res = RegQueryInfoKeyW(regbase.get(), nullptr, nullptr, nullptr, &numkeys, &maxkeylen, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr); if(res != ERROR_SUCCESS) { ERR("Error querying HKLM\\Software\\ASIO info: {}", res); return E_FAIL; } /* maxkeylen is the max number of unicode characters a subkey is. A unicode * character can occupy two WCHARs, so ensure there's enough space for them * and the null char. */ auto keyname = std::vector(maxkeylen*2 + 1); for(DWORD i{0};i < numkeys;++i) { auto namelen = static_cast(keyname.size()); res = RegEnumKeyExW(regbase.get(), i, keyname.data(), &namelen, nullptr, nullptr, nullptr, nullptr); if(res != ERROR_SUCCESS) { ERR("Error querying HKLM\\Software\\ASIO subkey {}: {}", i, res); continue; } if(namelen == 0) { ERR("HKLM\\Software\\ASIO subkey {} is blank?", i); continue; } auto subkeyname = wstr_to_utf8({keyname.data(), namelen}); auto subkey = KeyPtr{}; res = RegOpenKeyExW(regbase.get(), keyname.data(), 0, KEY_READ, al::out_ptr(subkey)); if(res != ERROR_SUCCESS) { ERR("Error opening HKLM\\Software\\ASIO\\{}: {}", subkeyname, res); continue; } auto idstr = std::array{}; auto readsize = DWORD{idstr.size()*sizeof(WCHAR)}; res = RegGetValueW(subkey.get(), L"", L"CLSID", RRF_RT_REG_SZ, nullptr, idstr.data(), &readsize); if(res != ERROR_SUCCESS) { ERR("Failed to read HKLM\\Software\\ASIO\\{}\\CLSID: {}", subkeyname, res); continue; } idstr.back() = 0; auto guid = CLSID{}; if(auto hr = CLSIDFromString(idstr.data(), &guid); FAILED(hr)) { ERR("Failed to parse CLSID \"{}\": {:#x}", wstr_to_utf8(idstr.data()), as_unsigned(hr)); continue; } /* The CLSID is also used for the IID. */ auto iface = ComPtr{}; auto hr = CoCreateInstance(guid, nullptr, CLSCTX_INPROC_SERVER, guid, al::out_ptr(iface)); if(SUCCEEDED(hr)) { #if !ALSOFT_UWP if(!iface->Init(GetForegroundWindow())) #else if(!iface->Init(nullptr)) #endif { ERR("Failed to initialize {}", subkeyname); continue; } auto drvname = std::array{}; iface->GetDriverName(drvname); auto drvver = iface->GetDriverVersion(); auto &entry = gDeviceList.emplace_back(); entry.mDrvName = drvname.data(); entry.mDrvGuid = guid; TRACE("Got {} v{}, CLSID {{{:08X}-{:04X}-{:04X}-{:02X}{:02X}-{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}}}", entry.mDrvName, drvver, guid.Data1, guid.Data2, guid.Data3, guid.Data4[0], guid.Data4[1], guid.Data4[2], guid.Data4[3], guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7]); } else ERR("Failed to create {} instance for CLSID {{{:08X}-{:04X}-{:04X}-{:02X}{:02X}-{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}}}: {:#x}", subkeyname.c_str(), guid.Data1, guid.Data2, guid.Data3, guid.Data4[0], guid.Data4[1], guid.Data4[2], guid.Data4[3], guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7], as_unsigned(hr)); } return S_OK; } enum class MsgType { OpenDevice, ResetDevice, StartDevice, StopDevice, CloseDevice, QuitThread }; constexpr const char *GetMessageTypeName(MsgType type) noexcept { switch(type) { case MsgType::OpenDevice: return "Open Device"; case MsgType::ResetDevice: return "Reset Device"; case MsgType::StartDevice: return "Start Device"; case MsgType::StopDevice: return "Stop Device"; case MsgType::CloseDevice: return "Close Device"; case MsgType::QuitThread: break; } return ""; } /* Proxy interface used by the message handler, to ensure COM objects are used * on a thread where COM is initialized. */ struct OtherIOProxy { OtherIOProxy() = default; OtherIOProxy(const OtherIOProxy&) = delete; OtherIOProxy(OtherIOProxy&&) = delete; virtual ~OtherIOProxy() = default; void operator=(const OtherIOProxy&) = delete; void operator=(OtherIOProxy&&) = delete; virtual HRESULT openProxy(std::string_view name) = 0; virtual void closeProxy() = 0; virtual HRESULT resetProxy() = 0; virtual HRESULT startProxy() = 0; virtual void stopProxy() = 0; struct Msg { MsgType mType; OtherIOProxy *mProxy; std::string_view mParam; std::promise mPromise; explicit operator bool() const noexcept { return mType != MsgType::QuitThread; } }; static inline std::deque mMsgQueue; static inline std::mutex mMsgQueueLock; static inline std::condition_variable mMsgQueueCond; auto pushMessage(MsgType type, std::string_view param={}) -> std::future { auto promise = std::promise{}; auto future = std::future{promise.get_future()}; { auto msglock = std::lock_guard{mMsgQueueLock}; mMsgQueue.emplace_back(Msg{type, this, param, std::move(promise)}); } mMsgQueueCond.notify_one(); return future; } static auto popMessage() -> Msg { auto lock = std::unique_lock{mMsgQueueLock}; mMsgQueueCond.wait(lock, []{return !mMsgQueue.empty();}); auto msg = Msg{std::move(mMsgQueue.front())}; mMsgQueue.pop_front(); return msg; } static void messageHandler(std::promise *promise); }; void OtherIOProxy::messageHandler(std::promise *promise) { TRACE("Starting COM message thread"); auto com = ComWrapper{COINIT_APARTMENTTHREADED}; if(!com) { WARN("Failed to initialize COM: {:#x}", as_unsigned(com.status())); promise->set_value(com.status()); return; } auto hr = PopulateDeviceList(); if(FAILED(hr)) { promise->set_value(hr); return; } promise->set_value(S_OK); promise = nullptr; TRACE("Starting message loop"); while(Msg msg{popMessage()}) { TRACE("Got message \"{}\" ({:#04x}, this={}, param=\"{}\")", GetMessageTypeName(msg.mType), static_cast(msg.mType), static_cast(msg.mProxy), msg.mParam); switch(msg.mType) { case MsgType::OpenDevice: hr = msg.mProxy->openProxy(msg.mParam); msg.mPromise.set_value(hr); continue; case MsgType::ResetDevice: hr = msg.mProxy->resetProxy(); msg.mPromise.set_value(hr); continue; case MsgType::StartDevice: hr = msg.mProxy->startProxy(); msg.mPromise.set_value(hr); continue; case MsgType::StopDevice: msg.mProxy->stopProxy(); msg.mPromise.set_value(S_OK); continue; case MsgType::CloseDevice: msg.mProxy->closeProxy(); msg.mPromise.set_value(S_OK); continue; case MsgType::QuitThread: break; } ERR("Unexpected message: {}", int{al::to_underlying(msg.mType)}); msg.mPromise.set_value(E_FAIL); } TRACE("Message loop finished"); } struct OtherIOPlayback final : public BackendBase, OtherIOProxy { explicit OtherIOPlayback(DeviceBase *device) noexcept : BackendBase{device} { } ~OtherIOPlayback() final; void mixerProc(); void open(std::string_view name) final; auto openProxy(std::string_view name) -> HRESULT final; void closeProxy() final; auto reset() -> bool final; auto resetProxy() -> HRESULT final; void start() final; auto startProxy() -> HRESULT final; void stop() final; void stopProxy() final; HRESULT mOpenStatus{E_FAIL}; std::atomic mKillNow{true}; std::thread mThread; }; OtherIOPlayback::~OtherIOPlayback() { if(SUCCEEDED(mOpenStatus)) pushMessage(MsgType::CloseDevice).wait(); } void OtherIOPlayback::mixerProc() { const auto restTime = milliseconds{mDevice->mUpdateSize*1000/mDevice->mSampleRate / 2}; SetRTPriority(); althrd_setname(GetMixerThreadName()); auto done = int64_t{0}; auto start = std::chrono::steady_clock::now(); while(!mKillNow.load(std::memory_order_acquire) && mDevice->Connected.load(std::memory_order_acquire)) { auto now = std::chrono::steady_clock::now(); /* This converts from nanoseconds to nanosamples, then to samples. */ const auto avail = int64_t{std::chrono::duration_cast((now-start) * mDevice->mSampleRate).count()}; if(avail-done < mDevice->mUpdateSize) { std::this_thread::sleep_for(restTime); continue; } while(avail-done >= mDevice->mUpdateSize) { mDevice->renderSamples(nullptr, mDevice->mUpdateSize, 0u); done += mDevice->mUpdateSize; } if(done >= mDevice->mSampleRate) { auto s = seconds{done/mDevice->mSampleRate}; start += s; done -= mDevice->mSampleRate*s.count(); } } } void OtherIOPlayback::open(std::string_view name) { if(name.empty() && !gDeviceList.empty()) name = gDeviceList[0].mDrvName; else { auto iter = std::find_if(gDeviceList.cbegin(), gDeviceList.cend(), [name](const DeviceEntry &entry) { return entry.mDrvName == name; }); if(iter == gDeviceList.cend()) throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found", name}; } mOpenStatus = pushMessage(MsgType::OpenDevice, name).get(); if(FAILED(mOpenStatus)) throw al::backend_exception{al::backend_error::DeviceError, "Failed to open \"{}\"", name}; mDeviceName = name; } auto OtherIOPlayback::openProxy(std::string_view name [[maybe_unused]]) -> HRESULT { return S_OK; } void OtherIOPlayback::closeProxy() { } auto OtherIOPlayback::reset() -> bool { return SUCCEEDED(pushMessage(MsgType::ResetDevice).get()); } auto OtherIOPlayback::resetProxy() -> HRESULT { setDefaultWFXChannelOrder(); return S_OK; } void OtherIOPlayback::start() { auto hr = pushMessage(MsgType::StartDevice).get(); if(FAILED(hr)) throw al::backend_exception{al::backend_error::DeviceError, "Failed to start playback: {:#x}", as_unsigned(hr)}; } auto OtherIOPlayback::startProxy() -> HRESULT { try { mKillNow.store(false, std::memory_order_release); mThread = std::thread{&OtherIOPlayback::mixerProc, this}; return S_OK; } catch(std::exception& e) { ERR("Failed to start mixing thread: {}", e.what()); } return E_FAIL; } void OtherIOPlayback::stop() { pushMessage(MsgType::StopDevice).wait(); } void OtherIOPlayback::stopProxy() { if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable()) return; mThread.join(); } } // namespace auto OtherIOBackendFactory::init() -> bool { static HRESULT InitResult{E_FAIL}; if(FAILED(InitResult)) try { auto promise = std::promise{}; auto future = promise.get_future(); std::thread{&OtherIOProxy::messageHandler, &promise}.detach(); InitResult = future.get(); } catch(...) { } return SUCCEEDED(InitResult); } auto OtherIOBackendFactory::querySupport(BackendType type) -> bool { return type == BackendType::Playback; } auto OtherIOBackendFactory::enumerate(BackendType type) -> std::vector { std::vector outnames; switch(type) { case BackendType::Playback: std::for_each(gDeviceList.cbegin(), gDeviceList.cend(), [&outnames](const DeviceEntry &entry) { outnames.emplace_back(entry.mDrvName); }); break; case BackendType::Capture: break; } return outnames; } auto OtherIOBackendFactory::createBackend(DeviceBase *device, BackendType type) -> BackendPtr { if(type == BackendType::Playback) return BackendPtr{new OtherIOPlayback{device}}; return nullptr; } auto OtherIOBackendFactory::getFactory() -> BackendFactory& { static auto factory = OtherIOBackendFactory{}; return factory; } auto OtherIOBackendFactory::queryEventSupport(alc::EventType, BackendType) -> alc::EventSupport { return alc::EventSupport::NoSupport; } openal-soft-1.24.2/alc/backends/otherio.h000066400000000000000000000010741474041540300201660ustar00rootroot00000000000000#ifndef BACKENDS_OTHERIO_H #define BACKENDS_OTHERIO_H #include "base.h" struct OtherIOBackendFactory final : public BackendFactory { public: auto init() -> bool final; auto querySupport(BackendType type) -> bool final; auto queryEventSupport(alc::EventType eventType, BackendType type) -> alc::EventSupport final; auto enumerate(BackendType type) -> std::vector final; auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_OTHERIO_H */ openal-soft-1.24.2/alc/backends/pipewire.cpp000066400000000000000000002422061474041540300207000ustar00rootroot00000000000000/** * OpenAL cross platform audio library * Copyright (C) 2010 by Chris Robinson * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * Or go to http://www.gnu.org/copyleft/lgpl.html */ #include "config.h" #include "pipewire.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "alc/alconfig.h" #include "alc/backends/base.h" #include "almalloc.h" #include "alspan.h" #include "alstring.h" #include "core/devformat.h" #include "core/device.h" #include "core/helpers.h" #include "core/logging.h" #include "dynload.h" #include "opthelpers.h" #include "ringbuffer.h" /* Ignore warnings caused by PipeWire headers (lots in standard C++ mode). GCC * doesn't support ignoring -Weverything, so we have the list the individual * warnings to ignore (and ignoring -Winline doesn't seem to work). */ _Pragma("GCC diagnostic push") _Pragma("GCC diagnostic ignored \"-Wpedantic\"") _Pragma("GCC diagnostic ignored \"-Wconversion\"") _Pragma("GCC diagnostic ignored \"-Wfloat-conversion\"") _Pragma("GCC diagnostic ignored \"-Wmissing-field-initializers\"") _Pragma("GCC diagnostic ignored \"-Wunused-parameter\"") _Pragma("GCC diagnostic ignored \"-Wold-style-cast\"") _Pragma("GCC diagnostic ignored \"-Wsign-compare\"") _Pragma("GCC diagnostic ignored \"-Winline\"") _Pragma("GCC diagnostic ignored \"-Wpragmas\"") _Pragma("GCC diagnostic ignored \"-Weverything\"") #include "pipewire/pipewire.h" #include "pipewire/extensions/metadata.h" #include "spa/buffer/buffer.h" #include "spa/param/audio/format-utils.h" #include "spa/param/audio/raw.h" #include "spa/param/format.h" #include "spa/param/param.h" #include "spa/pod/builder.h" #include "spa/utils/json.h" /* NOLINTBEGIN : All kinds of unsafe C stuff here from PipeWire headers * (function-like macros, C style casts in macros, etc), which we can't do * anything about except wrap into inline functions. */ namespace { /* Wrap some nasty macros here too... */ template auto ppw_core_add_listener(pw_core *core, Args&& ...args) { return pw_core_add_listener(core, std::forward(args)...); } template auto ppw_core_sync(pw_core *core, Args&& ...args) { return pw_core_sync(core, std::forward(args)...); } template auto ppw_registry_add_listener(pw_registry *reg, Args&& ...args) { return pw_registry_add_listener(reg, std::forward(args)...); } template auto ppw_node_add_listener(pw_node *node, Args&& ...args) { return pw_node_add_listener(node, std::forward(args)...); } template auto ppw_node_subscribe_params(pw_node *node, Args&& ...args) { return pw_node_subscribe_params(node, std::forward(args)...); } template auto ppw_metadata_add_listener(pw_metadata *mdata, Args&& ...args) { return pw_metadata_add_listener(mdata, std::forward(args)...); } constexpr auto get_pod_type(const spa_pod *pod) noexcept { return SPA_POD_TYPE(pod); } template constexpr auto get_pod_body(const spa_pod *pod, size_t count) noexcept { return al::span{static_cast(SPA_POD_BODY(pod)), count}; } template constexpr auto get_pod_body(const spa_pod *pod) noexcept { return al::span{static_cast(SPA_POD_BODY(pod)), N}; } constexpr auto get_array_value_type(const spa_pod *pod) noexcept { return SPA_POD_ARRAY_VALUE_TYPE(pod); } constexpr auto make_pod_builder(void *data, uint32_t size) noexcept { return SPA_POD_BUILDER_INIT(data, size); } constexpr auto PwIdAny = PW_ID_ANY; } // namespace /* NOLINTEND */ _Pragma("GCC diagnostic pop") namespace { template [[nodiscard]] constexpr auto as_const_ptr(T *ptr) noexcept -> std::add_const_t* { return ptr; } struct PodDynamicBuilder { private: std::vector mStorage; spa_pod_builder mPod{}; int overflow(uint32_t size) noexcept { try { mStorage.resize(size); } catch(...) { ERR("Failed to resize POD storage"); return -ENOMEM; } mPod.data = mStorage.data(); mPod.size = size; return 0; } public: explicit PodDynamicBuilder(uint32_t initSize=1024) : mStorage(initSize) , mPod{make_pod_builder(mStorage.data(), initSize)} { static constexpr auto callbacks{[] { spa_pod_builder_callbacks cb{}; cb.version = SPA_VERSION_POD_BUILDER_CALLBACKS; cb.overflow = [](void *data, uint32_t size) noexcept { return static_cast(data)->overflow(size); }; return cb; }()}; spa_pod_builder_set_callbacks(&mPod, &callbacks, this); } spa_pod_builder *get() noexcept { return &mPod; } }; /* Added in 0.3.33, but we currently only require 0.3.23. */ #ifndef PW_KEY_NODE_RATE #define PW_KEY_NODE_RATE "node.rate" #endif using namespace std::string_view_literals; using std::chrono::seconds; using std::chrono::milliseconds; using std::chrono::nanoseconds; using uint = unsigned int; bool check_version(const char *version) { /* There doesn't seem to be a function to get the version as an integer, so * instead we have to parse the string, which hopefully won't break in the * future. */ int major{0}, minor{0}, revision{0}; /* NOLINTNEXTLINE(cert-err34-c,cppcoreguidelines-pro-type-vararg) */ int ret{sscanf(version, "%d.%d.%d", &major, &minor, &revision)}; return ret == 3 && (major > PW_MAJOR || (major == PW_MAJOR && minor > PW_MINOR) || (major == PW_MAJOR && minor == PW_MINOR && revision >= PW_MICRO)); } #if HAVE_DYNLOAD #define PWIRE_FUNCS(MAGIC) \ MAGIC(pw_context_connect) \ MAGIC(pw_context_destroy) \ MAGIC(pw_context_new) \ MAGIC(pw_core_disconnect) \ MAGIC(pw_get_library_version) \ MAGIC(pw_init) \ MAGIC(pw_properties_free) \ MAGIC(pw_properties_new) \ MAGIC(pw_properties_set) \ MAGIC(pw_properties_setf) \ MAGIC(pw_proxy_add_object_listener) \ MAGIC(pw_proxy_destroy) \ MAGIC(pw_proxy_get_user_data) \ MAGIC(pw_stream_add_listener) \ MAGIC(pw_stream_connect) \ MAGIC(pw_stream_dequeue_buffer) \ MAGIC(pw_stream_destroy) \ MAGIC(pw_stream_get_state) \ MAGIC(pw_stream_new) \ MAGIC(pw_stream_queue_buffer) \ MAGIC(pw_stream_set_active) \ MAGIC(pw_thread_loop_new) \ MAGIC(pw_thread_loop_destroy) \ MAGIC(pw_thread_loop_get_loop) \ MAGIC(pw_thread_loop_start) \ MAGIC(pw_thread_loop_stop) \ MAGIC(pw_thread_loop_lock) \ MAGIC(pw_thread_loop_wait) \ MAGIC(pw_thread_loop_signal) \ MAGIC(pw_thread_loop_unlock) #if PW_CHECK_VERSION(0,3,50) #define PWIRE_FUNCS2(MAGIC) \ MAGIC(pw_stream_get_time_n) #else #define PWIRE_FUNCS2(MAGIC) \ MAGIC(pw_stream_get_time) #endif void *pwire_handle; #define MAKE_FUNC(f) decltype(f) * p##f; PWIRE_FUNCS(MAKE_FUNC) PWIRE_FUNCS2(MAKE_FUNC) #undef MAKE_FUNC bool pwire_load() { if(pwire_handle) return true; const char *pwire_library{"libpipewire-0.3.so.0"}; std::string missing_funcs; pwire_handle = LoadLib(pwire_library); if(!pwire_handle) { WARN("Failed to load {}", pwire_library); return false; } #define LOAD_FUNC(f) do { \ p##f = reinterpret_cast(GetSymbol(pwire_handle, #f)); \ if(p##f == nullptr) missing_funcs += "\n" #f; \ } while(0); PWIRE_FUNCS(LOAD_FUNC) PWIRE_FUNCS2(LOAD_FUNC) #undef LOAD_FUNC if(!missing_funcs.empty()) { WARN("Missing expected functions:{}", missing_funcs); CloseLib(pwire_handle); pwire_handle = nullptr; return false; } return true; } #ifndef IN_IDE_PARSER #define pw_context_connect ppw_context_connect #define pw_context_destroy ppw_context_destroy #define pw_context_new ppw_context_new #define pw_core_disconnect ppw_core_disconnect #define pw_get_library_version ppw_get_library_version #define pw_init ppw_init #define pw_properties_free ppw_properties_free #define pw_properties_new ppw_properties_new #define pw_properties_set ppw_properties_set #define pw_properties_setf ppw_properties_setf #define pw_proxy_add_object_listener ppw_proxy_add_object_listener #define pw_proxy_destroy ppw_proxy_destroy #define pw_proxy_get_user_data ppw_proxy_get_user_data #define pw_stream_add_listener ppw_stream_add_listener #define pw_stream_connect ppw_stream_connect #define pw_stream_dequeue_buffer ppw_stream_dequeue_buffer #define pw_stream_destroy ppw_stream_destroy #define pw_stream_get_state ppw_stream_get_state #define pw_stream_new ppw_stream_new #define pw_stream_queue_buffer ppw_stream_queue_buffer #define pw_stream_set_active ppw_stream_set_active #define pw_thread_loop_destroy ppw_thread_loop_destroy #define pw_thread_loop_get_loop ppw_thread_loop_get_loop #define pw_thread_loop_lock ppw_thread_loop_lock #define pw_thread_loop_new ppw_thread_loop_new #define pw_thread_loop_signal ppw_thread_loop_signal #define pw_thread_loop_start ppw_thread_loop_start #define pw_thread_loop_stop ppw_thread_loop_stop #define pw_thread_loop_unlock ppw_thread_loop_unlock #define pw_thread_loop_wait ppw_thread_loop_wait #if PW_CHECK_VERSION(0,3,50) #define pw_stream_get_time_n ppw_stream_get_time_n #else inline auto pw_stream_get_time_n(pw_stream *stream, pw_time *ptime, size_t /*size*/) { return ppw_stream_get_time(stream, ptime); } #endif #endif #else constexpr bool pwire_load() { return true; } #endif /* Helpers for retrieving values from params */ template struct PodInfo { }; template<> struct PodInfo { using Type = int32_t; static auto get_value(const spa_pod *pod, int32_t *val) { return spa_pod_get_int(pod, val); } }; template<> struct PodInfo { using Type = uint32_t; static auto get_value(const spa_pod *pod, uint32_t *val) { return spa_pod_get_id(pod, val); } }; template using Pod_t = typename PodInfo::Type; template al::span> get_array_span(const spa_pod *pod) { uint32_t nvals{}; if(void *v{spa_pod_get_array(pod, &nvals)}) { if(get_array_value_type(pod) == T) return {static_cast*>(v), nvals}; } return {}; } template std::optional> get_value(const spa_pod *value) { Pod_t val{}; if(PodInfo::get_value(value, &val) == 0) return val; return std::nullopt; } /* Internally, PipeWire types "inherit" from each other, but this is hidden * from the API and the caller is expected to C-style cast to inherited types * as needed. It's also not made very clear what types a given type can be * casted to. To make it a bit safer, this as() method allows casting pw_* * types to known inherited types, generating a compile-time error for * unexpected/invalid casts. */ template To as(From) noexcept = delete; /* pw_proxy * - pw_registry * - pw_node * - pw_metadata */ template<> pw_proxy* as(pw_registry *reg) noexcept { return reinterpret_cast(reg); } template<> pw_proxy* as(pw_node *node) noexcept { return reinterpret_cast(node); } template<> pw_proxy* as(pw_metadata *mdata) noexcept { return reinterpret_cast(mdata); } struct PwContextDeleter { void operator()(pw_context *context) const { pw_context_destroy(context); } }; using PwContextPtr = std::unique_ptr; struct PwCoreDeleter { void operator()(pw_core *core) const { pw_core_disconnect(core); } }; using PwCorePtr = std::unique_ptr; struct PwRegistryDeleter { void operator()(pw_registry *reg) const { pw_proxy_destroy(as(reg)); } }; using PwRegistryPtr = std::unique_ptr; struct PwNodeDeleter { void operator()(pw_node *node) const { pw_proxy_destroy(as(node)); } }; using PwNodePtr = std::unique_ptr; struct PwMetadataDeleter { void operator()(pw_metadata *mdata) const { pw_proxy_destroy(as(mdata)); } }; using PwMetadataPtr = std::unique_ptr; struct PwStreamDeleter { void operator()(pw_stream *stream) const { pw_stream_destroy(stream); } }; using PwStreamPtr = std::unique_ptr; /* NOLINTBEGIN(*EnumCastOutOfRange) Enums for bitflags... again... *sigh* */ constexpr pw_stream_flags operator|(pw_stream_flags lhs, pw_stream_flags rhs) noexcept { return static_cast(lhs | al::to_underlying(rhs)); } /* NOLINTEND(*EnumCastOutOfRange) */ constexpr pw_stream_flags& operator|=(pw_stream_flags &lhs, pw_stream_flags rhs) noexcept { lhs = lhs | rhs; return lhs; } class ThreadMainloop { pw_thread_loop *mLoop{}; public: ThreadMainloop() = default; ThreadMainloop(const ThreadMainloop&) = delete; ThreadMainloop(ThreadMainloop&& rhs) noexcept : mLoop{rhs.mLoop} { rhs.mLoop = nullptr; } explicit ThreadMainloop(pw_thread_loop *loop) noexcept : mLoop{loop} { } ~ThreadMainloop() { if(mLoop) pw_thread_loop_destroy(mLoop); } ThreadMainloop& operator=(const ThreadMainloop&) = delete; ThreadMainloop& operator=(ThreadMainloop&& rhs) noexcept { std::swap(mLoop, rhs.mLoop); return *this; } ThreadMainloop& operator=(std::nullptr_t) noexcept { if(mLoop) pw_thread_loop_destroy(mLoop); mLoop = nullptr; return *this; } explicit operator bool() const noexcept { return mLoop != nullptr; } [[nodiscard]] auto start() const { return pw_thread_loop_start(mLoop); } auto stop() const { return pw_thread_loop_stop(mLoop); } [[nodiscard]] auto getLoop() const { return pw_thread_loop_get_loop(mLoop); } auto lock() const { return pw_thread_loop_lock(mLoop); } auto unlock() const { return pw_thread_loop_unlock(mLoop); } auto signal(bool wait) const { return pw_thread_loop_signal(mLoop, wait); } auto newContext(pw_properties *props=nullptr, size_t user_data_size=0) const { return PwContextPtr{pw_context_new(getLoop(), props, user_data_size)}; } static auto Create(const char *name, spa_dict *props=nullptr) { return ThreadMainloop{pw_thread_loop_new(name, props)}; } friend struct MainloopUniqueLock; }; struct MainloopUniqueLock : public std::unique_lock { using std::unique_lock::unique_lock; MainloopUniqueLock& operator=(MainloopUniqueLock&&) = default; auto wait() const -> void { pw_thread_loop_wait(mutex()->mLoop); } template auto wait(Predicate done_waiting) const -> void { while(!done_waiting()) wait(); } }; using MainloopLockGuard = std::lock_guard; /* There's quite a mess here, but the purpose is to track active devices and * their default formats, so playback devices can be configured to match. The * device list is updated asynchronously, so it will have the latest list of * devices provided by the server. */ /* A generic PipeWire node proxy object used to track changes to sink and * source nodes. */ struct NodeProxy { static constexpr pw_node_events CreateNodeEvents() { pw_node_events ret{}; ret.version = PW_VERSION_NODE_EVENTS; ret.info = infoCallback; ret.param = [](void *object, int seq, uint32_t id, uint32_t index, uint32_t next, const spa_pod *param) noexcept { static_cast(object)->paramCallback(seq, id, index, next, param); }; return ret; } uint32_t mId{}; PwNodePtr mNode; spa_hook mListener{}; NodeProxy(uint32_t id, PwNodePtr node) : mId{id}, mNode{std::move(node)} { static constexpr pw_node_events nodeEvents{CreateNodeEvents()}; ppw_node_add_listener(mNode.get(), &mListener, &nodeEvents, this); /* Track changes to the enumerable and current formats (indicates the * default and active format, which is what we're interested in). */ std::array fmtids{{SPA_PARAM_EnumFormat, SPA_PARAM_Format}}; ppw_node_subscribe_params(mNode.get(), fmtids.data(), fmtids.size()); } ~NodeProxy() { spa_hook_remove(&mListener); } static void infoCallback(void *object, const pw_node_info *info) noexcept; void paramCallback(int seq, uint32_t id, uint32_t index, uint32_t next, const spa_pod *param) const noexcept; }; /* A metadata proxy object used to query the default sink and source. */ struct MetadataProxy { static constexpr pw_metadata_events CreateMetadataEvents() { pw_metadata_events ret{}; ret.version = PW_VERSION_METADATA_EVENTS; ret.property = propertyCallback; return ret; } uint32_t mId{}; PwMetadataPtr mMetadata; spa_hook mListener{}; MetadataProxy(uint32_t id, PwMetadataPtr mdata) : mId{id}, mMetadata{std::move(mdata)} { static constexpr pw_metadata_events metadataEvents{CreateMetadataEvents()}; ppw_metadata_add_listener(mMetadata.get(), &mListener, &metadataEvents, this); } ~MetadataProxy() { spa_hook_remove(&mListener); } static auto propertyCallback(void *object, uint32_t id, const char *key, const char *type, const char *value) noexcept -> int; }; /* The global thread watching for global events. This particular class responds * to objects being added to or removed from the registry. */ struct EventManager { ThreadMainloop mLoop; PwContextPtr mContext; PwCorePtr mCore; PwRegistryPtr mRegistry; spa_hook mRegistryListener{}; spa_hook mCoreListener{}; /* A list of proxy objects watching for events about changes to objects in * the registry. */ std::vector> mNodeList; std::optional mDefaultMetadata; /* Initialization handling. When init() is called, mInitSeq is set to a * SequenceID that marks the end of populating the registry. As objects of * interest are found, events to parse them are generated and mInitSeq is * updated with a newer ID. When mInitSeq stops being updated and the event * corresponding to it is reached, mInitDone will be set to true. */ std::atomic mInitDone{false}; std::atomic mHasAudio{false}; int mInitSeq{}; ~EventManager() { if(mLoop) mLoop.stop(); } bool init(); void kill(); auto lock() const { return mLoop.lock(); } auto unlock() const { return mLoop.unlock(); } [[nodiscard]] auto initIsDone(std::memory_order m=std::memory_order_seq_cst) const noexcept -> bool { return mInitDone.load(m); } /** * Waits for initialization to finish. The event manager must *NOT* be * locked when calling this. */ void waitForInit() { if(!initIsDone(std::memory_order_acquire)) UNLIKELY { MainloopUniqueLock plock{mLoop}; plock.wait([this](){ return initIsDone(std::memory_order_acquire); }); } } /** * Waits for audio support to be detected, or initialization to finish, * whichever is first. Returns true if audio support was detected. The * event manager must *NOT* be locked when calling this. */ bool waitForAudio() { MainloopUniqueLock plock{mLoop}; bool has_audio{}; plock.wait([this,&has_audio]() { has_audio = mHasAudio.load(std::memory_order_acquire); return has_audio || initIsDone(std::memory_order_acquire); }); return has_audio; } void syncInit() { /* If initialization isn't done, update the sequence ID so it won't * complete until after currently scheduled events. */ if(!initIsDone(std::memory_order_relaxed)) mInitSeq = ppw_core_sync(mCore.get(), PW_ID_CORE, mInitSeq); } void addCallback(uint32_t id, uint32_t permissions, const char *type, uint32_t version, const spa_dict *props) noexcept; void removeCallback(uint32_t id) noexcept; static constexpr pw_registry_events CreateRegistryEvents() { pw_registry_events ret{}; ret.version = PW_VERSION_REGISTRY_EVENTS; ret.global = [](void *object, uint32_t id, uint32_t permissions, const char *type, uint32_t version, const spa_dict *props) noexcept { static_cast(object)->addCallback(id, permissions, type, version, props); }; ret.global_remove = [](void *object, uint32_t id) noexcept { static_cast(object)->removeCallback(id); }; return ret; } void coreCallback(uint32_t id, int seq) noexcept; static constexpr pw_core_events CreateCoreEvents() { pw_core_events ret{}; ret.version = PW_VERSION_CORE_EVENTS; ret.done = [](void *object, uint32_t id, int seq) noexcept { static_cast(object)->coreCallback(id, seq); }; return ret; } }; using EventWatcherUniqueLock = std::unique_lock; using EventWatcherLockGuard = std::lock_guard; EventManager gEventHandler; /* Enumerated devices. This is updated asynchronously as the app runs, and the * gEventHandler thread loop must be locked when accessing the list. */ enum class NodeType : unsigned char { Sink, Source, Duplex }; constexpr auto InvalidChannelConfig = DevFmtChannels(255); struct DeviceNode { uint32_t mId{}; uint64_t mSerial{}; std::string mName; std::string mDevName; NodeType mType{}; bool mIsHeadphones{}; bool mIs51Rear{}; uint mSampleRate{}; DevFmtChannels mChannels{InvalidChannelConfig}; static std::vector sList; static DeviceNode &Add(uint32_t id); static DeviceNode *Find(uint32_t id); static DeviceNode *FindByDevName(std::string_view devname); static void Remove(uint32_t id); static auto GetList() noexcept { return al::span{sList}; } void parseSampleRate(const spa_pod *value, bool force_update) noexcept; void parsePositions(const spa_pod *value, bool force_update) noexcept; void parseChannelCount(const spa_pod *value, bool force_update) noexcept; void callEvent(alc::EventType type, std::string_view message) const { /* Source nodes aren't recognized for playback, only Sink and Duplex * nodes are. All node types are recognized for capture. */ if(mType != NodeType::Source) alc::Event(type, alc::DeviceType::Playback, message); alc::Event(type, alc::DeviceType::Capture, message); } }; std::vector DeviceNode::sList; std::string DefaultSinkDevice; std::string DefaultSourceDevice; const char *AsString(NodeType type) noexcept { switch(type) { case NodeType::Sink: return "sink"; case NodeType::Source: return "source"; case NodeType::Duplex: return "duplex"; } return ""; } DeviceNode &DeviceNode::Add(uint32_t id) { auto match_id = [id](DeviceNode &n) noexcept -> bool { return n.mId == id; }; /* If the node is already in the list, return the existing entry. */ auto match = std::find_if(sList.begin(), sList.end(), match_id); if(match != sList.end()) return *match; auto &n = sList.emplace_back(); n.mId = id; return n; } DeviceNode *DeviceNode::Find(uint32_t id) { auto match_id = [id](DeviceNode &n) noexcept -> bool { return n.mId == id; }; auto match = std::find_if(sList.begin(), sList.end(), match_id); if(match != sList.end()) return al::to_address(match); return nullptr; } DeviceNode *DeviceNode::FindByDevName(std::string_view devname) { auto match_id = [devname](DeviceNode &n) noexcept -> bool { return n.mDevName == devname; }; auto match = std::find_if(sList.begin(), sList.end(), match_id); if(match != sList.end()) return al::to_address(match); return nullptr; } void DeviceNode::Remove(uint32_t id) { auto match_id = [id](DeviceNode &n) noexcept -> bool { if(n.mId != id) return false; TRACE("Removing device \"{}\"", n.mDevName); if(gEventHandler.initIsDone(std::memory_order_relaxed)) { const std::string msg{"Device removed: "+n.mName}; n.callEvent(alc::EventType::DeviceRemoved, msg); } return true; }; auto end = std::remove_if(sList.begin(), sList.end(), match_id); sList.erase(end, sList.end()); } constexpr std::array MonoMap{ SPA_AUDIO_CHANNEL_MONO }; constexpr std::array StereoMap{ SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR }; constexpr std::array QuadMap{ SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR }; constexpr std::array X51Map{ SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE, SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR }; constexpr std::array X51RearMap{ SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE, SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR }; constexpr std::array X61Map{ SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE, SPA_AUDIO_CHANNEL_RC, SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR }; constexpr std::array X71Map{ SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE, SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR, SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR }; constexpr std::array X714Map{ SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE, SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR, SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR, SPA_AUDIO_CHANNEL_TFL, SPA_AUDIO_CHANNEL_TFR, SPA_AUDIO_CHANNEL_TRL, SPA_AUDIO_CHANNEL_TRR }; /** * Checks if every channel in 'map1' exists in 'map0' (that is, map0 is equal * to or a superset of map1). */ bool MatchChannelMap(const al::span map0, const al::span map1) { if(map0.size() < map1.size()) return false; auto find_channel = [map0](const spa_audio_channel chid) -> bool { return std::find(map0.begin(), map0.end(), chid) != map0.end(); }; return std::all_of(map1.cbegin(), map1.cend(), find_channel); } void DeviceNode::parseSampleRate(const spa_pod *value, bool force_update) noexcept { /* TODO: Can this be anything else? Long, Float, Double? */ uint32_t nvals{}, choiceType{}; value = spa_pod_get_values(value, &nvals, &choiceType); const uint podType{get_pod_type(value)}; if(podType != SPA_TYPE_Int) { WARN(" Unhandled sample rate POD type: {}", podType); return; } if(choiceType == SPA_CHOICE_Range) { if(nvals != 3) { WARN(" Unexpected SPA_CHOICE_Range count: {}", nvals); return; } auto srates = get_pod_body(value); /* [0] is the default, [1] is the min, and [2] is the max. */ TRACE(" sample rate: {} (range: {} -> {})", srates[0], srates[1], srates[2]); if(!mSampleRate || force_update) mSampleRate = static_cast(std::clamp(srates[0], MinOutputRate, MaxOutputRate)); return; } if(choiceType == SPA_CHOICE_Enum) { if(nvals == 0) { WARN(" Unexpected SPA_CHOICE_Enum count: {}", nvals); return; } auto srates = get_pod_body(value, nvals); /* [0] is the default, [1...size()-1] are available selections. */ std::string others{(srates.size() > 1) ? std::to_string(srates[1]) : std::string{}}; for(size_t i{2};i < srates.size();++i) { others += ", "; others += std::to_string(srates[i]); } TRACE(" sample rate: {} ({})", srates[0], others); /* Pick the first rate listed that's within the allowed range (default * rate if possible). */ for(const auto &rate : srates) { if(rate >= int{MinOutputRate} && rate <= int{MaxOutputRate}) { if(!mSampleRate || force_update) mSampleRate = static_cast(rate); break; } } return; } if(choiceType == SPA_CHOICE_None) { if(nvals != 1) { WARN(" Unexpected SPA_CHOICE_None count: {}", nvals); return; } auto srates = get_pod_body(value); TRACE(" sample rate: {}", srates[0]); if(!mSampleRate || force_update) mSampleRate = static_cast(std::clamp(srates[0], MinOutputRate, MaxOutputRate)); return; } WARN(" Unhandled sample rate choice type: {}", choiceType); } void DeviceNode::parsePositions(const spa_pod *value, bool force_update) noexcept { uint32_t choiceCount{}, choiceType{}; value = spa_pod_get_values(value, &choiceCount, &choiceType); if(choiceType != SPA_CHOICE_None || choiceCount != 1) { ERR(" Unexpected positions choice: type={}, count={}", choiceType, choiceCount); return; } const auto chanmap = get_array_span(value); if(chanmap.empty()) return; if(mChannels == InvalidChannelConfig || force_update) { mIs51Rear = false; if(MatchChannelMap(chanmap, X714Map)) mChannels = DevFmtX714; else if(MatchChannelMap(chanmap, X71Map)) mChannels = DevFmtX71; else if(MatchChannelMap(chanmap, X61Map)) mChannels = DevFmtX61; else if(MatchChannelMap(chanmap, X51Map)) mChannels = DevFmtX51; else if(MatchChannelMap(chanmap, X51RearMap)) { mChannels = DevFmtX51; mIs51Rear = true; } else if(MatchChannelMap(chanmap, QuadMap)) mChannels = DevFmtQuad; else if(MatchChannelMap(chanmap, StereoMap)) mChannels = DevFmtStereo; else mChannels = DevFmtMono; } TRACE(" {} position{} for {}{}", chanmap.size(), (chanmap.size()==1)?"":"s", DevFmtChannelsString(mChannels), mIs51Rear?"(rear)":""); } void DeviceNode::parseChannelCount(const spa_pod *value, bool force_update) noexcept { /* As a fallback with just a channel count, just assume mono or stereo. */ uint32_t choiceCount{}, choiceType{}; value = spa_pod_get_values(value, &choiceCount, &choiceType); if(choiceType != SPA_CHOICE_None || choiceCount != 1) { ERR(" Unexpected positions choice: type={}, count={}", choiceType, choiceCount); return; } const auto chancount = get_value(value); if(!chancount) return; if(mChannels == InvalidChannelConfig || force_update) { mIs51Rear = false; if(*chancount >= 2) mChannels = DevFmtStereo; else if(*chancount >= 1) mChannels = DevFmtMono; } TRACE(" {} channel{} for {}", *chancount, (*chancount==1)?"":"s", DevFmtChannelsString(mChannels)); } [[nodiscard]] constexpr auto GetMonitorPrefix() noexcept { return "Monitor of "sv; } [[nodiscard]] constexpr auto GetMonitorSuffix() noexcept { return ".monitor"sv; } [[nodiscard]] constexpr auto GetAudioSinkClassName() noexcept { return "Audio/Sink"sv; } [[nodiscard]] constexpr auto GetAudioSourceClassName() noexcept { return "Audio/Source"sv; } [[nodiscard]] constexpr auto GetAudioDuplexClassName() noexcept { return "Audio/Duplex"sv; } [[nodiscard]] constexpr auto GetAudioSourceVirtualClassName() noexcept { return "Audio/Source/Virtual"sv; } void NodeProxy::infoCallback(void*, const pw_node_info *info) noexcept { /* We only care about property changes here (media class, name/desc). * Format changes will automatically invoke the param callback. * * TODO: Can the media class or name/desc change without being removed and * readded? */ if((info->change_mask&PW_NODE_CHANGE_MASK_PROPS)) { /* Can this actually change? */ const char *media_class{spa_dict_lookup(info->props, PW_KEY_MEDIA_CLASS)}; if(!media_class) UNLIKELY return; const std::string_view className{media_class}; NodeType ntype{}; if(al::case_compare(className, GetAudioSinkClassName()) == 0) ntype = NodeType::Sink; else if(al::case_compare(className, GetAudioSourceClassName()) == 0 || al::case_compare(className, GetAudioSourceVirtualClassName()) == 0) ntype = NodeType::Source; else if(al::case_compare(className, GetAudioDuplexClassName()) == 0) ntype = NodeType::Duplex; else { TRACE("Dropping device node {} which became type \"{}\"", info->id, media_class); DeviceNode::Remove(info->id); return; } const char *devName{spa_dict_lookup(info->props, PW_KEY_NODE_NAME)}; const char *nodeName{spa_dict_lookup(info->props, PW_KEY_NODE_DESCRIPTION)}; if(!nodeName || !*nodeName) nodeName = spa_dict_lookup(info->props, PW_KEY_NODE_NICK); if(!nodeName || !*nodeName) nodeName = devName; uint64_t serial_id{info->id}; #ifdef PW_KEY_OBJECT_SERIAL if(const char *serial_str{spa_dict_lookup(info->props, PW_KEY_OBJECT_SERIAL)}) { errno = 0; char *serial_end{}; serial_id = std::strtoull(serial_str, &serial_end, 0); if(*serial_end != '\0' || errno == ERANGE) { ERR("Unexpected object serial: {}", serial_str); serial_id = info->id; } } #endif std::string name; if(nodeName && *nodeName) name = nodeName; else name = "PipeWire node #"+std::to_string(info->id); const char *form_factor{spa_dict_lookup(info->props, PW_KEY_DEVICE_FORM_FACTOR)}; TRACE("Got {} device \"{}\"{}{}{}", AsString(ntype), devName ? devName : "(nil)", form_factor?" (":"", form_factor?form_factor:"", form_factor?")":""); TRACE(" \"{}\" = ID {}", name, serial_id); DeviceNode &node = DeviceNode::Add(info->id); node.mSerial = serial_id; /* This method is called both to notify about a new sink/source node, * and update properties for the node. It's unclear what properties can * change for an existing node without being removed first, so err on * the side of caution: send a DeviceRemoved event if it had a name * that's being changed, and send a DeviceAdded event when the name * differs or it didn't have one. * * The DeviceRemoved event needs to be called before the potentially * new NodeType is set, so the removal event is called for the previous * device type, while the DeviceAdded event needs to be called after. * * This is overkill if the node type, name, and devname can't change. */ bool notifyAdd{false}; if(node.mName != name) { if(gEventHandler.initIsDone(std::memory_order_relaxed)) { if(!node.mName.empty()) { const std::string msg{"Device removed: "+node.mName}; node.callEvent(alc::EventType::DeviceRemoved, msg); } notifyAdd = true; } node.mName = std::move(name); } node.mDevName = devName ? devName : ""; node.mType = ntype; node.mIsHeadphones = form_factor && (al::case_compare(form_factor, "headphones"sv) == 0 || al::case_compare(form_factor, "headset"sv) == 0); if(notifyAdd) { const std::string msg{"Device added: "+node.mName}; node.callEvent(alc::EventType::DeviceAdded, msg); } } } void NodeProxy::paramCallback(int, uint32_t id, uint32_t, uint32_t, const spa_pod *param) const noexcept { if(id == SPA_PARAM_EnumFormat || id == SPA_PARAM_Format) { DeviceNode *node{DeviceNode::Find(mId)}; if(!node) UNLIKELY return; TRACE("Device ID {} {} format{}:", node->mSerial, (id == SPA_PARAM_EnumFormat) ? "available" : "current", (id == SPA_PARAM_EnumFormat) ? "s" : ""); const bool force_update{id == SPA_PARAM_Format}; if(const spa_pod_prop *prop{spa_pod_find_prop(param, nullptr, SPA_FORMAT_AUDIO_rate)}) node->parseSampleRate(&prop->value, force_update); if(const spa_pod_prop *prop{spa_pod_find_prop(param, nullptr, SPA_FORMAT_AUDIO_position)}) node->parsePositions(&prop->value, force_update); else { prop = spa_pod_find_prop(param, nullptr, SPA_FORMAT_AUDIO_channels); if(prop) node->parseChannelCount(&prop->value, force_update); } } } auto MetadataProxy::propertyCallback(void*, uint32_t id, const char *key, const char *type, const char *value) noexcept -> int { if(id != PW_ID_CORE) return 0; bool isCapture{}; if("default.audio.sink"sv == key) isCapture = false; else if("default.audio.source"sv == key) isCapture = true; else return 0; if(!type) { TRACE("Default {} device cleared", isCapture ? "capture" : "playback"); if(!isCapture) DefaultSinkDevice.clear(); else DefaultSourceDevice.clear(); return 0; } if("Spa:String:JSON"sv != type) { ERR("Unexpected {} property type: {}", key, type); return 0; } std::array it{}; spa_json_init(it.data(), value, strlen(value)); if(spa_json_enter_object(&std::get<0>(it), &std::get<1>(it)) <= 0) return 0; auto get_json_string = [](spa_json *iter) { std::optional str; const char *val{}; int len{spa_json_next(iter, &val)}; if(len <= 0) return str; str.emplace(static_cast(len), '\0'); if(spa_json_parse_string(val, len, str->data()) <= 0) str.reset(); else while(!str->empty() && str->back() == '\0') str->pop_back(); return str; }; while(auto propKey = get_json_string(&std::get<1>(it))) { if("name"sv == *propKey) { auto propValue = get_json_string(&std::get<1>(it)); if(!propValue) break; TRACE("Got default {} device \"{}\"", isCapture ? "capture" : "playback", *propValue); if(!isCapture && DefaultSinkDevice != *propValue) { if(gEventHandler.mInitDone.load(std::memory_order_relaxed)) { auto entry = DeviceNode::FindByDevName(*propValue); const std::string message{"Default playback device changed: "+ (entry ? entry->mName : std::string{})}; alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Playback, message); } DefaultSinkDevice = std::move(*propValue); } else if(isCapture && DefaultSourceDevice != *propValue) { if(gEventHandler.mInitDone.load(std::memory_order_relaxed)) { auto entry = DeviceNode::FindByDevName(*propValue); const std::string message{"Default capture device changed: "+ (entry ? entry->mName : std::string{})}; alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Capture, message); } DefaultSourceDevice = std::move(*propValue); } } else { const char *v{}; if(spa_json_next(&std::get<1>(it), &v) <= 0) break; } } return 0; } bool EventManager::init() { mLoop = ThreadMainloop::Create("PWEventThread"); if(!mLoop) { ERR("Failed to create PipeWire event thread loop (errno: {})", errno); return false; } mContext = mLoop.newContext(); if(!mContext) { ERR("Failed to create PipeWire event context (errno: {})", errno); return false; } mCore = PwCorePtr{pw_context_connect(mContext.get(), nullptr, 0)}; if(!mCore) { ERR("Failed to connect PipeWire event context (errno: {})", errno); return false; } mRegistry = PwRegistryPtr{pw_core_get_registry(mCore.get(), PW_VERSION_REGISTRY, 0)}; if(!mRegistry) { ERR("Failed to get PipeWire event registry (errno: {})", errno); return false; } static constexpr pw_core_events coreEvents{CreateCoreEvents()}; static constexpr pw_registry_events registryEvents{CreateRegistryEvents()}; ppw_core_add_listener(mCore.get(), &mCoreListener, &coreEvents, this); ppw_registry_add_listener(mRegistry.get(), &mRegistryListener, ®istryEvents, this); /* Set an initial sequence ID for initialization, to trigger after the * registry is first populated. */ mInitSeq = ppw_core_sync(mCore.get(), PW_ID_CORE, 0); if(int res{mLoop.start()}) { ERR("Failed to start PipeWire event thread loop (res: {})", res); return false; } return true; } void EventManager::kill() { if(mLoop) mLoop.stop(); mDefaultMetadata.reset(); mNodeList.clear(); mRegistry = nullptr; mCore = nullptr; mContext = nullptr; mLoop = nullptr; } void EventManager::addCallback(uint32_t id, uint32_t, const char *type, uint32_t version, const spa_dict *props) noexcept { /* We're only interested in interface nodes. */ if(std::strcmp(type, PW_TYPE_INTERFACE_Node) == 0) { const char *media_class{spa_dict_lookup(props, PW_KEY_MEDIA_CLASS)}; if(!media_class) return; const std::string_view className{media_class}; /* Specifically, audio sinks and sources (and duplexes). */ const bool isGood{al::case_compare(className, GetAudioSinkClassName()) == 0 || al::case_compare(className, GetAudioSourceClassName()) == 0 || al::case_compare(className, GetAudioSourceVirtualClassName()) == 0 || al::case_compare(className, GetAudioDuplexClassName()) == 0}; if(!isGood) { if(!al::contains(className, "/Video"sv) && !al::starts_with(className, "Stream/"sv)) TRACE("Ignoring node class {}", media_class); return; } /* Create the proxy object. */ auto node = PwNodePtr{static_cast(pw_registry_bind(mRegistry.get(), id, type, version, 0))}; if(!node) { ERR("Failed to create node proxy object (errno: {})", errno); return; } /* Initialize the NodeProxy to hold the node object, add it to the * active node list, and update the sync point. */ mNodeList.emplace_back(std::make_unique(id, std::move(node))); syncInit(); /* Signal any waiters that we have found a source or sink for audio * support. */ if(!mHasAudio.exchange(true, std::memory_order_acq_rel)) mLoop.signal(false); } else if(std::strcmp(type, PW_TYPE_INTERFACE_Metadata) == 0) { const char *data_class{spa_dict_lookup(props, PW_KEY_METADATA_NAME)}; if(!data_class) return; if("default"sv != data_class) { TRACE("Ignoring metadata \"{}\"", data_class); return; } if(mDefaultMetadata) { ERR("Duplicate default metadata"); return; } auto mdata = PwMetadataPtr{static_cast(pw_registry_bind(mRegistry.get(), id, type, version, 0))}; if(!mdata) { ERR("Failed to create metadata proxy object (errno: {})", errno); return; } mDefaultMetadata.emplace(id, std::move(mdata)); syncInit(); } } void EventManager::removeCallback(uint32_t id) noexcept { DeviceNode::Remove(id); auto clear_node = [id](std::unique_ptr &node) noexcept { return node->mId == id; }; auto node_end = std::remove_if(mNodeList.begin(), mNodeList.end(), clear_node); mNodeList.erase(node_end, mNodeList.end()); if(mDefaultMetadata && mDefaultMetadata->mId == id) mDefaultMetadata.reset(); } void EventManager::coreCallback(uint32_t id, int seq) noexcept { if(id == PW_ID_CORE && seq == mInitSeq) { /* Initialization done. Remove this callback and signal anyone that may * be waiting. */ spa_hook_remove(&mCoreListener); mInitDone.store(true); mLoop.signal(false); } } enum use_f32p_e : bool { UseDevType=false, ForceF32Planar=true }; spa_audio_info_raw make_spa_info(DeviceBase *device, bool is51rear, use_f32p_e use_f32p) { spa_audio_info_raw info{}; if(use_f32p) { device->FmtType = DevFmtFloat; info.format = SPA_AUDIO_FORMAT_F32P; } else switch(device->FmtType) { case DevFmtByte: info.format = SPA_AUDIO_FORMAT_S8; break; case DevFmtUByte: info.format = SPA_AUDIO_FORMAT_U8; break; case DevFmtShort: info.format = SPA_AUDIO_FORMAT_S16; break; case DevFmtUShort: info.format = SPA_AUDIO_FORMAT_U16; break; case DevFmtInt: info.format = SPA_AUDIO_FORMAT_S32; break; case DevFmtUInt: info.format = SPA_AUDIO_FORMAT_U32; break; case DevFmtFloat: info.format = SPA_AUDIO_FORMAT_F32; break; } info.rate = device->mSampleRate; al::span map{}; switch(device->FmtChans) { case DevFmtMono: map = MonoMap; break; case DevFmtStereo: map = StereoMap; break; case DevFmtQuad: map = QuadMap; break; case DevFmtX51: if(is51rear) map = X51RearMap; else map = X51Map; break; case DevFmtX61: map = X61Map; break; case DevFmtX71: map = X71Map; break; case DevFmtX714: map = X714Map; break; case DevFmtX3D71: map = X71Map; break; case DevFmtX7144: case DevFmtAmbi3D: info.flags |= SPA_AUDIO_FLAG_UNPOSITIONED; info.channels = device->channelsFromFmt(); break; } if(!map.empty()) { info.channels = static_cast(map.size()); std::copy(map.begin(), map.end(), std::begin(info.position)); } return info; } class PipeWirePlayback final : public BackendBase { void stateChangedCallback(pw_stream_state old, pw_stream_state state, const char *error) noexcept; void ioChangedCallback(uint32_t id, void *area, uint32_t size) noexcept; void outputCallback() noexcept; void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; ClockLatency getClockLatency() override; uint64_t mTargetId{PwIdAny}; nanoseconds mTimeBase{0}; ThreadMainloop mLoop; PwContextPtr mContext; PwCorePtr mCore; PwStreamPtr mStream; spa_hook mStreamListener{}; spa_io_rate_match *mRateMatch{}; std::vector mChannelPtrs; static constexpr pw_stream_events CreateEvents() { pw_stream_events ret{}; ret.version = PW_VERSION_STREAM_EVENTS; ret.state_changed = [](void *data, pw_stream_state old, pw_stream_state state, const char *error) noexcept { static_cast(data)->stateChangedCallback(old, state, error); }; ret.io_changed = [](void *data, uint32_t id, void *area, uint32_t size) noexcept { static_cast(data)->ioChangedCallback(id, area, size); }; ret.process = [](void *data) noexcept { static_cast(data)->outputCallback(); }; return ret; } public: explicit PipeWirePlayback(DeviceBase *device) noexcept : BackendBase{device} { } ~PipeWirePlayback() final { /* Stop the mainloop so the stream can be properly destroyed. */ if(mLoop) mLoop.stop(); } }; void PipeWirePlayback::stateChangedCallback(pw_stream_state, pw_stream_state, const char*) noexcept { mLoop.signal(false); } void PipeWirePlayback::ioChangedCallback(uint32_t id, void *area, uint32_t size) noexcept { switch(id) { case SPA_IO_RateMatch: if(size >= sizeof(spa_io_rate_match)) mRateMatch = static_cast(area); else mRateMatch = nullptr; break; } } void PipeWirePlayback::outputCallback() noexcept { pw_buffer *pw_buf{pw_stream_dequeue_buffer(mStream.get())}; if(!pw_buf) UNLIKELY return; const al::span datas{pw_buf->buffer->datas, std::min(mChannelPtrs.size(), size_t{pw_buf->buffer->n_datas})}; #if PW_CHECK_VERSION(0,3,49) /* In 0.3.49, pw_buffer::requested specifies the number of samples needed * by the resampler/graph for this audio update. */ uint length{static_cast(pw_buf->requested)}; #else /* In 0.3.48 and earlier, spa_io_rate_match::size apparently has the number * of samples per update. */ uint length{mRateMatch ? mRateMatch->size : 0u}; #endif /* If no length is specified, use the device's update size as a fallback. */ if(!length) UNLIKELY length = mDevice->mUpdateSize; /* For planar formats, each datas[] seems to contain one channel, so store * the pointers in an array. Limit the render length in case the available * buffer length in any one channel is smaller than we wanted (shouldn't * be, but just in case). */ auto chanptr_end = mChannelPtrs.begin(); for(const auto &data : datas) { length = std::min(length, data.maxsize/uint{sizeof(float)}); *chanptr_end = data.data; ++chanptr_end; data.chunk->offset = 0; data.chunk->stride = sizeof(float); data.chunk->size = length * sizeof(float); } mDevice->renderSamples(mChannelPtrs, length); pw_buf->size = length; pw_stream_queue_buffer(mStream.get(), pw_buf); } void PipeWirePlayback::open(std::string_view name) { static std::atomic OpenCount{0}; uint64_t targetid{PwIdAny}; std::string devname{}; gEventHandler.waitForInit(); if(name.empty()) { EventWatcherLockGuard evtlock{gEventHandler}; auto&& devlist = DeviceNode::GetList(); auto match = devlist.cend(); if(!DefaultSinkDevice.empty()) { auto match_default = [](const DeviceNode &n) -> bool { return n.mDevName == DefaultSinkDevice; }; match = std::find_if(devlist.cbegin(), devlist.cend(), match_default); } if(match == devlist.cend()) { auto match_playback = [](const DeviceNode &n) -> bool { return n.mType != NodeType::Source; }; match = std::find_if(devlist.cbegin(), devlist.cend(), match_playback); if(match == devlist.cend()) throw al::backend_exception{al::backend_error::NoDevice, "No PipeWire playback device found"}; } targetid = match->mSerial; devname = match->mName; } else { EventWatcherLockGuard evtlock{gEventHandler}; auto&& devlist = DeviceNode::GetList(); auto match_name = [name](const DeviceNode &n) -> bool { return n.mType != NodeType::Source && (n.mName == name || n.mDevName == name); }; auto match = std::find_if(devlist.cbegin(), devlist.cend(), match_name); if(match == devlist.cend()) throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found", name}; targetid = match->mSerial; devname = match->mName; } if(!mLoop) { const uint count{OpenCount.fetch_add(1, std::memory_order_relaxed)}; const std::string thread_name{"ALSoftP" + std::to_string(count)}; mLoop = ThreadMainloop::Create(thread_name.c_str()); if(!mLoop) throw al::backend_exception{al::backend_error::DeviceError, "Failed to create PipeWire mainloop (errno: {})", errno}; if(int res{mLoop.start()}) throw al::backend_exception{al::backend_error::DeviceError, "Failed to start PipeWire mainloop (res: {})", res}; } MainloopUniqueLock mlock{mLoop}; if(!mContext) { pw_properties *cprops{pw_properties_new(PW_KEY_CONFIG_NAME, "client-rt.conf", nullptr)}; mContext = mLoop.newContext(cprops); if(!mContext) throw al::backend_exception{al::backend_error::DeviceError, "Failed to create PipeWire event context (errno: {})\n", errno}; } if(!mCore) { mCore = PwCorePtr{pw_context_connect(mContext.get(), nullptr, 0)}; if(!mCore) throw al::backend_exception{al::backend_error::DeviceError, "Failed to connect PipeWire event context (errno: {})\n", errno}; } mlock.unlock(); /* TODO: Ensure the target ID is still valid/usable and accepts streams. */ mTargetId = targetid; if(!devname.empty()) mDeviceName = std::move(devname); else mDeviceName = "PipeWire Output"sv; } bool PipeWirePlayback::reset() { if(mStream) { MainloopLockGuard looplock{mLoop}; mStream = nullptr; } mStreamListener = {}; mRateMatch = nullptr; mTimeBase = mDevice->getClockTime(); /* If connecting to a specific device, update various device parameters to * match its format. */ bool is51rear{false}; mDevice->Flags.reset(DirectEar); if(mTargetId != PwIdAny) { EventWatcherLockGuard evtlock{gEventHandler}; auto&& devlist = DeviceNode::GetList(); auto match_id = [targetid=mTargetId](const DeviceNode &n) -> bool { return targetid == n.mSerial; }; auto match = std::find_if(devlist.cbegin(), devlist.cend(), match_id); if(match != devlist.cend()) { if(!mDevice->Flags.test(FrequencyRequest) && match->mSampleRate > 0) { /* Scale the update size if the sample rate changes. */ const double scale{static_cast(match->mSampleRate) / mDevice->mSampleRate}; const double updatesize{std::round(mDevice->mUpdateSize * scale)}; const double buffersize{std::round(mDevice->mBufferSize * scale)}; mDevice->mSampleRate = match->mSampleRate; mDevice->mUpdateSize = static_cast(std::clamp(updatesize, 64.0, 8192.0)); mDevice->mBufferSize = static_cast(std::max(buffersize, 128.0)); } if(!mDevice->Flags.test(ChannelsRequest) && match->mChannels != InvalidChannelConfig) mDevice->FmtChans = match->mChannels; if(match->mChannels == DevFmtStereo && match->mIsHeadphones) mDevice->Flags.set(DirectEar); is51rear = match->mIs51Rear; } } /* Force planar 32-bit float output for playback. This is what PipeWire * handles internally, and it's easier for us too. */ auto info = spa_audio_info_raw{make_spa_info(mDevice, is51rear, ForceF32Planar)}; auto b = PodDynamicBuilder{}; auto params = as_const_ptr(spa_format_audio_raw_build(b.get(), SPA_PARAM_EnumFormat, &info)); if(!params) throw al::backend_exception{al::backend_error::DeviceError, "Failed to set PipeWire audio format parameters"}; /* TODO: Which properties are actually needed here? Any others that could * be useful? */ auto&& binary = GetProcBinary(); const char *appname{!binary.fname.empty() ? binary.fname.c_str() : "OpenAL Soft"}; pw_properties *props{pw_properties_new(PW_KEY_NODE_NAME, appname, PW_KEY_NODE_DESCRIPTION, appname, PW_KEY_MEDIA_TYPE, "Audio", PW_KEY_MEDIA_CATEGORY, "Playback", PW_KEY_MEDIA_ROLE, "Game", PW_KEY_NODE_ALWAYS_PROCESS, "true", nullptr)}; if(!props) throw al::backend_exception{al::backend_error::DeviceError, "Failed to create PipeWire stream properties (errno: {})", errno}; pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%u/%u", mDevice->mUpdateSize, mDevice->mSampleRate); pw_properties_setf(props, PW_KEY_NODE_RATE, "1/%u", mDevice->mSampleRate); #ifdef PW_KEY_TARGET_OBJECT pw_properties_setf(props, PW_KEY_TARGET_OBJECT, "%" PRIu64, mTargetId); #else pw_properties_setf(props, PW_KEY_NODE_TARGET, "%" PRIu64, mTargetId); #endif MainloopUniqueLock plock{mLoop}; /* The stream takes overship of 'props', even in the case of failure. */ mStream = PwStreamPtr{pw_stream_new(mCore.get(), "Playback Stream", props)}; if(!mStream) throw al::backend_exception{al::backend_error::NoDevice, "Failed to create PipeWire stream (errno: {})", errno}; static constexpr pw_stream_events streamEvents{CreateEvents()}; pw_stream_add_listener(mStream.get(), &mStreamListener, &streamEvents, this); pw_stream_flags flags{PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_INACTIVE | PW_STREAM_FLAG_MAP_BUFFERS}; if(GetConfigValueBool(mDevice->mDeviceName, "pipewire", "rt-mix", false)) flags |= PW_STREAM_FLAG_RT_PROCESS; if(int res{pw_stream_connect(mStream.get(), PW_DIRECTION_OUTPUT, PwIdAny, flags, ¶ms, 1)}) throw al::backend_exception{al::backend_error::DeviceError, "Error connecting PipeWire stream (res: {})", res}; /* Wait for the stream to become paused (ready to start streaming). */ plock.wait([stream=mStream.get()]() { const char *error{}; pw_stream_state state{pw_stream_get_state(stream, &error)}; if(state == PW_STREAM_STATE_ERROR) throw al::backend_exception{al::backend_error::DeviceError, "Error connecting PipeWire stream: \"{}\"", error}; return state == PW_STREAM_STATE_PAUSED; }); /* TODO: Update mDevice->mUpdateSize with the stream's quantum, and * mDevice->mBufferSize with the total known buffering delay from the head * of this playback stream to the tail of the device output. * * This info is apparently not available until after the stream starts. */ plock.unlock(); mChannelPtrs.resize(mDevice->channelsFromFmt()); setDefaultWFXChannelOrder(); return true; } void PipeWirePlayback::start() { MainloopUniqueLock plock{mLoop}; if(int res{pw_stream_set_active(mStream.get(), true)}) throw al::backend_exception{al::backend_error::DeviceError, "Failed to start PipeWire stream (res: {})", res}; /* Wait for the stream to start playing (would be nice to not, but we need * the actual update size which is only available after starting). */ plock.wait([stream=mStream.get()]() { const char *error{}; pw_stream_state state{pw_stream_get_state(stream, &error)}; if(state == PW_STREAM_STATE_ERROR) throw al::backend_exception{al::backend_error::DeviceError, "PipeWire stream error: {}", error ? error : "(unknown)"}; return state == PW_STREAM_STATE_STREAMING; }); /* HACK: Try to work out the update size and total buffering size. There's * no actual query for this, so we have to work it out from the stream time * info, and assume it stays accurate with future updates. The stream time * info may also not be available right away, so we have to wait until it * is (up to about 2 seconds). */ int wait_count{100}; do { pw_time ptime{}; if(int res{pw_stream_get_time_n(mStream.get(), &ptime, sizeof(ptime))}) { ERR("Failed to get PipeWire stream time (res: {})", res); break; } /* The rate match size is the update size for each buffer. */ const uint updatesize{mRateMatch ? mRateMatch->size : 0u}; #if PW_CHECK_VERSION(0,3,50) /* Assume ptime.avail_buffers+ptime.queued_buffers is the target buffer * queue size. */ if(ptime.rate.denom > 0 && (ptime.avail_buffers || ptime.queued_buffers) && updatesize > 0) { const uint totalbuffers{ptime.avail_buffers + ptime.queued_buffers}; /* Ensure the delay is in sample frames. */ const uint64_t delay{static_cast(ptime.delay) * mDevice->mSampleRate * ptime.rate.num / ptime.rate.denom}; mDevice->mUpdateSize = updatesize; mDevice->mBufferSize = static_cast(ptime.buffered + delay + uint64_t{totalbuffers}*updatesize); break; } #else /* Prior to 0.3.50, we can only measure the delay with the update size, * assuming one buffer and no resample buffering. */ if(ptime.rate.denom > 0 && updatesize > 0) { /* Ensure the delay is in sample frames. */ const uint64_t delay{static_cast(ptime.delay) * mDevice->mSampleRate * ptime.rate.num / ptime.rate.denom}; mDevice->mUpdateSize = updatesize; mDevice->mBufferSize = static_cast(delay + updatesize); break; } #endif if(!--wait_count) { ERR("Timeout getting PipeWire stream buffering info"); break; } plock.unlock(); std::this_thread::sleep_for(milliseconds{20}); plock.lock(); } while(pw_stream_get_state(mStream.get(), nullptr) == PW_STREAM_STATE_STREAMING); } void PipeWirePlayback::stop() { MainloopUniqueLock plock{mLoop}; if(int res{pw_stream_set_active(mStream.get(), false)}) ERR("Failed to stop PipeWire stream (res: {})", res); /* Wait for the stream to stop playing. */ plock.wait([stream=mStream.get()]() { return pw_stream_get_state(stream, nullptr) != PW_STREAM_STATE_STREAMING; }); } ClockLatency PipeWirePlayback::getClockLatency() { /* Given a real-time low-latency output, this is rather complicated to get * accurate timing. So, here we go. */ /* First, get the stream time info (tick delay, ticks played, and the * CLOCK_MONOTONIC time closest to when that last tick was played). */ pw_time ptime{}; if(mStream) { MainloopLockGuard looplock{mLoop}; if(int res{pw_stream_get_time_n(mStream.get(), &ptime, sizeof(ptime))}) ERR("Failed to get PipeWire stream time (res: {})", res); } /* Now get the mixer time and the CLOCK_MONOTONIC time atomically (i.e. the * monotonic clock closest to 'now', and the last mixer time at 'now'). */ nanoseconds mixtime{}; timespec tspec{}; uint refcount; do { refcount = mDevice->waitForMix(); mixtime = mDevice->getClockTime(); clock_gettime(CLOCK_MONOTONIC, &tspec); std::atomic_thread_fence(std::memory_order_acquire); } while(refcount != mDevice->mMixCount.load(std::memory_order_relaxed)); /* Convert the monotonic clock, stream ticks, and stream delay to * nanoseconds. */ nanoseconds monoclock{seconds{tspec.tv_sec} + nanoseconds{tspec.tv_nsec}}; nanoseconds curtic{}, delay{}; if(ptime.rate.denom < 1) UNLIKELY { /* If there's no stream rate, the stream hasn't had a chance to get * going and return time info yet. Just use dummy values. */ ptime.now = monoclock.count(); curtic = mixtime; delay = nanoseconds{seconds{mDevice->mBufferSize}} / mDevice->mSampleRate; } else { /* The stream gets recreated with each reset, so include the time that * had already passed with previous streams. */ curtic = mTimeBase; /* More safely scale the ticks to avoid overflowing the pre-division * temporary as it gets larger. */ curtic += seconds{ptime.ticks / ptime.rate.denom} * ptime.rate.num; curtic += nanoseconds{seconds{ptime.ticks%ptime.rate.denom} * ptime.rate.num} / ptime.rate.denom; /* The delay should be small enough to not worry about overflow. */ delay = nanoseconds{seconds{ptime.delay} * ptime.rate.num} / ptime.rate.denom; } /* If the mixer time is ahead of the stream time, there's that much more * delay relative to the stream delay. */ if(mixtime > curtic) delay += mixtime - curtic; /* Reduce the delay according to how much time has passed since the known * stream time. This isn't 100% accurate since the system monotonic clock * doesn't tick at the exact same rate as the audio device, but it should * be good enough with ptime.now being constantly updated every few * milliseconds with ptime.ticks. */ delay -= monoclock - nanoseconds{ptime.now}; /* Return the mixer time and delay. Clamp the delay to no less than 0, * in case timer drift got that severe. */ ClockLatency ret{}; ret.ClockTime = mixtime; ret.Latency = std::max(delay, nanoseconds{}); return ret; } class PipeWireCapture final : public BackendBase { void stateChangedCallback(pw_stream_state old, pw_stream_state state, const char *error) noexcept; void inputCallback() noexcept; void open(std::string_view name) override; void start() override; void stop() override; void captureSamples(std::byte *buffer, uint samples) override; uint availableSamples() override; uint64_t mTargetId{PwIdAny}; ThreadMainloop mLoop; PwContextPtr mContext; PwCorePtr mCore; PwStreamPtr mStream; spa_hook mStreamListener{}; RingBufferPtr mRing; static constexpr pw_stream_events CreateEvents() { pw_stream_events ret{}; ret.version = PW_VERSION_STREAM_EVENTS; ret.state_changed = [](void *data, pw_stream_state old, pw_stream_state state, const char *error) noexcept { static_cast(data)->stateChangedCallback(old, state, error); }; ret.process = [](void *data) noexcept { static_cast(data)->inputCallback(); }; return ret; } public: explicit PipeWireCapture(DeviceBase *device) noexcept : BackendBase{device} { } ~PipeWireCapture() final { if(mLoop) mLoop.stop(); } }; void PipeWireCapture::stateChangedCallback(pw_stream_state, pw_stream_state, const char*) noexcept { mLoop.signal(false); } void PipeWireCapture::inputCallback() noexcept { pw_buffer *pw_buf{pw_stream_dequeue_buffer(mStream.get())}; if(!pw_buf) UNLIKELY return; spa_data *bufdata{pw_buf->buffer->datas}; const uint offset{bufdata->chunk->offset % bufdata->maxsize}; const auto input = al::span{static_cast(bufdata->data), bufdata->maxsize} .subspan(offset, std::min(bufdata->chunk->size, bufdata->maxsize - offset)); std::ignore = mRing->write(input.data(), input.size() / mRing->getElemSize()); pw_stream_queue_buffer(mStream.get(), pw_buf); } void PipeWireCapture::open(std::string_view name) { static std::atomic OpenCount{0}; uint64_t targetid{PwIdAny}; std::string devname{}; gEventHandler.waitForInit(); if(name.empty()) { EventWatcherLockGuard evtlock{gEventHandler}; auto&& devlist = DeviceNode::GetList(); auto match = devlist.cend(); if(!DefaultSourceDevice.empty()) { auto match_default = [](const DeviceNode &n) -> bool { return n.mDevName == DefaultSourceDevice; }; match = std::find_if(devlist.cbegin(), devlist.cend(), match_default); } if(match == devlist.cend()) { auto match_capture = [](const DeviceNode &n) -> bool { return n.mType != NodeType::Sink; }; match = std::find_if(devlist.cbegin(), devlist.cend(), match_capture); } if(match == devlist.cend()) { match = devlist.cbegin(); if(match == devlist.cend()) throw al::backend_exception{al::backend_error::NoDevice, "No PipeWire capture device found"}; } targetid = match->mSerial; if(match->mType != NodeType::Sink) devname = match->mName; else devname = std::string{GetMonitorPrefix()}+match->mName; } else { EventWatcherLockGuard evtlock{gEventHandler}; auto&& devlist = DeviceNode::GetList(); const std::string_view prefix{GetMonitorPrefix()}; const std::string_view suffix{GetMonitorSuffix()}; auto match_name = [name](const DeviceNode &n) -> bool { return n.mType != NodeType::Sink && n.mName == name; }; auto match = std::find_if(devlist.cbegin(), devlist.cend(), match_name); if(match == devlist.cend() && al::starts_with(name, prefix)) { const std::string_view sinkname{name.substr(prefix.length())}; auto match_sinkname = [sinkname](const DeviceNode &n) -> bool { return n.mType == NodeType::Sink && n.mName == sinkname; }; match = std::find_if(devlist.cbegin(), devlist.cend(), match_sinkname); } else if(match == devlist.cend() && al::ends_with(name, suffix)) { const std::string_view sinkname{name.substr(0, name.size()-suffix.size())}; auto match_sinkname = [sinkname](const DeviceNode &n) -> bool { return n.mType == NodeType::Sink && n.mDevName == sinkname; }; match = std::find_if(devlist.cbegin(), devlist.cend(), match_sinkname); } if(match == devlist.cend()) throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found", name}; targetid = match->mSerial; if(match->mType != NodeType::Sink) devname = match->mName; else devname = std::string{GetMonitorPrefix()}+match->mName; } if(!mLoop) { const uint count{OpenCount.fetch_add(1, std::memory_order_relaxed)}; const std::string thread_name{"ALSoftC" + std::to_string(count)}; mLoop = ThreadMainloop::Create(thread_name.c_str()); if(!mLoop) throw al::backend_exception{al::backend_error::DeviceError, "Failed to create PipeWire mainloop (errno: {})", errno}; if(int res{mLoop.start()}) throw al::backend_exception{al::backend_error::DeviceError, "Failed to start PipeWire mainloop (res: {})", res}; } MainloopUniqueLock mlock{mLoop}; if(!mContext) { pw_properties *cprops{pw_properties_new(PW_KEY_CONFIG_NAME, "client-rt.conf", nullptr)}; mContext = mLoop.newContext(cprops); if(!mContext) throw al::backend_exception{al::backend_error::DeviceError, "Failed to create PipeWire event context (errno: {})\n", errno}; } if(!mCore) { mCore = PwCorePtr{pw_context_connect(mContext.get(), nullptr, 0)}; if(!mCore) throw al::backend_exception{al::backend_error::DeviceError, "Failed to connect PipeWire event context (errno: {})\n", errno}; } mlock.unlock(); /* TODO: Ensure the target ID is still valid/usable and accepts streams. */ mTargetId = targetid; if(!devname.empty()) mDeviceName = std::move(devname); else mDeviceName = "PipeWire Input"sv; bool is51rear{false}; if(mTargetId != PwIdAny) { EventWatcherLockGuard evtlock{gEventHandler}; auto&& devlist = DeviceNode::GetList(); auto match_id = [targetid=mTargetId](const DeviceNode &n) -> bool { return targetid == n.mSerial; }; auto match = std::find_if(devlist.cbegin(), devlist.cend(), match_id); if(match != devlist.cend()) is51rear = match->mIs51Rear; } auto info = spa_audio_info_raw{make_spa_info(mDevice, is51rear, UseDevType)}; auto b = PodDynamicBuilder{}; auto params = as_const_ptr(spa_format_audio_raw_build(b.get(), SPA_PARAM_EnumFormat, &info)); if(!params) throw al::backend_exception{al::backend_error::DeviceError, "Failed to set PipeWire audio format parameters"}; auto&& binary = GetProcBinary(); const char *appname{!binary.fname.empty() ? binary.fname.c_str() : "OpenAL Soft"}; pw_properties *props{pw_properties_new( PW_KEY_NODE_NAME, appname, PW_KEY_NODE_DESCRIPTION, appname, PW_KEY_MEDIA_TYPE, "Audio", PW_KEY_MEDIA_CATEGORY, "Capture", PW_KEY_MEDIA_ROLE, "Game", PW_KEY_NODE_ALWAYS_PROCESS, "true", nullptr)}; if(!props) throw al::backend_exception{al::backend_error::DeviceError, "Failed to create PipeWire stream properties (errno: {})", errno}; /* We don't actually care what the latency/update size is, as long as it's * reasonable. Unfortunately, when unspecified PipeWire seems to default to * around 40ms, which isn't great. So request 20ms instead. */ pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%u/%u", (mDevice->mSampleRate+25) / 50, mDevice->mSampleRate); pw_properties_setf(props, PW_KEY_NODE_RATE, "1/%u", mDevice->mSampleRate); #ifdef PW_KEY_TARGET_OBJECT pw_properties_setf(props, PW_KEY_TARGET_OBJECT, "%" PRIu64, mTargetId); #else pw_properties_setf(props, PW_KEY_NODE_TARGET, "%" PRIu64, mTargetId); #endif MainloopUniqueLock plock{mLoop}; mStream = PwStreamPtr{pw_stream_new(mCore.get(), "Capture Stream", props)}; if(!mStream) throw al::backend_exception{al::backend_error::NoDevice, "Failed to create PipeWire stream (errno: {})", errno}; static constexpr pw_stream_events streamEvents{CreateEvents()}; pw_stream_add_listener(mStream.get(), &mStreamListener, &streamEvents, this); constexpr pw_stream_flags Flags{PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_INACTIVE | PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS}; if(int res{pw_stream_connect(mStream.get(), PW_DIRECTION_INPUT, PwIdAny, Flags, ¶ms, 1)}) throw al::backend_exception{al::backend_error::DeviceError, "Error connecting PipeWire stream (res: {})", res}; /* Wait for the stream to become paused (ready to start streaming). */ plock.wait([stream=mStream.get()]() { const char *error{}; pw_stream_state state{pw_stream_get_state(stream, &error)}; if(state == PW_STREAM_STATE_ERROR) throw al::backend_exception{al::backend_error::DeviceError, "Error connecting PipeWire stream: \"{}\"", error}; return state == PW_STREAM_STATE_PAUSED; }); plock.unlock(); setDefaultWFXChannelOrder(); /* Ensure at least a 100ms capture buffer. */ mRing = RingBuffer::Create(std::max(mDevice->mSampleRate/10u, mDevice->mBufferSize), mDevice->frameSizeFromFmt(), false); } void PipeWireCapture::start() { MainloopUniqueLock plock{mLoop}; if(int res{pw_stream_set_active(mStream.get(), true)}) throw al::backend_exception{al::backend_error::DeviceError, "Failed to start PipeWire stream (res: {})", res}; plock.wait([stream=mStream.get()]() { const char *error{}; pw_stream_state state{pw_stream_get_state(stream, &error)}; if(state == PW_STREAM_STATE_ERROR) throw al::backend_exception{al::backend_error::DeviceError, "PipeWire stream error: {}", error ? error : "(unknown)"}; return state == PW_STREAM_STATE_STREAMING; }); } void PipeWireCapture::stop() { MainloopUniqueLock plock{mLoop}; if(int res{pw_stream_set_active(mStream.get(), false)}) ERR("Failed to stop PipeWire stream (res: {})", res); plock.wait([stream=mStream.get()]() { return pw_stream_get_state(stream, nullptr) != PW_STREAM_STATE_STREAMING; }); } uint PipeWireCapture::availableSamples() { return static_cast(mRing->readSpace()); } void PipeWireCapture::captureSamples(std::byte *buffer, uint samples) { std::ignore = mRing->read(buffer, samples); } } // namespace bool PipeWireBackendFactory::init() { if(!pwire_load()) return false; const char *version{pw_get_library_version()}; if(!check_version(version)) { WARN("PipeWire version \"{}\" too old ({} or newer required)", version, pw_get_headers_version()); return false; } TRACE("Found PipeWire version \"{}\" ({} or newer)", version, pw_get_headers_version()); pw_init(nullptr, nullptr); if(!gEventHandler.init()) return false; if(!GetConfigValueBool({}, "pipewire", "assume-audio", false) && !gEventHandler.waitForAudio()) { gEventHandler.kill(); /* TODO: Temporary warning, until PipeWire gets a proper way to report * audio support. */ WARN("No audio support detected in PipeWire. See the PipeWire options in alsoftrc.sample if this is wrong."); return false; } return true; } bool PipeWireBackendFactory::querySupport(BackendType type) { return type == BackendType::Playback || type == BackendType::Capture; } auto PipeWireBackendFactory::enumerate(BackendType type) -> std::vector { std::vector outnames; gEventHandler.waitForInit(); EventWatcherLockGuard evtlock{gEventHandler}; auto&& devlist = DeviceNode::GetList(); auto match_defsink = [](const DeviceNode &n) -> bool { return n.mDevName == DefaultSinkDevice; }; auto match_defsource = [](const DeviceNode &n) -> bool { return n.mDevName == DefaultSourceDevice; }; auto sort_devnode = [](DeviceNode &lhs, DeviceNode &rhs) noexcept -> bool { return lhs.mId < rhs.mId; }; std::sort(devlist.begin(), devlist.end(), sort_devnode); auto defmatch = devlist.cbegin(); switch(type) { case BackendType::Playback: defmatch = std::find_if(defmatch, devlist.cend(), match_defsink); if(defmatch != devlist.cend()) outnames.emplace_back(defmatch->mName); for(auto iter = devlist.cbegin();iter != devlist.cend();++iter) { if(iter != defmatch && iter->mType != NodeType::Source) outnames.emplace_back(iter->mName); } break; case BackendType::Capture: outnames.reserve(devlist.size()); defmatch = std::find_if(defmatch, devlist.cend(), match_defsource); if(defmatch != devlist.cend()) { if(defmatch->mType == NodeType::Sink) outnames.emplace_back(std::string{GetMonitorPrefix()}+defmatch->mName); else outnames.emplace_back(defmatch->mName); } for(auto iter = devlist.cbegin();iter != devlist.cend();++iter) { if(iter != defmatch) { if(iter->mType == NodeType::Sink) outnames.emplace_back(std::string{GetMonitorPrefix()}+iter->mName); else outnames.emplace_back(iter->mName); } } break; } return outnames; } BackendPtr PipeWireBackendFactory::createBackend(DeviceBase *device, BackendType type) { if(type == BackendType::Playback) return BackendPtr{new PipeWirePlayback{device}}; if(type == BackendType::Capture) return BackendPtr{new PipeWireCapture{device}}; return nullptr; } BackendFactory &PipeWireBackendFactory::getFactory() { static PipeWireBackendFactory factory{}; return factory; } alc::EventSupport PipeWireBackendFactory::queryEventSupport(alc::EventType eventType, BackendType) { switch(eventType) { case alc::EventType::DefaultDeviceChanged: case alc::EventType::DeviceAdded: case alc::EventType::DeviceRemoved: return alc::EventSupport::FullSupport; case alc::EventType::Count: break; } return alc::EventSupport::NoSupport; } openal-soft-1.24.2/alc/backends/pipewire.h000066400000000000000000000012211474041540300203330ustar00rootroot00000000000000#ifndef BACKENDS_PIPEWIRE_H #define BACKENDS_PIPEWIRE_H #include #include #include "alc/events.h" #include "base.h" struct DeviceBase; struct PipeWireBackendFactory final : public BackendFactory { public: auto init() -> bool final; auto querySupport(BackendType type) -> bool final; auto queryEventSupport(alc::EventType eventType, BackendType type) -> alc::EventSupport final; auto enumerate(BackendType type) -> std::vector final; auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_PIPEWIRE_H */ openal-soft-1.24.2/alc/backends/portaudio.cpp000066400000000000000000000436341474041540300210660ustar00rootroot00000000000000/** * OpenAL cross platform audio library * Copyright (C) 1999-2007 by authors. * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * Or go to http://www.gnu.org/copyleft/lgpl.html */ #include "config.h" #include "portaudio.hpp" #include #include #include #include #include "alc/alconfig.h" #include "core/device.h" #include "core/logging.h" #include "dynload.h" #include "ringbuffer.h" #include namespace { #if HAVE_DYNLOAD void *pa_handle; #define MAKE_FUNC(x) decltype(x) * p##x MAKE_FUNC(Pa_Initialize); MAKE_FUNC(Pa_Terminate); MAKE_FUNC(Pa_GetErrorText); MAKE_FUNC(Pa_StartStream); MAKE_FUNC(Pa_StopStream); MAKE_FUNC(Pa_OpenStream); MAKE_FUNC(Pa_CloseStream); MAKE_FUNC(Pa_GetDeviceCount); MAKE_FUNC(Pa_GetDeviceInfo); MAKE_FUNC(Pa_GetDefaultOutputDevice); MAKE_FUNC(Pa_GetDefaultInputDevice); MAKE_FUNC(Pa_GetStreamInfo); #undef MAKE_FUNC #ifndef IN_IDE_PARSER #define Pa_Initialize pPa_Initialize #define Pa_Terminate pPa_Terminate #define Pa_GetErrorText pPa_GetErrorText #define Pa_StartStream pPa_StartStream #define Pa_StopStream pPa_StopStream #define Pa_OpenStream pPa_OpenStream #define Pa_CloseStream pPa_CloseStream #define Pa_GetDeviceCount pPa_GetDeviceCount #define Pa_GetDeviceInfo pPa_GetDeviceInfo #define Pa_GetDefaultOutputDevice pPa_GetDefaultOutputDevice #define Pa_GetDefaultInputDevice pPa_GetDefaultInputDevice #define Pa_GetStreamInfo pPa_GetStreamInfo #endif #endif struct DeviceEntry { std::string mName; uint mPlaybackChannels{}; uint mCaptureChannels{}; }; std::vector DeviceNames; void EnumerateDevices() { const auto devcount = Pa_GetDeviceCount(); if(devcount < 0) { ERR("Error getting device count: {}", Pa_GetErrorText(devcount)); return; } std::vector(static_cast(devcount)).swap(DeviceNames); PaDeviceIndex idx{0}; for(auto &entry : DeviceNames) { if(auto info = Pa_GetDeviceInfo(idx); info && info->name) { entry.mName = info->name; entry.mPlaybackChannels = static_cast(std::max(info->maxOutputChannels, 0)); entry.mCaptureChannels = static_cast(std::max(info->maxInputChannels, 0)); TRACE("Device {} \"{}\": {} playback, {} capture channels", idx, entry.mName, info->maxOutputChannels, info->maxInputChannels); } ++idx; } } struct StreamParamsExt : public PaStreamParameters { uint updateSize; }; struct PortPlayback final : public BackendBase { explicit PortPlayback(DeviceBase *device) noexcept : BackendBase{device} { } ~PortPlayback() override; int writeCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo, const PaStreamCallbackFlags statusFlags) noexcept; void createStream(PaDeviceIndex deviceid); void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; PaStream *mStream{nullptr}; StreamParamsExt mParams{}; PaDeviceIndex mDeviceIdx{-1}; }; PortPlayback::~PortPlayback() { PaError err{mStream ? Pa_CloseStream(mStream) : paNoError}; if(err != paNoError) ERR("Error closing stream: {}", Pa_GetErrorText(err)); mStream = nullptr; } int PortPlayback::writeCallback(const void*, void *outputBuffer, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo*, const PaStreamCallbackFlags) noexcept { mDevice->renderSamples(outputBuffer, static_cast(framesPerBuffer), static_cast(mParams.channelCount)); return 0; } void PortPlayback::createStream(PaDeviceIndex deviceid) { auto &devinfo = DeviceNames.at(static_cast(deviceid)); auto params = StreamParamsExt{}; params.device = deviceid; params.suggestedLatency = mDevice->mBufferSize / static_cast(mDevice->mSampleRate); params.hostApiSpecificStreamInfo = nullptr; params.channelCount = static_cast(std::min(devinfo.mPlaybackChannels, mDevice->channelsFromFmt())); switch(mDevice->FmtType) { case DevFmtByte: params.sampleFormat = paInt8; break; case DevFmtUByte: params.sampleFormat = paUInt8; break; case DevFmtUShort: [[fallthrough]]; case DevFmtShort: params.sampleFormat = paInt16; break; case DevFmtUInt: [[fallthrough]]; case DevFmtInt: params.sampleFormat = paInt32; break; case DevFmtFloat: params.sampleFormat = paFloat32; break; } params.updateSize = mDevice->mUpdateSize; auto srate = uint{mDevice->mSampleRate}; static constexpr auto writeCallback = [](const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo, const PaStreamCallbackFlags statusFlags, void *userData) noexcept { return static_cast(userData)->writeCallback(inputBuffer, outputBuffer, framesPerBuffer, timeInfo, statusFlags); }; while(PaError err{Pa_OpenStream(&mStream, nullptr, ¶ms, srate, params.updateSize, paNoFlag, writeCallback, this)}) { if(params.updateSize != DefaultUpdateSize) params.updateSize = DefaultUpdateSize; else if(srate != 48000u) srate = (srate != 44100u) ? 44100u : 48000u; else if(params.sampleFormat != paInt16) params.sampleFormat = paInt16; else if(params.channelCount != 2) params.channelCount = 2; else throw al::backend_exception{al::backend_error::NoDevice, "Failed to open stream: {}", Pa_GetErrorText(err)}; } mParams = params; } void PortPlayback::open(std::string_view name) { if(DeviceNames.empty()) EnumerateDevices(); PaDeviceIndex deviceid{-1}; if(name.empty()) { if(auto devidopt = ConfigValueInt({}, "port", "device")) deviceid = *devidopt; if(deviceid < 0 || static_cast(deviceid) >= DeviceNames.size()) deviceid = Pa_GetDefaultOutputDevice(); name = DeviceNames.at(static_cast(deviceid)).mName; } else { auto iter = std::find_if(DeviceNames.cbegin(), DeviceNames.cend(), [name](const DeviceEntry &entry) { return entry.mPlaybackChannels > 0 && name == entry.mName; }); if(iter == DeviceNames.cend()) throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found", name}; deviceid = static_cast(std::distance(DeviceNames.cbegin(), iter)); } createStream(deviceid); mDeviceIdx = deviceid; mDeviceName = name; } bool PortPlayback::reset() { if(mStream) { auto err = Pa_CloseStream(mStream); if(err != paNoError) ERR("Error closing stream: {}", Pa_GetErrorText(err)); mStream = nullptr; } createStream(mDeviceIdx); switch(mParams.sampleFormat) { case paFloat32: mDevice->FmtType = DevFmtFloat; break; case paInt32: mDevice->FmtType = DevFmtInt; break; case paInt16: mDevice->FmtType = DevFmtShort; break; case paInt8: mDevice->FmtType = DevFmtByte; break; case paUInt8: mDevice->FmtType = DevFmtUByte; break; default: ERR("Unexpected PortAudio sample format: {}", mParams.sampleFormat); throw al::backend_exception{al::backend_error::NoDevice, "Invalid sample format: {}", mParams.sampleFormat}; } if(mParams.channelCount != static_cast(mDevice->channelsFromFmt())) { if(mParams.channelCount >= 2) mDevice->FmtChans = DevFmtStereo; else if(mParams.channelCount == 1) mDevice->FmtChans = DevFmtMono; mDevice->mAmbiOrder = 0; } const PaStreamInfo *streamInfo{Pa_GetStreamInfo(mStream)}; mDevice->mSampleRate = static_cast(std::lround(streamInfo->sampleRate)); mDevice->mUpdateSize = mParams.updateSize; mDevice->mBufferSize = mDevice->mUpdateSize * 2; if(streamInfo->outputLatency > 0.0f) { const double sampleLatency{streamInfo->outputLatency * streamInfo->sampleRate}; TRACE("Reported stream latency: {:f} sec ({:f} samples)", streamInfo->outputLatency, sampleLatency); mDevice->mBufferSize = static_cast(std::clamp(sampleLatency, double(mDevice->mBufferSize), double{std::numeric_limits::max()})); } setDefaultChannelOrder(); return true; } void PortPlayback::start() { if(const PaError err{Pa_StartStream(mStream)}; err != paNoError) throw al::backend_exception{al::backend_error::DeviceError, "Failed to start playback: {}", Pa_GetErrorText(err)}; } void PortPlayback::stop() { if(PaError err{Pa_StopStream(mStream)}; err != paNoError) ERR("Error stopping stream: {}", Pa_GetErrorText(err)); } struct PortCapture final : public BackendBase { explicit PortCapture(DeviceBase *device) noexcept : BackendBase{device} { } ~PortCapture() override; int readCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo, const PaStreamCallbackFlags statusFlags) const noexcept; void open(std::string_view name) override; void start() override; void stop() override; void captureSamples(std::byte *buffer, uint samples) override; uint availableSamples() override; PaStream *mStream{nullptr}; PaStreamParameters mParams{}; RingBufferPtr mRing{nullptr}; }; PortCapture::~PortCapture() { PaError err{mStream ? Pa_CloseStream(mStream) : paNoError}; if(err != paNoError) ERR("Error closing stream: {}", Pa_GetErrorText(err)); mStream = nullptr; } int PortCapture::readCallback(const void *inputBuffer, void*, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo*, const PaStreamCallbackFlags) const noexcept { std::ignore = mRing->write(inputBuffer, framesPerBuffer); return 0; } void PortCapture::open(std::string_view name) { if(DeviceNames.empty()) EnumerateDevices(); int deviceid{}; if(name.empty()) { if(auto devidopt = ConfigValueInt({}, "port", "capture")) deviceid = *devidopt; if(deviceid < 0 || static_cast(deviceid) >= DeviceNames.size()) deviceid = Pa_GetDefaultInputDevice(); name = DeviceNames.at(static_cast(deviceid)).mName; } else { auto iter = std::find_if(DeviceNames.cbegin(), DeviceNames.cend(), [name](const DeviceEntry &entry) { return entry.mCaptureChannels > 0 && name == entry.mName; }); if(iter == DeviceNames.cend()) throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found", name}; deviceid = static_cast(std::distance(DeviceNames.cbegin(), iter)); } const uint samples{std::max(mDevice->mBufferSize, mDevice->mSampleRate/10u)}; const uint frame_size{mDevice->frameSizeFromFmt()}; mRing = RingBuffer::Create(samples, frame_size, false); mParams.device = deviceid; mParams.suggestedLatency = 0.0f; mParams.hostApiSpecificStreamInfo = nullptr; switch(mDevice->FmtType) { case DevFmtByte: mParams.sampleFormat = paInt8; break; case DevFmtUByte: mParams.sampleFormat = paUInt8; break; case DevFmtShort: mParams.sampleFormat = paInt16; break; case DevFmtInt: mParams.sampleFormat = paInt32; break; case DevFmtFloat: mParams.sampleFormat = paFloat32; break; case DevFmtUInt: case DevFmtUShort: throw al::backend_exception{al::backend_error::DeviceError, "{} samples not supported", DevFmtTypeString(mDevice->FmtType)}; } mParams.channelCount = static_cast(mDevice->channelsFromFmt()); static constexpr auto readCallback = [](const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo, const PaStreamCallbackFlags statusFlags, void *userData) noexcept { return static_cast(userData)->readCallback(inputBuffer, outputBuffer, framesPerBuffer, timeInfo, statusFlags); }; PaError err{Pa_OpenStream(&mStream, &mParams, nullptr, mDevice->mSampleRate, paFramesPerBufferUnspecified, paNoFlag, readCallback, this)}; if(err != paNoError) throw al::backend_exception{al::backend_error::NoDevice, "Failed to open stream: {}", Pa_GetErrorText(err)}; mDeviceName = name; } void PortCapture::start() { if(const PaError err{Pa_StartStream(mStream)}; err != paNoError) throw al::backend_exception{al::backend_error::DeviceError, "Failed to start recording: {}", Pa_GetErrorText(err)}; } void PortCapture::stop() { if(PaError err{Pa_StopStream(mStream)}; err != paNoError) ERR("Error stopping stream: {}", Pa_GetErrorText(err)); } uint PortCapture::availableSamples() { return static_cast(mRing->readSpace()); } void PortCapture::captureSamples(std::byte *buffer, uint samples) { std::ignore = mRing->read(buffer, samples); } } // namespace bool PortBackendFactory::init() { #if HAVE_DYNLOAD if(!pa_handle) { #ifdef _WIN32 # define PALIB "portaudio.dll" #elif defined(__APPLE__) && defined(__MACH__) # define PALIB "libportaudio.2.dylib" #elif defined(__OpenBSD__) # define PALIB "libportaudio.so" #else # define PALIB "libportaudio.so.2" #endif pa_handle = LoadLib(PALIB); if(!pa_handle) return false; #define LOAD_FUNC(f) do { \ p##f = reinterpret_cast(GetSymbol(pa_handle, #f)); \ if(p##f == nullptr) \ { \ CloseLib(pa_handle); \ pa_handle = nullptr; \ return false; \ } \ } while(0) LOAD_FUNC(Pa_Initialize); LOAD_FUNC(Pa_Terminate); LOAD_FUNC(Pa_GetErrorText); LOAD_FUNC(Pa_StartStream); LOAD_FUNC(Pa_StopStream); LOAD_FUNC(Pa_OpenStream); LOAD_FUNC(Pa_CloseStream); LOAD_FUNC(Pa_GetDeviceCount); LOAD_FUNC(Pa_GetDeviceInfo); LOAD_FUNC(Pa_GetDefaultOutputDevice); LOAD_FUNC(Pa_GetDefaultInputDevice); LOAD_FUNC(Pa_GetStreamInfo); #undef LOAD_FUNC const PaError err{Pa_Initialize()}; if(err != paNoError) { ERR("Pa_Initialize() returned an error: {}", Pa_GetErrorText(err)); CloseLib(pa_handle); pa_handle = nullptr; return false; } } #else const PaError err{Pa_Initialize()}; if(err != paNoError) { ERR("Pa_Initialize() returned an error: {}", Pa_GetErrorText(err)); return false; } #endif return true; } bool PortBackendFactory::querySupport(BackendType type) { return (type == BackendType::Playback || type == BackendType::Capture); } auto PortBackendFactory::enumerate(BackendType type) -> std::vector { std::vector devices; EnumerateDevices(); int defaultid{-1}; switch(type) { case BackendType::Playback: defaultid = Pa_GetDefaultOutputDevice(); if(auto devidopt = ConfigValueInt({}, "port", "device"); devidopt && *devidopt >= 0 && static_cast(*devidopt) < DeviceNames.size()) defaultid = *devidopt; for(size_t i{0};i < DeviceNames.size();++i) { if(DeviceNames[i].mPlaybackChannels > 0) { if(defaultid >= 0 && static_cast(defaultid) == i) devices.emplace(devices.cbegin(), DeviceNames[i].mName); else devices.emplace_back(DeviceNames[i].mName); } } break; case BackendType::Capture: defaultid = Pa_GetDefaultInputDevice(); if(auto devidopt = ConfigValueInt({}, "port", "capture"); devidopt && *devidopt >= 0 && static_cast(*devidopt) < DeviceNames.size()) defaultid = *devidopt; for(size_t i{0};i < DeviceNames.size();++i) { if(DeviceNames[i].mCaptureChannels > 0) { if(defaultid >= 0 && static_cast(defaultid) == i) devices.emplace(devices.cbegin(), DeviceNames[i].mName); else devices.emplace_back(DeviceNames[i].mName); } } break; } return devices; } BackendPtr PortBackendFactory::createBackend(DeviceBase *device, BackendType type) { if(type == BackendType::Playback) return BackendPtr{new PortPlayback{device}}; if(type == BackendType::Capture) return BackendPtr{new PortCapture{device}}; return nullptr; } BackendFactory &PortBackendFactory::getFactory() { static PortBackendFactory factory{}; return factory; } openal-soft-1.24.2/alc/backends/portaudio.hpp000066400000000000000000000007411474041540300210630ustar00rootroot00000000000000#ifndef BACKENDS_PORTAUDIO_HPP #define BACKENDS_PORTAUDIO_HPP #include "base.h" struct PortBackendFactory final : public BackendFactory { public: auto init() -> bool final; auto querySupport(BackendType type) -> bool final; auto enumerate(BackendType type) -> std::vector final; auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_PORTAUDIO_HPP */ openal-soft-1.24.2/alc/backends/pulseaudio.cpp000066400000000000000000001623621474041540300212320ustar00rootroot00000000000000/** * OpenAL cross platform audio library * Copyright (C) 2009 by Konstantinos Natsakis * Copyright (C) 2010 by Chris Robinson * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * Or go to http://www.gnu.org/copyleft/lgpl.html */ #include "config.h" #include "pulseaudio.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "alc/alconfig.h" #include "alnumeric.h" #include "alspan.h" #include "base.h" #include "core/devformat.h" #include "core/device.h" #include "core/logging.h" #include "dynload.h" #include "fmt/core.h" #include "opthelpers.h" #include "strutils.h" #include namespace { using namespace std::string_view_literals; using uint = unsigned int; #if HAVE_DYNLOAD #define PULSE_FUNCS(MAGIC) \ MAGIC(pa_context_new); \ MAGIC(pa_context_unref); \ MAGIC(pa_context_get_state); \ MAGIC(pa_context_disconnect); \ MAGIC(pa_context_set_state_callback); \ MAGIC(pa_context_set_subscribe_callback); \ MAGIC(pa_context_subscribe); \ MAGIC(pa_context_errno); \ MAGIC(pa_context_connect); \ MAGIC(pa_context_get_server_info); \ MAGIC(pa_context_get_sink_info_by_index); \ MAGIC(pa_context_get_sink_info_by_name); \ MAGIC(pa_context_get_sink_info_list); \ MAGIC(pa_context_get_source_info_by_index); \ MAGIC(pa_context_get_source_info_by_name); \ MAGIC(pa_context_get_source_info_list); \ MAGIC(pa_stream_new); \ MAGIC(pa_stream_unref); \ MAGIC(pa_stream_drop); \ MAGIC(pa_stream_get_state); \ MAGIC(pa_stream_peek); \ MAGIC(pa_stream_write); \ MAGIC(pa_stream_connect_record); \ MAGIC(pa_stream_connect_playback); \ MAGIC(pa_stream_readable_size); \ MAGIC(pa_stream_writable_size); \ MAGIC(pa_stream_is_corked); \ MAGIC(pa_stream_cork); \ MAGIC(pa_stream_is_suspended); \ MAGIC(pa_stream_get_device_name); \ MAGIC(pa_stream_get_latency); \ MAGIC(pa_stream_set_write_callback); \ MAGIC(pa_stream_set_buffer_attr); \ MAGIC(pa_stream_get_buffer_attr); \ MAGIC(pa_stream_get_sample_spec); \ MAGIC(pa_stream_get_time); \ MAGIC(pa_stream_set_read_callback); \ MAGIC(pa_stream_set_state_callback); \ MAGIC(pa_stream_set_moved_callback); \ MAGIC(pa_stream_set_underflow_callback); \ MAGIC(pa_stream_new_with_proplist); \ MAGIC(pa_stream_disconnect); \ MAGIC(pa_stream_set_buffer_attr_callback); \ MAGIC(pa_stream_begin_write); \ MAGIC(pa_threaded_mainloop_free); \ MAGIC(pa_threaded_mainloop_get_api); \ MAGIC(pa_threaded_mainloop_lock); \ MAGIC(pa_threaded_mainloop_new); \ MAGIC(pa_threaded_mainloop_signal); \ MAGIC(pa_threaded_mainloop_start); \ MAGIC(pa_threaded_mainloop_stop); \ MAGIC(pa_threaded_mainloop_unlock); \ MAGIC(pa_threaded_mainloop_wait); \ MAGIC(pa_channel_map_init_auto); \ MAGIC(pa_channel_map_parse); \ MAGIC(pa_channel_map_snprint); \ MAGIC(pa_channel_map_equal); \ MAGIC(pa_channel_map_superset); \ MAGIC(pa_channel_position_to_string); \ MAGIC(pa_operation_get_state); \ MAGIC(pa_operation_unref); \ MAGIC(pa_sample_spec_valid); \ MAGIC(pa_frame_size); \ MAGIC(pa_strerror); \ MAGIC(pa_path_get_filename); \ MAGIC(pa_get_binary_name); \ MAGIC(pa_xmalloc); \ MAGIC(pa_xfree); void *pulse_handle; #define MAKE_FUNC(x) decltype(x) * p##x PULSE_FUNCS(MAKE_FUNC) #undef MAKE_FUNC #ifndef IN_IDE_PARSER #define pa_context_new ppa_context_new #define pa_context_unref ppa_context_unref #define pa_context_get_state ppa_context_get_state #define pa_context_disconnect ppa_context_disconnect #define pa_context_set_state_callback ppa_context_set_state_callback #define pa_context_set_subscribe_callback ppa_context_set_subscribe_callback #define pa_context_subscribe ppa_context_subscribe #define pa_context_errno ppa_context_errno #define pa_context_connect ppa_context_connect #define pa_context_get_server_info ppa_context_get_server_info #define pa_context_get_sink_info_by_index ppa_context_get_sink_info_by_index #define pa_context_get_sink_info_by_name ppa_context_get_sink_info_by_name #define pa_context_get_sink_info_list ppa_context_get_sink_info_list #define pa_context_get_source_info_by_index ppa_context_get_source_info_by_index #define pa_context_get_source_info_by_name ppa_context_get_source_info_by_name #define pa_context_get_source_info_list ppa_context_get_source_info_list #define pa_stream_new ppa_stream_new #define pa_stream_unref ppa_stream_unref #define pa_stream_disconnect ppa_stream_disconnect #define pa_stream_drop ppa_stream_drop #define pa_stream_set_write_callback ppa_stream_set_write_callback #define pa_stream_set_buffer_attr ppa_stream_set_buffer_attr #define pa_stream_get_buffer_attr ppa_stream_get_buffer_attr #define pa_stream_get_sample_spec ppa_stream_get_sample_spec #define pa_stream_get_time ppa_stream_get_time #define pa_stream_set_read_callback ppa_stream_set_read_callback #define pa_stream_set_state_callback ppa_stream_set_state_callback #define pa_stream_set_moved_callback ppa_stream_set_moved_callback #define pa_stream_set_underflow_callback ppa_stream_set_underflow_callback #define pa_stream_connect_record ppa_stream_connect_record #define pa_stream_connect_playback ppa_stream_connect_playback #define pa_stream_readable_size ppa_stream_readable_size #define pa_stream_writable_size ppa_stream_writable_size #define pa_stream_is_corked ppa_stream_is_corked #define pa_stream_cork ppa_stream_cork #define pa_stream_is_suspended ppa_stream_is_suspended #define pa_stream_get_device_name ppa_stream_get_device_name #define pa_stream_get_latency ppa_stream_get_latency #define pa_stream_set_buffer_attr_callback ppa_stream_set_buffer_attr_callback #define pa_stream_begin_write ppa_stream_begin_write #define pa_threaded_mainloop_free ppa_threaded_mainloop_free #define pa_threaded_mainloop_get_api ppa_threaded_mainloop_get_api #define pa_threaded_mainloop_lock ppa_threaded_mainloop_lock #define pa_threaded_mainloop_new ppa_threaded_mainloop_new #define pa_threaded_mainloop_signal ppa_threaded_mainloop_signal #define pa_threaded_mainloop_start ppa_threaded_mainloop_start #define pa_threaded_mainloop_stop ppa_threaded_mainloop_stop #define pa_threaded_mainloop_unlock ppa_threaded_mainloop_unlock #define pa_threaded_mainloop_wait ppa_threaded_mainloop_wait #define pa_channel_map_init_auto ppa_channel_map_init_auto #define pa_channel_map_parse ppa_channel_map_parse #define pa_channel_map_snprint ppa_channel_map_snprint #define pa_channel_map_equal ppa_channel_map_equal #define pa_channel_map_superset ppa_channel_map_superset #define pa_channel_position_to_string ppa_channel_position_to_string #define pa_operation_get_state ppa_operation_get_state #define pa_operation_unref ppa_operation_unref #define pa_sample_spec_valid ppa_sample_spec_valid #define pa_frame_size ppa_frame_size #define pa_strerror ppa_strerror #define pa_stream_get_state ppa_stream_get_state #define pa_stream_peek ppa_stream_peek #define pa_stream_write ppa_stream_write #define pa_xfree ppa_xfree #define pa_path_get_filename ppa_path_get_filename #define pa_get_binary_name ppa_get_binary_name #define pa_xmalloc ppa_xmalloc #endif /* IN_IDE_PARSER */ #endif constexpr pa_channel_map MonoChanMap{ 1, {PA_CHANNEL_POSITION_MONO} }, StereoChanMap{ 2, {PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT} }, QuadChanMap{ 4, { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT } }, X51ChanMap{ 6, { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, PA_CHANNEL_POSITION_FRONT_CENTER, PA_CHANNEL_POSITION_LFE, PA_CHANNEL_POSITION_SIDE_LEFT, PA_CHANNEL_POSITION_SIDE_RIGHT } }, X51RearChanMap{ 6, { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, PA_CHANNEL_POSITION_FRONT_CENTER, PA_CHANNEL_POSITION_LFE, PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT } }, X61ChanMap{ 7, { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, PA_CHANNEL_POSITION_FRONT_CENTER, PA_CHANNEL_POSITION_LFE, PA_CHANNEL_POSITION_REAR_CENTER, PA_CHANNEL_POSITION_SIDE_LEFT, PA_CHANNEL_POSITION_SIDE_RIGHT } }, X71ChanMap{ 8, { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, PA_CHANNEL_POSITION_FRONT_CENTER, PA_CHANNEL_POSITION_LFE, PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, PA_CHANNEL_POSITION_SIDE_LEFT, PA_CHANNEL_POSITION_SIDE_RIGHT } }, X714ChanMap{ 12, { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, PA_CHANNEL_POSITION_FRONT_CENTER, PA_CHANNEL_POSITION_LFE, PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, PA_CHANNEL_POSITION_SIDE_LEFT, PA_CHANNEL_POSITION_SIDE_RIGHT, PA_CHANNEL_POSITION_TOP_FRONT_LEFT, PA_CHANNEL_POSITION_TOP_FRONT_RIGHT, PA_CHANNEL_POSITION_TOP_REAR_LEFT, PA_CHANNEL_POSITION_TOP_REAR_RIGHT } }; /* NOLINTBEGIN(*EnumCastOutOfRange) *grumble* Don't use enums for bitflags. */ constexpr pa_stream_flags_t operator|(pa_stream_flags_t lhs, pa_stream_flags_t rhs) { return pa_stream_flags_t(lhs | al::to_underlying(rhs)); } constexpr pa_stream_flags_t& operator|=(pa_stream_flags_t &lhs, pa_stream_flags_t rhs) { lhs = lhs | rhs; return lhs; } constexpr pa_stream_flags_t operator~(pa_stream_flags_t flag) { return pa_stream_flags_t(~al::to_underlying(flag)); } constexpr pa_stream_flags_t& operator&=(pa_stream_flags_t &lhs, pa_stream_flags_t rhs) { lhs = pa_stream_flags_t(al::to_underlying(lhs) & rhs); return lhs; } constexpr pa_context_flags_t operator|(pa_context_flags_t lhs, pa_context_flags_t rhs) { return pa_context_flags_t(lhs | al::to_underlying(rhs)); } constexpr pa_context_flags_t& operator|=(pa_context_flags_t &lhs, pa_context_flags_t rhs) { lhs = lhs | rhs; return lhs; } constexpr pa_subscription_mask_t operator|(pa_subscription_mask_t lhs, pa_subscription_mask_t rhs) { return pa_subscription_mask_t(lhs | al::to_underlying(rhs)); } /* NOLINTEND(*EnumCastOutOfRange) */ struct DevMap { std::string name; std::string device_name; uint32_t index{}; }; bool checkName(const al::span list, const std::string &name) { auto match_name = [&name](const DevMap &entry) -> bool { return entry.name == name; }; return std::find_if(list.cbegin(), list.cend(), match_name) != list.cend(); } std::vector PlaybackDevices; std::vector CaptureDevices; std::string DefaultPlaybackDevName; std::string DefaultCaptureDevName; /* Global flags and properties */ pa_context_flags_t pulse_ctx_flags; class PulseMainloop { pa_threaded_mainloop *mLoop{}; pa_context *mContext{}; public: PulseMainloop() = default; PulseMainloop(const PulseMainloop&) = delete; PulseMainloop(PulseMainloop&& rhs) noexcept : mLoop{rhs.mLoop} { rhs.mLoop = nullptr; } explicit PulseMainloop(pa_threaded_mainloop *loop) noexcept : mLoop{loop} { } ~PulseMainloop(); PulseMainloop& operator=(const PulseMainloop&) = delete; PulseMainloop& operator=(PulseMainloop&& rhs) noexcept { std::swap(mLoop, rhs.mLoop); return *this; } PulseMainloop& operator=(std::nullptr_t) noexcept { if(mLoop) pa_threaded_mainloop_free(mLoop); mLoop = nullptr; return *this; } explicit operator bool() const noexcept { return mLoop != nullptr; } [[nodiscard]] auto start() const { return pa_threaded_mainloop_start(mLoop); } auto stop() const { return pa_threaded_mainloop_stop(mLoop); } [[nodiscard]] auto getApi() const { return pa_threaded_mainloop_get_api(mLoop); } [[nodiscard]] auto getContext() const noexcept { return mContext; } auto lock() const { return pa_threaded_mainloop_lock(mLoop); } auto unlock() const { return pa_threaded_mainloop_unlock(mLoop); } auto signal(bool wait=false) const { return pa_threaded_mainloop_signal(mLoop, wait); } static auto Create() { return PulseMainloop{pa_threaded_mainloop_new()}; } void streamSuccessCallback(pa_stream*, int) const noexcept { signal(); } static void streamSuccessCallbackC(pa_stream *stream, int success, void *pdata) noexcept { static_cast(pdata)->streamSuccessCallback(stream, success); } void close(pa_stream *stream=nullptr); void updateDefaultDevice(pa_context*, const pa_server_info *info) const { auto default_sink = info->default_sink_name ? std::string_view{info->default_sink_name} : std::string_view{}; auto default_src = info->default_source_name ? std::string_view{info->default_source_name} : std::string_view{}; if(default_sink != DefaultPlaybackDevName) { TRACE("Default playback device: {}", default_sink); DefaultPlaybackDevName = default_sink; const auto msg = fmt::format("Default playback device changed: {}", default_sink); alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Playback, msg); } if(default_src != DefaultCaptureDevName) { TRACE("Default capture device: {}", default_src); DefaultCaptureDevName = default_src; const auto msg = fmt::format("Default capture device changed: {}", default_src); alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Capture, msg); } signal(); } void deviceSinkCallback(pa_context*, const pa_sink_info *info, int eol) const noexcept { if(eol) { signal(); return; } /* Skip this device is if it's already in the list. */ auto match_devname = [info](const DevMap &entry) -> bool { return entry.device_name == info->name; }; if(std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(), match_devname) != PlaybackDevices.cend()) return; /* Make sure the display name (description) is unique. Append a number * counter as needed. */ auto count = 1; auto newname = std::string{info->description}; while(checkName(PlaybackDevices, newname)) newname = fmt::format("{} #{}", info->description, ++count); const auto &newentry = PlaybackDevices.emplace_back(DevMap{std::move(newname), info->name, info->index}); TRACE("Got device \"{}\", \"{}\" ({})", newentry.name, newentry.device_name, newentry.index); const auto msg = fmt::format("Device added: {}", newentry.device_name); alc::Event(alc::EventType::DeviceAdded, alc::DeviceType::Playback, msg); } void deviceSourceCallback(pa_context*, const pa_source_info *info, int eol) const noexcept { if(eol) { signal(); return; } /* Skip this device is if it's already in the list. */ auto match_devname = [info](const DevMap &entry) -> bool { return entry.device_name == info->name; }; if(std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(), match_devname) != CaptureDevices.cend()) return; /* Make sure the display name (description) is unique. Append a number * counter as needed. */ auto count = 1; auto newname = std::string{info->description}; while(checkName(CaptureDevices, newname)) newname = fmt::format("{} #{}", info->description, ++count); const auto &newentry = CaptureDevices.emplace_back(DevMap{std::move(newname), info->name, info->index}); TRACE("Got device \"{}\", \"{}\" ({})", newentry.name, newentry.device_name, newentry.index); const auto msg = fmt::format("Device added: {}", newentry.device_name); alc::Event(alc::EventType::DeviceAdded, alc::DeviceType::Capture, msg); } void eventCallback(pa_context *context, pa_subscription_event_type_t t, uint32_t idx) noexcept { const auto eventFacility = (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK); const auto eventType = (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK); if(eventFacility == PA_SUBSCRIPTION_EVENT_SERVER && eventType == PA_SUBSCRIPTION_EVENT_CHANGE) { static constexpr auto server_cb = [](pa_context *ctx, const pa_server_info *info, void *pdata) noexcept { return static_cast(pdata)->updateDefaultDevice(ctx, info); }; auto *op = pa_context_get_server_info(context, server_cb, this); if(op) pa_operation_unref(op); } if(eventFacility != PA_SUBSCRIPTION_EVENT_SINK && eventFacility != PA_SUBSCRIPTION_EVENT_SOURCE) return; const auto devtype = (eventFacility == PA_SUBSCRIPTION_EVENT_SINK) ? alc::DeviceType::Playback : alc::DeviceType::Capture; if(eventType == PA_SUBSCRIPTION_EVENT_NEW) { if(eventFacility == PA_SUBSCRIPTION_EVENT_SINK) { static constexpr auto devcallback = [](pa_context *ctx, const pa_sink_info *info, int eol, void *pdata) noexcept { return static_cast(pdata)->deviceSinkCallback(ctx, info, eol); }; auto *op = pa_context_get_sink_info_by_index(context, idx, devcallback, this); if(op) pa_operation_unref(op); } else { static constexpr auto devcallback = [](pa_context *ctx, const pa_source_info *info, int eol, void *pdata) noexcept { return static_cast(pdata)->deviceSourceCallback(ctx,info,eol); }; auto *op = pa_context_get_source_info_by_index(context, idx, devcallback, this); if(op) pa_operation_unref(op); } } else if(eventType == PA_SUBSCRIPTION_EVENT_REMOVE) { auto find_index = [idx](const DevMap &entry) noexcept { return entry.index == idx; }; auto &devlist = (eventFacility == PA_SUBSCRIPTION_EVENT_SINK) ? PlaybackDevices : CaptureDevices; auto iter = std::find_if(devlist.cbegin(), devlist.cend(), find_index); if(iter != devlist.cend()) { devlist.erase(iter); const auto msg = fmt::format("Device removed: {}", idx); alc::Event(alc::EventType::DeviceRemoved, devtype, msg); } } } friend struct MainloopUniqueLock; }; struct MainloopUniqueLock : public std::unique_lock { using std::unique_lock::unique_lock; MainloopUniqueLock& operator=(MainloopUniqueLock&&) = default; auto wait() const -> void { pa_threaded_mainloop_wait(mutex()->mLoop); } template auto wait(Predicate done_waiting) const -> void { while(!done_waiting()) wait(); } void waitForOperation(pa_operation *op) const { if(op) { wait([op]{ return pa_operation_get_state(op) != PA_OPERATION_RUNNING; }); pa_operation_unref(op); } } void setEventHandler() { auto *context = mutex()->mContext; /* Watch for device added/removed and server changed events. */ static constexpr auto submask = PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE | PA_SUBSCRIPTION_MASK_SERVER; static constexpr auto do_signal = [](pa_context*, int, void *pdata) noexcept { static_cast(pdata)->signal(); }; auto *op = pa_context_subscribe(context, submask, do_signal, mutex()); waitForOperation(op); static constexpr auto handler = [](pa_context *ctx, pa_subscription_event_type_t t, uint32_t index, void *pdata) noexcept { return static_cast(pdata)->eventCallback(ctx, t, index); }; pa_context_set_subscribe_callback(context, handler, mutex()); /* Fill in the initial device lists, and get the defaults. */ auto sink_callback = [](pa_context *ctx, const pa_sink_info *info, int eol, void *pdata) noexcept { return static_cast(pdata)->deviceSinkCallback(ctx, info, eol); }; auto src_callback = [](pa_context *ctx, const pa_source_info *info, int eol, void *pdata) noexcept { return static_cast(pdata)->deviceSourceCallback(ctx, info, eol); }; auto server_callback = [](pa_context *ctx, const pa_server_info *info, void *pdata) noexcept { return static_cast(pdata)->updateDefaultDevice(ctx, info); }; auto *sinkop = pa_context_get_sink_info_list(context, sink_callback, mutex()); auto *srcop = pa_context_get_source_info_list(context, src_callback, mutex()); auto *serverop = pa_context_get_server_info(context, server_callback, mutex()); waitForOperation(sinkop); waitForOperation(srcop); waitForOperation(serverop); } void contextStateCallback(pa_context *context) noexcept { pa_context_state_t state{pa_context_get_state(context)}; if(state == PA_CONTEXT_READY || !PA_CONTEXT_IS_GOOD(state)) mutex()->signal(); } void streamStateCallback(pa_stream *stream) noexcept { pa_stream_state_t state{pa_stream_get_state(stream)}; if(state == PA_STREAM_READY || !PA_STREAM_IS_GOOD(state)) mutex()->signal(); } void connectContext(); pa_stream *connectStream(const char *device_name, pa_stream_flags_t flags, pa_buffer_attr *attr, pa_sample_spec *spec, pa_channel_map *chanmap, BackendType type); pa_stream *connectStream(const std::string &device_name, pa_stream_flags_t flags, pa_buffer_attr *attr, pa_sample_spec *spec, pa_channel_map *chanmap, BackendType type) { return connectStream(device_name.empty() ? nullptr : device_name.c_str(), flags, attr, spec, chanmap, type); } }; using MainloopLockGuard = std::lock_guard; PulseMainloop::~PulseMainloop() { if(mContext) { MainloopUniqueLock looplock{*this}; pa_context_disconnect(mContext); pa_context_unref(mContext); } if(mLoop) pa_threaded_mainloop_free(mLoop); } void MainloopUniqueLock::connectContext() { if(mutex()->mContext) return; mutex()->mContext = pa_context_new(mutex()->getApi(), nullptr); if(!mutex()->mContext) throw al::backend_exception{al::backend_error::OutOfMemory, "pa_context_new() failed"}; pa_context_set_state_callback(mutex()->mContext, [](pa_context *ctx, void *pdata) noexcept { return static_cast(pdata)->contextStateCallback(ctx); }, this); int err{pa_context_connect(mutex()->mContext, nullptr, pulse_ctx_flags, nullptr)}; if(err >= 0) { wait([&err,this]() { pa_context_state_t state{pa_context_get_state(mutex()->mContext)}; if(!PA_CONTEXT_IS_GOOD(state)) { err = pa_context_errno(mutex()->mContext); if(err > 0) err = -err; return true; } return state == PA_CONTEXT_READY; }); } pa_context_set_state_callback(mutex()->mContext, nullptr, nullptr); if(err < 0) { pa_context_unref(mutex()->mContext); mutex()->mContext = nullptr; throw al::backend_exception{al::backend_error::DeviceError, "Context did not connect ({})", pa_strerror(err)}; } } pa_stream *MainloopUniqueLock::connectStream(const char *device_name, pa_stream_flags_t flags, pa_buffer_attr *attr, pa_sample_spec *spec, pa_channel_map *chanmap, BackendType type) { const char *stream_id{(type==BackendType::Playback) ? "Playback Stream" : "Capture Stream"}; pa_stream *stream{pa_stream_new(mutex()->mContext, stream_id, spec, chanmap)}; if(!stream) throw al::backend_exception{al::backend_error::OutOfMemory, "pa_stream_new() failed ({})", pa_strerror(pa_context_errno(mutex()->mContext))}; pa_stream_set_state_callback(stream, [](pa_stream *strm, void *pdata) noexcept { return static_cast(pdata)->streamStateCallback(strm); }, this); int err{(type==BackendType::Playback) ? pa_stream_connect_playback(stream, device_name, attr, flags, nullptr, nullptr) : pa_stream_connect_record(stream, device_name, attr, flags)}; if(err < 0) { pa_stream_unref(stream); throw al::backend_exception{al::backend_error::DeviceError, "%s did not connect ({})", stream_id, pa_strerror(err)}; } wait([&err,stream,stream_id,this]() { pa_stream_state_t state{pa_stream_get_state(stream)}; if(!PA_STREAM_IS_GOOD(state)) { err = pa_context_errno(mutex()->mContext); pa_stream_unref(stream); throw al::backend_exception{al::backend_error::DeviceError, "{} did not get ready ({})", stream_id, pa_strerror(err)}; } return state == PA_STREAM_READY; }); pa_stream_set_state_callback(stream, nullptr, nullptr); return stream; } void PulseMainloop::close(pa_stream *stream) { if(!stream) return; MainloopUniqueLock looplock{*this}; pa_stream_set_state_callback(stream, nullptr, nullptr); pa_stream_set_moved_callback(stream, nullptr, nullptr); pa_stream_set_write_callback(stream, nullptr, nullptr); pa_stream_set_buffer_attr_callback(stream, nullptr, nullptr); pa_stream_disconnect(stream); pa_stream_unref(stream); } /* Used for initial connection test and enumeration. */ PulseMainloop gGlobalMainloop; struct PulsePlayback final : public BackendBase { explicit PulsePlayback(DeviceBase *device) noexcept : BackendBase{device} { } ~PulsePlayback() override; void bufferAttrCallback(pa_stream *stream) noexcept; void streamStateCallback(pa_stream *stream) noexcept; void streamWriteCallback(pa_stream *stream, size_t nbytes) noexcept; void sinkInfoCallback(pa_context *context, const pa_sink_info *info, int eol) noexcept; void sinkNameCallback(pa_context *context, const pa_sink_info *info, int eol) noexcept; void streamMovedCallback(pa_stream *stream) noexcept; void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; ClockLatency getClockLatency() override; PulseMainloop mMainloop; std::optional mDeviceId{std::nullopt}; bool mIs51Rear{false}; pa_buffer_attr mAttr{}; pa_sample_spec mSpec{}; pa_stream *mStream{nullptr}; uint mFrameSize{0u}; }; PulsePlayback::~PulsePlayback() { if(mStream) mMainloop.close(mStream); } void PulsePlayback::bufferAttrCallback(pa_stream *stream) noexcept { /* FIXME: Update the device's UpdateSize (and/or BufferSize) using the new * buffer attributes? Changing UpdateSize will change the ALC_REFRESH * property, which probably shouldn't change between device resets. But * leaving it alone means ALC_REFRESH will be off. */ mAttr = *(pa_stream_get_buffer_attr(stream)); TRACE("minreq={}, tlength={}, prebuf={}", mAttr.minreq, mAttr.tlength, mAttr.prebuf); } void PulsePlayback::streamStateCallback(pa_stream *stream) noexcept { if(pa_stream_get_state(stream) == PA_STREAM_FAILED) { ERR("Received stream failure!"); mDevice->handleDisconnect("Playback stream failure"); } mMainloop.signal(); } void PulsePlayback::streamWriteCallback(pa_stream *stream, size_t nbytes) noexcept { do { pa_free_cb_t free_func{nullptr}; auto buflen = static_cast(-1); void *buf{}; if(pa_stream_begin_write(stream, &buf, &buflen) || !buf) UNLIKELY { buflen = nbytes; buf = pa_xmalloc(buflen); free_func = pa_xfree; } else buflen = std::min(buflen, nbytes); nbytes -= buflen; mDevice->renderSamples(buf, static_cast(buflen/mFrameSize), mSpec.channels); int ret{pa_stream_write(stream, buf, buflen, free_func, 0, PA_SEEK_RELATIVE)}; if(ret != PA_OK) UNLIKELY ERR("Failed to write to stream: {}, {}", ret, pa_strerror(ret)); } while(nbytes > 0); } void PulsePlayback::sinkInfoCallback(pa_context*, const pa_sink_info *info, int eol) noexcept { struct ChannelMap { DevFmtChannels fmt; pa_channel_map map; bool is_51rear; }; static constexpr std::array chanmaps{{ { DevFmtX714, X714ChanMap, false }, { DevFmtX71, X71ChanMap, false }, { DevFmtX61, X61ChanMap, false }, { DevFmtX51, X51ChanMap, false }, { DevFmtX51, X51RearChanMap, true }, { DevFmtQuad, QuadChanMap, false }, { DevFmtStereo, StereoChanMap, false }, { DevFmtMono, MonoChanMap, false } }}; if(eol) { mMainloop.signal(); return; } auto chaniter = std::find_if(chanmaps.cbegin(), chanmaps.cend(), [info](const ChannelMap &chanmap) -> bool { return pa_channel_map_superset(&info->channel_map, &chanmap.map); } ); if(chaniter != chanmaps.cend()) { if(!mDevice->Flags.test(ChannelsRequest)) mDevice->FmtChans = chaniter->fmt; mIs51Rear = chaniter->is_51rear; } else { mIs51Rear = false; std::array chanmap_str{}; pa_channel_map_snprint(chanmap_str.data(), chanmap_str.size(), &info->channel_map); WARN("Failed to find format for channel map:\n {}", chanmap_str.data()); } if(info->active_port) TRACE("Active port: {} ({})", info->active_port->name, info->active_port->description); mDevice->Flags.set(DirectEar, (info->active_port && strcmp(info->active_port->name, "analog-output-headphones") == 0)); } void PulsePlayback::sinkNameCallback(pa_context*, const pa_sink_info *info, int eol) noexcept { if(eol) { mMainloop.signal(); return; } mDeviceName = info->description; } void PulsePlayback::streamMovedCallback(pa_stream *stream) noexcept { mDeviceId = pa_stream_get_device_name(stream); TRACE("Stream moved to {}", *mDeviceId); } void PulsePlayback::open(std::string_view name) { mMainloop = PulseMainloop::Create(); if(mMainloop.start() != 0) throw al::backend_exception{al::backend_error::DeviceError, "Failed to start device mainloop"}; auto pulse_name = std::string{}; if(!name.empty()) { auto match_name = [name](const DevMap &entry) -> bool { return entry.name == name || entry.device_name == name; }; auto plock = MainloopUniqueLock{gGlobalMainloop}; auto iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(), match_name); if(iter == PlaybackDevices.cend()) throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found", name}; pulse_name = iter->device_name; mDeviceName = iter->name; } MainloopUniqueLock plock{mMainloop}; plock.connectContext(); pa_stream_flags_t flags{PA_STREAM_START_CORKED | PA_STREAM_FIX_FORMAT | PA_STREAM_FIX_RATE | PA_STREAM_FIX_CHANNELS}; if(!GetConfigValueBool({}, "pulse", "allow-moves", true)) flags |= PA_STREAM_DONT_MOVE; pa_sample_spec spec{}; spec.format = PA_SAMPLE_S16NE; spec.rate = 44100; spec.channels = 2; if(pulse_name.empty()) { static const auto defname = al::getenv("ALSOFT_PULSE_DEFAULT"); if(defname) pulse_name = *defname; } TRACE("Connecting to \"{}\"", pulse_name.empty() ? "(default)"sv:std::string_view{pulse_name}); mStream = plock.connectStream(pulse_name, flags, nullptr, &spec, nullptr, BackendType::Playback); static constexpr auto move_callback = [](pa_stream *stream, void *pdata) noexcept { return static_cast(pdata)->streamMovedCallback(stream); }; pa_stream_set_moved_callback(mStream, move_callback, this); mFrameSize = static_cast(pa_frame_size(pa_stream_get_sample_spec(mStream))); if(!pulse_name.empty()) mDeviceId.emplace(std::move(pulse_name)); if(mDeviceName.empty()) { static constexpr auto name_callback = [](pa_context *context, const pa_sink_info *info, int eol, void *pdata) noexcept { return static_cast(pdata)->sinkNameCallback(context, info, eol); }; pa_operation *op{pa_context_get_sink_info_by_name(mMainloop.getContext(), pa_stream_get_device_name(mStream), name_callback, this)}; plock.waitForOperation(op); } } bool PulsePlayback::reset() { MainloopUniqueLock plock{mMainloop}; const auto deviceName = mDeviceId ? mDeviceId->c_str() : nullptr; if(mStream) { pa_stream_set_state_callback(mStream, nullptr, nullptr); pa_stream_set_moved_callback(mStream, nullptr, nullptr); pa_stream_set_write_callback(mStream, nullptr, nullptr); pa_stream_set_buffer_attr_callback(mStream, nullptr, nullptr); pa_stream_disconnect(mStream); pa_stream_unref(mStream); mStream = nullptr; } auto info_cb = [](pa_context *context, const pa_sink_info *info, int eol, void *pdata) noexcept { return static_cast(pdata)->sinkInfoCallback(context, info, eol); }; pa_operation *op{pa_context_get_sink_info_by_name(mMainloop.getContext(), deviceName, info_cb, this)}; plock.waitForOperation(op); pa_stream_flags_t flags{PA_STREAM_START_CORKED | PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_EARLY_REQUESTS}; if(!GetConfigValueBool({}, "pulse", "allow-moves", true)) flags |= PA_STREAM_DONT_MOVE; if(GetConfigValueBool(mDevice->mDeviceName, "pulse", "adjust-latency", false)) { /* ADJUST_LATENCY can't be specified with EARLY_REQUESTS, for some * reason. So if the user wants to adjust the overall device latency, * we can't ask to get write signals as soon as minreq is reached. */ flags &= ~PA_STREAM_EARLY_REQUESTS; flags |= PA_STREAM_ADJUST_LATENCY; } if(GetConfigValueBool(mDevice->mDeviceName, "pulse", "fix-rate", false) || !mDevice->Flags.test(FrequencyRequest)) flags |= PA_STREAM_FIX_RATE; pa_channel_map chanmap{}; switch(mDevice->FmtChans) { case DevFmtMono: chanmap = MonoChanMap; break; case DevFmtAmbi3D: mDevice->FmtChans = DevFmtStereo; /*fall-through*/ case DevFmtStereo: chanmap = StereoChanMap; break; case DevFmtQuad: chanmap = QuadChanMap; break; case DevFmtX51: chanmap = (mIs51Rear ? X51RearChanMap : X51ChanMap); break; case DevFmtX61: chanmap = X61ChanMap; break; case DevFmtX71: case DevFmtX3D71: chanmap = X71ChanMap; break; case DevFmtX7144: mDevice->FmtChans = DevFmtX714; /*fall-through*/ case DevFmtX714: chanmap = X714ChanMap; break; } setDefaultWFXChannelOrder(); switch(mDevice->FmtType) { case DevFmtByte: mDevice->FmtType = DevFmtUByte; /* fall-through */ case DevFmtUByte: mSpec.format = PA_SAMPLE_U8; break; case DevFmtUShort: mDevice->FmtType = DevFmtShort; /* fall-through */ case DevFmtShort: mSpec.format = PA_SAMPLE_S16NE; break; case DevFmtUInt: mDevice->FmtType = DevFmtInt; /* fall-through */ case DevFmtInt: mSpec.format = PA_SAMPLE_S32NE; break; case DevFmtFloat: mSpec.format = PA_SAMPLE_FLOAT32NE; break; } mSpec.rate = mDevice->mSampleRate; mSpec.channels = static_cast(mDevice->channelsFromFmt()); if(pa_sample_spec_valid(&mSpec) == 0) throw al::backend_exception{al::backend_error::DeviceError, "Invalid sample spec"}; const auto frame_size = static_cast(pa_frame_size(&mSpec)); mAttr.maxlength = ~0u; mAttr.tlength = mDevice->mBufferSize * frame_size; mAttr.prebuf = 0u; mAttr.minreq = mDevice->mUpdateSize * frame_size; mAttr.fragsize = ~0u; mStream = plock.connectStream(deviceName, flags, &mAttr, &mSpec, &chanmap, BackendType::Playback); constexpr auto state_callback = [](pa_stream *stream, void *pdata) noexcept { return static_cast(pdata)->streamStateCallback(stream); }; pa_stream_set_state_callback(mStream, state_callback, this); constexpr auto move_callback = [](pa_stream *stream, void *pdata) noexcept { return static_cast(pdata)->streamMovedCallback(stream); }; pa_stream_set_moved_callback(mStream, move_callback, this); mSpec = *(pa_stream_get_sample_spec(mStream)); mFrameSize = static_cast(pa_frame_size(&mSpec)); if(mDevice->mSampleRate != mSpec.rate) { /* Server updated our playback rate, so modify the buffer attribs * accordingly. */ const auto scale = static_cast(mSpec.rate) / mDevice->mSampleRate; const auto perlen = std::clamp(std::round(scale*mDevice->mUpdateSize), 64.0, 8192.0); const auto bufmax = uint{std::numeric_limits::max()} / mFrameSize; const auto buflen = std::clamp(std::round(scale*mDevice->mBufferSize), perlen*2.0, static_cast(bufmax)); mAttr.maxlength = ~0u; mAttr.tlength = static_cast(buflen) * mFrameSize; mAttr.prebuf = 0u; mAttr.minreq = static_cast(perlen) * mFrameSize; op = pa_stream_set_buffer_attr(mStream, &mAttr, &PulseMainloop::streamSuccessCallbackC, &mMainloop); plock.waitForOperation(op); mDevice->mSampleRate = mSpec.rate; } constexpr auto attr_callback = [](pa_stream *stream, void *pdata) noexcept { return static_cast(pdata)->bufferAttrCallback(stream); }; pa_stream_set_buffer_attr_callback(mStream, attr_callback, this); bufferAttrCallback(mStream); mDevice->mBufferSize = mAttr.tlength / mFrameSize; mDevice->mUpdateSize = mAttr.minreq / mFrameSize; return true; } void PulsePlayback::start() { MainloopUniqueLock plock{mMainloop}; /* Write some samples to fill the buffer before we start feeding it newly * mixed samples. */ if(size_t todo{pa_stream_writable_size(mStream)}) { void *buf{pa_xmalloc(todo)}; mDevice->renderSamples(buf, static_cast(todo/mFrameSize), mSpec.channels); pa_stream_write(mStream, buf, todo, pa_xfree, 0, PA_SEEK_RELATIVE); } constexpr auto stream_write = [](pa_stream *stream, size_t nbytes, void *pdata) noexcept { return static_cast(pdata)->streamWriteCallback(stream, nbytes); }; pa_stream_set_write_callback(mStream, stream_write, this); pa_operation *op{pa_stream_cork(mStream, 0, &PulseMainloop::streamSuccessCallbackC, &mMainloop)}; plock.waitForOperation(op); } void PulsePlayback::stop() { MainloopUniqueLock plock{mMainloop}; pa_operation *op{pa_stream_cork(mStream, 1, &PulseMainloop::streamSuccessCallbackC, &mMainloop)}; plock.waitForOperation(op); pa_stream_set_write_callback(mStream, nullptr, nullptr); } ClockLatency PulsePlayback::getClockLatency() { ClockLatency ret{}; pa_usec_t latency{}; int neg{}, err{}; { MainloopUniqueLock plock{mMainloop}; ret.ClockTime = mDevice->getClockTime(); err = pa_stream_get_latency(mStream, &latency, &neg); } if(err != 0) UNLIKELY { /* If err = -PA_ERR_NODATA, it means we were called too soon after * starting the stream and no timing info has been received from the * server yet. Give a generic value since nothing better is available. */ if(err != -PA_ERR_NODATA) ERR("Failed to get stream latency: {:#x}", as_unsigned(err)); latency = mDevice->mBufferSize - mDevice->mUpdateSize; neg = 0; } else if(neg) UNLIKELY latency = 0; ret.Latency = std::chrono::microseconds{latency}; return ret; } struct PulseCapture final : public BackendBase { explicit PulseCapture(DeviceBase *device) noexcept : BackendBase{device} { } ~PulseCapture() override; void streamStateCallback(pa_stream *stream) noexcept; void sourceNameCallback(pa_context *context, const pa_source_info *info, int eol) noexcept; void streamMovedCallback(pa_stream *stream) noexcept; void open(std::string_view name) override; void start() override; void stop() override; void captureSamples(std::byte *buffer, uint samples) override; uint availableSamples() override; ClockLatency getClockLatency() override; PulseMainloop mMainloop; std::optional mDeviceId{std::nullopt}; al::span mCapBuffer; size_t mHoleLength{0}; size_t mPacketLength{0}; uint mLastReadable{0u}; std::byte mSilentVal{}; pa_buffer_attr mAttr{}; pa_sample_spec mSpec{}; pa_stream *mStream{nullptr}; }; PulseCapture::~PulseCapture() { if(mStream) mMainloop.close(mStream); } void PulseCapture::streamStateCallback(pa_stream *stream) noexcept { if(pa_stream_get_state(stream) == PA_STREAM_FAILED) { ERR("Received stream failure!"); mDevice->handleDisconnect("Capture stream failure"); } mMainloop.signal(); } void PulseCapture::sourceNameCallback(pa_context*, const pa_source_info *info, int eol) noexcept { if(eol) { mMainloop.signal(); return; } mDeviceName = info->description; } void PulseCapture::streamMovedCallback(pa_stream *stream) noexcept { mDeviceId = pa_stream_get_device_name(stream); TRACE("Stream moved to {}", *mDeviceId); } void PulseCapture::open(std::string_view name) { if(!mMainloop) { mMainloop = PulseMainloop::Create(); if(mMainloop.start() != 0) throw al::backend_exception{al::backend_error::DeviceError, "Failed to start device mainloop"}; } auto pulse_name = std::string{}; if(!name.empty()) { auto match_name = [name](const DevMap &entry) -> bool { return entry.name == name || entry.device_name == name; }; auto plock = MainloopUniqueLock{gGlobalMainloop}; auto iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(), match_name); if(iter == CaptureDevices.cend()) throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found", name}; pulse_name = iter->device_name; mDeviceName = iter->name; } MainloopUniqueLock plock{mMainloop}; plock.connectContext(); pa_channel_map chanmap{}; switch(mDevice->FmtChans) { case DevFmtMono: chanmap = MonoChanMap; break; case DevFmtStereo: chanmap = StereoChanMap; break; case DevFmtQuad: chanmap = QuadChanMap; break; case DevFmtX51: chanmap = X51ChanMap; break; case DevFmtX61: chanmap = X61ChanMap; break; case DevFmtX71: chanmap = X71ChanMap; break; case DevFmtX714: chanmap = X714ChanMap; break; case DevFmtX7144: case DevFmtX3D71: case DevFmtAmbi3D: throw al::backend_exception{al::backend_error::DeviceError, "{} capture not supported", DevFmtChannelsString(mDevice->FmtChans)}; } setDefaultWFXChannelOrder(); switch(mDevice->FmtType) { case DevFmtUByte: mSilentVal = std::byte(0x80); mSpec.format = PA_SAMPLE_U8; break; case DevFmtShort: mSpec.format = PA_SAMPLE_S16NE; break; case DevFmtInt: mSpec.format = PA_SAMPLE_S32NE; break; case DevFmtFloat: mSpec.format = PA_SAMPLE_FLOAT32NE; break; case DevFmtByte: case DevFmtUShort: case DevFmtUInt: throw al::backend_exception{al::backend_error::DeviceError, "{} capture samples not supported", DevFmtTypeString(mDevice->FmtType)}; } mSpec.rate = mDevice->mSampleRate; mSpec.channels = static_cast(mDevice->channelsFromFmt()); if(pa_sample_spec_valid(&mSpec) == 0) throw al::backend_exception{al::backend_error::DeviceError, "Invalid sample format"}; const auto frame_size = static_cast(pa_frame_size(&mSpec)); const uint samples{std::max(mDevice->mBufferSize, mDevice->mSampleRate*100u/1000u)}; mAttr.minreq = ~0u; mAttr.prebuf = ~0u; mAttr.maxlength = samples * frame_size; mAttr.tlength = ~0u; mAttr.fragsize = std::min(samples, mDevice->mSampleRate*50u/1000u) * frame_size; pa_stream_flags_t flags{PA_STREAM_START_CORKED | PA_STREAM_ADJUST_LATENCY}; if(!GetConfigValueBool({}, "pulse", "allow-moves", true)) flags |= PA_STREAM_DONT_MOVE; TRACE("Connecting to \"{}\"", pulse_name.empty() ? "(default)"sv:std::string_view{pulse_name}); mStream = plock.connectStream(pulse_name, flags, &mAttr, &mSpec, &chanmap, BackendType::Capture); constexpr auto move_callback = [](pa_stream *stream, void *pdata) noexcept { return static_cast(pdata)->streamMovedCallback(stream); }; pa_stream_set_moved_callback(mStream, move_callback, this); constexpr auto state_callback = [](pa_stream *stream, void *pdata) noexcept { return static_cast(pdata)->streamStateCallback(stream); }; pa_stream_set_state_callback(mStream, state_callback, this); if(!pulse_name.empty()) mDeviceId.emplace(std::move(pulse_name)); if(mDeviceName.empty()) { constexpr auto name_callback = [](pa_context *context, const pa_source_info *info, int eol, void *pdata) noexcept { return static_cast(pdata)->sourceNameCallback(context, info, eol); }; pa_operation *op{pa_context_get_source_info_by_name(mMainloop.getContext(), pa_stream_get_device_name(mStream), name_callback, this)}; plock.waitForOperation(op); } } void PulseCapture::start() { MainloopUniqueLock plock{mMainloop}; pa_operation *op{pa_stream_cork(mStream, 0, &PulseMainloop::streamSuccessCallbackC, &mMainloop)}; plock.waitForOperation(op); } void PulseCapture::stop() { MainloopUniqueLock plock{mMainloop}; pa_operation *op{pa_stream_cork(mStream, 1, &PulseMainloop::streamSuccessCallbackC, &mMainloop)}; plock.waitForOperation(op); } void PulseCapture::captureSamples(std::byte *buffer, uint samples) { al::span dstbuf{buffer, samples * pa_frame_size(&mSpec)}; /* Capture is done in fragment-sized chunks, so we loop until we get all * that's available. */ mLastReadable -= static_cast(dstbuf.size()); while(!dstbuf.empty()) { if(mHoleLength > 0) UNLIKELY { const size_t rem{std::min(dstbuf.size(), mHoleLength)}; std::fill_n(dstbuf.begin(), rem, mSilentVal); dstbuf = dstbuf.subspan(rem); mHoleLength -= rem; continue; } if(!mCapBuffer.empty()) { const size_t rem{std::min(dstbuf.size(), mCapBuffer.size())}; std::copy_n(mCapBuffer.begin(), rem, dstbuf.begin()); dstbuf = dstbuf.subspan(rem); mCapBuffer = mCapBuffer.subspan(rem); continue; } if(!mDevice->Connected.load(std::memory_order_acquire)) UNLIKELY break; MainloopUniqueLock plock{mMainloop}; if(mPacketLength > 0) { pa_stream_drop(mStream); mPacketLength = 0; } const pa_stream_state_t state{pa_stream_get_state(mStream)}; if(!PA_STREAM_IS_GOOD(state)) UNLIKELY { mDevice->handleDisconnect("Bad capture state: {}", al::to_underlying(state)); break; } const void *capbuf{}; size_t caplen{}; if(pa_stream_peek(mStream, &capbuf, &caplen) < 0) UNLIKELY { mDevice->handleDisconnect("Failed retrieving capture samples: {}", pa_strerror(pa_context_errno(mMainloop.getContext()))); break; } plock.unlock(); if(caplen == 0) break; if(!capbuf) UNLIKELY mHoleLength = caplen; else mCapBuffer = {static_cast(capbuf), caplen}; mPacketLength = caplen; } if(!dstbuf.empty()) std::fill(dstbuf.begin(), dstbuf.end(), mSilentVal); } uint PulseCapture::availableSamples() { size_t readable{std::max(mCapBuffer.size(), mHoleLength)}; if(mDevice->Connected.load(std::memory_order_acquire)) { MainloopUniqueLock plock{mMainloop}; size_t got{pa_stream_readable_size(mStream)}; if(static_cast(got) < 0) UNLIKELY { const char *err{pa_strerror(static_cast(got))}; ERR("pa_stream_readable_size() failed: {}", err); mDevice->handleDisconnect("Failed getting readable size: {}", err); } else { /* "readable" is the number of bytes from the last packet that have * not yet been read by the caller. So add the stream's readable * size excluding the last packet (the stream size includes the * last packet until it's dropped). */ if(got > mPacketLength) readable += got - mPacketLength; } } /* Avoid uint overflow, and avoid decreasing the readable count. */ readable = std::min(readable, std::numeric_limits::max()); mLastReadable = std::max(mLastReadable, static_cast(readable)); return mLastReadable / static_cast(pa_frame_size(&mSpec)); } ClockLatency PulseCapture::getClockLatency() { ClockLatency ret{}; pa_usec_t latency{}; int neg{}, err{}; { MainloopUniqueLock plock{mMainloop}; ret.ClockTime = mDevice->getClockTime(); err = pa_stream_get_latency(mStream, &latency, &neg); } if(err != 0) UNLIKELY { ERR("Failed to get stream latency: {:#x}", as_unsigned(err)); latency = 0; neg = 0; } else if(neg) UNLIKELY latency = 0; ret.Latency = std::chrono::microseconds{latency}; return ret; } } // namespace bool PulseBackendFactory::init() { #if HAVE_DYNLOAD if(!pulse_handle) { #ifdef _WIN32 #define PALIB "libpulse-0.dll" #elif defined(__APPLE__) && defined(__MACH__) #define PALIB "libpulse.0.dylib" #else #define PALIB "libpulse.so.0" #endif pulse_handle = LoadLib(PALIB); if(!pulse_handle) { WARN("Failed to load {}", PALIB); return false; } std::string missing_funcs; #define LOAD_FUNC(x) do { \ p##x = reinterpret_cast(GetSymbol(pulse_handle, #x)); \ if(!(p##x)) missing_funcs += "\n" #x; \ } while(0) PULSE_FUNCS(LOAD_FUNC) #undef LOAD_FUNC if(!missing_funcs.empty()) { WARN("Missing expected functions:{}", missing_funcs); CloseLib(pulse_handle); pulse_handle = nullptr; return false; } } #endif pulse_ctx_flags = PA_CONTEXT_NOFLAGS; if(!GetConfigValueBool({}, "pulse", "spawn-server", false)) pulse_ctx_flags |= PA_CONTEXT_NOAUTOSPAWN; try { if(!gGlobalMainloop) { gGlobalMainloop = PulseMainloop::Create(); if(gGlobalMainloop.start() != 0) { gGlobalMainloop = nullptr; return false; } } MainloopUniqueLock plock{gGlobalMainloop}; plock.connectContext(); plock.setEventHandler(); return true; } catch(...) { return false; } } bool PulseBackendFactory::querySupport(BackendType type) { return type == BackendType::Playback || type == BackendType::Capture; } auto PulseBackendFactory::enumerate(BackendType type) -> std::vector { std::vector outnames; auto add_playback_device = [&outnames](const DevMap &entry) -> void { if(entry.device_name == DefaultPlaybackDevName) outnames.emplace(outnames.cbegin(), entry.name); else outnames.push_back(entry.name); }; auto add_capture_device = [&outnames](const DevMap &entry) -> void { if(entry.device_name == DefaultCaptureDevName) outnames.emplace(outnames.cbegin(), entry.name); else outnames.push_back(entry.name); }; auto plock = MainloopUniqueLock{gGlobalMainloop}; switch(type) { case BackendType::Playback: outnames.reserve(PlaybackDevices.size()); std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_playback_device); break; case BackendType::Capture: outnames.reserve(CaptureDevices.size()); std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_capture_device); break; } return outnames; } BackendPtr PulseBackendFactory::createBackend(DeviceBase *device, BackendType type) { if(type == BackendType::Playback) return BackendPtr{new PulsePlayback{device}}; if(type == BackendType::Capture) return BackendPtr{new PulseCapture{device}}; return nullptr; } BackendFactory &PulseBackendFactory::getFactory() { static PulseBackendFactory factory{}; return factory; } alc::EventSupport PulseBackendFactory::queryEventSupport(alc::EventType eventType, BackendType) { switch(eventType) { case alc::EventType::DeviceAdded: case alc::EventType::DeviceRemoved: case alc::EventType::DefaultDeviceChanged: return alc::EventSupport::FullSupport; case alc::EventType::Count: break; } return alc::EventSupport::NoSupport; } openal-soft-1.24.2/alc/backends/pulseaudio.h000066400000000000000000000012231474041540300206630ustar00rootroot00000000000000#ifndef BACKENDS_PULSEAUDIO_H #define BACKENDS_PULSEAUDIO_H #include #include #include "alc/events.h" #include "base.h" struct DeviceBase; class PulseBackendFactory final : public BackendFactory { public: auto init() -> bool final; auto querySupport(BackendType type) -> bool final; auto queryEventSupport(alc::EventType eventType, BackendType type) -> alc::EventSupport final; auto enumerate(BackendType type) -> std::vector final; auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_PULSEAUDIO_H */ openal-soft-1.24.2/alc/backends/sdl2.cpp000066400000000000000000000214301474041540300177120ustar00rootroot00000000000000/** * OpenAL cross platform audio library * Copyright (C) 2018 by authors. * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * Or go to http://www.gnu.org/copyleft/lgpl.html */ #include "config.h" #include "sdl2.h" #include #include #include #include #include #include "alnumeric.h" #include "core/device.h" _Pragma("GCC diagnostic push") _Pragma("GCC diagnostic ignored \"-Wold-style-cast\"") #include "SDL.h" _Pragma("GCC diagnostic pop") namespace { using namespace std::string_view_literals; [[nodiscard]] constexpr auto getDefaultDeviceName() noexcept -> std::string_view { return "Default Device"sv; } struct Sdl2Backend final : public BackendBase { explicit Sdl2Backend(DeviceBase *device) noexcept : BackendBase{device} { } ~Sdl2Backend() override; void audioCallback(Uint8 *stream, int len) noexcept; void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; std::string mSDLName; SDL_AudioDeviceID mDeviceID{0u}; uint mFrameSize{0}; }; Sdl2Backend::~Sdl2Backend() { if(mDeviceID) SDL_CloseAudioDevice(mDeviceID); mDeviceID = 0; } void Sdl2Backend::audioCallback(Uint8 *stream, int len) noexcept { const auto ulen = static_cast(len); assert((ulen % mFrameSize) == 0); mDevice->renderSamples(stream, ulen / mFrameSize, mDevice->channelsFromFmt()); } void Sdl2Backend::open(std::string_view name) { SDL_AudioSpec want{}, have{}; want.freq = static_cast(mDevice->mSampleRate); switch(mDevice->FmtType) { case DevFmtUByte: want.format = AUDIO_U8; break; case DevFmtByte: want.format = AUDIO_S8; break; case DevFmtUShort: want.format = AUDIO_U16SYS; break; case DevFmtShort: want.format = AUDIO_S16SYS; break; case DevFmtUInt: /* fall-through */ case DevFmtInt: want.format = AUDIO_S32SYS; break; case DevFmtFloat: want.format = AUDIO_F32; break; } want.channels = static_cast(std::min(mDevice->channelsFromFmt(), std::numeric_limits::max())); want.samples = static_cast(std::min(mDevice->mUpdateSize, 8192u)); want.callback = [](void *ptr, Uint8 *stream, int len) noexcept { return static_cast(ptr)->audioCallback(stream, len); }; want.userdata = this; /* Passing nullptr to SDL_OpenAudioDevice opens a default, which isn't * necessarily the first in the list. */ const auto defaultDeviceName = getDefaultDeviceName(); if(name.empty() || name == defaultDeviceName) { name = defaultDeviceName; mSDLName.clear(); mDeviceID = SDL_OpenAudioDevice(nullptr, SDL_FALSE, &want, &have, SDL_AUDIO_ALLOW_ANY_CHANGE); } else { mSDLName = name; mDeviceID = SDL_OpenAudioDevice(mSDLName.c_str(), SDL_FALSE, &want, &have, SDL_AUDIO_ALLOW_ANY_CHANGE); } if(!mDeviceID) throw al::backend_exception{al::backend_error::NoDevice, "{}", SDL_GetError()}; DevFmtType devtype{}; switch(have.format) { case AUDIO_U8: devtype = DevFmtUByte; break; case AUDIO_S8: devtype = DevFmtByte; break; case AUDIO_U16SYS: devtype = DevFmtUShort; break; case AUDIO_S16SYS: devtype = DevFmtShort; break; case AUDIO_S32SYS: devtype = DevFmtInt; break; case AUDIO_F32SYS: devtype = DevFmtFloat; break; default: throw al::backend_exception{al::backend_error::DeviceError, "Unhandled SDL format: {:#04x}", have.format}; } mFrameSize = BytesFromDevFmt(devtype) * have.channels; mDeviceName = name; } bool Sdl2Backend::reset() { if(mDeviceID) SDL_CloseAudioDevice(mDeviceID); mDeviceID = 0; auto want = SDL_AudioSpec{}; want.freq = static_cast(mDevice->mSampleRate); switch(mDevice->FmtType) { case DevFmtUByte: want.format = AUDIO_U8; break; case DevFmtByte: want.format = AUDIO_S8; break; case DevFmtUShort: want.format = AUDIO_U16SYS; break; case DevFmtShort: want.format = AUDIO_S16SYS; break; case DevFmtUInt: [[fallthrough]]; case DevFmtInt: want.format = AUDIO_S32SYS; break; case DevFmtFloat: want.format = AUDIO_F32; break; } want.channels = static_cast(std::min(mDevice->channelsFromFmt(), std::numeric_limits::max())); want.samples = static_cast(std::min(mDevice->mUpdateSize, 8192u)); want.callback = [](void *ptr, Uint8 *stream, int len) noexcept { return static_cast(ptr)->audioCallback(stream, len); }; want.userdata = this; auto have = SDL_AudioSpec{}; if(mSDLName.empty()) { mDeviceID = SDL_OpenAudioDevice(nullptr, SDL_FALSE, &want, &have, SDL_AUDIO_ALLOW_ANY_CHANGE); } else { mDeviceID = SDL_OpenAudioDevice(mSDLName.c_str(), SDL_FALSE, &want, &have, SDL_AUDIO_ALLOW_ANY_CHANGE); } if(!mDeviceID) throw al::backend_exception{al::backend_error::NoDevice, "{}", SDL_GetError()}; if(have.channels != mDevice->channelsFromFmt()) { /* SDL guarantees these layouts for the given channel count. */ if(have.channels == 8) mDevice->FmtChans = DevFmtX71; else if(have.channels == 7) mDevice->FmtChans = DevFmtX61; else if(have.channels == 6) mDevice->FmtChans = DevFmtX51; else if(have.channels == 4) mDevice->FmtChans = DevFmtQuad; else if(have.channels >= 2) mDevice->FmtChans = DevFmtStereo; else if(have.channels == 1) mDevice->FmtChans = DevFmtMono; else throw al::backend_exception{al::backend_error::DeviceError, "Unhandled SDL channel count: {}", int{have.channels}}; mDevice->mAmbiOrder = 0; } switch(have.format) { case AUDIO_U8: mDevice->FmtType = DevFmtUByte; break; case AUDIO_S8: mDevice->FmtType = DevFmtByte; break; case AUDIO_U16SYS: mDevice->FmtType = DevFmtUShort; break; case AUDIO_S16SYS: mDevice->FmtType = DevFmtShort; break; case AUDIO_S32SYS: mDevice->FmtType = DevFmtInt; break; case AUDIO_F32SYS: mDevice->FmtType = DevFmtFloat; break; default: throw al::backend_exception{al::backend_error::DeviceError, "Unhandled SDL format: {:#04x}", have.format}; } mFrameSize = BytesFromDevFmt(mDevice->FmtType) * have.channels; if(have.freq < int{MinOutputRate}) throw al::backend_exception{al::backend_error::DeviceError, "Unhandled SDL sample rate: {}", have.freq}; mDevice->mSampleRate = static_cast(have.freq); mDevice->mUpdateSize = have.samples; mDevice->mBufferSize = std::max(have.size/mFrameSize, mDevice->mUpdateSize*2u); setDefaultWFXChannelOrder(); return true; } void Sdl2Backend::start() { SDL_PauseAudioDevice(mDeviceID, 0); } void Sdl2Backend::stop() { SDL_PauseAudioDevice(mDeviceID, 1); } } // namespace BackendFactory &SDL2BackendFactory::getFactory() { static SDL2BackendFactory factory{}; return factory; } bool SDL2BackendFactory::init() { return (SDL_InitSubSystem(SDL_INIT_AUDIO) == 0); } bool SDL2BackendFactory::querySupport(BackendType type) { return type == BackendType::Playback; } auto SDL2BackendFactory::enumerate(BackendType type) -> std::vector { std::vector outnames; if(type != BackendType::Playback) return outnames; int num_devices{SDL_GetNumAudioDevices(SDL_FALSE)}; if(num_devices <= 0) return outnames; outnames.reserve(static_cast(num_devices)+1_uz); outnames.emplace_back(getDefaultDeviceName()); for(int i{0};i < num_devices;++i) { if(const char *name = SDL_GetAudioDeviceName(i, SDL_FALSE)) outnames.emplace_back(name); else outnames.emplace_back("Unknown Device Name #"+std::to_string(i)); } return outnames; } BackendPtr SDL2BackendFactory::createBackend(DeviceBase *device, BackendType type) { if(type == BackendType::Playback) return BackendPtr{new Sdl2Backend{device}}; return nullptr; } openal-soft-1.24.2/alc/backends/sdl2.h000066400000000000000000000007141474041540300173610ustar00rootroot00000000000000#ifndef BACKENDS_SDL2_H #define BACKENDS_SDL2_H #include "base.h" struct SDL2BackendFactory final : public BackendFactory { public: auto init() -> bool final; auto querySupport(BackendType type) -> bool final; auto enumerate(BackendType type) -> std::vector final; auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_SDL2_H */ openal-soft-1.24.2/alc/backends/sdl3.cpp000066400000000000000000000314541474041540300177220ustar00rootroot00000000000000/** * OpenAL cross platform audio library * Copyright (C) 2024 by authors. * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * Or go to http://www.gnu.org/copyleft/lgpl.html */ #include "config.h" #include "sdl3.h" #include #include #include #include #include #include #include "almalloc.h" #include "core/device.h" #include "core/logging.h" _Pragma("GCC diagnostic push") _Pragma("GCC diagnostic ignored \"-Wold-style-cast\"") #include "SDL3/SDL_audio.h" #include "SDL3/SDL_init.h" #include "SDL3/SDL_stdinc.h" _Pragma("GCC diagnostic pop") namespace { using namespace std::string_view_literals; _Pragma("GCC diagnostic push") _Pragma("GCC diagnostic ignored \"-Wold-style-cast\"") constexpr auto DefaultPlaybackDeviceID = SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK; _Pragma("GCC diagnostic pop") template struct SdlDeleter { /* NOLINTNEXTLINE(cppcoreguidelines-no-malloc) */ void operator()(gsl::owner ptr) const { SDL_free(ptr); } }; template using unique_sdl_ptr = std::unique_ptr>; struct DeviceEntry { std::string mName; SDL_AudioDeviceID mPhysDeviceID{}; }; std::vector gPlaybackDevices; void EnumeratePlaybackDevices() { auto numdevs = int{}; auto devicelist = unique_sdl_ptr{SDL_GetAudioPlaybackDevices(&numdevs)}; if(!devicelist || numdevs < 0) { ERR("Failed to get playback devices: {}", SDL_GetError()); return; } auto devids = al::span{devicelist.get(), static_cast(numdevs)}; auto newlist = std::vector{}; newlist.reserve(devids.size()); std::transform(devids.begin(), devids.end(), std::back_inserter(newlist), [](SDL_AudioDeviceID id) { auto *name = SDL_GetAudioDeviceName(id); if(!name) return DeviceEntry{}; TRACE("Got device \"{}\", ID {}", name, id); return DeviceEntry{name, id}; }); gPlaybackDevices.swap(newlist); } [[nodiscard]] constexpr auto getDefaultDeviceName() noexcept -> std::string_view { return "Default Device"sv; } struct Sdl3Backend final : public BackendBase { explicit Sdl3Backend(DeviceBase *device) noexcept : BackendBase{device} { } ~Sdl3Backend() final; void audioCallback(SDL_AudioStream *stream, int additional_amount, int total_amount) noexcept; void open(std::string_view name) final; auto reset() -> bool final; void start() final; void stop() final; SDL_AudioDeviceID mDeviceID{0}; SDL_AudioStream *mStream{nullptr}; uint mNumChannels{0}; uint mFrameSize{0}; std::vector mBuffer; }; Sdl3Backend::~Sdl3Backend() { if(mStream) SDL_DestroyAudioStream(mStream); mStream = nullptr; } void Sdl3Backend::audioCallback(SDL_AudioStream *stream, int additional_amount, int total_amount) noexcept { if(additional_amount < 0) additional_amount = total_amount; if(additional_amount <= 0) return; const auto ulen = static_cast(additional_amount); assert((ulen % mFrameSize) == 0); if(ulen > mBuffer.size()) { mBuffer.resize(ulen); std::fill(mBuffer.begin(), mBuffer.end(), (mDevice->FmtType == DevFmtUByte) ? std::byte{0x80} : std::byte{}); } mDevice->renderSamples(mBuffer.data(), ulen / mFrameSize, mNumChannels); SDL_PutAudioStreamData(stream, mBuffer.data(), additional_amount); } void Sdl3Backend::open(std::string_view name) { const auto defaultDeviceName = getDefaultDeviceName(); if(name.empty() || name == defaultDeviceName) { name = defaultDeviceName; mDeviceID = DefaultPlaybackDeviceID; } else { if(gPlaybackDevices.empty()) EnumeratePlaybackDevices(); const auto iter = std::find_if(gPlaybackDevices.cbegin(), gPlaybackDevices.cend(), [name](const DeviceEntry &entry) { return name == entry.mName; }); if(iter == gPlaybackDevices.cend()) throw al::backend_exception{al::backend_error::NoDevice, "No device named {}", name}; mDeviceID = iter->mPhysDeviceID; } mStream = SDL_OpenAudioDeviceStream(mDeviceID, nullptr, nullptr, nullptr); if(!mStream) throw al::backend_exception{al::backend_error::NoDevice, "{}", SDL_GetError()}; auto have = SDL_AudioSpec{}; auto update_size = int{}; if(SDL_GetAudioDeviceFormat(SDL_GetAudioStreamDevice(mStream), &have, &update_size)) { auto devtype = mDevice->FmtType; switch(have.format) { case SDL_AUDIO_U8: devtype = DevFmtUByte; break; case SDL_AUDIO_S8: devtype = DevFmtByte; break; case SDL_AUDIO_S16: devtype = DevFmtShort; break; case SDL_AUDIO_S32: devtype = DevFmtInt; break; case SDL_AUDIO_F32: devtype = DevFmtFloat; break; default: break; } mDevice->FmtType = devtype; if(have.freq >= int{MinOutputRate} && have.freq <= int{MaxOutputRate}) mDevice->mSampleRate = static_cast(have.freq); /* SDL guarantees these layouts for the given channel count. */ if(have.channels == 8) mDevice->FmtChans = DevFmtX71; else if(have.channels == 7) mDevice->FmtChans = DevFmtX61; else if(have.channels == 6) mDevice->FmtChans = DevFmtX51; else if(have.channels == 4) mDevice->FmtChans = DevFmtQuad; else if(have.channels >= 2) mDevice->FmtChans = DevFmtStereo; else if(have.channels == 1) mDevice->FmtChans = DevFmtMono; mDevice->mAmbiOrder = 0; mNumChannels = static_cast(have.channels); mFrameSize = mDevice->bytesFromFmt() * mNumChannels; if(update_size >= 64) { /* We have to assume the total buffer size is just twice the update * size. SDL doesn't tell us the full end-to-end buffer latency. */ mDevice->mUpdateSize = static_cast(update_size); mDevice->mBufferSize = mDevice->mUpdateSize*2u; } else ERR("Invalid update size from SDL stream: {}", update_size); } else ERR("Failed to get format from SDL stream: {}", SDL_GetError()); mDeviceName = name; } auto Sdl3Backend::reset() -> bool { static constexpr auto callback = [](void *ptr, SDL_AudioStream *stream, int additional_amount, int total_amount) noexcept { return static_cast(ptr)->audioCallback(stream, additional_amount, total_amount); }; if(mStream) SDL_DestroyAudioStream(mStream); mStream = nullptr; mBuffer.clear(); mBuffer.shrink_to_fit(); auto want = SDL_AudioSpec{}; if(!SDL_GetAudioDeviceFormat(mDeviceID, &want, nullptr)) ERR("Failed to get device format: {}", SDL_GetError()); if(mDevice->Flags.test(FrequencyRequest) || want.freq < int{MinOutputRate}) want.freq = static_cast(mDevice->mSampleRate); if(mDevice->Flags.test(SampleTypeRequest) || !(want.format == SDL_AUDIO_U8 || want.format == SDL_AUDIO_S8 || want.format == SDL_AUDIO_S16 || want.format == SDL_AUDIO_S32 || want.format == SDL_AUDIO_F32)) { switch(mDevice->FmtType) { case DevFmtUByte: want.format = SDL_AUDIO_U8; break; case DevFmtByte: want.format = SDL_AUDIO_S8; break; case DevFmtUShort: [[fallthrough]]; case DevFmtShort: want.format = SDL_AUDIO_S16; break; case DevFmtUInt: [[fallthrough]]; case DevFmtInt: want.format = SDL_AUDIO_S32; break; case DevFmtFloat: want.format = SDL_AUDIO_F32; break; } } if(mDevice->Flags.test(ChannelsRequest) || want.channels < 1) want.channels = static_cast(std::min(mDevice->channelsFromFmt(), std::numeric_limits::max())); mStream = SDL_OpenAudioDeviceStream(mDeviceID, &want, callback, this); if(!mStream) { /* If creating the stream failed, try again without a specific format. */ mStream = SDL_OpenAudioDeviceStream(mDeviceID, nullptr, callback, this); if(!mStream) throw al::backend_exception{al::backend_error::DeviceError, "Failed to recreate stream: {}", SDL_GetError()}; } auto update_size = int{}; auto have = SDL_AudioSpec{}; SDL_GetAudioDeviceFormat(SDL_GetAudioStreamDevice(mStream), &have, &update_size); have = SDL_AudioSpec{}; if(!SDL_GetAudioStreamFormat(mStream, &have, nullptr)) throw al::backend_exception{al::backend_error::DeviceError, "Failed to get stream format: {}", SDL_GetError()}; if(!mDevice->Flags.test(ChannelsRequest) || (static_cast(have.channels) != mDevice->channelsFromFmt() && !(mDevice->FmtChans == DevFmtStereo && have.channels >= 2))) { /* SDL guarantees these layouts for the given channel count. */ if(have.channels == 8) mDevice->FmtChans = DevFmtX71; else if(have.channels == 7) mDevice->FmtChans = DevFmtX61; else if(have.channels == 6) mDevice->FmtChans = DevFmtX51; else if(have.channels == 4) mDevice->FmtChans = DevFmtQuad; else if(have.channels >= 2) mDevice->FmtChans = DevFmtStereo; else if(have.channels == 1) mDevice->FmtChans = DevFmtMono; else throw al::backend_exception{al::backend_error::DeviceError, "Unhandled SDL channel count: {}", have.channels}; mDevice->mAmbiOrder = 0; } mNumChannels = static_cast(have.channels); switch(have.format) { case SDL_AUDIO_U8: mDevice->FmtType = DevFmtUByte; break; case SDL_AUDIO_S8: mDevice->FmtType = DevFmtByte; break; case SDL_AUDIO_S16: mDevice->FmtType = DevFmtShort; break; case SDL_AUDIO_S32: mDevice->FmtType = DevFmtInt; break; case SDL_AUDIO_F32: mDevice->FmtType = DevFmtFloat; break; default: throw al::backend_exception{al::backend_error::DeviceError, "Unhandled SDL format: {:#04x}", al::to_underlying(have.format)}; } mFrameSize = mDevice->bytesFromFmt() * mNumChannels; if(have.freq < int{MinOutputRate}) throw al::backend_exception{al::backend_error::DeviceError, "Unhandled SDL sample rate: {}", have.freq}; mDevice->mSampleRate = static_cast(have.freq); if(update_size >= 64) { mDevice->mUpdateSize = static_cast(update_size); mDevice->mBufferSize = mDevice->mUpdateSize*2u; mBuffer.resize(size_t{mDevice->mUpdateSize} * mFrameSize); std::fill(mBuffer.begin(), mBuffer.end(), (mDevice->FmtType == DevFmtUByte) ? std::byte{0x80} : std::byte{}); } else ERR("Invalid update size from SDL stream: {}", update_size); setDefaultWFXChannelOrder(); return true; } void Sdl3Backend::start() { SDL_ResumeAudioStreamDevice(mStream); } void Sdl3Backend::stop() { SDL_PauseAudioStreamDevice(mStream); } } // namespace auto SDL3BackendFactory::getFactory() -> BackendFactory& { static SDL3BackendFactory factory{}; return factory; } auto SDL3BackendFactory::init() -> bool { if(!SDL_InitSubSystem(SDL_INIT_AUDIO)) return false; TRACE("Current SDL3 audio driver: \"{}\"", SDL_GetCurrentAudioDriver()); return true; } auto SDL3BackendFactory::querySupport(BackendType type) -> bool { return type == BackendType::Playback; } auto SDL3BackendFactory::enumerate(BackendType type) -> std::vector { auto outnames = std::vector{}; if(type != BackendType::Playback) return outnames; EnumeratePlaybackDevices(); outnames.reserve(gPlaybackDevices.size()+1); outnames.emplace_back(getDefaultDeviceName()); std::transform(gPlaybackDevices.begin(), gPlaybackDevices.end(), std::back_inserter(outnames), std::mem_fn(&DeviceEntry::mName)); return outnames; } auto SDL3BackendFactory::createBackend(DeviceBase *device, BackendType type) -> BackendPtr { if(type == BackendType::Playback) return BackendPtr{new Sdl3Backend{device}}; return nullptr; } openal-soft-1.24.2/alc/backends/sdl3.h000066400000000000000000000007141474041540300173620ustar00rootroot00000000000000#ifndef BACKENDS_SDL3_H #define BACKENDS_SDL3_H #include "base.h" struct SDL3BackendFactory final : public BackendFactory { public: auto init() -> bool final; auto querySupport(BackendType type) -> bool final; auto enumerate(BackendType type) -> std::vector final; auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_SDL3_H */ openal-soft-1.24.2/alc/backends/sndio.cpp000066400000000000000000000377451474041540300202020ustar00rootroot00000000000000/** * OpenAL cross platform audio library * Copyright (C) 1999-2007 by authors. * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * Or go to http://www.gnu.org/copyleft/lgpl.html */ #include "config.h" #include "sndio.hpp" #include #include #include #include #include #include #include #include "althrd_setname.h" #include "core/device.h" #include "core/helpers.h" #include "core/logging.h" #include "ringbuffer.h" #include namespace { using namespace std::string_view_literals; [[nodiscard]] constexpr auto GetDefaultName() noexcept { return "SndIO Default"sv; } struct SioPar : public sio_par { SioPar() : sio_par{} { sio_initpar(this); } void clear() { sio_initpar(this); } }; struct SndioPlayback final : public BackendBase { explicit SndioPlayback(DeviceBase *device) noexcept : BackendBase{device} { } ~SndioPlayback() override; int mixerProc(); void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; sio_hdl *mSndHandle{nullptr}; uint mFrameStep{}; std::vector mBuffer; std::atomic mKillNow{true}; std::thread mThread; }; SndioPlayback::~SndioPlayback() { if(mSndHandle) sio_close(mSndHandle); mSndHandle = nullptr; } int SndioPlayback::mixerProc() { const size_t frameStep{mFrameStep}; const size_t frameSize{frameStep * mDevice->bytesFromFmt()}; SetRTPriority(); althrd_setname(GetMixerThreadName()); while(!mKillNow.load(std::memory_order_acquire) && mDevice->Connected.load(std::memory_order_acquire)) { al::span buffer{mBuffer}; mDevice->renderSamples(buffer.data(), static_cast(buffer.size() / frameSize), frameStep); while(!buffer.empty() && !mKillNow.load(std::memory_order_acquire)) { size_t wrote{sio_write(mSndHandle, buffer.data(), buffer.size())}; if(wrote > buffer.size() || wrote == 0) { ERR("sio_write failed: {:#x}", wrote); mDevice->handleDisconnect("Failed to write playback samples"); break; } buffer = buffer.subspan(wrote); } } return 0; } void SndioPlayback::open(std::string_view name) { if(name.empty()) name = GetDefaultName(); else if(name != GetDefaultName()) throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found", name}; sio_hdl *sndHandle{sio_open(nullptr, SIO_PLAY, 0)}; if(!sndHandle) throw al::backend_exception{al::backend_error::NoDevice, "Could not open backend device"}; if(mSndHandle) sio_close(mSndHandle); mSndHandle = sndHandle; mDeviceName = name; } bool SndioPlayback::reset() { SioPar par; auto tryfmt = mDevice->FmtType; while(true) { switch(tryfmt) { case DevFmtByte: par.bits = 8; par.sig = 1; break; case DevFmtUByte: par.bits = 8; par.sig = 0; break; case DevFmtShort: par.bits = 16; par.sig = 1; break; case DevFmtUShort: par.bits = 16; par.sig = 0; break; case DevFmtFloat: case DevFmtInt: par.bits = 32; par.sig = 1; break; case DevFmtUInt: par.bits = 32; par.sig = 0; break; } par.bps = SIO_BPS(par.bits); par.le = SIO_LE_NATIVE; par.msb = 1; par.rate = mDevice->mSampleRate; par.pchan = mDevice->channelsFromFmt(); par.round = mDevice->mUpdateSize; par.appbufsz = mDevice->mBufferSize - mDevice->mUpdateSize; if(!par.appbufsz) par.appbufsz = mDevice->mUpdateSize; try { if(!sio_setpar(mSndHandle, &par)) throw al::backend_exception{al::backend_error::DeviceError, "Failed to set device parameters"}; par.clear(); if(!sio_getpar(mSndHandle, &par)) throw al::backend_exception{al::backend_error::DeviceError, "Failed to get device parameters"}; if(par.bps > 1 && par.le != SIO_LE_NATIVE) throw al::backend_exception{al::backend_error::DeviceError, "{}-endian samples not supported", par.le ? "Little" : "Big"}; if(par.bits < par.bps*8 && !par.msb) throw al::backend_exception{al::backend_error::DeviceError, "MSB-padded samples not supported ({} of {} bits)", par.bits, par.bps*8}; if(par.pchan < 1) throw al::backend_exception{al::backend_error::DeviceError, "No playback channels on device"}; break; } catch(al::backend_exception &e) { if(tryfmt == DevFmtShort) throw; par.clear(); tryfmt = DevFmtShort; } } if(par.bps == 1) mDevice->FmtType = (par.sig==1) ? DevFmtByte : DevFmtUByte; else if(par.bps == 2) mDevice->FmtType = (par.sig==1) ? DevFmtShort : DevFmtUShort; else if(par.bps == 4) mDevice->FmtType = (par.sig==1) ? DevFmtInt : DevFmtUInt; else throw al::backend_exception{al::backend_error::DeviceError, "Unhandled sample format: {} {}-bit", (par.sig?"signed":"unsigned"), par.bps*8}; mFrameStep = par.pchan; if(par.pchan != mDevice->channelsFromFmt()) { WARN("Got {} channel{} for {}", par.pchan, (par.pchan==1)?"":"s", DevFmtChannelsString(mDevice->FmtChans)); if(par.pchan < 2) mDevice->FmtChans = DevFmtMono; else mDevice->FmtChans = DevFmtStereo; } mDevice->mSampleRate = par.rate; setDefaultChannelOrder(); mDevice->mUpdateSize = par.round; mDevice->mBufferSize = par.bufsz + par.round; mBuffer.resize(size_t{mDevice->mUpdateSize} * par.pchan*par.bps); if(par.sig == 1) std::fill(mBuffer.begin(), mBuffer.end(), std::byte{}); else if(par.bits == 8) std::fill_n(mBuffer.data(), mBuffer.size(), std::byte(0x80)); else if(par.bits == 16) std::fill_n(reinterpret_cast(mBuffer.data()), mBuffer.size()/2, 0x8000); else if(par.bits == 32) std::fill_n(reinterpret_cast(mBuffer.data()), mBuffer.size()/4, 0x80000000u); return true; } void SndioPlayback::start() { if(!sio_start(mSndHandle)) throw al::backend_exception{al::backend_error::DeviceError, "Error starting playback"}; try { mKillNow.store(false, std::memory_order_release); mThread = std::thread{&SndioPlayback::mixerProc, this}; } catch(std::exception& e) { sio_stop(mSndHandle); throw al::backend_exception{al::backend_error::DeviceError, "Failed to start mixing thread: {}", e.what()}; } } void SndioPlayback::stop() { if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable()) return; mThread.join(); if(!sio_stop(mSndHandle)) ERR("Error stopping device"); } /* TODO: This could be improved by avoiding the ring buffer and record thread, * counting the available samples with the sio_onmove callback and reading * directly from the device. However, this depends on reasonable support for * capture buffer sizes apps may request. */ struct SndioCapture final : public BackendBase { explicit SndioCapture(DeviceBase *device) noexcept : BackendBase{device} { } ~SndioCapture() override; int recordProc(); void open(std::string_view name) override; void start() override; void stop() override; void captureSamples(std::byte *buffer, uint samples) override; uint availableSamples() override; sio_hdl *mSndHandle{nullptr}; RingBufferPtr mRing; std::atomic mKillNow{true}; std::thread mThread; }; SndioCapture::~SndioCapture() { if(mSndHandle) sio_close(mSndHandle); mSndHandle = nullptr; } int SndioCapture::recordProc() { SetRTPriority(); althrd_setname(GetRecordThreadName()); const uint frameSize{mDevice->frameSizeFromFmt()}; int nfds_pre{sio_nfds(mSndHandle)}; if(nfds_pre <= 0) { mDevice->handleDisconnect("Incorrect return value from sio_nfds(): {}", nfds_pre); return 1; } auto fds = std::vector(static_cast(nfds_pre)); while(!mKillNow.load(std::memory_order_acquire) && mDevice->Connected.load(std::memory_order_acquire)) { /* Wait until there's some samples to read. */ const int nfds{sio_pollfd(mSndHandle, fds.data(), POLLIN)}; if(nfds <= 0) { mDevice->handleDisconnect("Failed to get polling fds: {}", nfds); break; } int pollres{::poll(fds.data(), fds.size(), 2000)}; if(pollres < 0) { if(errno == EINTR) continue; mDevice->handleDisconnect("Poll error: {}", std::generic_category().message(errno)); break; } if(pollres == 0) continue; const int revents{sio_revents(mSndHandle, fds.data())}; if((revents&POLLHUP)) { mDevice->handleDisconnect("Got POLLHUP from poll events"); break; } if(!(revents&POLLIN)) continue; auto data = mRing->getWriteVector(); al::span buffer{data[0].buf, data[0].len*frameSize}; while(!buffer.empty()) { size_t got{sio_read(mSndHandle, buffer.data(), buffer.size())}; if(got == 0) break; if(got > buffer.size()) { ERR("sio_read failed: {:#x}", got); mDevice->handleDisconnect("sio_read failed: {:#x}", got); break; } mRing->writeAdvance(got / frameSize); buffer = buffer.subspan(got); if(buffer.empty()) { data = mRing->getWriteVector(); buffer = {data[0].buf, data[0].len*frameSize}; } } if(buffer.empty()) { /* Got samples to read, but no place to store it. Drop it. */ static std::array junk; sio_read(mSndHandle, junk.data(), junk.size() - (junk.size()%frameSize)); } } return 0; } void SndioCapture::open(std::string_view name) { if(name.empty()) name = GetDefaultName(); else if(name != GetDefaultName()) throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found", name}; mSndHandle = sio_open(nullptr, SIO_REC, true); if(mSndHandle == nullptr) throw al::backend_exception{al::backend_error::NoDevice, "Could not open backend device"}; SioPar par; switch(mDevice->FmtType) { case DevFmtByte: par.bits = 8; par.sig = 1; break; case DevFmtUByte: par.bits = 8; par.sig = 0; break; case DevFmtShort: par.bits = 16; par.sig = 1; break; case DevFmtUShort: par.bits = 16; par.sig = 0; break; case DevFmtInt: par.bits = 32; par.sig = 1; break; case DevFmtUInt: par.bits = 32; par.sig = 0; break; case DevFmtFloat: throw al::backend_exception{al::backend_error::DeviceError, "{} capture samples not supported", DevFmtTypeString(mDevice->FmtType)}; } par.bps = SIO_BPS(par.bits); par.le = SIO_LE_NATIVE; par.msb = 1; par.rchan = mDevice->channelsFromFmt(); par.rate = mDevice->mSampleRate; par.appbufsz = std::max(mDevice->mBufferSize, mDevice->mSampleRate/10u); par.round = std::min(par.appbufsz/2u, mDevice->mSampleRate/40u); if(!sio_setpar(mSndHandle, &par) || !sio_getpar(mSndHandle, &par)) throw al::backend_exception{al::backend_error::DeviceError, "Failed to set device parameters"}; if(par.bps > 1 && par.le != SIO_LE_NATIVE) throw al::backend_exception{al::backend_error::DeviceError, "{}-endian samples not supported", par.le ? "Little" : "Big"}; if(par.bits < par.bps*8 && !par.msb) throw al::backend_exception{al::backend_error::DeviceError, "Padded samples not supported (got {} of {} bits)", par.bits, par.bps*8}; auto match_fmt = [](DevFmtType fmttype, const sio_par &p) -> bool { return (fmttype == DevFmtByte && p.bps == 1 && p.sig != 0) || (fmttype == DevFmtUByte && p.bps == 1 && p.sig == 0) || (fmttype == DevFmtShort && p.bps == 2 && p.sig != 0) || (fmttype == DevFmtUShort && p.bps == 2 && p.sig == 0) || (fmttype == DevFmtInt && p.bps == 4 && p.sig != 0) || (fmttype == DevFmtUInt && p.bps == 4 && p.sig == 0); }; if(!match_fmt(mDevice->FmtType, par) || mDevice->channelsFromFmt() != par.rchan || mDevice->mSampleRate != par.rate) throw al::backend_exception{al::backend_error::DeviceError, "Failed to set format {} {} {}hz, got {}{} {}-channel {}hz instead", DevFmtTypeString(mDevice->FmtType), DevFmtChannelsString(mDevice->FmtChans), mDevice->mSampleRate, par.sig?'s':'u', par.bps*8, par.rchan, par.rate}; mRing = RingBuffer::Create(mDevice->mBufferSize, size_t{par.bps}*par.rchan, false); mDevice->mBufferSize = static_cast(mRing->writeSpace()); mDevice->mUpdateSize = par.round; setDefaultChannelOrder(); mDeviceName = name; } void SndioCapture::start() { if(!sio_start(mSndHandle)) throw al::backend_exception{al::backend_error::DeviceError, "Error starting capture"}; try { mKillNow.store(false, std::memory_order_release); mThread = std::thread{&SndioCapture::recordProc, this}; } catch(std::exception& e) { sio_stop(mSndHandle); throw al::backend_exception{al::backend_error::DeviceError, "Failed to start capture thread: {}", e.what()}; } } void SndioCapture::stop() { if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable()) return; mThread.join(); if(!sio_stop(mSndHandle)) ERR("Error stopping device"); } void SndioCapture::captureSamples(std::byte *buffer, uint samples) { std::ignore = mRing->read(buffer, samples); } uint SndioCapture::availableSamples() { return static_cast(mRing->readSpace()); } } // namespace BackendFactory &SndIOBackendFactory::getFactory() { static SndIOBackendFactory factory{}; return factory; } bool SndIOBackendFactory::init() { return true; } bool SndIOBackendFactory::querySupport(BackendType type) { return (type == BackendType::Playback || type == BackendType::Capture); } auto SndIOBackendFactory::enumerate(BackendType type) -> std::vector { switch(type) { case BackendType::Playback: case BackendType::Capture: return std::vector{std::string{GetDefaultName()}}; } return {}; } BackendPtr SndIOBackendFactory::createBackend(DeviceBase *device, BackendType type) { if(type == BackendType::Playback) return BackendPtr{new SndioPlayback{device}}; if(type == BackendType::Capture) return BackendPtr{new SndioCapture{device}}; return nullptr; } openal-soft-1.24.2/alc/backends/sndio.hpp000066400000000000000000000007261474041540300201740ustar00rootroot00000000000000#ifndef BACKENDS_SNDIO_HPP #define BACKENDS_SNDIO_HPP #include "base.h" struct SndIOBackendFactory final : public BackendFactory { public: auto init() -> bool final; auto querySupport(BackendType type) -> bool final; auto enumerate(BackendType type) -> std::vector final; auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_SNDIO_HPP */ openal-soft-1.24.2/alc/backends/solaris.cpp000066400000000000000000000202661474041540300205300ustar00rootroot00000000000000/** * OpenAL cross platform audio library * Copyright (C) 1999-2007 by authors. * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * Or go to http://www.gnu.org/copyleft/lgpl.html */ #include "config.h" #include "solaris.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "alc/alconfig.h" #include "alstring.h" #include "althrd_setname.h" #include "core/device.h" #include "core/helpers.h" #include "core/logging.h" #include namespace { using namespace std::string_view_literals; [[nodiscard]] constexpr auto GetDefaultName() noexcept { return "Solaris Default"sv; } std::string solaris_driver{"/dev/audio"}; struct SolarisBackend final : public BackendBase { explicit SolarisBackend(DeviceBase *device) noexcept : BackendBase{device} { } ~SolarisBackend() override; int mixerProc(); void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; int mFd{-1}; uint mFrameStep{}; std::vector mBuffer; std::atomic mKillNow{true}; std::thread mThread; }; SolarisBackend::~SolarisBackend() { if(mFd != -1) close(mFd); mFd = -1; } int SolarisBackend::mixerProc() { SetRTPriority(); althrd_setname(GetMixerThreadName()); const size_t frame_step{mDevice->channelsFromFmt()}; const size_t frame_size{mDevice->frameSizeFromFmt()}; while(!mKillNow.load(std::memory_order_acquire) && mDevice->Connected.load(std::memory_order_acquire)) { pollfd pollitem{}; pollitem.fd = mFd; pollitem.events = POLLOUT; int pret{poll(&pollitem, 1, 1000)}; if(pret < 0) { if(errno == EINTR || errno == EAGAIN) continue; ERR("poll failed: {}", strerror(errno)); mDevice->handleDisconnect("Failed to wait for playback buffer: {}", strerror(errno)); break; } else if(pret == 0) { WARN("poll timeout"); continue; } al::span buffer{mBuffer}; mDevice->renderSamples(buffer.data(), static_cast(buffer.size()/frame_size), frame_step); while(!buffer.empty() && !mKillNow.load(std::memory_order_acquire)) { ssize_t wrote{write(mFd, buffer.data(), buffer.size())}; if(wrote < 0) { if(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) continue; ERR("write failed: {}", strerror(errno)); mDevice->handleDisconnect("Failed to write playback samples: {}", strerror(errno)); break; } buffer = buffer.subspan(static_cast(wrote)); } } return 0; } void SolarisBackend::open(std::string_view name) { if(name.empty()) name = GetDefaultName(); else if(name != GetDefaultName()) throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found", name}; int fd{::open(solaris_driver.c_str(), O_WRONLY)}; if(fd == -1) throw al::backend_exception{al::backend_error::NoDevice, "Could not open {}: {}", solaris_driver, strerror(errno)}; if(mFd != -1) ::close(mFd); mFd = fd; mDeviceName = name; } bool SolarisBackend::reset() { audio_info_t info; AUDIO_INITINFO(&info); info.play.sample_rate = mDevice->mSampleRate; info.play.channels = mDevice->channelsFromFmt(); switch(mDevice->FmtType) { case DevFmtByte: info.play.precision = 8; info.play.encoding = AUDIO_ENCODING_LINEAR; break; case DevFmtUByte: info.play.precision = 8; info.play.encoding = AUDIO_ENCODING_LINEAR8; break; case DevFmtUShort: case DevFmtInt: case DevFmtUInt: case DevFmtFloat: mDevice->FmtType = DevFmtShort; /* fall-through */ case DevFmtShort: info.play.precision = 16; info.play.encoding = AUDIO_ENCODING_LINEAR; break; } info.play.buffer_size = mDevice->mBufferSize * mDevice->frameSizeFromFmt(); if(ioctl(mFd, AUDIO_SETINFO, &info) < 0) { ERR("ioctl failed: {}", strerror(errno)); return false; } if(mDevice->channelsFromFmt() != info.play.channels) { if(info.play.channels >= 2) mDevice->FmtChans = DevFmtStereo; else if(info.play.channels == 1) mDevice->FmtChans = DevFmtMono; else throw al::backend_exception{al::backend_error::DeviceError, "Got {} device channels", info.play.channels}; } if(info.play.precision == 8 && info.play.encoding == AUDIO_ENCODING_LINEAR8) mDevice->FmtType = DevFmtUByte; else if(info.play.precision == 8 && info.play.encoding == AUDIO_ENCODING_LINEAR) mDevice->FmtType = DevFmtByte; else if(info.play.precision == 16 && info.play.encoding == AUDIO_ENCODING_LINEAR) mDevice->FmtType = DevFmtShort; else if(info.play.precision == 32 && info.play.encoding == AUDIO_ENCODING_LINEAR) mDevice->FmtType = DevFmtInt; else { ERR("Got unhandled sample type: {} ({:#x})", info.play.precision, info.play.encoding); return false; } uint frame_size{mDevice->bytesFromFmt() * info.play.channels}; mFrameStep = info.play.channels; mDevice->mSampleRate = info.play.sample_rate; mDevice->mBufferSize = info.play.buffer_size / frame_size; /* How to get the actual period size/count? */ mDevice->mUpdateSize = mDevice->mBufferSize / 2; setDefaultChannelOrder(); mBuffer.resize(mDevice->mUpdateSize * size_t{frame_size}); std::fill(mBuffer.begin(), mBuffer.end(), std::byte{}); return true; } void SolarisBackend::start() { try { mKillNow.store(false, std::memory_order_release); mThread = std::thread{&SolarisBackend::mixerProc, this}; } catch(std::exception& e) { throw al::backend_exception{al::backend_error::DeviceError, "Failed to start mixing thread: {}", e.what()}; } } void SolarisBackend::stop() { if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable()) return; mThread.join(); if(ioctl(mFd, AUDIO_DRAIN) < 0) ERR("Error draining device: {}", strerror(errno)); } } // namespace BackendFactory &SolarisBackendFactory::getFactory() { static SolarisBackendFactory factory{}; return factory; } bool SolarisBackendFactory::init() { if(auto devopt = ConfigValueStr({}, "solaris", "device")) solaris_driver = std::move(*devopt); return true; } bool SolarisBackendFactory::querySupport(BackendType type) { return type == BackendType::Playback; } auto SolarisBackendFactory::enumerate(BackendType type) -> std::vector { switch(type) { case BackendType::Playback: if(struct stat buf{}; stat(solaris_driver.c_str(), &buf) == 0) return std::vector{std::string{GetDefaultName()}}; break; case BackendType::Capture: break; } return {}; } BackendPtr SolarisBackendFactory::createBackend(DeviceBase *device, BackendType type) { if(type == BackendType::Playback) return BackendPtr{new SolarisBackend{device}}; return nullptr; } openal-soft-1.24.2/alc/backends/solaris.h000066400000000000000000000007301474041540300201670ustar00rootroot00000000000000#ifndef BACKENDS_SOLARIS_H #define BACKENDS_SOLARIS_H #include "base.h" struct SolarisBackendFactory final : public BackendFactory { public: auto init() -> bool final; auto querySupport(BackendType type) -> bool final; auto enumerate(BackendType type) -> std::vector final; auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_SOLARIS_H */ openal-soft-1.24.2/alc/backends/wasapi.cpp000066400000000000000000003273141474041540300203440ustar00rootroot00000000000000/** * OpenAL cross platform audio library * Copyright (C) 2011 by authors. * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * Or go to http://www.gnu.org/copyleft/lgpl.html */ #include "config.h" #include "wasapi.h" #define WIN32_LEAN_AND_MEAN #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef _WAVEFORMATEXTENSIBLE_ #include #include #endif #include #include #include #include #include #include #include #include #include #include #include #include "albit.h" #include "alc/alconfig.h" #include "alnumeric.h" #include "alspan.h" #include "althrd_setname.h" #include "comptr.h" #include "core/converter.h" #include "core/device.h" #include "core/logging.h" #include "fmt/core.h" #include "fmt/chrono.h" #include "ringbuffer.h" #include "strutils.h" #if ALSOFT_UWP #include // !!This is important!! #include #include #include #include #include #include "alstring.h" #endif /* Some headers seem to define these as macros for __uuidof, which is annoying * since some headers don't declare them at all. Hopefully the ifdef is enough * to tell if they need to be declared. */ #ifndef KSDATAFORMAT_SUBTYPE_PCM DEFINE_GUID(KSDATAFORMAT_SUBTYPE_PCM, 0x00000001, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71); #endif #ifndef KSDATAFORMAT_SUBTYPE_IEEE_FLOAT DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, 0x00000003, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71); #endif #if !ALSOFT_UWP DEFINE_DEVPROPKEY(DEVPKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80,0x20, 0x67,0xd1,0x46,0xa8,0x50,0xe0, 14); DEFINE_PROPERTYKEY(PKEY_AudioEndpoint_FormFactor, 0x1da5d803, 0xd492, 0x4edd, 0x8c,0x23, 0xe0,0xc0,0xff,0xee,0x7f,0x0e, 0); DEFINE_PROPERTYKEY(PKEY_AudioEndpoint_GUID, 0x1da5d803, 0xd492, 0x4edd, 0x8c, 0x23,0xe0, 0xc0,0xff,0xee,0x7f,0x0e, 4 ); #endif namespace { #if ALSOFT_UWP using namespace winrt; using namespace Windows::Foundation; using namespace Windows::Media::Devices; using namespace Windows::Devices::Enumeration; using namespace Windows::Media::Devices; #endif #ifndef E_NOTFOUND #define E_NOTFOUND E_NOINTERFACE #endif using namespace std::string_view_literals; using std::chrono::nanoseconds; using std::chrono::milliseconds; using std::chrono::seconds; using ReferenceTime = std::chrono::duration>; #define MONO SPEAKER_FRONT_CENTER #define STEREO (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT) #define QUAD (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT) #define X5DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT) #define X5DOT1REAR (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT) #define X6DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_CENTER|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT) #define X7DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT) #define X7DOT1DOT4 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT|SPEAKER_TOP_FRONT_LEFT|SPEAKER_TOP_FRONT_RIGHT|SPEAKER_TOP_BACK_LEFT|SPEAKER_TOP_BACK_RIGHT) constexpr auto MaskFromTopBits(DWORD b) noexcept -> DWORD { b |= b>>1; b |= b>>2; b |= b>>4; b |= b>>8; b |= b>>16; return b; } constexpr DWORD MonoMask{MaskFromTopBits(MONO)}; constexpr DWORD StereoMask{MaskFromTopBits(STEREO)}; constexpr DWORD QuadMask{MaskFromTopBits(QUAD)}; constexpr DWORD X51Mask{MaskFromTopBits(X5DOT1)}; constexpr DWORD X51RearMask{MaskFromTopBits(X5DOT1REAR)}; constexpr DWORD X61Mask{MaskFromTopBits(X6DOT1)}; constexpr DWORD X71Mask{MaskFromTopBits(X7DOT1)}; constexpr DWORD X714Mask{MaskFromTopBits(X7DOT1DOT4)}; #ifndef _MSC_VER constexpr AudioObjectType operator|(AudioObjectType lhs, AudioObjectType rhs) noexcept { return static_cast(lhs | al::to_underlying(rhs)); } #endif constexpr AudioObjectType ChannelMask_Mono{AudioObjectType_FrontCenter}; constexpr AudioObjectType ChannelMask_Stereo{AudioObjectType_FrontLeft | AudioObjectType_FrontRight}; constexpr AudioObjectType ChannelMask_Quad{AudioObjectType_FrontLeft | AudioObjectType_FrontRight | AudioObjectType_BackLeft | AudioObjectType_BackRight}; constexpr AudioObjectType ChannelMask_X51{AudioObjectType_FrontLeft | AudioObjectType_FrontRight | AudioObjectType_FrontCenter | AudioObjectType_LowFrequency | AudioObjectType_SideLeft | AudioObjectType_SideRight}; constexpr AudioObjectType ChannelMask_X61{AudioObjectType_FrontLeft | AudioObjectType_FrontRight | AudioObjectType_FrontCenter | AudioObjectType_LowFrequency | AudioObjectType_SideLeft | AudioObjectType_SideRight | AudioObjectType_BackCenter}; constexpr AudioObjectType ChannelMask_X71{AudioObjectType_FrontLeft | AudioObjectType_FrontRight | AudioObjectType_FrontCenter | AudioObjectType_LowFrequency | AudioObjectType_SideLeft | AudioObjectType_SideRight | AudioObjectType_BackLeft | AudioObjectType_BackRight}; constexpr AudioObjectType ChannelMask_X714{AudioObjectType_FrontLeft | AudioObjectType_FrontRight | AudioObjectType_FrontCenter | AudioObjectType_LowFrequency | AudioObjectType_SideLeft | AudioObjectType_SideRight | AudioObjectType_BackLeft | AudioObjectType_BackRight | AudioObjectType_TopFrontLeft | AudioObjectType_TopFrontRight | AudioObjectType_TopBackLeft | AudioObjectType_TopBackRight}; constexpr AudioObjectType ChannelMask_X7144{AudioObjectType_FrontLeft | AudioObjectType_FrontRight | AudioObjectType_FrontCenter | AudioObjectType_LowFrequency | AudioObjectType_SideLeft | AudioObjectType_SideRight | AudioObjectType_BackLeft | AudioObjectType_BackRight | AudioObjectType_TopFrontLeft | AudioObjectType_TopFrontRight | AudioObjectType_TopBackLeft | AudioObjectType_TopBackRight | AudioObjectType_BottomFrontLeft | AudioObjectType_BottomFrontRight | AudioObjectType_BottomBackLeft | AudioObjectType_BottomBackRight}; template struct overloaded : Ts... { using Ts::operator()...; }; template overloaded(Ts...) -> overloaded; template struct CoTaskMemDeleter { void operator()(T *ptr) const { CoTaskMemFree(ptr); } }; template using unique_coptr = std::unique_ptr>; /* Scales the given reftime value, rounding the result. */ constexpr auto RefTime2Samples(const ReferenceTime &val, DWORD srate) noexcept -> uint { const auto retval = (val*srate + ReferenceTime{seconds{1}}/2) / seconds{1}; return static_cast(std::min(retval, std::numeric_limits::max())); } class GuidPrinter { std::string mMsg; public: explicit GuidPrinter(const GUID &guid) : mMsg{fmt::format( "{{{:08x}-{:04x}-{:04x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}}}", guid.Data1, guid.Data2, guid.Data3, guid.Data4[0], guid.Data4[1], guid.Data4[2], guid.Data4[3], guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7])} { } [[nodiscard]] auto str() const noexcept -> const std::string& { return mMsg; } }; struct PropVariant { PROPVARIANT mProp{}; public: PropVariant() { PropVariantInit(&mProp); } PropVariant(const PropVariant &rhs) : PropVariant{} { PropVariantCopy(&mProp, &rhs.mProp); } ~PropVariant() { clear(); } auto operator=(const PropVariant &rhs) -> PropVariant& { if(this != &rhs) PropVariantCopy(&mProp, &rhs.mProp); return *this; } void clear() { PropVariantClear(&mProp); } PROPVARIANT* get() noexcept { return &mProp; } /* NOLINTBEGIN(cppcoreguidelines-pro-type-union-access) */ [[nodiscard]] auto type() const noexcept -> VARTYPE { return mProp.vt; } template [[nodiscard]] auto value() const -> T { if constexpr(std::is_same_v) { alassert(mProp.vt == VT_UI4 || mProp.vt == VT_UINT); return mProp.uintVal; } else if constexpr(std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v) { alassert(mProp.vt == VT_LPWSTR); return mProp.pwszVal; } } void setBlob(const al::span data) { if constexpr(sizeof(size_t) > sizeof(ULONG)) alassert(data.size() <= std::numeric_limits::max()); mProp.vt = VT_BLOB; mProp.blob.cbSize = static_cast(data.size()); mProp.blob.pBlobData = data.data(); } /* NOLINTEND(cppcoreguidelines-pro-type-union-access) */ }; struct DevMap { std::string name; std::string endpoint_guid; // obtained from PKEY_AudioEndpoint_GUID , set to "Unknown device GUID" if absent. std::wstring devid; template DevMap(T0&& name_, T1&& guid_, T2&& devid_) : name{std::forward(name_)} , endpoint_guid{std::forward(guid_)} , devid{std::forward(devid_)} { } /* To prevent GCC from complaining it doesn't want to inline this. */ ~DevMap(); }; DevMap::~DevMap() = default; bool checkName(const al::span list, const std::string_view name) { auto match_name = [name](const DevMap &entry) -> bool { return entry.name == name; }; return std::find_if(list.cbegin(), list.cend(), match_name) != list.cend(); } struct DeviceList { auto lock() noexcept(noexcept(mMutex.lock())) { return mMutex.lock(); } auto unlock() noexcept(noexcept(mMutex.unlock())) { return mMutex.unlock(); } private: std::mutex mMutex; std::vector mPlayback; std::vector mCapture; std::wstring mPlaybackDefaultId; std::wstring mCaptureDefaultId; friend struct DeviceListLock; }; struct DeviceListLock : public std::unique_lock { using std::unique_lock::unique_lock; [[nodiscard]] auto& getPlaybackList() const noexcept { return mutex()->mPlayback; } [[nodiscard]] auto& getCaptureList() const noexcept { return mutex()->mCapture; } void setPlaybackDefaultId(std::wstring_view devid) const { mutex()->mPlaybackDefaultId = devid; } [[nodiscard]] auto getPlaybackDefaultId() const noexcept -> std::wstring_view { return mutex()->mPlaybackDefaultId; } void setCaptureDefaultId(std::wstring_view devid) const { mutex()->mCaptureDefaultId = devid; } [[nodiscard]] auto getCaptureDefaultId() const noexcept -> std::wstring_view { return mutex()->mCaptureDefaultId; } }; DeviceList gDeviceList; #ifdef AVRTAPI struct AvrtHandleCloser { void operator()(HANDLE handle) { AvRevertMmThreadCharacteristics(handle); } }; using AvrtHandlePtr = std::unique_ptr,AvrtHandleCloser>; #endif #if ALSOFT_UWP enum EDataFlow { eRender = 0, eCapture = (eRender + 1), eAll = (eCapture + 1), EDataFlow_enum_count = (eAll + 1) }; #endif #if ALSOFT_UWP using DeviceHandle = Windows::Devices::Enumeration::DeviceInformation; using EventRegistrationToken = winrt::event_token; #else using DeviceHandle = ComPtr; #endif struct NameGUIDPair { std::string mName; std::string mGuid; }; auto GetDeviceNameAndGuid(const DeviceHandle &device) -> NameGUIDPair { constexpr auto UnknownName = "Unknown Device Name"sv; constexpr auto UnknownGuid = "Unknown Device GUID"sv; #if !ALSOFT_UWP auto ps = ComPtr{}; auto hr = device->OpenPropertyStore(STGM_READ, al::out_ptr(ps)); if(FAILED(hr)) { WARN("OpenPropertyStore failed: {:#x}", as_unsigned(hr)); return NameGUIDPair{std::string{UnknownName}, std::string{UnknownGuid}}; } auto ret = NameGUIDPair{}; auto pvprop = PropVariant{}; hr = ps->GetValue(al::bit_cast(DEVPKEY_Device_FriendlyName), pvprop.get()); if(FAILED(hr)) WARN("GetValue Device_FriendlyName failed: {:#x}", as_unsigned(hr)); else if(pvprop.type() == VT_LPWSTR) ret.mName = wstr_to_utf8(pvprop.value()); else WARN("Unexpected Device_FriendlyName PROPVARIANT type: {:#04x}", pvprop.type()); pvprop.clear(); hr = ps->GetValue(al::bit_cast(PKEY_AudioEndpoint_GUID), pvprop.get()); if(FAILED(hr)) WARN("GetValue AudioEndpoint_GUID failed: {:#x}", as_unsigned(hr)); else if(pvprop.type() == VT_LPWSTR) ret.mGuid = wstr_to_utf8(pvprop.value()); else WARN("Unexpected AudioEndpoint_GUID PROPVARIANT type: {:#04x}", pvprop.type()); #else auto ret = NameGUIDPair{wstr_to_utf8(device.Name()), {}}; // device->Id is DeviceInterfacePath: \\?\SWD#MMDEVAPI#{0.0.0.00000000}.{a21c17a0-fc1d-405e-ab5a-b513422b57d1}#{e6327cad-dcec-4949-ae8a-991e976a79d2} auto devIfPath = device.Id(); if(auto devIdStart = wcsstr(devIfPath.data(), L"}.")) { devIdStart += 2; // L"}." if(auto devIdStartEnd = wcschr(devIdStart, L'#')) { ret.mGuid = wstr_to_utf8(std::wstring_view{devIdStart, static_cast(devIdStartEnd - devIdStart)}); std::transform(ret.mGuid.begin(), ret.mGuid.end(), ret.mGuid.begin(), [](char ch) { return static_cast(std::toupper(ch)); }); } } #endif if(ret.mName.empty()) ret.mName = UnknownName; if(ret.mGuid.empty()) ret.mGuid = UnknownGuid; return ret; } #if !ALSOFT_UWP EndpointFormFactor GetDeviceFormfactor(IMMDevice *device) { ComPtr ps; HRESULT hr{device->OpenPropertyStore(STGM_READ, al::out_ptr(ps))}; if(FAILED(hr)) { WARN("OpenPropertyStore failed: {:#x}", as_unsigned(hr)); return UnknownFormFactor; } EndpointFormFactor formfactor{UnknownFormFactor}; PropVariant pvform; hr = ps->GetValue(PKEY_AudioEndpoint_FormFactor, pvform.get()); if(FAILED(hr)) WARN("GetValue AudioEndpoint_FormFactor failed: {:#x}", as_unsigned(hr)); else if(pvform.type() == VT_UI4) formfactor = static_cast(pvform.value()); else if(pvform.type() != VT_EMPTY) WARN("Unexpected PROPVARIANT type: {:#04x}", pvform.type()); return formfactor; } #endif #if ALSOFT_UWP struct DeviceEnumHelper final : public IActivateAudioInterfaceCompletionHandler { DeviceEnumHelper() { /* TODO: UWP also needs to watch for device added/removed events and * dynamically add/remove devices from the lists. */ mActiveClientEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr); static constexpr auto playback_cb = [](const IInspectable &sender [[maybe_unused]], const DefaultAudioRenderDeviceChangedEventArgs &args) { if(args.Role() == AudioDeviceRole::Default) { const auto msg = std::string{"Default playback device changed: " + wstr_to_utf8(args.Id())}; alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Playback, msg); } }; mRenderDeviceChangedToken = MediaDevice::DefaultAudioRenderDeviceChanged(playback_cb); static constexpr auto capture_cb = [](const IInspectable &sender [[maybe_unused]], const DefaultAudioCaptureDeviceChangedEventArgs &args) { if(args.Role() == AudioDeviceRole::Default) { const auto msg = std::string{"Default capture device changed: " + wstr_to_utf8(args.Id())}; alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Capture, msg); } }; mCaptureDeviceChangedToken = MediaDevice::DefaultAudioCaptureDeviceChanged(capture_cb); } ~DeviceEnumHelper() { MediaDevice::DefaultAudioRenderDeviceChanged(mRenderDeviceChangedToken); MediaDevice::DefaultAudioCaptureDeviceChanged(mCaptureDeviceChangedToken); if(mActiveClientEvent != nullptr) CloseHandle(mActiveClientEvent); mActiveClientEvent = nullptr; } #else struct DeviceEnumHelper final : private IMMNotificationClient { DeviceEnumHelper() = default; ~DeviceEnumHelper() { if(mEnumerator) mEnumerator->UnregisterEndpointNotificationCallback(this); mEnumerator = nullptr; } #endif template auto as() noexcept -> T { return T{this}; } /** -------------------------- IUnknown ----------------------------- */ std::atomic mRefCount{1}; STDMETHODIMP_(ULONG) AddRef() noexcept override { return mRefCount.fetch_add(1u) + 1u; } STDMETHODIMP_(ULONG) Release() noexcept override { return mRefCount.fetch_sub(1u) - 1u; } STDMETHODIMP QueryInterface(const IID& IId, void **UnknownPtrPtr) noexcept override { // Three rules of QueryInterface: // https://docs.microsoft.com/en-us/windows/win32/com/rules-for-implementing-queryinterface // 1. Objects must have identity. // 2. The set of interfaces on an object instance must be static. // 3. It must be possible to query successfully for any interface on an object from any other interface. // If ppvObject(the address) is nullptr, then this method returns E_POINTER. if(!UnknownPtrPtr) return E_POINTER; // https://docs.microsoft.com/en-us/windows/win32/com/implementing-reference-counting // Whenever a client calls a method(or API function), such as QueryInterface, that returns a new interface // pointer, the method being called is responsible for incrementing the reference count through the returned // pointer. For example, when a client first creates an object, it receives an interface pointer to an object // that, from the client's point of view, has a reference count of one. If the client then calls AddRef on the // interface pointer, the reference count becomes two. The client must call Release twice on the interface // pointer to drop all of its references to the object. #if ALSOFT_UWP if(IId == __uuidof(IActivateAudioInterfaceCompletionHandler)) { *UnknownPtrPtr = as(); AddRef(); return S_OK; } #else if(IId == __uuidof(IMMNotificationClient)) { *UnknownPtrPtr = as(); AddRef(); return S_OK; } #endif else if(IId == __uuidof(IAgileObject) || IId == __uuidof(IUnknown)) { *UnknownPtrPtr = as(); AddRef(); return S_OK; } // This method returns S_OK if the interface is supported, and E_NOINTERFACE otherwise. *UnknownPtrPtr = nullptr; return E_NOINTERFACE; } #if ALSOFT_UWP /** ----------------------- IActivateAudioInterfaceCompletionHandler ------------ */ HRESULT ActivateCompleted(IActivateAudioInterfaceAsyncOperation*) override { SetEvent(mActiveClientEvent); // Need to return S_OK return S_OK; } #else /** ----------------------- IMMNotificationClient ------------ */ STDMETHODIMP OnDeviceStateChanged(LPCWSTR /*pwstrDeviceId*/, DWORD /*dwNewState*/) noexcept override { return S_OK; } STDMETHODIMP OnDeviceAdded(LPCWSTR pwstrDeviceId) noexcept override { ComPtr device; HRESULT hr{mEnumerator->GetDevice(pwstrDeviceId, al::out_ptr(device))}; if(FAILED(hr)) { ERR("Failed to get device: {:#x}", as_unsigned(hr)); return S_OK; } ComPtr endpoint; hr = device->QueryInterface(__uuidof(IMMEndpoint), al::out_ptr(endpoint)); if(FAILED(hr)) { ERR("Failed to get device endpoint: {:#x}", as_unsigned(hr)); return S_OK; } EDataFlow flowdir{}; hr = endpoint->GetDataFlow(&flowdir); if(FAILED(hr)) { ERR("Failed to get endpoint data flow: {:#x}", as_unsigned(hr)); return S_OK; } auto devlock = DeviceListLock{gDeviceList}; auto &list = (flowdir==eRender) ? devlock.getPlaybackList() : devlock.getCaptureList(); if(AddDevice(device, pwstrDeviceId, list)) { const auto devtype = (flowdir==eRender) ? alc::DeviceType::Playback : alc::DeviceType::Capture; const std::string msg{"Device added: "+list.back().name}; alc::Event(alc::EventType::DeviceAdded, devtype, msg); } return S_OK; } STDMETHODIMP OnDeviceRemoved(LPCWSTR pwstrDeviceId) noexcept override { auto devlock = DeviceListLock{gDeviceList}; for(auto flowdir : std::array{eRender, eCapture}) { auto &list = (flowdir==eRender) ? devlock.getPlaybackList() : devlock.getCaptureList(); auto devtype = (flowdir==eRender)?alc::DeviceType::Playback : alc::DeviceType::Capture; /* Find the ID in the list to remove. */ auto iter = std::find_if(list.begin(), list.end(), [pwstrDeviceId](const DevMap &entry) noexcept { return pwstrDeviceId == entry.devid; }); if(iter == list.end()) continue; TRACE("Removing device \"{}\", \"{}\", \"{}\"", iter->name, iter->endpoint_guid, wstr_to_utf8(iter->devid)); std::string msg{"Device removed: "+std::move(iter->name)}; list.erase(iter); alc::Event(alc::EventType::DeviceRemoved, devtype, msg); } return S_OK; } /* NOLINTNEXTLINE(clazy-function-args-by-ref) */ STDMETHODIMP OnPropertyValueChanged(LPCWSTR /*pwstrDeviceId*/, const PROPERTYKEY /*key*/) noexcept override { return S_OK; } STDMETHODIMP OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR pwstrDefaultDeviceId) noexcept override { if(role != eMultimedia) return S_OK; const std::wstring_view devid{pwstrDefaultDeviceId ? pwstrDefaultDeviceId : std::wstring_view{}}; if(flow == eRender) { DeviceListLock{gDeviceList}.setPlaybackDefaultId(devid); const std::string msg{"Default playback device changed: " + wstr_to_utf8(devid)}; alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Playback, msg); } else if(flow == eCapture) { DeviceListLock{gDeviceList}.setCaptureDefaultId(devid); const std::string msg{"Default capture device changed: " + wstr_to_utf8(devid)}; alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Capture, msg); } return S_OK; } #endif /** ------------------------ DeviceEnumHelper -------------------------- */ HRESULT init() { #if !ALSOFT_UWP HRESULT hr{CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER, __uuidof(IMMDeviceEnumerator), al::out_ptr(mEnumerator))}; if(SUCCEEDED(hr)) mEnumerator->RegisterEndpointNotificationCallback(this); else WARN("Failed to create IMMDeviceEnumerator instance: {:#x}", as_unsigned(hr)); return hr; #else return S_OK; #endif } std::wstring probeDevices(EDataFlow flowdir, std::vector &list) { std::wstring defaultId; std::vector{}.swap(list); #if !ALSOFT_UWP ComPtr coll; HRESULT hr{mEnumerator->EnumAudioEndpoints(flowdir, DEVICE_STATE_ACTIVE, al::out_ptr(coll))}; if(FAILED(hr)) { ERR("Failed to enumerate audio endpoints: {:#x}", as_unsigned(hr)); return defaultId; } UINT count{0}; hr = coll->GetCount(&count); if(SUCCEEDED(hr) && count > 0) list.reserve(count); ComPtr device; hr = mEnumerator->GetDefaultAudioEndpoint(flowdir, eMultimedia, al::out_ptr(device)); if(SUCCEEDED(hr)) { auto devid = unique_coptr{}; if(auto hr2 = device->GetId(al::out_ptr(devid)); SUCCEEDED(hr2)) defaultId = devid.get(); else ERR("Failed to get device id: {:#x}", as_unsigned(hr)); device = nullptr; } for(UINT i{0};i < count;++i) { hr = coll->Item(i, al::out_ptr(device)); if(FAILED(hr)) continue; auto devid = unique_coptr{}; if(auto hr2 = device->GetId(al::out_ptr(devid)); SUCCEEDED(hr2)) std::ignore = AddDevice(device, devid.get(), list); else ERR("Failed to get device id: {:#x}", as_unsigned(hr)); device = nullptr; } #else const auto deviceRole = Windows::Media::Devices::AudioDeviceRole::Default; auto DefaultAudioId = flowdir == eRender ? MediaDevice::GetDefaultAudioRenderId(deviceRole) : MediaDevice::GetDefaultAudioCaptureId(deviceRole); if(!DefaultAudioId.empty()) { auto deviceInfo = DeviceInformation::CreateFromIdAsync(DefaultAudioId, nullptr, DeviceInformationKind::DeviceInterface).get(); if(deviceInfo) defaultId = deviceInfo.Id().data(); } // Get the string identifier of the audio renderer auto AudioSelector = flowdir == eRender ? MediaDevice::GetAudioRenderSelector() : MediaDevice::GetAudioCaptureSelector(); // Setup the asynchronous callback auto&& DeviceInfoCollection = DeviceInformation::FindAllAsync(AudioSelector, /*PropertyList*/nullptr, DeviceInformationKind::DeviceInterface).get(); if(DeviceInfoCollection) { try { auto deviceCount = DeviceInfoCollection.Size(); for(unsigned int i{0};i < deviceCount;++i) { auto deviceInfo = DeviceInfoCollection.GetAt(i); if(deviceInfo) std::ignore = AddDevice(deviceInfo, deviceInfo.Id().data(), list); } } catch (const winrt::hresult_error& /*ex*/) { } } #endif return defaultId; } private: static bool AddDevice(const DeviceHandle &device, const WCHAR *devid, std::vector &list) { for(auto &entry : list) { if(entry.devid == devid) return false; } auto [name, guid] = GetDeviceNameAndGuid(device); auto count = 1; auto newname = name; while(checkName(list, newname)) newname = fmt::format("{} #{}", name, ++count); const auto &newentry = list.emplace_back(std::move(newname), std::move(guid), devid); TRACE("Got device \"{}\", \"{}\", \"{}\"", newentry.name, newentry.endpoint_guid, wstr_to_utf8(newentry.devid)); return true; } #if !ALSOFT_UWP ComPtr mEnumerator{nullptr}; #else HANDLE mActiveClientEvent{nullptr}; EventRegistrationToken mRenderDeviceChangedToken; EventRegistrationToken mCaptureDeviceChangedToken; #endif static inline std::mutex mMsgLock; static inline std::condition_variable mMsgCond; static inline bool mQuit{false}; [[nodiscard]] static bool quit() { auto lock = std::unique_lock{mMsgLock}; mMsgCond.wait(lock, []{return mQuit;}); return mQuit; } public: static void messageHandler(std::promise *promise); }; /* Manages a DeviceEnumHelper on its own thread, to track available devices. */ void DeviceEnumHelper::messageHandler(std::promise *promise) { TRACE("Starting watcher thread"); ComWrapper com{COINIT_MULTITHREADED}; if(!com) { WARN("Failed to initialize COM: {:#x}", as_unsigned(com.status())); promise->set_value(com.status()); return; } DeviceEnumHelper helper; auto hr = helper.init(); promise->set_value(hr); promise = nullptr; if(FAILED(hr)) return; { auto devlock = DeviceListLock{gDeviceList}; auto defaultId = helper.probeDevices(eRender, devlock.getPlaybackList()); if(!defaultId.empty()) devlock.setPlaybackDefaultId(defaultId); defaultId = helper.probeDevices(eCapture, devlock.getCaptureList()); if(!defaultId.empty()) devlock.setCaptureDefaultId(defaultId); } TRACE("Watcher thread started"); while(!quit()) { /* Do nothing. */ } } #if ALSOFT_UWP struct DeviceHelper final : public IActivateAudioInterfaceCompletionHandler { DeviceHelper() { mActiveClientEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr); } ~DeviceHelper() { if(mActiveClientEvent != nullptr) CloseHandle(mActiveClientEvent); mActiveClientEvent = nullptr; } template auto as() noexcept -> T { return T{this}; } /** -------------------------- IUnknown ----------------------------- */ std::atomic mRefCount{1}; STDMETHODIMP_(ULONG) AddRef() noexcept override { return mRefCount.fetch_add(1u) + 1u; } STDMETHODIMP_(ULONG) Release() noexcept override { return mRefCount.fetch_sub(1u) - 1u; } STDMETHODIMP QueryInterface(const IID& IId, void **UnknownPtrPtr) noexcept override { // Three rules of QueryInterface: // https://docs.microsoft.com/en-us/windows/win32/com/rules-for-implementing-queryinterface // 1. Objects must have identity. // 2. The set of interfaces on an object instance must be static. // 3. It must be possible to query successfully for any interface on an object from any other interface. // If ppvObject(the address) is nullptr, then this method returns E_POINTER. if(!UnknownPtrPtr) return E_POINTER; if(IId == __uuidof(IActivateAudioInterfaceCompletionHandler)) { *UnknownPtrPtr = as(); AddRef(); return S_OK; } else if(IId == __uuidof(IAgileObject) || IId == __uuidof(IUnknown)) { *UnknownPtrPtr = as(); AddRef(); return S_OK; } // This method returns S_OK if the interface is supported, and E_NOINTERFACE otherwise. *UnknownPtrPtr = nullptr; return E_NOINTERFACE; } /** ----------------------- IActivateAudioInterfaceCompletionHandler ------------ */ HRESULT ActivateCompleted(IActivateAudioInterfaceAsyncOperation*) override { SetEvent(mActiveClientEvent); // Need to return S_OK return S_OK; } /** -------------------------- DeviceHelper ----------------------------- */ [[nodiscard]] constexpr auto init() -> HRESULT { return S_OK; } [[nodiscard]] auto openDevice(const std::wstring &devid, EDataFlow flow, DeviceHandle &device) -> HRESULT { const auto deviceRole = Windows::Media::Devices::AudioDeviceRole::Default; auto devIfPath = devid.empty() ? (flow == eRender ? MediaDevice::GetDefaultAudioRenderId(deviceRole) : MediaDevice::GetDefaultAudioCaptureId(deviceRole)) : winrt::hstring(devid.c_str()); if (devIfPath.empty()) return E_POINTER; auto&& deviceInfo = DeviceInformation::CreateFromIdAsync(devIfPath, nullptr, DeviceInformationKind::DeviceInterface).get(); if(!deviceInfo) return E_NOINTERFACE; device = deviceInfo; return S_OK; } [[nodiscard]] auto activateAudioClient(_In_ DeviceHandle &device, _In_ REFIID iid, void **ppv) -> HRESULT { ComPtr asyncOp; HRESULT hr{ActivateAudioInterfaceAsync(device.Id().data(), iid, nullptr, this, al::out_ptr(asyncOp))}; if(FAILED(hr)) return hr; /* I don't like waiting for INFINITE time, but the activate operation * can take an indefinite amount of time since it can require user * input. */ DWORD res{WaitForSingleObjectEx(mActiveClientEvent, INFINITE, FALSE)}; if(res != WAIT_OBJECT_0) { ERR("WaitForSingleObjectEx error: {:#x}", res); return E_FAIL; } HRESULT hrActivateRes{E_FAIL}; ComPtr punkAudioIface; hr = asyncOp->GetActivateResult(&hrActivateRes, al::out_ptr(punkAudioIface)); if(SUCCEEDED(hr)) hr = hrActivateRes; if(FAILED(hr)) return hr; return punkAudioIface->QueryInterface(iid, ppv); } HANDLE mActiveClientEvent{nullptr}; }; #else struct DeviceHelper { DeviceHelper() = default; ~DeviceHelper() = default; [[nodiscard]] auto init() -> HRESULT { HRESULT hr{CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER, __uuidof(IMMDeviceEnumerator), al::out_ptr(mEnumerator))}; if(FAILED(hr)) WARN("Failed to create IMMDeviceEnumerator instance: {:#x}", as_unsigned(hr)); return hr; } [[nodiscard]] auto openDevice(const std::wstring &devid, EDataFlow flow, DeviceHandle &device) const -> HRESULT { HRESULT hr{E_FAIL}; if(mEnumerator) { if(devid.empty()) hr = mEnumerator->GetDefaultAudioEndpoint(flow, eMultimedia, al::out_ptr(device)); else hr = mEnumerator->GetDevice(devid.c_str(), al::out_ptr(device)); } return hr; } [[nodiscard]] static auto activateAudioClient(_In_ DeviceHandle &device, REFIID iid, void **ppv) -> HRESULT { return device->Activate(iid, CLSCTX_INPROC_SERVER, nullptr, ppv); } ComPtr mEnumerator{nullptr}; }; #endif bool MakeExtensible(WAVEFORMATEXTENSIBLE *out, const WAVEFORMATEX *in) { *out = WAVEFORMATEXTENSIBLE{}; if(in->wFormatTag == WAVE_FORMAT_EXTENSIBLE) { *out = *CONTAINING_RECORD(in, const WAVEFORMATEXTENSIBLE, Format); out->Format.cbSize = sizeof(*out) - sizeof(out->Format); } else if(in->wFormatTag == WAVE_FORMAT_PCM) { out->Format = *in; out->Format.cbSize = 0; /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) */ out->Samples.wValidBitsPerSample = out->Format.wBitsPerSample; if(out->Format.nChannels == 1) out->dwChannelMask = MONO; else if(out->Format.nChannels == 2) out->dwChannelMask = STEREO; else ERR("Unhandled PCM channel count: {}", out->Format.nChannels); out->SubFormat = KSDATAFORMAT_SUBTYPE_PCM; } else if(in->wFormatTag == WAVE_FORMAT_IEEE_FLOAT) { out->Format = *in; out->Format.cbSize = 0; /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) */ out->Samples.wValidBitsPerSample = out->Format.wBitsPerSample; if(out->Format.nChannels == 1) out->dwChannelMask = MONO; else if(out->Format.nChannels == 2) out->dwChannelMask = STEREO; else ERR("Unhandled IEEE float channel count: {}", out->Format.nChannels); out->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; } else { ERR("Unhandled format tag: {:#06x}", in->wFormatTag); return false; } return true; } void TraceFormat(const std::string_view msg, const WAVEFORMATEX *format) { constexpr size_t fmtex_extra_size{sizeof(WAVEFORMATEXTENSIBLE)-sizeof(WAVEFORMATEX)}; if(format->wFormatTag == WAVE_FORMAT_EXTENSIBLE && format->cbSize >= fmtex_extra_size) { const auto *fmtex = CONTAINING_RECORD(format, const WAVEFORMATEXTENSIBLE, Format); /* NOLINTBEGIN(cppcoreguidelines-pro-type-union-access) */ TRACE("{}:\n" " FormatTag = {:#06x}\n" " Channels = {}\n" " SamplesPerSec = {}\n" " AvgBytesPerSec = {}\n" " BlockAlign = {}\n" " BitsPerSample = {}\n" " Size = {}\n" " Samples = {}\n" " ChannelMask = {:#x}\n" " SubFormat = {}", msg, fmtex->Format.wFormatTag, fmtex->Format.nChannels, fmtex->Format.nSamplesPerSec, fmtex->Format.nAvgBytesPerSec, fmtex->Format.nBlockAlign, fmtex->Format.wBitsPerSample, fmtex->Format.cbSize, fmtex->Samples.wReserved, fmtex->dwChannelMask, GuidPrinter{fmtex->SubFormat}.str()); /* NOLINTEND(cppcoreguidelines-pro-type-union-access) */ } else TRACE("{}:\n" " FormatTag = {:#06x}\n" " Channels = {}\n" " SamplesPerSec = {}\n" " AvgBytesPerSec = {}\n" " BlockAlign = {}\n" " BitsPerSample = {}\n" " Size = {}", msg, format->wFormatTag, format->nChannels, format->nSamplesPerSec, format->nAvgBytesPerSec, format->nBlockAlign, format->wBitsPerSample, format->cbSize); } /* Duplicates the first sample of each sample frame to the second sample, at * half volume. Essentially converting mono to stereo. */ template void DuplicateSamples(al::span insamples, size_t step) { auto samples = al::span{reinterpret_cast(insamples.data()), insamples.size()/sizeof(T)}; if constexpr(std::is_floating_point_v) { for(size_t i{0};i < samples.size();i+=step) { const auto s = samples[i] * T{0.5}; samples[i+1] = samples[i] = s; } } else if constexpr(std::is_signed_v) { for(size_t i{0};i < samples.size();i+=step) { const auto s = samples[i] / 2; samples[i+1] = samples[i] = T(s); } } else { using ST = std::make_signed_t; static constexpr auto SignBit = T{1u << (sizeof(T)*8 - 1)}; for(size_t i{0};i < samples.size();i+=step) { const auto s = static_cast(samples[i]^SignBit) / 2; samples[i+1] = samples[i] = T(s)^SignBit; } } } void DuplicateSamples(al::span insamples, DevFmtType sampletype, size_t step) { switch(sampletype) { case DevFmtByte: return DuplicateSamples(insamples, step); case DevFmtUByte: return DuplicateSamples(insamples, step); case DevFmtShort: return DuplicateSamples(insamples, step); case DevFmtUShort: return DuplicateSamples(insamples, step); case DevFmtInt: return DuplicateSamples(insamples, step); case DevFmtUInt: return DuplicateSamples(insamples, step); case DevFmtFloat: return DuplicateSamples(insamples, step); } } struct WasapiPlayback final : public BackendBase { explicit WasapiPlayback(DeviceBase *device) noexcept : BackendBase{device} { } ~WasapiPlayback() override; struct PlainDevice { ComPtr mClient{nullptr}; ComPtr mRender{nullptr}; }; struct SpatialDevice { ComPtr mClient{nullptr}; ComPtr mRender{nullptr}; AudioObjectType mStaticMask{}; }; void mixerProc(PlainDevice &audio); void mixerProc(SpatialDevice &audio); auto openProxy(const std::string_view name, DeviceHelper &helper, DeviceHandle &mmdev) -> HRESULT; void finalizeFormat(WAVEFORMATEXTENSIBLE &OutputType); auto initSpatial(DeviceHelper &helper, DeviceHandle &mmdev, SpatialDevice &audio) -> bool; auto resetProxy(DeviceHelper &helper, DeviceHandle &mmdev, std::variant &audiodev) -> HRESULT; void proc_thread(std::string&& name); void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; ClockLatency getClockLatency() override; std::thread mProcThread; std::mutex mProcMutex; std::condition_variable mProcCond; HRESULT mProcResult{E_FAIL}; enum class ThreadState : uint8_t { Initializing, Waiting, Playing, Done }; ThreadState mState{ThreadState::Initializing}; enum class ThreadAction : uint8_t { Nothing, Configure, Play, Quit }; ThreadAction mAction{ThreadAction::Nothing}; static inline DWORD sAvIndex{}; HANDLE mNotifyEvent{nullptr}; UINT32 mOutBufferSize{}, mOutUpdateSize{}; std::vector mResampleBuffer; uint mBufferFilled{0}; SampleConverterPtr mResampler; bool mMonoUpsample{false}; bool mExclusiveMode{false}; WAVEFORMATEXTENSIBLE mFormat{}; std::atomic mPadding{0u}; std::mutex mMutex; std::atomic mKillNow{true}; }; WasapiPlayback::~WasapiPlayback() { if(mProcThread.joinable()) { { auto plock = std::lock_guard{mProcMutex}; mKillNow = true; mAction = ThreadAction::Quit; } mProcCond.notify_all(); mProcThread.join(); } if(mNotifyEvent != nullptr) CloseHandle(mNotifyEvent); mNotifyEvent = nullptr; } FORCE_ALIGN void WasapiPlayback::mixerProc(PlainDevice &audio) { class PriorityControl { const int mOldPriority; public: PriorityControl() : mOldPriority{GetThreadPriority(GetCurrentThread())} { if(!SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL)) ERR("Failed to set priority level for thread"); } ~PriorityControl() { SetThreadPriority(GetCurrentThread(), mOldPriority); } }; auto prioctrl = PriorityControl{}; const uint frame_size{mFormat.Format.nChannels * mFormat.Format.wBitsPerSample / 8u}; const UINT32 buffer_len{mOutBufferSize}; const void *resbufferptr{}; assert(buffer_len > 0); #ifdef AVRTAPI /* TODO: "Audio" or "Pro Audio"? The suggestion is to use "Pro Audio" for * device periods less than 10ms, and "Audio" for greater than or equal to * 10ms. */ auto taskname = (mOutUpdateSize < mFormat.Format.nSamplesPerSec/100) ? L"Pro Audio" : L"Audio"; auto avhandle = AvrtHandlePtr{AvSetMmThreadCharacteristicsW(taskname, &sAvIndex)}; #endif auto prefilling = true; mBufferFilled = 0; while(!mKillNow.load(std::memory_order_relaxed)) { /* For exclusive mode, assume buffer_len sample frames are writable. * The first pass will be a prefill of the buffer, while subsequent * passes will only occur after notify events. * IAudioClient::GetCurrentPadding shouldn't be used with exclusive * streams that use event notifications, according to the docs, we * should just assume a buffer length is writable after notification. */ auto written = UINT32{}; if(!mExclusiveMode) { if(auto hr = audio.mClient->GetCurrentPadding(&written); FAILED(hr)) { ERR("Failed to get padding: {:#x}", as_unsigned(hr)); mDevice->handleDisconnect("Failed to retrieve buffer padding: {:#x}", as_unsigned(hr)); break; } mPadding.store(written, std::memory_order_relaxed); } if(const auto len = uint{buffer_len - written}) { auto buffer = LPBYTE{}; auto hr = audio.mRender->GetBuffer(len, &buffer); if(SUCCEEDED(hr)) { if(mResampler) { auto dlock = std::lock_guard{mMutex}; auto dst = al::span{buffer, size_t{len}*frame_size}; for(UINT32 done{0};done < len;) { if(mBufferFilled == 0) { mDevice->renderSamples(mResampleBuffer.data(), mDevice->mUpdateSize, mFormat.Format.nChannels); resbufferptr = mResampleBuffer.data(); mBufferFilled = mDevice->mUpdateSize; } const auto got = mResampler->convert(&resbufferptr, &mBufferFilled, dst.data(), len-done); dst = dst.subspan(size_t{got}*frame_size); done += got; } mPadding.store(written + len, std::memory_order_relaxed); } else { auto dlock = std::lock_guard{mMutex}; mDevice->renderSamples(buffer, len, mFormat.Format.nChannels); mPadding.store(written + len, std::memory_order_relaxed); } if(mMonoUpsample) { DuplicateSamples(al::span{buffer, size_t{len}*frame_size}, mDevice->FmtType, mFormat.Format.nChannels); } hr = audio.mRender->ReleaseBuffer(len, 0); } if(FAILED(hr)) { ERR("Failed to buffer data: {:#x}", as_unsigned(hr)); mDevice->handleDisconnect("Failed to send playback samples: {:#x}", as_unsigned(hr)); break; } } if(prefilling) { prefilling = false; ResetEvent(mNotifyEvent); if(auto hr = audio.mClient->Start(); FAILED(hr)) { ERR("Failed to start audio client: {:#x}", as_unsigned(hr)); mDevice->handleDisconnect("Failed to start audio client: {:#x}", as_unsigned(hr)); break; } } if(DWORD res{WaitForSingleObjectEx(mNotifyEvent, 2000, FALSE)}; res != WAIT_OBJECT_0) ERR("WaitForSingleObjectEx error: {:#x}", res); } mPadding.store(0u, std::memory_order_release); audio.mClient->Stop(); } FORCE_ALIGN void WasapiPlayback::mixerProc(SpatialDevice &audio) { class PriorityControl { int mOldPriority; public: PriorityControl() : mOldPriority{GetThreadPriority(GetCurrentThread())} { if(!SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL)) ERR("Failed to set priority level for thread"); } ~PriorityControl() { SetThreadPriority(GetCurrentThread(), mOldPriority); } }; auto prioctrl = PriorityControl{}; #ifdef AVRTAPI auto taskname = (mOutUpdateSize < mFormat.Format.nSamplesPerSec/100) ? L"Pro Audio" : L"Audio"; auto avhandle = AvrtHandlePtr{AvSetMmThreadCharacteristicsW(taskname, &sAvIndex)}; #endif std::vector> channels; std::vector buffers; std::vector resbuffers; std::vector tmpbuffers; /* TODO: Set mPadding appropriately. There doesn't seem to be a way to * update it dynamically based on the stream, so a fixed size may be the * best we can do. */ mPadding.store(mOutBufferSize-mOutUpdateSize, std::memory_order_release); ResetEvent(mNotifyEvent); if(HRESULT hr{audio.mRender->Start()}; FAILED(hr)) { ERR("Failed to start spatial audio stream: {:#x}", as_unsigned(hr)); mDevice->handleDisconnect("Failed to start spatial audio stream: {:#x}", as_unsigned(hr)); return; } mBufferFilled = 0; while(!mKillNow.load(std::memory_order_relaxed)) { if(DWORD res{WaitForSingleObjectEx(mNotifyEvent, 1000, FALSE)}; res != WAIT_OBJECT_0) { ERR("WaitForSingleObjectEx error: {:#x}", res); HRESULT hr{audio.mRender->Reset()}; if(FAILED(hr)) { ERR("ISpatialAudioObjectRenderStream::Reset failed: {:#x}", as_unsigned(hr)); mDevice->handleDisconnect("Device lost: {:#x}", as_unsigned(hr)); break; } } UINT32 dynamicCount{}, framesToDo{}; HRESULT hr{audio.mRender->BeginUpdatingAudioObjects(&dynamicCount, &framesToDo)}; if(SUCCEEDED(hr)) { if(channels.empty()) UNLIKELY { auto flags = as_unsigned(al::to_underlying(audio.mStaticMask)); channels.reserve(as_unsigned(al::popcount(flags))); while(flags) { auto id = decltype(flags){1} << al::countr_zero(flags); flags &= ~id; channels.emplace_back(); audio.mRender->ActivateSpatialAudioObject(static_cast(id), al::out_ptr(channels.back())); } buffers.resize(channels.size()); if(mResampler) { tmpbuffers.resize(buffers.size()); resbuffers.resize(buffers.size()); auto bufptr = mResampleBuffer.begin(); for(size_t i{0};i < tmpbuffers.size();++i) { resbuffers[i] = al::to_address(bufptr); bufptr += ptrdiff_t(mDevice->mUpdateSize*sizeof(float)); } } } /* We have to call to get each channel's buffer individually every * update, unfortunately. */ std::transform(channels.cbegin(), channels.cend(), buffers.begin(), [](const ComPtr &obj) -> void* { auto buffer = LPBYTE{}; auto size = UINT32{}; obj->GetBuffer(&buffer, &size); return buffer; }); if(!mResampler) mDevice->renderSamples(buffers, framesToDo); else { std::lock_guard dlock{mMutex}; for(UINT32 pos{0};pos < framesToDo;) { if(mBufferFilled == 0) { mDevice->renderSamples(resbuffers, mDevice->mUpdateSize); std::copy(resbuffers.cbegin(), resbuffers.cend(), tmpbuffers.begin()); mBufferFilled = mDevice->mUpdateSize; } const uint got{mResampler->convertPlanar(tmpbuffers.data(), &mBufferFilled, buffers.data(), framesToDo-pos)}; for(auto &buf : buffers) buf = static_cast(buf) + got; /* NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) */ pos += got; } } hr = audio.mRender->EndUpdatingAudioObjects(); } if(FAILED(hr)) ERR("Failed to update playback objects: {:#x}", as_unsigned(hr)); } mPadding.store(0u, std::memory_order_release); audio.mRender->Stop(); audio.mRender->Reset(); } void WasapiPlayback::proc_thread(std::string&& name) try { auto com = ComWrapper{COINIT_MULTITHREADED}; if(!com) { const auto hr = as_unsigned(com.status()); ERR("CoInitializeEx(nullptr, COINIT_MULTITHREADED) failed: {:#x}", hr); mDevice->handleDisconnect("COM init failed: {:#x}", hr); auto plock = std::lock_guard{mProcMutex}; mProcResult = com.status(); mState = ThreadState::Done; mProcCond.notify_all(); return; } auto helper = DeviceHelper{}; if(HRESULT hr{helper.init()}; FAILED(hr)) { mDevice->handleDisconnect("Helper init failed: {:#x}", as_unsigned(hr)); auto plock = std::lock_guard{mProcMutex}; mProcResult = hr; mState = ThreadState::Done; mProcCond.notify_all(); return; } althrd_setname(GetMixerThreadName()); auto mmdev = DeviceHandle{nullptr}; if(auto hr = openProxy(name, helper, mmdev); FAILED(hr)) { auto plock = std::lock_guard{mProcMutex}; mProcResult = hr; mState = ThreadState::Done; mProcCond.notify_all(); return; } auto audiodev = std::variant{std::in_place_index_t<0>{}}; auto plock = std::unique_lock{mProcMutex}; mProcResult = S_OK; while(mState != ThreadState::Done) { mAction = ThreadAction::Nothing; mState = ThreadState::Waiting; mProcCond.notify_all(); mProcCond.wait(plock, [this]() noexcept { return mAction != ThreadAction::Nothing; }); switch(mAction) { case ThreadAction::Nothing: break; case ThreadAction::Configure: { plock.unlock(); const auto hr = resetProxy(helper, mmdev, audiodev); plock.lock(); mProcResult = hr; } break; case ThreadAction::Play: mKillNow.store(false, std::memory_order_release); mAction = ThreadAction::Nothing; mState = ThreadState::Playing; mProcResult = S_OK; plock.unlock(); mProcCond.notify_all(); std::visit([this](auto &audio) -> void { mixerProc(audio); }, audiodev); plock.lock(); break; case ThreadAction::Quit: mAction = ThreadAction::Nothing; mState = ThreadState::Done; mProcCond.notify_all(); break; } } } catch(...) { auto plock = std::lock_guard{mProcMutex}; mProcResult = E_FAIL; mAction = ThreadAction::Nothing; mState = ThreadState::Done; mProcCond.notify_all(); } void WasapiPlayback::open(std::string_view name) { mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr); if(mNotifyEvent == nullptr) { ERR("Failed to create notify events: {}", GetLastError()); throw al::backend_exception{al::backend_error::DeviceError, "Failed to create notify events"}; } mProcThread = std::thread{&WasapiPlayback::proc_thread, this, std::string{name}}; auto plock = std::unique_lock{mProcMutex}; mProcCond.wait(plock, [this]() noexcept { return mState != ThreadState::Initializing; }); if(mProcResult == E_NOTFOUND) throw al::backend_exception{al::backend_error::NoDevice, "Device \"{}\" not found", name}; if(FAILED(mProcResult) || mState == ThreadState::Done) throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: {:#x}", as_unsigned(mProcResult)}; } auto WasapiPlayback::openProxy(const std::string_view name, DeviceHelper &helper, DeviceHandle &mmdev) -> HRESULT { auto devname = std::string{}; auto devid = std::wstring{}; if(!name.empty()) { auto devlock = DeviceListLock{gDeviceList}; auto list = al::span{devlock.getPlaybackList()}; auto iter = std::find_if(list.cbegin(), list.cend(), [name](const DevMap &entry) -> bool { return entry.name == name || entry.endpoint_guid == name; }); if(iter == list.cend()) { const std::wstring wname{utf8_to_wstr(name)}; iter = std::find_if(list.cbegin(), list.cend(), [&wname](const DevMap &entry) -> bool { return entry.devid == wname; }); } if(iter == list.cend()) { WARN("Failed to find device name matching \"{}\"", name); return E_NOTFOUND; } devname = iter->name; devid = iter->devid; } if(HRESULT hr{helper.openDevice(devid, eRender, mmdev)}; FAILED(hr)) { WARN("Failed to open device \"{}\": {}", devname.empty() ? "(default)"sv : std::string_view{devname}, as_unsigned(hr)); return hr; } if(!devname.empty()) mDeviceName = std::move(devname); else mDeviceName = GetDeviceNameAndGuid(mmdev).mName; return S_OK; } void WasapiPlayback::finalizeFormat(WAVEFORMATEXTENSIBLE &OutputType) { if(!GetConfigValueBool(mDevice->mDeviceName, "wasapi", "allow-resampler", true)) mDevice->mSampleRate = uint(OutputType.Format.nSamplesPerSec); else mDevice->mSampleRate = std::min(mDevice->mSampleRate, uint(OutputType.Format.nSamplesPerSec)); const uint32_t chancount{OutputType.Format.nChannels}; const DWORD chanmask{OutputType.dwChannelMask}; /* Don't update the channel format if the requested format fits what's * supported. */ bool chansok{false}; if(mDevice->Flags.test(ChannelsRequest)) { /* When requesting a channel configuration, make sure it fits the * mask's lsb (to ensure no gaps in the output channels). If there's no * mask, assume the request fits with enough channels. */ switch(mDevice->FmtChans) { case DevFmtMono: chansok = (chancount >= 1 && ((chanmask&MonoMask) == MONO || !chanmask)); if(!chansok && chancount >= 2 && (chanmask&StereoMask) == STEREO) { /* Mono rendering with stereo+ output is handled specially. */ chansok = true; mMonoUpsample = true; } break; case DevFmtStereo: chansok = (chancount >= 2 && ((chanmask&StereoMask) == STEREO || !chanmask)); break; case DevFmtQuad: chansok = (chancount >= 4 && ((chanmask&QuadMask) == QUAD || !chanmask)); break; case DevFmtX51: chansok = (chancount >= 6 && ((chanmask&X51Mask) == X5DOT1 || (chanmask&X51RearMask) == X5DOT1REAR || !chanmask)); break; case DevFmtX61: chansok = (chancount >= 7 && ((chanmask&X61Mask) == X6DOT1 || !chanmask)); break; case DevFmtX71: case DevFmtX3D71: chansok = (chancount >= 8 && ((chanmask&X71Mask) == X7DOT1 || !chanmask)); break; case DevFmtX714: chansok = (chancount >= 12 && ((chanmask&X714Mask) == X7DOT1DOT4 || !chanmask)); case DevFmtX7144: case DevFmtAmbi3D: break; } } if(!chansok) { if(chancount >= 12 && (chanmask&X714Mask) == X7DOT1DOT4) mDevice->FmtChans = DevFmtX714; else if(chancount >= 8 && (chanmask&X71Mask) == X7DOT1) mDevice->FmtChans = DevFmtX71; else if(chancount >= 7 && (chanmask&X61Mask) == X6DOT1) mDevice->FmtChans = DevFmtX61; else if(chancount >= 6 && ((chanmask&X51Mask) == X5DOT1 || (chanmask&X51RearMask) == X5DOT1REAR)) mDevice->FmtChans = DevFmtX51; else if(chancount >= 4 && (chanmask&QuadMask) == QUAD) mDevice->FmtChans = DevFmtQuad; else if(chancount >= 2 && ((chanmask&StereoMask) == STEREO || !chanmask)) mDevice->FmtChans = DevFmtStereo; else if(chancount >= 1 && ((chanmask&MonoMask) == MONO || !chanmask)) mDevice->FmtChans = DevFmtMono; else { ERR("Unhandled extensible channels: {} -- {:#08x}", OutputType.Format.nChannels, OutputType.dwChannelMask); mDevice->FmtChans = DevFmtStereo; OutputType.Format.nChannels = 2; OutputType.dwChannelMask = STEREO; } } if(IsEqualGUID(OutputType.SubFormat, KSDATAFORMAT_SUBTYPE_PCM)) { if(OutputType.Format.wBitsPerSample == 8) mDevice->FmtType = DevFmtUByte; else if(OutputType.Format.wBitsPerSample == 16) mDevice->FmtType = DevFmtShort; else if(OutputType.Format.wBitsPerSample == 32) mDevice->FmtType = DevFmtInt; else { mDevice->FmtType = DevFmtShort; OutputType.Format.wBitsPerSample = 16; } } else if(IsEqualGUID(OutputType.SubFormat, KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)) { mDevice->FmtType = DevFmtFloat; OutputType.Format.wBitsPerSample = 32; } else { ERR("Unhandled format sub-type: {}", GuidPrinter{OutputType.SubFormat}.str()); mDevice->FmtType = DevFmtShort; if(OutputType.Format.wFormatTag != WAVE_FORMAT_EXTENSIBLE) OutputType.Format.wFormatTag = WAVE_FORMAT_PCM; OutputType.Format.wBitsPerSample = 16; OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; } /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) */ OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample; } auto WasapiPlayback::initSpatial(DeviceHelper &helper, DeviceHandle &mmdev, SpatialDevice &audio) -> bool { HRESULT hr{helper.activateAudioClient(mmdev, __uuidof(ISpatialAudioClient), al::out_ptr(audio.mClient))}; if(FAILED(hr)) { ERR("Failed to activate spatial audio client: {:#x}", as_unsigned(hr)); return false; } ComPtr fmtenum; hr = audio.mClient->GetSupportedAudioObjectFormatEnumerator(al::out_ptr(fmtenum)); if(FAILED(hr)) { ERR("Failed to get format enumerator: {:#x}", as_unsigned(hr)); return false; } UINT32 fmtcount{}; hr = fmtenum->GetCount(&fmtcount); if(FAILED(hr) || fmtcount == 0) { ERR("Failed to get format count: {:#08x}", as_unsigned(hr)); return false; } WAVEFORMATEX *preferredFormat{}; hr = fmtenum->GetFormat(0, &preferredFormat); if(FAILED(hr)) { ERR("Failed to get preferred format: {:#x}", as_unsigned(hr)); return false; } TraceFormat("Preferred mix format", preferredFormat); UINT32 maxFrames{}; hr = audio.mClient->GetMaxFrameCount(preferredFormat, &maxFrames); if(FAILED(hr)) ERR("Failed to get max frames: {:#x}", as_unsigned(hr)); else TRACE("Max sample frames: {}", maxFrames); for(UINT32 i{1};i < fmtcount;++i) { WAVEFORMATEX *otherFormat{}; hr = fmtenum->GetFormat(i, &otherFormat); if(FAILED(hr)) ERR("Failed to get format {}: {:#x}", i+1, as_unsigned(hr)); else { TraceFormat("Other mix format", otherFormat); UINT32 otherMaxFrames{}; hr = audio.mClient->GetMaxFrameCount(otherFormat, &otherMaxFrames); if(FAILED(hr)) ERR("Failed to get max frames: {:#x}", as_unsigned(hr)); else TRACE("Max sample frames: {}", otherMaxFrames); } } WAVEFORMATEXTENSIBLE OutputType; if(!MakeExtensible(&OutputType, preferredFormat)) return false; /* This seems to be the format of each "object", which should be mono. */ if(!(OutputType.Format.nChannels == 1 && (OutputType.dwChannelMask == MONO || !OutputType.dwChannelMask))) ERR("Unhandled channel config: {} -- {:#08x}", OutputType.Format.nChannels, OutputType.dwChannelMask); /* Force 32-bit float. This is currently required for planar output. */ if(OutputType.Format.wFormatTag != WAVE_FORMAT_EXTENSIBLE && OutputType.Format.wFormatTag != WAVE_FORMAT_IEEE_FLOAT) { OutputType.Format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT; OutputType.Format.cbSize = 0; } if(OutputType.Format.wBitsPerSample != 32) { OutputType.Format.nAvgBytesPerSec = OutputType.Format.nAvgBytesPerSec * 32u / OutputType.Format.wBitsPerSample; OutputType.Format.nBlockAlign = static_cast(OutputType.Format.nBlockAlign * 32 / OutputType.Format.wBitsPerSample); OutputType.Format.wBitsPerSample = 32; } /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) */ OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample; OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; /* Match the output rate if not requesting anything specific. */ if(!mDevice->Flags.test(FrequencyRequest)) mDevice->mSampleRate = OutputType.Format.nSamplesPerSec; auto getTypeMask = [](DevFmtChannels chans) noexcept { switch(chans) { case DevFmtMono: return ChannelMask_Mono; case DevFmtStereo: return ChannelMask_Stereo; case DevFmtQuad: return ChannelMask_Quad; case DevFmtX51: return ChannelMask_X51; case DevFmtX61: return ChannelMask_X61; case DevFmtX3D71: [[fallthrough]]; case DevFmtX71: return ChannelMask_X71; case DevFmtX714: return ChannelMask_X714; case DevFmtX7144: return ChannelMask_X7144; case DevFmtAmbi3D: break; } return ChannelMask_Stereo; }; SpatialAudioObjectRenderStreamActivationParams streamParams{}; streamParams.ObjectFormat = &OutputType.Format; streamParams.StaticObjectTypeMask = getTypeMask(mDevice->FmtChans); streamParams.Category = AudioCategory_Media; streamParams.EventHandle = mNotifyEvent; PropVariant paramProp{}; paramProp.setBlob({reinterpret_cast(&streamParams), sizeof(streamParams)}); hr = audio.mClient->ActivateSpatialAudioStream(paramProp.get(), __uuidof(ISpatialAudioObjectRenderStream), al::out_ptr(audio.mRender)); if(FAILED(hr)) { ERR("Failed to activate spatial audio stream: {:#x}", as_unsigned(hr)); return false; } audio.mStaticMask = streamParams.StaticObjectTypeMask; mFormat = OutputType; mDevice->FmtType = DevFmtFloat; mDevice->Flags.reset(DirectEar).set(Virtualization); if(streamParams.StaticObjectTypeMask == ChannelMask_Stereo) mDevice->FmtChans = DevFmtStereo; if(!GetConfigValueBool(mDevice->mDeviceName, "wasapi", "allow-resampler", true)) mDevice->mSampleRate = uint(OutputType.Format.nSamplesPerSec); else mDevice->mSampleRate = std::min(mDevice->mSampleRate, uint(OutputType.Format.nSamplesPerSec)); setDefaultWFXChannelOrder(); /* FIXME: Get the real update and buffer size. Presumably the actual device * is configured once ActivateSpatialAudioStream succeeds, and an * IAudioClient from the same IMMDevice accesses the same device * configuration. This isn't obviously correct, but for now assume * IAudioClient::GetDevicePeriod returns the current device period time * that ISpatialAudioObjectRenderStream will try to wake up at. * * Unfortunately this won't get the buffer size of the * ISpatialAudioObjectRenderStream, so we only assume there's two periods. */ mOutUpdateSize = mDevice->mUpdateSize; mOutBufferSize = mOutUpdateSize*2; ReferenceTime per_time{ReferenceTime{seconds{mDevice->mUpdateSize}} / mDevice->mSampleRate}; ComPtr tmpClient; hr = helper.activateAudioClient(mmdev, __uuidof(IAudioClient), al::out_ptr(tmpClient)); if(FAILED(hr)) ERR("Failed to activate audio client: {:#x}", as_unsigned(hr)); else { hr = tmpClient->GetDevicePeriod(&reinterpret_cast(per_time), nullptr); if(FAILED(hr)) ERR("Failed to get device period: {:#x}", as_unsigned(hr)); else { mOutUpdateSize = RefTime2Samples(per_time, mFormat.Format.nSamplesPerSec); mOutBufferSize = mOutUpdateSize*2; } } tmpClient = nullptr; mDevice->mUpdateSize = RefTime2Samples(per_time, mDevice->mSampleRate); mDevice->mBufferSize = mDevice->mUpdateSize*2; mResampler = nullptr; mResampleBuffer.clear(); mResampleBuffer.shrink_to_fit(); mBufferFilled = 0; if(mDevice->mSampleRate != mFormat.Format.nSamplesPerSec) { const auto flags = as_unsigned(al::to_underlying(streamParams.StaticObjectTypeMask)); const auto channelCount = as_unsigned(al::popcount(flags)); mResampler = SampleConverter::Create(mDevice->FmtType, mDevice->FmtType, channelCount, mDevice->mSampleRate, mFormat.Format.nSamplesPerSec, Resampler::FastBSinc24); mResampleBuffer.resize(size_t{mDevice->mUpdateSize} * channelCount * mFormat.Format.wBitsPerSample / 8); TRACE("Created converter for {}/{} format, dst: {}hz ({}), src: {}hz ({})", DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType), mFormat.Format.nSamplesPerSec, mOutUpdateSize, mDevice->mSampleRate, mDevice->mUpdateSize); } return true; } bool WasapiPlayback::reset() { auto plock = std::unique_lock{mProcMutex}; if(mState != ThreadState::Waiting) throw al::backend_exception{al::backend_error::DeviceError, "Invalid state: {}", unsigned{al::to_underlying(mState)}}; mAction = ThreadAction::Configure; mProcCond.notify_all(); mProcCond.wait(plock, [this]() noexcept { return mAction != ThreadAction::Configure; }); if(FAILED(mProcResult) || mState != ThreadState::Waiting) throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: {:#x}", as_unsigned(mProcResult)}; return true; } auto WasapiPlayback::resetProxy(DeviceHelper &helper, DeviceHandle &mmdev, std::variant &audiodev) -> HRESULT { if(GetConfigValueBool(mDevice->mDeviceName, "wasapi", "spatial-api", false)) { if(initSpatial(helper, mmdev, audiodev.emplace())) return S_OK; } mDevice->Flags.reset(Virtualization); mMonoUpsample = false; mExclusiveMode = false; auto &audio = audiodev.emplace(); auto hr = helper.activateAudioClient(mmdev, __uuidof(IAudioClient), al::out_ptr(audio.mClient)); if(FAILED(hr)) { ERR("Failed to reactivate audio client: {:#x}", as_unsigned(hr)); return hr; } auto wfx = unique_coptr{}; hr = audio.mClient->GetMixFormat(al::out_ptr(wfx)); if(FAILED(hr)) { ERR("Failed to get mix format: {:#x}", as_unsigned(hr)); return hr; } TraceFormat("Device mix format", wfx.get()); auto OutputType = WAVEFORMATEXTENSIBLE{}; if(!MakeExtensible(&OutputType, wfx.get())) return E_FAIL; wfx = nullptr; /* Get the buffer and update sizes as a ReferenceTime before potentially * altering the sample rate. */ const auto buf_time = ReferenceTime{seconds{mDevice->mBufferSize}} / mDevice->mSampleRate; const auto per_time = ReferenceTime{seconds{mDevice->mUpdateSize}} / mDevice->mSampleRate; /* Update the mDevice format for non-requested properties. */ bool isRear51{false}; if(!mDevice->Flags.test(FrequencyRequest)) mDevice->mSampleRate = OutputType.Format.nSamplesPerSec; if(!mDevice->Flags.test(ChannelsRequest)) { /* If not requesting a channel configuration, auto-select given what * fits the mask's lsb (to ensure no gaps in the output channels). If * there's no mask, we can only assume mono or stereo. */ const uint32_t chancount{OutputType.Format.nChannels}; const DWORD chanmask{OutputType.dwChannelMask}; if(chancount >= 12 && (chanmask&X714Mask) == X7DOT1DOT4) mDevice->FmtChans = DevFmtX714; else if(chancount >= 8 && (chanmask&X71Mask) == X7DOT1) mDevice->FmtChans = DevFmtX71; else if(chancount >= 7 && (chanmask&X61Mask) == X6DOT1) mDevice->FmtChans = DevFmtX61; else if(chancount >= 6 && (chanmask&X51Mask) == X5DOT1) mDevice->FmtChans = DevFmtX51; else if(chancount >= 6 && (chanmask&X51RearMask) == X5DOT1REAR) { mDevice->FmtChans = DevFmtX51; isRear51 = true; } else if(chancount >= 4 && (chanmask&QuadMask) == QUAD) mDevice->FmtChans = DevFmtQuad; else if(chancount >= 2 && ((chanmask&StereoMask) == STEREO || !chanmask)) mDevice->FmtChans = DevFmtStereo; else if(chancount >= 1 && ((chanmask&MonoMask) == MONO || !chanmask)) mDevice->FmtChans = DevFmtMono; else ERR("Unhandled channel config: {} -- {:#08x}", chancount, chanmask); } else { const uint32_t chancount{OutputType.Format.nChannels}; const DWORD chanmask{OutputType.dwChannelMask}; isRear51 = (chancount == 6 && (chanmask&X51RearMask) == X5DOT1REAR); } /* Request a format matching the mDevice. */ OutputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; switch(mDevice->FmtChans) { case DevFmtMono: OutputType.Format.nChannels = 1; OutputType.dwChannelMask = MONO; break; case DevFmtAmbi3D: mDevice->FmtChans = DevFmtStereo; [[fallthrough]]; case DevFmtStereo: OutputType.Format.nChannels = 2; OutputType.dwChannelMask = STEREO; break; case DevFmtQuad: OutputType.Format.nChannels = 4; OutputType.dwChannelMask = QUAD; break; case DevFmtX51: OutputType.Format.nChannels = 6; OutputType.dwChannelMask = isRear51 ? X5DOT1REAR : X5DOT1; break; case DevFmtX61: OutputType.Format.nChannels = 7; OutputType.dwChannelMask = X6DOT1; break; case DevFmtX71: case DevFmtX3D71: OutputType.Format.nChannels = 8; OutputType.dwChannelMask = X7DOT1; break; case DevFmtX7144: mDevice->FmtChans = DevFmtX714; [[fallthrough]]; case DevFmtX714: OutputType.Format.nChannels = 12; OutputType.dwChannelMask = X7DOT1DOT4; break; } switch(mDevice->FmtType) { case DevFmtByte: mDevice->FmtType = DevFmtUByte; [[fallthrough]]; case DevFmtUByte: OutputType.Format.wBitsPerSample = 8; OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; break; case DevFmtUShort: mDevice->FmtType = DevFmtShort; [[fallthrough]]; case DevFmtShort: OutputType.Format.wBitsPerSample = 16; OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; break; case DevFmtUInt: mDevice->FmtType = DevFmtInt; [[fallthrough]]; case DevFmtInt: OutputType.Format.wBitsPerSample = 32; OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; break; case DevFmtFloat: OutputType.Format.wBitsPerSample = 32; OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; break; } /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) */ OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample; OutputType.Format.nSamplesPerSec = mDevice->mSampleRate; OutputType.Format.nBlockAlign = static_cast(OutputType.Format.nChannels * OutputType.Format.wBitsPerSample / 8); OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec * OutputType.Format.nBlockAlign; const auto sharemode = GetConfigValueBool(mDevice->mDeviceName, "wasapi", "exclusive-mode", false) ? AUDCLNT_SHAREMODE_EXCLUSIVE : AUDCLNT_SHAREMODE_SHARED; mExclusiveMode = (sharemode == AUDCLNT_SHAREMODE_EXCLUSIVE); TraceFormat("Requesting playback format", &OutputType.Format); hr = audio.mClient->IsFormatSupported(sharemode, &OutputType.Format, al::out_ptr(wfx)); if(FAILED(hr)) { if(sharemode == AUDCLNT_SHAREMODE_EXCLUSIVE) { /* For exclusive mode, IAudioClient::IsFormatSupported won't give * back a supported format. However, a common failure is an * unsupported sample type, so try a fallback to 16-bit int. */ if(hr == AUDCLNT_E_UNSUPPORTED_FORMAT && mDevice->FmtType != DevFmtShort) { mDevice->FmtType = DevFmtShort; OutputType.Format.wBitsPerSample = 16; OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) */ OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample; OutputType.Format.nBlockAlign = static_cast(OutputType.Format.nChannels * OutputType.Format.wBitsPerSample / 8); OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec * OutputType.Format.nBlockAlign; hr = audio.mClient->IsFormatSupported(sharemode, &OutputType.Format, al::out_ptr(wfx)); } } else { WARN("Failed to check format support: {:#x}", as_unsigned(hr)); hr = audio.mClient->GetMixFormat(al::out_ptr(wfx)); } } if(FAILED(hr)) { ERR("Failed to find a supported format: {:#x}", as_unsigned(hr)); return hr; } if(wfx) { TraceFormat("Got playback format", wfx.get()); if(!MakeExtensible(&OutputType, wfx.get())) return E_FAIL; wfx = nullptr; finalizeFormat(OutputType); } mFormat = OutputType; #if !ALSOFT_UWP const EndpointFormFactor formfactor{GetDeviceFormfactor(mmdev.get())}; mDevice->Flags.set(DirectEar, (formfactor == Headphones || formfactor == Headset)); #else mDevice->Flags.set(DirectEar, false); #endif setDefaultWFXChannelOrder(); if(sharemode == AUDCLNT_SHAREMODE_EXCLUSIVE) { auto period_time = per_time; auto min_period = ReferenceTime{}; hr = audio.mClient->GetDevicePeriod(nullptr, &reinterpret_cast(min_period)); if(FAILED(hr)) ERR("Failed to get minimum period time: {:#x}", as_unsigned(hr)); else if(min_period > period_time) { period_time = min_period; WARN("Clamping to minimum period time, {}", nanoseconds{min_period}); } hr = audio.mClient->Initialize(sharemode, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, period_time.count(), period_time.count(), &OutputType.Format, nullptr); if(hr == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED) { auto newsize = UINT32{}; hr = audio.mClient->GetBufferSize(&newsize); if(SUCCEEDED(hr)) { period_time = ReferenceTime{seconds{newsize}} / OutputType.Format.nSamplesPerSec; WARN("Adjusting to supported period time, {}", nanoseconds{period_time}); audio.mClient = nullptr; hr = helper.activateAudioClient(mmdev, __uuidof(IAudioClient), al::out_ptr(audio.mClient)); if(FAILED(hr)) { ERR("Failed to reactivate audio client: {:#x}", as_unsigned(hr)); return hr; } hr = audio.mClient->Initialize(sharemode, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, period_time.count(), period_time.count(), &OutputType.Format, nullptr); } } } else hr = audio.mClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, buf_time.count(), 0, &OutputType.Format, nullptr); if(FAILED(hr)) { ERR("Failed to initialize audio client: {:#x}", as_unsigned(hr)); return hr; } auto buffer_len = UINT32{}; auto period_time = ReferenceTime{}; hr = audio.mClient->GetDevicePeriod(&reinterpret_cast(period_time), nullptr); if(SUCCEEDED(hr)) hr = audio.mClient->GetBufferSize(&buffer_len); if(FAILED(hr)) { ERR("Failed to get audio buffer info: {:#x}", as_unsigned(hr)); return hr; } hr = audio.mClient->SetEventHandle(mNotifyEvent); if(FAILED(hr)) { ERR("Failed to set event handle: {:#x}", as_unsigned(hr)); return hr; } hr = audio.mClient->GetService(__uuidof(IAudioRenderClient), al::out_ptr(audio.mRender)); if(FAILED(hr)) { ERR("Failed to get IAudioRenderClient: {:#x}", as_unsigned(hr)); return hr; } mOutBufferSize = buffer_len; if(sharemode == AUDCLNT_SHAREMODE_EXCLUSIVE) { /* For exclusive mode, the buffer size is the update size, and there's * implicitly two update periods on the device. */ mOutUpdateSize = buffer_len; mDevice->mUpdateSize = static_cast(uint64_t{buffer_len} * mDevice->mSampleRate / mFormat.Format.nSamplesPerSec); mDevice->mBufferSize = mDevice->mUpdateSize * 2; } else { mOutUpdateSize = RefTime2Samples(period_time, mFormat.Format.nSamplesPerSec); mDevice->mBufferSize = static_cast(uint64_t{buffer_len} * mDevice->mSampleRate / mFormat.Format.nSamplesPerSec); mDevice->mUpdateSize = std::min(RefTime2Samples(period_time, mDevice->mSampleRate), mDevice->mBufferSize/2u); } mResampler = nullptr; mResampleBuffer.clear(); mResampleBuffer.shrink_to_fit(); mBufferFilled = 0; if(mDevice->mSampleRate != mFormat.Format.nSamplesPerSec) { mResampler = SampleConverter::Create(mDevice->FmtType, mDevice->FmtType, mFormat.Format.nChannels, mDevice->mSampleRate, mFormat.Format.nSamplesPerSec, Resampler::FastBSinc24); mResampleBuffer.resize(size_t{mDevice->mUpdateSize} * mFormat.Format.nChannels * mFormat.Format.wBitsPerSample / 8); TRACE("Created converter for {}/{} format, dst: {}hz ({}), src: {}hz ({})", DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType), mFormat.Format.nSamplesPerSec, mOutUpdateSize, mDevice->mSampleRate, mDevice->mUpdateSize); } return hr; } void WasapiPlayback::start() { auto plock = std::unique_lock{mProcMutex}; if(mState != ThreadState::Waiting) throw al::backend_exception{al::backend_error::DeviceError, "Invalid state: {}", unsigned{al::to_underlying(mState)}}; mAction = ThreadAction::Play; mProcCond.notify_all(); mProcCond.wait(plock, [this]() noexcept { return mAction != ThreadAction::Play; }); if(FAILED(mProcResult) || mState != ThreadState::Playing) throw al::backend_exception{al::backend_error::DeviceError, "Device playback failed: {:#x}", as_unsigned(mProcResult)}; } void WasapiPlayback::stop() { auto plock = std::unique_lock{mProcMutex}; if(mState == ThreadState::Playing) { mKillNow = true; mProcCond.wait(plock, [this]() noexcept { return mState != ThreadState::Playing; }); } } ClockLatency WasapiPlayback::getClockLatency() { std::lock_guard dlock{mMutex}; ClockLatency ret{}; ret.ClockTime = mDevice->getClockTime(); ret.Latency = seconds{mPadding.load(std::memory_order_relaxed)}; ret.Latency /= mFormat.Format.nSamplesPerSec; if(mResampler) { auto extra = mResampler->currentInputDelay(); ret.Latency += std::chrono::duration_cast(extra) / mDevice->mSampleRate; ret.Latency += nanoseconds{seconds{mBufferFilled}} / mDevice->mSampleRate; } return ret; } struct WasapiCapture final : public BackendBase { explicit WasapiCapture(DeviceBase *device) noexcept : BackendBase{device} { } ~WasapiCapture() override; void recordProc(IAudioClient *client, IAudioCaptureClient *capture); void proc_thread(std::string&& name); auto openProxy(const std::string_view name, DeviceHelper &helper, DeviceHandle &mmdev) -> HRESULT; auto resetProxy(DeviceHelper &helper, DeviceHandle &mmdev, ComPtr &client, ComPtr &capture) -> HRESULT; void open(std::string_view name) override; void start() override; void stop() override; void captureSamples(std::byte *buffer, uint samples) override; uint availableSamples() override; std::thread mProcThread; std::mutex mProcMutex; std::condition_variable mProcCond; HRESULT mProcResult{E_FAIL}; enum class ThreadState : uint8_t { Initializing, Waiting, Recording, Done }; ThreadState mState{ThreadState::Initializing}; enum class ThreadAction : uint8_t { Nothing, Record, Quit }; ThreadAction mAction{ThreadAction::Nothing}; HANDLE mNotifyEvent{nullptr}; ChannelConverter mChannelConv{}; SampleConverterPtr mSampleConv; RingBufferPtr mRing; std::atomic mKillNow{true}; std::thread mThread; }; WasapiCapture::~WasapiCapture() { if(mProcThread.joinable()) { { auto plock = std::lock_guard{mProcMutex}; mKillNow = true; mAction = ThreadAction::Quit; } mProcCond.notify_all(); mProcThread.join(); } if(mNotifyEvent != nullptr) CloseHandle(mNotifyEvent); mNotifyEvent = nullptr; } FORCE_ALIGN void WasapiCapture::recordProc(IAudioClient *client, IAudioCaptureClient *capture) { ResetEvent(mNotifyEvent); if(HRESULT hr{client->Start()}; FAILED(hr)) { ERR("Failed to start audio client: {:#x}", as_unsigned(hr)); mDevice->handleDisconnect("Failed to start audio client: {:#x}", as_unsigned(hr)); return; } std::vector samples; while(!mKillNow.load(std::memory_order_relaxed)) { auto avail = UINT32{}; auto hr = capture->GetNextPacketSize(&avail); if(FAILED(hr)) ERR("Failed to get next packet size: {:#x}", as_unsigned(hr)); else if(avail > 0) { auto numsamples = UINT32{}; auto flags = DWORD{}; BYTE *rdata{}; hr = capture->GetBuffer(&rdata, &numsamples, &flags, nullptr, nullptr); if(FAILED(hr)) ERR("Failed to get capture buffer: {:#x}", as_unsigned(hr)); else { if(mChannelConv.is_active()) { samples.resize(numsamples*2_uz); mChannelConv.convert(rdata, samples.data(), numsamples); rdata = reinterpret_cast(samples.data()); } auto data = mRing->getWriteVector(); size_t dstframes; if(mSampleConv) { static constexpr auto lenlimit = size_t{std::numeric_limits::max()}; const void *srcdata{rdata}; uint srcframes{numsamples}; dstframes = mSampleConv->convert(&srcdata, &srcframes, data[0].buf, static_cast(std::min(data[0].len, lenlimit))); if(srcframes > 0 && dstframes == data[0].len && data[1].len > 0) { /* If some source samples remain, all of the first dest * block was filled, and there's space in the second * dest block, do another run for the second block. */ dstframes += mSampleConv->convert(&srcdata, &srcframes, data[1].buf, static_cast(std::min(data[1].len, lenlimit))); } } else { const uint framesize{mDevice->frameSizeFromFmt()}; auto dst = al::span{rdata, size_t{numsamples}*framesize}; size_t len1{std::min(data[0].len, size_t{numsamples})}; size_t len2{std::min(data[1].len, numsamples-len1)}; memcpy(data[0].buf, dst.data(), len1*framesize); if(len2 > 0) { dst = dst.subspan(len1*framesize); memcpy(data[1].buf, dst.data(), len2*framesize); } dstframes = len1 + len2; } mRing->writeAdvance(dstframes); hr = capture->ReleaseBuffer(numsamples); if(FAILED(hr)) ERR("Failed to release capture buffer: {:#x}", as_unsigned(hr)); } } if(FAILED(hr)) { mDevice->handleDisconnect("Failed to capture samples: {:#x}", as_unsigned(hr)); break; } if(DWORD res{WaitForSingleObjectEx(mNotifyEvent, 2000, FALSE)}; res != WAIT_OBJECT_0) ERR("WaitForSingleObjectEx error: {:#x}", res); } client->Stop(); client->Reset(); } void WasapiCapture::proc_thread(std::string&& name) try { auto com = ComWrapper{COINIT_MULTITHREADED}; if(!com) { const auto hr = as_unsigned(com.status()); ERR("CoInitializeEx(nullptr, COINIT_MULTITHREADED) failed: {:#x}", hr); mDevice->handleDisconnect("COM init failed: {:#x}", hr); auto plock = std::lock_guard{mProcMutex}; mProcResult = com.status(); mState = ThreadState::Done; mProcCond.notify_all(); return; } auto helper = DeviceHelper{}; if(HRESULT hr{helper.init()}; FAILED(hr)) { mDevice->handleDisconnect("Helper init failed: {:#x}", as_unsigned(hr)); auto plock = std::lock_guard{mProcMutex}; mProcResult = hr; mState = ThreadState::Done; mProcCond.notify_all(); return; } althrd_setname(GetRecordThreadName()); auto mmdev = DeviceHandle{nullptr}; if(auto hr = openProxy(name, helper, mmdev); FAILED(hr)) { auto plock = std::lock_guard{mProcMutex}; mProcResult = hr; mState = ThreadState::Done; mProcCond.notify_all(); return; } auto client = ComPtr{}; auto capture = ComPtr{}; if(auto hr = resetProxy(helper, mmdev, client, capture); FAILED(hr)) { auto plock = std::lock_guard{mProcMutex}; mProcResult = hr; mState = ThreadState::Done; mProcCond.notify_all(); return; } auto plock = std::unique_lock{mProcMutex}; mProcResult = S_OK; while(mState != ThreadState::Done) { mAction = ThreadAction::Nothing; mState = ThreadState::Waiting; mProcCond.notify_all(); mProcCond.wait(plock, [this]() noexcept { return mAction != ThreadAction::Nothing; }); switch(mAction) { case ThreadAction::Nothing: break; case ThreadAction::Record: mKillNow.store(false, std::memory_order_release); mAction = ThreadAction::Nothing; mState = ThreadState::Recording; mProcResult = S_OK; plock.unlock(); mProcCond.notify_all(); recordProc(client.get(), capture.get()); plock.lock(); break; case ThreadAction::Quit: mAction = ThreadAction::Nothing; mState = ThreadState::Done; mProcCond.notify_all(); break; } } } catch(...) { auto plock = std::lock_guard{mProcMutex}; mProcResult = E_FAIL; mAction = ThreadAction::Nothing; mState = ThreadState::Done; mProcCond.notify_all(); } void WasapiCapture::open(std::string_view name) { mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr); if(mNotifyEvent == nullptr) { ERR("Failed to create notify events: {}", GetLastError()); throw al::backend_exception{al::backend_error::DeviceError, "Failed to create notify events"}; } mProcThread = std::thread{&WasapiCapture::proc_thread, this, std::string{name}}; auto plock = std::unique_lock{mProcMutex}; mProcCond.wait(plock, [this]() noexcept { return mState != ThreadState::Initializing; }); if(mProcResult == E_NOTFOUND) throw al::backend_exception{al::backend_error::NoDevice, "Device \"{}\" not found", name}; if(mProcResult == E_OUTOFMEMORY) throw al::backend_exception{al::backend_error::OutOfMemory, "Out of memory"}; if(FAILED(mProcResult) || mState == ThreadState::Done) throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: {:#x}", as_unsigned(mProcResult)}; } auto WasapiCapture::openProxy(const std::string_view name, DeviceHelper &helper, DeviceHandle &mmdev) -> HRESULT { auto devname = std::string{}; auto devid = std::wstring{}; if(!name.empty()) { auto devlock = DeviceListLock{gDeviceList}; auto devlist = al::span{devlock.getCaptureList()}; auto iter = std::find_if(devlist.cbegin(), devlist.cend(), [name](const DevMap &entry) -> bool { return entry.name == name || entry.endpoint_guid == name; }); if(iter == devlist.cend()) { const std::wstring wname{utf8_to_wstr(name)}; iter = std::find_if(devlist.cbegin(), devlist.cend(), [&wname](const DevMap &entry) -> bool { return entry.devid == wname; }); } if(iter == devlist.cend()) { WARN("Failed to find device name matching \"{}\"", name); return E_NOTFOUND; } devname = iter->name; devid = iter->devid; } auto hr = helper.openDevice(devid, eCapture, mmdev); if(FAILED(hr)) { WARN("Failed to open device \"{}\"", devname.empty() ? "(default)"sv : std::string_view{devname}); return hr; } if(!devname.empty()) mDeviceName = std::move(devname); else mDeviceName = GetDeviceNameAndGuid(mmdev).mName; return S_OK; } auto WasapiCapture::resetProxy(DeviceHelper &helper, DeviceHandle &mmdev, ComPtr &client, ComPtr &capture) -> HRESULT { capture = nullptr; client = nullptr; auto hr = helper.activateAudioClient(mmdev, __uuidof(IAudioClient), al::out_ptr(client)); if(FAILED(hr)) { ERR("Failed to reactivate audio client: {:#x}", as_unsigned(hr)); return hr; } auto wfx = unique_coptr{}; hr = client->GetMixFormat(al::out_ptr(wfx)); if(FAILED(hr)) { ERR("Failed to get capture format: {:#x}", as_unsigned(hr)); return hr; } TraceFormat("Device capture format", wfx.get()); auto InputType = WAVEFORMATEXTENSIBLE{}; if(!MakeExtensible(&InputType, wfx.get())) return E_FAIL; wfx = nullptr; const bool isRear51{InputType.Format.nChannels == 6 && (InputType.dwChannelMask&X51RearMask) == X5DOT1REAR}; // Make sure buffer is at least 100ms in size ReferenceTime buf_time{ReferenceTime{seconds{mDevice->mBufferSize}} / mDevice->mSampleRate}; buf_time = std::max(buf_time, ReferenceTime{milliseconds{100}}); InputType = {}; InputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; switch(mDevice->FmtChans) { case DevFmtMono: InputType.Format.nChannels = 1; InputType.dwChannelMask = MONO; break; case DevFmtStereo: InputType.Format.nChannels = 2; InputType.dwChannelMask = STEREO; break; case DevFmtQuad: InputType.Format.nChannels = 4; InputType.dwChannelMask = QUAD; break; case DevFmtX51: InputType.Format.nChannels = 6; InputType.dwChannelMask = isRear51 ? X5DOT1REAR : X5DOT1; break; case DevFmtX61: InputType.Format.nChannels = 7; InputType.dwChannelMask = X6DOT1; break; case DevFmtX71: InputType.Format.nChannels = 8; InputType.dwChannelMask = X7DOT1; break; case DevFmtX714: InputType.Format.nChannels = 12; InputType.dwChannelMask = X7DOT1DOT4; break; case DevFmtX7144: case DevFmtX3D71: case DevFmtAmbi3D: return E_FAIL; } switch(mDevice->FmtType) { /* NOTE: Signedness doesn't matter, the converter will handle it. */ case DevFmtByte: case DevFmtUByte: InputType.Format.wBitsPerSample = 8; InputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; break; case DevFmtShort: case DevFmtUShort: InputType.Format.wBitsPerSample = 16; InputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; break; case DevFmtInt: case DevFmtUInt: InputType.Format.wBitsPerSample = 32; InputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; break; case DevFmtFloat: InputType.Format.wBitsPerSample = 32; InputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; break; } /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) */ InputType.Samples.wValidBitsPerSample = InputType.Format.wBitsPerSample; InputType.Format.nSamplesPerSec = mDevice->mSampleRate; InputType.Format.nBlockAlign = static_cast(InputType.Format.nChannels * InputType.Format.wBitsPerSample / 8); InputType.Format.nAvgBytesPerSec = InputType.Format.nSamplesPerSec * InputType.Format.nBlockAlign; InputType.Format.cbSize = sizeof(InputType) - sizeof(InputType.Format); TraceFormat("Requesting capture format", &InputType.Format); hr = client->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, &InputType.Format, al::out_ptr(wfx)); if(FAILED(hr)) { WARN("Failed to check capture format support: {:#x}", as_unsigned(hr)); hr = client->GetMixFormat(al::out_ptr(wfx)); } if(FAILED(hr)) { ERR("Failed to find a supported capture format: {:#x}", as_unsigned(hr)); return hr; } mSampleConv = nullptr; mChannelConv = {}; if(wfx != nullptr) { TraceFormat("Got capture format", wfx.get()); if(!MakeExtensible(&InputType, wfx.get())) return E_FAIL; wfx = nullptr; auto validate_fmt = [](DeviceBase *device, uint32_t chancount, DWORD chanmask) noexcept -> bool { switch(device->FmtChans) { /* If the device wants mono, we can handle any input. */ case DevFmtMono: return true; /* If the device wants stereo, we can handle mono or stereo input. */ case DevFmtStereo: return (chancount == 2 && (chanmask == 0 || (chanmask&StereoMask) == STEREO)) || (chancount == 1 && (chanmask&MonoMask) == MONO); /* Otherwise, the device must match the input type. */ case DevFmtQuad: return (chancount == 4 && (chanmask == 0 || (chanmask&QuadMask) == QUAD)); /* 5.1 (Side) and 5.1 (Rear) are interchangeable here. */ case DevFmtX51: return (chancount == 6 && (chanmask == 0 || (chanmask&X51Mask) == X5DOT1 || (chanmask&X51RearMask) == X5DOT1REAR)); case DevFmtX61: return (chancount == 7 && (chanmask == 0 || (chanmask&X61Mask) == X6DOT1)); case DevFmtX71: case DevFmtX3D71: return (chancount == 8 && (chanmask == 0 || (chanmask&X71Mask) == X7DOT1)); case DevFmtX714: return (chancount == 12 && (chanmask == 0 || (chanmask&X714Mask) == X7DOT1DOT4)); case DevFmtX7144: return (chancount == 16 && chanmask == 0); case DevFmtAmbi3D: return (chanmask == 0 && chancount == device->channelsFromFmt()); } return false; }; if(!validate_fmt(mDevice, InputType.Format.nChannels, InputType.dwChannelMask)) { ERR("Failed to match format, wanted: {} {} {}hz, got: {:#08x} mask {} channel{} {}-bit {}hz", DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType), mDevice->mSampleRate, InputType.dwChannelMask, InputType.Format.nChannels, (InputType.Format.nChannels==1)?"":"s", InputType.Format.wBitsPerSample, InputType.Format.nSamplesPerSec); return E_FAIL; } } DevFmtType srcType{}; if(IsEqualGUID(InputType.SubFormat, KSDATAFORMAT_SUBTYPE_PCM)) { if(InputType.Format.wBitsPerSample == 8) srcType = DevFmtUByte; else if(InputType.Format.wBitsPerSample == 16) srcType = DevFmtShort; else if(InputType.Format.wBitsPerSample == 32) srcType = DevFmtInt; else { ERR("Unhandled integer bit depth: {}", InputType.Format.wBitsPerSample); return E_FAIL; } } else if(IsEqualGUID(InputType.SubFormat, KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)) { if(InputType.Format.wBitsPerSample == 32) srcType = DevFmtFloat; else { ERR("Unhandled float bit depth: {}", InputType.Format.wBitsPerSample); return E_FAIL; } } else { ERR("Unhandled format sub-type: {}", GuidPrinter{InputType.SubFormat}.str()); return E_FAIL; } if(mDevice->FmtChans == DevFmtMono && InputType.Format.nChannels != 1) { uint chanmask{(1u<FmtChans}; TRACE("Created {} multichannel-to-mono converter", DevFmtTypeString(srcType)); /* The channel converter always outputs float, so change the input type * for the resampler/type-converter. */ srcType = DevFmtFloat; } else if(mDevice->FmtChans == DevFmtStereo && InputType.Format.nChannels == 1) { mChannelConv = ChannelConverter{srcType, 1, 0x1, mDevice->FmtChans}; TRACE("Created {} mono-to-stereo converter", DevFmtTypeString(srcType)); srcType = DevFmtFloat; } if(mDevice->mSampleRate != InputType.Format.nSamplesPerSec || mDevice->FmtType != srcType) { mSampleConv = SampleConverter::Create(srcType, mDevice->FmtType, mDevice->channelsFromFmt(), InputType.Format.nSamplesPerSec, mDevice->mSampleRate, Resampler::FastBSinc24); if(!mSampleConv) { ERR("Failed to create converter for {} format, dst: {} {}hz, src: {} {}hz", DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType), mDevice->mSampleRate, DevFmtTypeString(srcType), InputType.Format.nSamplesPerSec); return E_FAIL; } TRACE("Created converter for {} format, dst: {} {}hz, src: {} {}hz", DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType), mDevice->mSampleRate, DevFmtTypeString(srcType), InputType.Format.nSamplesPerSec); } hr = client->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, buf_time.count(), 0, &InputType.Format, nullptr); if(FAILED(hr)) { ERR("Failed to initialize audio client: {:#x}", as_unsigned(hr)); return hr; } hr = client->GetService(__uuidof(IAudioCaptureClient), al::out_ptr(capture)); if(FAILED(hr)) { ERR("Failed to get IAudioCaptureClient: {:#x}", as_unsigned(hr)); return hr; } UINT32 buffer_len{}; ReferenceTime min_per{}; hr = client->GetDevicePeriod(&reinterpret_cast(min_per), nullptr); if(SUCCEEDED(hr)) hr = client->GetBufferSize(&buffer_len); if(FAILED(hr)) { ERR("Failed to get buffer size: {:#x}", as_unsigned(hr)); return hr; } mDevice->mUpdateSize = RefTime2Samples(min_per, mDevice->mSampleRate); mDevice->mBufferSize = buffer_len; mRing = RingBuffer::Create(buffer_len, mDevice->frameSizeFromFmt(), false); hr = client->SetEventHandle(mNotifyEvent); if(FAILED(hr)) { ERR("Failed to set event handle: {:#x}", as_unsigned(hr)); return hr; } return hr; } void WasapiCapture::start() { auto plock = std::unique_lock{mProcMutex}; if(mState != ThreadState::Waiting) throw al::backend_exception{al::backend_error::DeviceError, "Invalid state: {}", unsigned{al::to_underlying(mState)}}; mAction = ThreadAction::Record; mProcCond.notify_all(); mProcCond.wait(plock, [this]() noexcept { return mAction != ThreadAction::Record; }); if(FAILED(mProcResult) || mState != ThreadState::Recording) throw al::backend_exception{al::backend_error::DeviceError, "Device capture failed: {:#x}", as_unsigned(mProcResult)}; } void WasapiCapture::stop() { auto plock = std::unique_lock{mProcMutex}; if(mState == ThreadState::Recording) { mKillNow = true; mProcCond.wait(plock, [this]() noexcept { return mState != ThreadState::Recording; }); } } void WasapiCapture::captureSamples(std::byte *buffer, uint samples) { std::ignore = mRing->read(buffer, samples); } uint WasapiCapture::availableSamples() { return static_cast(mRing->readSpace()); } } // namespace bool WasapiBackendFactory::init() { static HRESULT InitResult{E_FAIL}; if(FAILED(InitResult)) try { std::promise promise; auto future = promise.get_future(); std::thread{&DeviceEnumHelper::messageHandler, &promise}.detach(); InitResult = future.get(); } catch(...) { } return SUCCEEDED(InitResult); } bool WasapiBackendFactory::querySupport(BackendType type) { return type == BackendType::Playback || type == BackendType::Capture; } auto WasapiBackendFactory::enumerate(BackendType type) -> std::vector { std::vector outnames; auto devlock = DeviceListLock{gDeviceList}; switch(type) { case BackendType::Playback: { auto defaultId = devlock.getPlaybackDefaultId(); for(const DevMap &entry : devlock.getPlaybackList()) { if(entry.devid != defaultId) { outnames.emplace_back(entry.name); continue; } /* Default device goes first. */ outnames.emplace(outnames.cbegin(), entry.name); } } break; case BackendType::Capture: { auto defaultId = devlock.getCaptureDefaultId(); for(const DevMap &entry : devlock.getCaptureList()) { if(entry.devid != defaultId) { outnames.emplace_back(entry.name); continue; } outnames.emplace(outnames.cbegin(), entry.name); } } break; } return outnames; } BackendPtr WasapiBackendFactory::createBackend(DeviceBase *device, BackendType type) { if(type == BackendType::Playback) return BackendPtr{new WasapiPlayback{device}}; if(type == BackendType::Capture) return BackendPtr{new WasapiCapture{device}}; return nullptr; } BackendFactory &WasapiBackendFactory::getFactory() { static WasapiBackendFactory factory{}; return factory; } alc::EventSupport WasapiBackendFactory::queryEventSupport(alc::EventType eventType, BackendType) { switch(eventType) { case alc::EventType::DefaultDeviceChanged: return alc::EventSupport::FullSupport; case alc::EventType::DeviceAdded: case alc::EventType::DeviceRemoved: #if !ALSOFT_UWP return alc::EventSupport::FullSupport; #endif case alc::EventType::Count: break; } return alc::EventSupport::NoSupport; } openal-soft-1.24.2/alc/backends/wasapi.h000066400000000000000000000010701474041540300177750ustar00rootroot00000000000000#ifndef BACKENDS_WASAPI_H #define BACKENDS_WASAPI_H #include "base.h" struct WasapiBackendFactory final : public BackendFactory { public: auto init() -> bool final; auto querySupport(BackendType type) -> bool final; auto queryEventSupport(alc::EventType eventType, BackendType type) -> alc::EventSupport final; auto enumerate(BackendType type) -> std::vector final; auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_WASAPI_H */ openal-soft-1.24.2/alc/backends/wave.cpp000066400000000000000000000306551474041540300200210ustar00rootroot00000000000000/** * OpenAL cross platform audio library * Copyright (C) 1999-2007 by authors. * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * Or go to http://www.gnu.org/copyleft/lgpl.html */ #include "config.h" #include "wave.h" #include #include #include #include #include #include #include #include #include #include #include #include "albit.h" #include "alc/alconfig.h" #include "almalloc.h" #include "alnumeric.h" #include "althrd_setname.h" #include "core/device.h" #include "core/logging.h" #include "strutils.h" namespace { using namespace std::string_view_literals; using std::chrono::seconds; using std::chrono::milliseconds; using std::chrono::nanoseconds; using ubyte = unsigned char; using ushort = unsigned short; struct FileDeleter { void operator()(gsl::owner f) { fclose(f); } }; using FilePtr = std::unique_ptr; [[nodiscard]] constexpr auto GetDeviceName() noexcept { return "Wave File Writer"sv; } constexpr std::array SUBTYPE_PCM{{ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 }}; constexpr std::array SUBTYPE_FLOAT{{ 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 }}; constexpr std::array SUBTYPE_BFORMAT_PCM{{ 0x01, 0x00, 0x00, 0x00, 0x21, 0x07, 0xd3, 0x11, 0x86, 0x44, 0xc8, 0xc1, 0xca, 0x00, 0x00, 0x00 }}; constexpr std::array SUBTYPE_BFORMAT_FLOAT{{ 0x03, 0x00, 0x00, 0x00, 0x21, 0x07, 0xd3, 0x11, 0x86, 0x44, 0xc8, 0xc1, 0xca, 0x00, 0x00, 0x00 }}; void fwrite16le(ushort val, FILE *f) { std::array data{static_cast(val&0xff), static_cast((val>>8)&0xff)}; fwrite(data.data(), 1, data.size(), f); } void fwrite32le(uint val, FILE *f) { std::array data{static_cast(val&0xff), static_cast((val>>8)&0xff), static_cast((val>>16)&0xff), static_cast((val>>24)&0xff)}; fwrite(data.data(), 1, data.size(), f); } struct WaveBackend final : public BackendBase { explicit WaveBackend(DeviceBase *device) noexcept : BackendBase{device} { } ~WaveBackend() override; int mixerProc(); void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; FilePtr mFile{nullptr}; long mDataStart{-1}; std::vector mBuffer; std::atomic mKillNow{true}; std::thread mThread; }; WaveBackend::~WaveBackend() = default; int WaveBackend::mixerProc() { const milliseconds restTime{mDevice->mUpdateSize*1000/mDevice->mSampleRate / 2}; althrd_setname(GetMixerThreadName()); const size_t frameStep{mDevice->channelsFromFmt()}; const size_t frameSize{mDevice->frameSizeFromFmt()}; int64_t done{0}; auto start = std::chrono::steady_clock::now(); while(!mKillNow.load(std::memory_order_acquire) && mDevice->Connected.load(std::memory_order_acquire)) { auto now = std::chrono::steady_clock::now(); /* This converts from nanoseconds to nanosamples, then to samples. */ const auto avail = int64_t{std::chrono::duration_cast((now-start) * mDevice->mSampleRate).count()}; if(avail-done < mDevice->mUpdateSize) { std::this_thread::sleep_for(restTime); continue; } while(avail-done >= mDevice->mUpdateSize) { mDevice->renderSamples(mBuffer.data(), mDevice->mUpdateSize, frameStep); done += mDevice->mUpdateSize; if(al::endian::native != al::endian::little) { const uint bytesize{mDevice->bytesFromFmt()}; if(bytesize == 2) { const size_t len{mBuffer.size() & ~1_uz}; for(size_t i{0};i < len;i+=2) std::swap(mBuffer[i], mBuffer[i+1]); } else if(bytesize == 4) { const size_t len{mBuffer.size() & ~3_uz}; for(size_t i{0};i < len;i+=4) { std::swap(mBuffer[i ], mBuffer[i+3]); std::swap(mBuffer[i+1], mBuffer[i+2]); } } } const size_t fs{fwrite(mBuffer.data(), frameSize, mDevice->mUpdateSize, mFile.get())}; if(fs < mDevice->mUpdateSize || ferror(mFile.get())) { ERR("Error writing to file"); mDevice->handleDisconnect("Failed to write playback samples"); break; } } /* For every completed second, increment the start time and reduce the * samples done. This prevents the difference between the start time * and current time from growing too large, while maintaining the * correct number of samples to render. */ if(done >= mDevice->mSampleRate) { seconds s{done/mDevice->mSampleRate}; done %= mDevice->mSampleRate; start += s; } } return 0; } void WaveBackend::open(std::string_view name) { auto fname = ConfigValueStr({}, "wave", "file"); if(!fname) throw al::backend_exception{al::backend_error::NoDevice, "No wave output filename"}; if(name.empty()) name = GetDeviceName(); else if(name != GetDeviceName()) throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found", name}; /* There's only one "device", so if it's already open, we're done. */ if(mFile) return; #ifdef _WIN32 { std::wstring wname{utf8_to_wstr(fname.value())}; mFile = FilePtr{_wfopen(wname.c_str(), L"wb")}; } #else mFile = FilePtr{fopen(fname->c_str(), "wb")}; #endif if(!mFile) throw al::backend_exception{al::backend_error::DeviceError, "Could not open file '{}': {}", *fname, std::generic_category().message(errno)}; mDeviceName = name; } bool WaveBackend::reset() { if(GetConfigValueBool({}, "wave", "bformat", false)) { mDevice->FmtChans = DevFmtAmbi3D; mDevice->mAmbiOrder = 1; } switch(mDevice->FmtType) { case DevFmtByte: mDevice->FmtType = DevFmtUByte; break; case DevFmtUShort: mDevice->FmtType = DevFmtShort; break; case DevFmtUInt: mDevice->FmtType = DevFmtInt; break; case DevFmtUByte: case DevFmtShort: case DevFmtInt: case DevFmtFloat: break; } auto chanmask = 0u; auto isbformat = false; switch(mDevice->FmtChans) { case DevFmtMono: chanmask = 0x04; break; case DevFmtStereo: chanmask = 0x01 | 0x02; break; case DevFmtQuad: chanmask = 0x01 | 0x02 | 0x10 | 0x20; break; case DevFmtX51: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x200 | 0x400; break; case DevFmtX61: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x100 | 0x200 | 0x400; break; case DevFmtX71: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x010 | 0x020 | 0x200 | 0x400; break; case DevFmtX7144: mDevice->FmtChans = DevFmtX714; [[fallthrough]]; case DevFmtX714: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x010 | 0x020 | 0x200 | 0x400 | 0x1000 | 0x4000 | 0x8000 | 0x20000; break; /* NOTE: Same as 7.1. */ case DevFmtX3D71: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x010 | 0x020 | 0x200 | 0x400; break; case DevFmtAmbi3D: /* .amb output requires FuMa */ mDevice->mAmbiOrder = std::min(mDevice->mAmbiOrder, 3u); mDevice->mAmbiLayout = DevAmbiLayout::FuMa; mDevice->mAmbiScale = DevAmbiScaling::FuMa; isbformat = true; chanmask = 0; break; } const auto bytes = mDevice->bytesFromFmt(); const auto channels = mDevice->channelsFromFmt(); if(fseek(mFile.get(), 0, SEEK_CUR) != 0) { /* ESPIPE means the underlying file isn't seekable, which is fine for * piped output. */ if(auto errcode = errno; errcode != ESPIPE) { ERR("Failed to reset file offset: {} ({})", std::generic_category().message(errcode), errcode); } } clearerr(mFile.get()); fputs("RIFF", mFile.get()); fwrite32le(0xFFFFFFFF, mFile.get()); // 'RIFF' header len; filled in at stop fputs("WAVE", mFile.get()); fputs("fmt ", mFile.get()); fwrite32le(40, mFile.get()); // 'fmt ' header len; 40 bytes for EXTENSIBLE // 16-bit val, format type id (extensible: 0xFFFE) fwrite16le(0xFFFE, mFile.get()); // 16-bit val, channel count fwrite16le(static_cast(channels), mFile.get()); // 32-bit val, frequency fwrite32le(mDevice->mSampleRate, mFile.get()); // 32-bit val, bytes per second fwrite32le(mDevice->mSampleRate * channels * bytes, mFile.get()); // 16-bit val, frame size fwrite16le(static_cast(channels * bytes), mFile.get()); // 16-bit val, bits per sample fwrite16le(static_cast(bytes * 8), mFile.get()); // 16-bit val, extra byte count fwrite16le(22, mFile.get()); // 16-bit val, valid bits per sample fwrite16le(static_cast(bytes * 8), mFile.get()); // 32-bit val, channel mask fwrite32le(chanmask, mFile.get()); // 16 byte GUID, sub-type format std::ignore = fwrite((mDevice->FmtType == DevFmtFloat) ? (isbformat ? SUBTYPE_BFORMAT_FLOAT.data() : SUBTYPE_FLOAT.data()) : (isbformat ? SUBTYPE_BFORMAT_PCM.data() : SUBTYPE_PCM.data()), 1, 16, mFile.get()); fputs("data", mFile.get()); fwrite32le(0xFFFFFFFF, mFile.get()); // 'data' header len; filled in at stop if(ferror(mFile.get())) { ERR("Error writing header: {}", std::generic_category().message(errno)); return false; } mDataStart = ftell(mFile.get()); setDefaultWFXChannelOrder(); const uint bufsize{mDevice->frameSizeFromFmt() * mDevice->mUpdateSize}; mBuffer.resize(bufsize); return true; } void WaveBackend::start() { if(mDataStart > 0 && fseek(mFile.get(), 0, SEEK_END) != 0) WARN("Failed to seek on output file"); try { mKillNow.store(false, std::memory_order_release); mThread = std::thread{&WaveBackend::mixerProc, this}; } catch(std::exception& e) { throw al::backend_exception{al::backend_error::DeviceError, "Failed to start mixing thread: {}", e.what()}; } } void WaveBackend::stop() { if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable()) return; mThread.join(); if(mDataStart > 0) { long size{ftell(mFile.get())}; if(size > 0) { long dataLen{size - mDataStart}; if(fseek(mFile.get(), 4, SEEK_SET) == 0) fwrite32le(static_cast(size-8), mFile.get()); // 'WAVE' header len if(fseek(mFile.get(), mDataStart-4, SEEK_SET) == 0) fwrite32le(static_cast(dataLen), mFile.get()); // 'data' header len } } } } // namespace bool WaveBackendFactory::init() { return true; } bool WaveBackendFactory::querySupport(BackendType type) { return type == BackendType::Playback; } auto WaveBackendFactory::enumerate(BackendType type) -> std::vector { switch(type) { case BackendType::Playback: return std::vector{std::string{GetDeviceName()}}; case BackendType::Capture: break; } return {}; } BackendPtr WaveBackendFactory::createBackend(DeviceBase *device, BackendType type) { if(type == BackendType::Playback) return BackendPtr{new WaveBackend{device}}; return nullptr; } BackendFactory &WaveBackendFactory::getFactory() { static WaveBackendFactory factory{}; return factory; } openal-soft-1.24.2/alc/backends/wave.h000066400000000000000000000007141474041540300174570ustar00rootroot00000000000000#ifndef BACKENDS_WAVE_H #define BACKENDS_WAVE_H #include "base.h" struct WaveBackendFactory final : public BackendFactory { public: auto init() -> bool final; auto querySupport(BackendType type) -> bool final; auto enumerate(BackendType type) -> std::vector final; auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_WAVE_H */ openal-soft-1.24.2/alc/backends/winmm.cpp000066400000000000000000000437151474041540300202070ustar00rootroot00000000000000/** * OpenAL cross platform audio library * Copyright (C) 1999-2007 by authors. * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * Or go to http://www.gnu.org/copyleft/lgpl.html */ #include "config.h" #include "winmm.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "alnumeric.h" #include "alsem.h" #include "althrd_setname.h" #include "core/device.h" #include "core/helpers.h" #include "core/logging.h" #include "fmt/core.h" #include "ringbuffer.h" #include "strutils.h" #include "vector.h" #ifndef WAVE_FORMAT_IEEE_FLOAT #define WAVE_FORMAT_IEEE_FLOAT 0x0003 #endif namespace { std::vector PlaybackDevices; std::vector CaptureDevices; bool checkName(const std::vector &list, const std::string &name) { return std::find(list.cbegin(), list.cend(), name) != list.cend(); } void ProbePlaybackDevices() { PlaybackDevices.clear(); UINT numdevs{waveOutGetNumDevs()}; PlaybackDevices.reserve(numdevs); for(UINT i{0};i < numdevs;++i) { std::string dname; WAVEOUTCAPSW WaveCaps{}; if(waveOutGetDevCapsW(i, &WaveCaps, sizeof(WaveCaps)) == MMSYSERR_NOERROR) { const auto basename = wstr_to_utf8(std::data(WaveCaps.szPname)); auto count = 1; auto newname = basename; while(checkName(PlaybackDevices, newname)) newname = fmt::format("{} #{}", basename, ++count); dname = std::move(newname); TRACE("Got device \"{}\", ID {}", dname, i); } PlaybackDevices.emplace_back(std::move(dname)); } } void ProbeCaptureDevices() { CaptureDevices.clear(); UINT numdevs{waveInGetNumDevs()}; CaptureDevices.reserve(numdevs); for(UINT i{0};i < numdevs;++i) { std::string dname; WAVEINCAPSW WaveCaps{}; if(waveInGetDevCapsW(i, &WaveCaps, sizeof(WaveCaps)) == MMSYSERR_NOERROR) { const auto basename = wstr_to_utf8(std::data(WaveCaps.szPname)); auto count = 1; auto newname = basename; while(checkName(CaptureDevices, newname)) newname = fmt::format("{} #{}", basename, ++count); dname = std::move(newname); TRACE("Got device \"{}\", ID {}", dname, i); } CaptureDevices.emplace_back(std::move(dname)); } } struct WinMMPlayback final : public BackendBase { explicit WinMMPlayback(DeviceBase *device) noexcept : BackendBase{device} { } ~WinMMPlayback() override; void CALLBACK waveOutProc(HWAVEOUT device, UINT msg, DWORD_PTR param1, DWORD_PTR param2) noexcept; static void CALLBACK waveOutProcC(HWAVEOUT device, UINT msg, DWORD_PTR instance, DWORD_PTR param1, DWORD_PTR param2) noexcept { reinterpret_cast(instance)->waveOutProc(device, msg, param1, param2); } int mixerProc(); void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; std::atomic mWritable{0u}; al::semaphore mSem; uint mIdx{0u}; std::array mWaveBuffer{}; al::vector mBuffer; HWAVEOUT mOutHdl{nullptr}; WAVEFORMATEX mFormat{}; std::atomic mKillNow{true}; std::thread mThread; }; WinMMPlayback::~WinMMPlayback() { if(mOutHdl) waveOutClose(mOutHdl); mOutHdl = nullptr; } /* WinMMPlayback::waveOutProc * * Posts a message to 'WinMMPlayback::mixerProc' every time a WaveOut Buffer is * completed and returns to the application (for more data) */ void CALLBACK WinMMPlayback::waveOutProc(HWAVEOUT, UINT msg, DWORD_PTR, DWORD_PTR) noexcept { if(msg != WOM_DONE) return; mWritable.fetch_add(1, std::memory_order_acq_rel); mSem.post(); } FORCE_ALIGN int WinMMPlayback::mixerProc() { SetRTPriority(); althrd_setname(GetMixerThreadName()); while(!mKillNow.load(std::memory_order_acquire) && mDevice->Connected.load(std::memory_order_acquire)) { uint todo{mWritable.load(std::memory_order_acquire)}; if(todo < 1) { mSem.wait(); continue; } size_t widx{mIdx}; do { WAVEHDR &waveHdr = mWaveBuffer[widx]; if(++widx == mWaveBuffer.size()) widx = 0; mDevice->renderSamples(waveHdr.lpData, mDevice->mUpdateSize, mFormat.nChannels); mWritable.fetch_sub(1, std::memory_order_acq_rel); waveOutWrite(mOutHdl, &waveHdr, sizeof(WAVEHDR)); } while(--todo); mIdx = static_cast(widx); } return 0; } void WinMMPlayback::open(std::string_view name) { if(PlaybackDevices.empty()) ProbePlaybackDevices(); // Find the Device ID matching the deviceName if valid auto iter = !name.empty() ? std::find(PlaybackDevices.cbegin(), PlaybackDevices.cend(), name) : PlaybackDevices.cbegin(); if(iter == PlaybackDevices.cend()) throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found", name}; auto DeviceID = static_cast(std::distance(PlaybackDevices.cbegin(), iter)); DevFmtType fmttype{mDevice->FmtType}; WAVEFORMATEX format{}; do { format = WAVEFORMATEX{}; if(fmttype == DevFmtFloat) { format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT; format.wBitsPerSample = 32; } else { format.wFormatTag = WAVE_FORMAT_PCM; if(fmttype == DevFmtUByte || fmttype == DevFmtByte) format.wBitsPerSample = 8; else format.wBitsPerSample = 16; } format.nChannels = ((mDevice->FmtChans == DevFmtMono) ? 1 : 2); format.nBlockAlign = static_cast(format.wBitsPerSample * format.nChannels / 8); format.nSamplesPerSec = mDevice->mSampleRate; format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign; format.cbSize = 0; MMRESULT res{waveOutOpen(&mOutHdl, DeviceID, &format, reinterpret_cast(&WinMMPlayback::waveOutProcC), reinterpret_cast(this), CALLBACK_FUNCTION)}; if(res == MMSYSERR_NOERROR) break; if(fmttype != DevFmtFloat) throw al::backend_exception{al::backend_error::DeviceError, "waveOutOpen failed: {}", res}; fmttype = DevFmtShort; } while(true); mFormat = format; mDeviceName = PlaybackDevices[DeviceID]; } bool WinMMPlayback::reset() { mDevice->mBufferSize = static_cast(uint64_t{mDevice->mBufferSize} * mFormat.nSamplesPerSec / mDevice->mSampleRate); mDevice->mBufferSize = (mDevice->mBufferSize+3) & ~0x3u; mDevice->mUpdateSize = mDevice->mBufferSize / 4; mDevice->mSampleRate = mFormat.nSamplesPerSec; if(mFormat.wFormatTag == WAVE_FORMAT_IEEE_FLOAT) { if(mFormat.wBitsPerSample == 32) mDevice->FmtType = DevFmtFloat; else { ERR("Unhandled IEEE float sample depth: {}", mFormat.wBitsPerSample); return false; } } else if(mFormat.wFormatTag == WAVE_FORMAT_PCM) { if(mFormat.wBitsPerSample == 16) mDevice->FmtType = DevFmtShort; else if(mFormat.wBitsPerSample == 8) mDevice->FmtType = DevFmtUByte; else { ERR("Unhandled PCM sample depth: {}", mFormat.wBitsPerSample); return false; } } else { ERR("Unhandled format tag: {:#04x}", as_unsigned(mFormat.wFormatTag)); return false; } if(mFormat.nChannels >= 2) mDevice->FmtChans = DevFmtStereo; else if(mFormat.nChannels == 1) mDevice->FmtChans = DevFmtMono; else { ERR("Unhandled channel count: {}", mFormat.nChannels); return false; } setDefaultWFXChannelOrder(); const uint BufferSize{mDevice->mUpdateSize * mFormat.nChannels * mDevice->bytesFromFmt()}; decltype(mBuffer)(BufferSize*mWaveBuffer.size()).swap(mBuffer); mWaveBuffer[0] = WAVEHDR{}; mWaveBuffer[0].lpData = mBuffer.data(); mWaveBuffer[0].dwBufferLength = BufferSize; for(size_t i{1};i < mWaveBuffer.size();i++) { mWaveBuffer[i] = WAVEHDR{}; mWaveBuffer[i].lpData = mWaveBuffer[i-1].lpData + mWaveBuffer[i-1].dwBufferLength; mWaveBuffer[i].dwBufferLength = BufferSize; } mIdx = 0; return true; } void WinMMPlayback::start() { try { for(auto &waveHdr : mWaveBuffer) waveOutPrepareHeader(mOutHdl, &waveHdr, sizeof(WAVEHDR)); mWritable.store(static_cast(mWaveBuffer.size()), std::memory_order_release); mKillNow.store(false, std::memory_order_release); mThread = std::thread{&WinMMPlayback::mixerProc, this}; } catch(std::exception& e) { throw al::backend_exception{al::backend_error::DeviceError, "Failed to start mixing thread: {}", e.what()}; } } void WinMMPlayback::stop() { if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable()) return; mThread.join(); while(mWritable.load(std::memory_order_acquire) < mWaveBuffer.size()) mSem.wait(); for(auto &waveHdr : mWaveBuffer) waveOutUnprepareHeader(mOutHdl, &waveHdr, sizeof(WAVEHDR)); mWritable.store(0, std::memory_order_release); } struct WinMMCapture final : public BackendBase { explicit WinMMCapture(DeviceBase *device) noexcept : BackendBase{device} { } ~WinMMCapture() override; void CALLBACK waveInProc(HWAVEIN device, UINT msg, DWORD_PTR param1, DWORD_PTR param2) noexcept; static void CALLBACK waveInProcC(HWAVEIN device, UINT msg, DWORD_PTR instance, DWORD_PTR param1, DWORD_PTR param2) noexcept { reinterpret_cast(instance)->waveInProc(device, msg, param1, param2); } int captureProc(); void open(std::string_view name) override; void start() override; void stop() override; void captureSamples(std::byte *buffer, uint samples) override; uint availableSamples() override; std::atomic mReadable{0u}; al::semaphore mSem; uint mIdx{0}; std::array mWaveBuffer{}; al::vector mBuffer; HWAVEIN mInHdl{nullptr}; RingBufferPtr mRing{nullptr}; WAVEFORMATEX mFormat{}; std::atomic mKillNow{true}; std::thread mThread; }; WinMMCapture::~WinMMCapture() { // Close the Wave device if(mInHdl) waveInClose(mInHdl); mInHdl = nullptr; } /* WinMMCapture::waveInProc * * Posts a message to 'WinMMCapture::captureProc' every time a WaveIn Buffer is * completed and returns to the application (with more data). */ void CALLBACK WinMMCapture::waveInProc(HWAVEIN, UINT msg, DWORD_PTR, DWORD_PTR) noexcept { if(msg != WIM_DATA) return; mReadable.fetch_add(1, std::memory_order_acq_rel); mSem.post(); } int WinMMCapture::captureProc() { althrd_setname(GetRecordThreadName()); while(!mKillNow.load(std::memory_order_acquire) && mDevice->Connected.load(std::memory_order_acquire)) { uint todo{mReadable.load(std::memory_order_acquire)}; if(todo < 1) { mSem.wait(); continue; } size_t widx{mIdx}; do { WAVEHDR &waveHdr = mWaveBuffer[widx]; widx = (widx+1) % mWaveBuffer.size(); std::ignore = mRing->write(waveHdr.lpData, waveHdr.dwBytesRecorded / mFormat.nBlockAlign); mReadable.fetch_sub(1, std::memory_order_acq_rel); waveInAddBuffer(mInHdl, &waveHdr, sizeof(WAVEHDR)); } while(--todo); mIdx = static_cast(widx); } return 0; } void WinMMCapture::open(std::string_view name) { if(CaptureDevices.empty()) ProbeCaptureDevices(); // Find the Device ID matching the deviceName if valid auto iter = !name.empty() ? std::find(CaptureDevices.cbegin(), CaptureDevices.cend(), name) : CaptureDevices.cbegin(); if(iter == CaptureDevices.cend()) throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found", name}; auto DeviceID = static_cast(std::distance(CaptureDevices.cbegin(), iter)); switch(mDevice->FmtChans) { case DevFmtMono: case DevFmtStereo: break; case DevFmtQuad: case DevFmtX51: case DevFmtX61: case DevFmtX71: case DevFmtX714: case DevFmtX7144: case DevFmtX3D71: case DevFmtAmbi3D: throw al::backend_exception{al::backend_error::DeviceError, "{} capture not supported", DevFmtChannelsString(mDevice->FmtChans)}; } switch(mDevice->FmtType) { case DevFmtUByte: case DevFmtShort: case DevFmtInt: case DevFmtFloat: break; case DevFmtByte: case DevFmtUShort: case DevFmtUInt: throw al::backend_exception{al::backend_error::DeviceError, "{} samples not supported", DevFmtTypeString(mDevice->FmtType)}; } mFormat = WAVEFORMATEX{}; mFormat.wFormatTag = (mDevice->FmtType == DevFmtFloat) ? WAVE_FORMAT_IEEE_FLOAT : WAVE_FORMAT_PCM; mFormat.nChannels = static_cast(mDevice->channelsFromFmt()); mFormat.wBitsPerSample = static_cast(mDevice->bytesFromFmt() * 8); mFormat.nBlockAlign = static_cast(mFormat.wBitsPerSample * mFormat.nChannels / 8); mFormat.nSamplesPerSec = mDevice->mSampleRate; mFormat.nAvgBytesPerSec = mFormat.nSamplesPerSec * mFormat.nBlockAlign; mFormat.cbSize = 0; MMRESULT res{waveInOpen(&mInHdl, DeviceID, &mFormat, reinterpret_cast(&WinMMCapture::waveInProcC), reinterpret_cast(this), CALLBACK_FUNCTION)}; if(res != MMSYSERR_NOERROR) throw al::backend_exception{al::backend_error::DeviceError, "waveInOpen failed: {}", res}; // Ensure each buffer is 50ms each DWORD BufferSize{mFormat.nAvgBytesPerSec / 20u}; BufferSize -= (BufferSize % mFormat.nBlockAlign); // Allocate circular memory buffer for the captured audio // Make sure circular buffer is at least 100ms in size const auto CapturedDataSize = std::max(mDevice->mBufferSize, BufferSize*mWaveBuffer.size()); mRing = RingBuffer::Create(CapturedDataSize, mFormat.nBlockAlign, false); decltype(mBuffer)(BufferSize*mWaveBuffer.size()).swap(mBuffer); mWaveBuffer[0] = WAVEHDR{}; mWaveBuffer[0].lpData = mBuffer.data(); mWaveBuffer[0].dwBufferLength = BufferSize; for(size_t i{1};i < mWaveBuffer.size();++i) { mWaveBuffer[i] = WAVEHDR{}; mWaveBuffer[i].lpData = mWaveBuffer[i-1].lpData + mWaveBuffer[i-1].dwBufferLength; mWaveBuffer[i].dwBufferLength = mWaveBuffer[i-1].dwBufferLength; } mDeviceName = CaptureDevices[DeviceID]; } void WinMMCapture::start() { try { for(size_t i{0};i < mWaveBuffer.size();++i) { waveInPrepareHeader(mInHdl, &mWaveBuffer[i], sizeof(WAVEHDR)); waveInAddBuffer(mInHdl, &mWaveBuffer[i], sizeof(WAVEHDR)); } mKillNow.store(false, std::memory_order_release); mThread = std::thread{&WinMMCapture::captureProc, this}; waveInStart(mInHdl); } catch(std::exception& e) { throw al::backend_exception{al::backend_error::DeviceError, "Failed to start recording thread: {}", e.what()}; } } void WinMMCapture::stop() { waveInStop(mInHdl); mKillNow.store(true, std::memory_order_release); if(mThread.joinable()) { mSem.post(); mThread.join(); } waveInReset(mInHdl); for(size_t i{0};i < mWaveBuffer.size();++i) waveInUnprepareHeader(mInHdl, &mWaveBuffer[i], sizeof(WAVEHDR)); mReadable.store(0, std::memory_order_release); mIdx = 0; } void WinMMCapture::captureSamples(std::byte *buffer, uint samples) { std::ignore = mRing->read(buffer, samples); } uint WinMMCapture::availableSamples() { return static_cast(mRing->readSpace()); } } // namespace bool WinMMBackendFactory::init() { return true; } bool WinMMBackendFactory::querySupport(BackendType type) { return type == BackendType::Playback || type == BackendType::Capture; } auto WinMMBackendFactory::enumerate(BackendType type) -> std::vector { std::vector outnames; auto add_device = [&outnames](const std::string &dname) -> void { if(!dname.empty()) outnames.emplace_back(dname); }; switch(type) { case BackendType::Playback: ProbePlaybackDevices(); outnames.reserve(PlaybackDevices.size()); std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device); break; case BackendType::Capture: ProbeCaptureDevices(); outnames.reserve(CaptureDevices.size()); std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device); break; } return outnames; } BackendPtr WinMMBackendFactory::createBackend(DeviceBase *device, BackendType type) { if(type == BackendType::Playback) return BackendPtr{new WinMMPlayback{device}}; if(type == BackendType::Capture) return BackendPtr{new WinMMCapture{device}}; return nullptr; } BackendFactory &WinMMBackendFactory::getFactory() { static WinMMBackendFactory factory{}; return factory; } openal-soft-1.24.2/alc/backends/winmm.h000066400000000000000000000007201474041540300176410ustar00rootroot00000000000000#ifndef BACKENDS_WINMM_H #define BACKENDS_WINMM_H #include "base.h" struct WinMMBackendFactory final : public BackendFactory { public: auto init() -> bool final; auto querySupport(BackendType type) -> bool final; auto enumerate(BackendType type) -> std::vector final; auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_WINMM_H */ openal-soft-1.24.2/alc/context.cpp000066400000000000000000000746641474041540300170010ustar00rootroot00000000000000 #include "config.h" #include "context.h" #include #include #include #include #include #include #include #include #include #include #include "AL/efx.h" #include "al/auxeffectslot.h" #include "al/debug.h" #include "al/source.h" #include "al/effect.h" #include "al/event.h" #include "al/listener.h" #include "albit.h" #include "alc/alu.h" #include "alc/backends/base.h" #include "alnumeric.h" #include "alspan.h" #include "atomic.h" #include "core/async_event.h" #include "core/devformat.h" #include "core/device.h" #include "core/effectslot.h" #include "core/logging.h" #include "core/voice_change.h" #include "device.h" #include "flexarray.h" #include "ringbuffer.h" #include "vecmat.h" #if ALSOFT_EAX #include "al/eax/call.h" #include "al/eax/globals.h" #endif // ALSOFT_EAX namespace { using namespace std::string_view_literals; using voidp = void*; /* Default context extensions */ std::vector getContextExtensions() noexcept { return std::vector{ "AL_EXT_ALAW"sv, "AL_EXT_BFORMAT"sv, "AL_EXT_debug"sv, "AL_EXT_direct_context"sv, "AL_EXT_DOUBLE"sv, "AL_EXT_EXPONENT_DISTANCE"sv, "AL_EXT_FLOAT32"sv, "AL_EXT_IMA4"sv, "AL_EXT_LINEAR_DISTANCE"sv, "AL_EXT_MCFORMATS"sv, "AL_EXT_MULAW"sv, "AL_EXT_MULAW_BFORMAT"sv, "AL_EXT_MULAW_MCFORMATS"sv, "AL_EXT_OFFSET"sv, "AL_EXT_source_distance_model"sv, "AL_EXT_SOURCE_RADIUS"sv, "AL_EXT_STATIC_BUFFER"sv, "AL_EXT_STEREO_ANGLES"sv, "AL_LOKI_quadriphonic"sv, "AL_SOFT_bformat_ex"sv, "AL_SOFT_bformat_hoa"sv, "AL_SOFT_block_alignment"sv, "AL_SOFT_buffer_length_query"sv, "AL_SOFT_callback_buffer"sv, "AL_SOFTX_convolution_effect"sv, "AL_SOFT_deferred_updates"sv, "AL_SOFT_direct_channels"sv, "AL_SOFT_direct_channels_remix"sv, "AL_SOFT_effect_target"sv, "AL_SOFT_events"sv, "AL_SOFT_gain_clamp_ex"sv, "AL_SOFTX_hold_on_disconnect"sv, "AL_SOFT_loop_points"sv, "AL_SOFTX_map_buffer"sv, "AL_SOFT_MSADPCM"sv, "AL_SOFT_source_latency"sv, "AL_SOFT_source_length"sv, "AL_SOFTX_source_panning"sv, "AL_SOFT_source_resampler"sv, "AL_SOFT_source_spatialize"sv, "AL_SOFT_source_start_delay"sv, "AL_SOFT_UHJ"sv, "AL_SOFT_UHJ_ex"sv, }; } } // namespace std::atomic ALCcontext::sGlobalContextLock{false}; std::atomic ALCcontext::sGlobalContext{nullptr}; ALCcontext::ThreadCtx::~ThreadCtx() { if(ALCcontext *ctx{std::exchange(ALCcontext::sLocalContext, nullptr)}) { const bool result{ctx->releaseIfNoDelete()}; ERR("Context {} current for thread being destroyed{}!", voidp{ctx}, result ? "" : ", leak detected"); } } thread_local ALCcontext::ThreadCtx ALCcontext::sThreadContext; ALeffect ALCcontext::sDefaultEffect; ALCcontext::ALCcontext(al::intrusive_ptr device, ContextFlagBitset flags) : ContextBase{device.get()}, mALDevice{std::move(device)}, mContextFlags{flags} { mDebugGroups.emplace_back(DebugSource::Other, 0, std::string{}); mDebugEnabled.store(mContextFlags.test(ContextFlags::DebugBit), std::memory_order_relaxed); /* Low-severity debug messages are disabled by default. */ alDebugMessageControlDirectEXT(this, AL_DONT_CARE_EXT, AL_DONT_CARE_EXT, AL_DEBUG_SEVERITY_LOW_EXT, 0, nullptr, AL_FALSE); } ALCcontext::~ALCcontext() { TRACE("Freeing context {}", voidp{this}); size_t count{std::accumulate(mSourceList.cbegin(), mSourceList.cend(), 0_uz, [](size_t cur, const SourceSubList &sublist) noexcept -> size_t { return cur + static_cast(al::popcount(~sublist.FreeMask)); })}; if(count > 0) WARN("{} Source{} not deleted", count, (count==1)?"":"s"); mSourceList.clear(); mNumSources = 0; #if ALSOFT_EAX eaxUninitialize(); #endif // ALSOFT_EAX mDefaultSlot = nullptr; count = std::accumulate(mEffectSlotList.cbegin(), mEffectSlotList.cend(), 0_uz, [](size_t cur, const EffectSlotSubList &sublist) noexcept -> size_t { return cur + static_cast(al::popcount(~sublist.FreeMask)); }); if(count > 0) WARN("{} AuxiliaryEffectSlot{} not deleted", count, (count==1)?"":"s"); mEffectSlotList.clear(); mNumEffectSlots = 0; } void ALCcontext::init() { if(sDefaultEffect.type != AL_EFFECT_NULL && mDevice->Type == DeviceType::Playback) { mDefaultSlot = std::make_unique(this); aluInitEffectPanning(mDefaultSlot->mSlot, this); } std::unique_ptr auxslots; if(!mDefaultSlot) auxslots = EffectSlot::CreatePtrArray(0); else { auxslots = EffectSlot::CreatePtrArray(2); (*auxslots)[0] = mDefaultSlot->mSlot; (*auxslots)[1] = mDefaultSlot->mSlot; mDefaultSlot->mState = SlotState::Playing; } mActiveAuxSlots.store(std::move(auxslots), std::memory_order_relaxed); allocVoiceChanges(); { VoiceChange *cur{mVoiceChangeTail}; while(VoiceChange *next{cur->mNext.load(std::memory_order_relaxed)}) cur = next; mCurrentVoiceChange.store(cur, std::memory_order_relaxed); } mExtensions = getContextExtensions(); if(sBufferSubDataCompat) { auto iter = std::find(mExtensions.begin(), mExtensions.end(), "AL_EXT_SOURCE_RADIUS"sv); if(iter != mExtensions.end()) mExtensions.erase(iter); /* TODO: Would be nice to sort this alphabetically. Needs case- * insensitive searching. */ mExtensions.emplace_back("AL_SOFT_buffer_sub_data"sv); } #if ALSOFT_EAX eax_initialize_extensions(); #endif // ALSOFT_EAX if(!mExtensions.empty()) { const size_t len{std::accumulate(mExtensions.cbegin()+1, mExtensions.cend(), mExtensions.front().length(), [](size_t current, std::string_view ext) noexcept { return current + ext.length() + 1; })}; std::string extensions; extensions.reserve(len); extensions += mExtensions.front(); for(std::string_view ext : al::span{mExtensions}.subspan<1>()) { extensions += ' '; extensions += ext; } mExtensionsString = std::move(extensions); } #if ALSOFT_EAX eax_set_defaults(); #endif mParams.Position = alu::Vector{0.0f, 0.0f, 0.0f, 1.0f}; mParams.Matrix = alu::Matrix::Identity(); mParams.Velocity = alu::Vector{}; mParams.Gain = mListener.Gain; mParams.MetersPerUnit = mListener.mMetersPerUnit #if ALSOFT_EAX * eaxGetDistanceFactor() #endif ; mParams.AirAbsorptionGainHF = mAirAbsorptionGainHF; mParams.DopplerFactor = mDopplerFactor; mParams.SpeedOfSound = mSpeedOfSound * mDopplerVelocity #if ALSOFT_EAX / eaxGetDistanceFactor() #endif ; mParams.SourceDistanceModel = mSourceDistanceModel; mParams.mDistanceModel = mDistanceModel; mAsyncEvents = RingBuffer::Create(1024, sizeof(AsyncEvent), false); StartEventThrd(this); allocVoices(256); mActiveVoiceCount.store(64, std::memory_order_relaxed); } void ALCcontext::deinit() { if(sLocalContext == this) { WARN("{} released while current on thread", voidp{this}); auto _ = ContextRef{sLocalContext}; sThreadContext.set(nullptr); } if(ALCcontext *origctx{this}; sGlobalContext.compare_exchange_strong(origctx, nullptr)) { auto _ = ContextRef{origctx}; while(sGlobalContextLock.load()) { /* Wait to make sure another thread didn't get the context and is * trying to increment its refcount. */ } } bool stopPlayback{}; /* First make sure this context exists in the device's list. */ auto oldarray = al::span{*mDevice->mContexts.load(std::memory_order_acquire)}; if(auto toremove = static_cast(std::count(oldarray.begin(), oldarray.end(), this))) { using ContextArray = al::FlexArray; const auto newsize = size_t{oldarray.size() - toremove}; auto newarray = ContextArray::Create(newsize); /* Copy the current/old context handles to the new array, excluding the * given context. */ std::copy_if(oldarray.begin(), oldarray.end(), newarray->begin(), [this](ContextBase *ctx) { return ctx != this; }); /* Store the new context array in the device. Wait for any current mix * to finish before deleting the old array. */ auto prevarray = mDevice->mContexts.exchange(std::move(newarray)); std::ignore = mDevice->waitForMix(); stopPlayback = (newsize == 0); } else stopPlayback = oldarray.empty(); StopEventThrd(this); if(stopPlayback && mALDevice->mDeviceState == DeviceState::Playing) { mALDevice->Backend->stop(); mALDevice->mDeviceState = DeviceState::Configured; } } void ALCcontext::applyAllUpdates() { /* Tell the mixer to stop applying updates, then wait for any active * updating to finish, before providing updates. */ mHoldUpdates.store(true, std::memory_order_release); while((mUpdateCount.load(std::memory_order_acquire)&1) != 0) { /* busy-wait */ } #if ALSOFT_EAX if(mEaxNeedsCommit) eaxCommit(); #endif if(std::exchange(mPropsDirty, false)) UpdateContextProps(this); UpdateAllEffectSlotProps(this); UpdateAllSourceProps(this); /* Now with all updates declared, let the mixer continue applying them so * they all happen at once. */ mHoldUpdates.store(false, std::memory_order_release); } #if ALSOFT_EAX namespace { template void ForEachSource(ALCcontext *context, F func) { for(auto &sublist : context->mSourceList) { uint64_t usemask{~sublist.FreeMask}; while(usemask) { const auto idx = static_cast(al::countr_zero(usemask)); usemask &= ~(1_u64 << idx); func((*sublist.Sources)[idx]); } } } } // namespace bool ALCcontext::eaxIsCapable() const noexcept { return eax_has_enough_aux_sends(); } void ALCcontext::eaxUninitialize() noexcept { if(!mEaxIsInitialized) return; mEaxIsInitialized = false; mEaxIsTried = false; mEaxFxSlots.uninitialize(); } ALenum ALCcontext::eax_eax_set( const GUID* property_set_id, ALuint property_id, ALuint property_source_id, ALvoid* property_value, ALuint property_value_size) { const auto call = create_eax_call( EaxCallType::set, property_set_id, property_id, property_source_id, property_value, property_value_size); eax_initialize(); switch(call.get_property_set_id()) { case EaxCallPropertySetId::context: eax_set(call); break; case EaxCallPropertySetId::fx_slot: case EaxCallPropertySetId::fx_slot_effect: eax_dispatch_fx_slot(call); break; case EaxCallPropertySetId::source: eax_dispatch_source(call); break; default: eax_fail_unknown_property_set_id(); } mEaxNeedsCommit = true; if(!call.is_deferred()) { eaxCommit(); if(!mDeferUpdates) applyAllUpdates(); } return AL_NO_ERROR; } ALenum ALCcontext::eax_eax_get( const GUID* property_set_id, ALuint property_id, ALuint property_source_id, ALvoid* property_value, ALuint property_value_size) { const auto call = create_eax_call( EaxCallType::get, property_set_id, property_id, property_source_id, property_value, property_value_size); eax_initialize(); switch(call.get_property_set_id()) { case EaxCallPropertySetId::context: eax_get(call); break; case EaxCallPropertySetId::fx_slot: case EaxCallPropertySetId::fx_slot_effect: eax_dispatch_fx_slot(call); break; case EaxCallPropertySetId::source: eax_dispatch_source(call); break; default: eax_fail_unknown_property_set_id(); } return AL_NO_ERROR; } void ALCcontext::eaxSetLastError() noexcept { mEaxLastError = EAXERR_INVALID_OPERATION; } [[noreturn]] void ALCcontext::eax_fail(const char* message) { throw ContextException{message}; } [[noreturn]] void ALCcontext::eax_fail_unknown_property_set_id() { eax_fail("Unknown property ID."); } [[noreturn]] void ALCcontext::eax_fail_unknown_primary_fx_slot_id() { eax_fail("Unknown primary FX Slot ID."); } [[noreturn]] void ALCcontext::eax_fail_unknown_property_id() { eax_fail("Unknown property ID."); } [[noreturn]] void ALCcontext::eax_fail_unknown_version() { eax_fail("Unknown version."); } void ALCcontext::eax_initialize_extensions() { if(!eax_g_is_enabled) return; mExtensions.emplace(mExtensions.begin(), "EAX-RAM"sv); if(eaxIsCapable()) { mExtensions.emplace(mExtensions.begin(), "EAX5.0"sv); mExtensions.emplace(mExtensions.begin(), "EAX4.0"sv); mExtensions.emplace(mExtensions.begin(), "EAX3.0"sv); mExtensions.emplace(mExtensions.begin(), "EAX2.0"sv); mExtensions.emplace(mExtensions.begin(), "EAX"sv); } } void ALCcontext::eax_initialize() { if(mEaxIsInitialized) return; if(mEaxIsTried) eax_fail("No EAX."); mEaxIsTried = true; if(!eax_g_is_enabled) eax_fail("EAX disabled by a configuration."); eax_ensure_compatibility(); eax_set_defaults(); eax_context_commit_air_absorbtion_hf(); eax_update_speaker_configuration(); eax_initialize_fx_slots(); mEaxIsInitialized = true; } bool ALCcontext::eax_has_no_default_effect_slot() const noexcept { return mDefaultSlot == nullptr; } void ALCcontext::eax_ensure_no_default_effect_slot() const { if(!eax_has_no_default_effect_slot()) eax_fail("There is a default effect slot in the context."); } bool ALCcontext::eax_has_enough_aux_sends() const noexcept { return mALDevice->NumAuxSends >= EAX_MAX_FXSLOTS; } void ALCcontext::eax_ensure_enough_aux_sends() const { if(!eax_has_enough_aux_sends()) eax_fail("Not enough aux sends."); } void ALCcontext::eax_ensure_compatibility() { eax_ensure_enough_aux_sends(); } unsigned long ALCcontext::eax_detect_speaker_configuration() const { #define EAX_PREFIX "[EAX_DETECT_SPEAKER_CONFIG]" switch(mDevice->FmtChans) { case DevFmtMono: return SPEAKERS_2; case DevFmtStereo: /* Pretend 7.1 if using UHJ output, since they both provide full * horizontal surround. */ if(mDevice->mUhjEncoder) return SPEAKERS_7; if(mDevice->Flags.test(DirectEar)) return HEADPHONES; return SPEAKERS_2; case DevFmtQuad: return SPEAKERS_4; case DevFmtX51: return SPEAKERS_5; case DevFmtX61: return SPEAKERS_6; case DevFmtX71: return SPEAKERS_7; /* 7.1.4(.4) is compatible with 7.1. This could instead be HEADPHONES to * suggest with-height surround sound (like HRTF). */ case DevFmtX714: return SPEAKERS_7; case DevFmtX7144: return SPEAKERS_7; /* 3D7.1 is only compatible with 5.1. This could instead be HEADPHONES to * suggest full-sphere surround sound (like HRTF). */ case DevFmtX3D71: return SPEAKERS_5; /* This could also be HEADPHONES, since headphones-based HRTF and Ambi3D * provide full-sphere surround sound. Depends if apps are more likely to * consider headphones or 7.1 for surround sound support. */ case DevFmtAmbi3D: return SPEAKERS_7; } ERR(EAX_PREFIX "Unexpected device channel format {:#x}.", uint{al::to_underlying(mDevice->FmtChans)}); return HEADPHONES; #undef EAX_PREFIX } void ALCcontext::eax_update_speaker_configuration() { mEaxSpeakerConfig = eax_detect_speaker_configuration(); } void ALCcontext::eax_set_last_error_defaults() noexcept { mEaxLastError = EAXCONTEXT_DEFAULTLASTERROR; } void ALCcontext::eax_session_set_defaults() noexcept { mEaxSession.ulEAXVersion = EAXCONTEXT_DEFAULTEAXSESSION; mEaxSession.ulMaxActiveSends = EAXCONTEXT_DEFAULTMAXACTIVESENDS; } void ALCcontext::eax4_context_set_defaults(Eax4Props& props) noexcept { props.guidPrimaryFXSlotID = EAX40CONTEXT_DEFAULTPRIMARYFXSLOTID; props.flDistanceFactor = EAXCONTEXT_DEFAULTDISTANCEFACTOR; props.flAirAbsorptionHF = EAXCONTEXT_DEFAULTAIRABSORPTIONHF; props.flHFReference = EAXCONTEXT_DEFAULTHFREFERENCE; } void ALCcontext::eax4_context_set_defaults(Eax4State& state) noexcept { eax4_context_set_defaults(state.i); state.d = state.i; } void ALCcontext::eax5_context_set_defaults(Eax5Props& props) noexcept { props.guidPrimaryFXSlotID = EAX50CONTEXT_DEFAULTPRIMARYFXSLOTID; props.flDistanceFactor = EAXCONTEXT_DEFAULTDISTANCEFACTOR; props.flAirAbsorptionHF = EAXCONTEXT_DEFAULTAIRABSORPTIONHF; props.flHFReference = EAXCONTEXT_DEFAULTHFREFERENCE; props.flMacroFXFactor = EAXCONTEXT_DEFAULTMACROFXFACTOR; } void ALCcontext::eax5_context_set_defaults(Eax5State& state) noexcept { eax5_context_set_defaults(state.i); state.d = state.i; } void ALCcontext::eax_context_set_defaults() { eax5_context_set_defaults(mEax123); eax4_context_set_defaults(mEax4); eax5_context_set_defaults(mEax5); mEax = mEax5.i; mEaxVersion = 5; mEaxDf = EaxDirtyFlags{}; } void ALCcontext::eax_set_defaults() { eax_set_last_error_defaults(); eax_session_set_defaults(); eax_context_set_defaults(); } void ALCcontext::eax_dispatch_fx_slot(const EaxCall& call) { const auto fx_slot_index = call.get_fx_slot_index(); if(!fx_slot_index.has_value()) eax_fail("Invalid fx slot index."); auto& fx_slot = eaxGetFxSlot(*fx_slot_index); if(fx_slot.eax_dispatch(call)) { std::lock_guard source_lock{mSourceLock}; ForEachSource(this, std::mem_fn(&ALsource::eaxMarkAsChanged)); } } void ALCcontext::eax_dispatch_source(const EaxCall& call) { const auto source_id = call.get_property_al_name(); std::lock_guard source_lock{mSourceLock}; const auto source = ALsource::EaxLookupSource(*this, source_id); if (source == nullptr) eax_fail("Source not found."); source->eaxDispatch(call); } void ALCcontext::eax_get_misc(const EaxCall& call) { switch(call.get_property_id()) { case EAXCONTEXT_NONE: break; case EAXCONTEXT_LASTERROR: call.set_value(mEaxLastError); mEaxLastError = EAX_OK; break; case EAXCONTEXT_SPEAKERCONFIG: call.set_value(mEaxSpeakerConfig); break; case EAXCONTEXT_EAXSESSION: call.set_value(mEaxSession); break; default: eax_fail_unknown_property_id(); } } void ALCcontext::eax4_get(const EaxCall& call, const Eax4Props& props) { switch(call.get_property_id()) { case EAXCONTEXT_ALLPARAMETERS: call.set_value(props); break; case EAXCONTEXT_PRIMARYFXSLOTID: call.set_value(props.guidPrimaryFXSlotID); break; case EAXCONTEXT_DISTANCEFACTOR: call.set_value(props.flDistanceFactor); break; case EAXCONTEXT_AIRABSORPTIONHF: call.set_value(props.flAirAbsorptionHF); break; case EAXCONTEXT_HFREFERENCE: call.set_value(props.flHFReference); break; default: eax_get_misc(call); break; } } void ALCcontext::eax5_get(const EaxCall& call, const Eax5Props& props) { switch(call.get_property_id()) { case EAXCONTEXT_ALLPARAMETERS: call.set_value(props); break; case EAXCONTEXT_PRIMARYFXSLOTID: call.set_value(props.guidPrimaryFXSlotID); break; case EAXCONTEXT_DISTANCEFACTOR: call.set_value(props.flDistanceFactor); break; case EAXCONTEXT_AIRABSORPTIONHF: call.set_value(props.flAirAbsorptionHF); break; case EAXCONTEXT_HFREFERENCE: call.set_value(props.flHFReference); break; case EAXCONTEXT_MACROFXFACTOR: call.set_value(props.flMacroFXFactor); break; default: eax_get_misc(call); break; } } void ALCcontext::eax_get(const EaxCall& call) { switch(call.get_version()) { case 4: eax4_get(call, mEax4.i); break; case 5: eax5_get(call, mEax5.i); break; default: eax_fail_unknown_version(); } } void ALCcontext::eax_context_commit_primary_fx_slot_id() { mEaxPrimaryFxSlotIndex = mEax.guidPrimaryFXSlotID; } void ALCcontext::eax_context_commit_distance_factor() { /* mEax.flDistanceFactor was changed, so the context props are dirty. */ mPropsDirty = true; } void ALCcontext::eax_context_commit_air_absorbtion_hf() { const auto new_value = level_mb_to_gain(mEax.flAirAbsorptionHF); if(mAirAbsorptionGainHF == new_value) return; mAirAbsorptionGainHF = new_value; mPropsDirty = true; } void ALCcontext::eax_context_commit_hf_reference() { // TODO } void ALCcontext::eax_context_commit_macro_fx_factor() { // TODO } void ALCcontext::eax_initialize_fx_slots() { mEaxFxSlots.initialize(*this); mEaxPrimaryFxSlotIndex = mEax.guidPrimaryFXSlotID; } void ALCcontext::eax_update_sources() { std::unique_lock source_lock{mSourceLock}; auto update_source = [](ALsource &source) { source.eaxCommit(); }; ForEachSource(this, update_source); } void ALCcontext::eax_set_misc(const EaxCall& call) { switch(call.get_property_id()) { case EAXCONTEXT_NONE: break; case EAXCONTEXT_SPEAKERCONFIG: eax_set(call, mEaxSpeakerConfig); break; case EAXCONTEXT_EAXSESSION: eax_set(call, mEaxSession); break; default: eax_fail_unknown_property_id(); } } void ALCcontext::eax4_defer_all(const EaxCall& call, Eax4State& state) { const auto& src = call.get_value(); Eax4AllValidator{}(src); const auto& dst_i = state.i; auto& dst_d = state.d; dst_d = src; if(dst_i.guidPrimaryFXSlotID != dst_d.guidPrimaryFXSlotID) mEaxDf |= eax_primary_fx_slot_id_dirty_bit; if(dst_i.flDistanceFactor != dst_d.flDistanceFactor) mEaxDf |= eax_distance_factor_dirty_bit; if(dst_i.flAirAbsorptionHF != dst_d.flAirAbsorptionHF) mEaxDf |= eax_air_absorption_hf_dirty_bit; if(dst_i.flHFReference != dst_d.flHFReference) mEaxDf |= eax_hf_reference_dirty_bit; } void ALCcontext::eax4_defer(const EaxCall& call, Eax4State& state) { switch(call.get_property_id()) { case EAXCONTEXT_ALLPARAMETERS: eax4_defer_all(call, state); break; case EAXCONTEXT_PRIMARYFXSLOTID: eax_defer( call, state, &EAX40CONTEXTPROPERTIES::guidPrimaryFXSlotID); break; case EAXCONTEXT_DISTANCEFACTOR: eax_defer( call, state, &EAX40CONTEXTPROPERTIES::flDistanceFactor); break; case EAXCONTEXT_AIRABSORPTIONHF: eax_defer( call, state, &EAX40CONTEXTPROPERTIES::flAirAbsorptionHF); break; case EAXCONTEXT_HFREFERENCE: eax_defer( call, state, &EAX40CONTEXTPROPERTIES::flHFReference); break; default: eax_set_misc(call); break; } } void ALCcontext::eax5_defer_all(const EaxCall& call, Eax5State& state) { const auto& src = call.get_value(); Eax4AllValidator{}(src); const auto& dst_i = state.i; auto& dst_d = state.d; dst_d = src; if(dst_i.guidPrimaryFXSlotID != dst_d.guidPrimaryFXSlotID) mEaxDf |= eax_primary_fx_slot_id_dirty_bit; if(dst_i.flDistanceFactor != dst_d.flDistanceFactor) mEaxDf |= eax_distance_factor_dirty_bit; if(dst_i.flAirAbsorptionHF != dst_d.flAirAbsorptionHF) mEaxDf |= eax_air_absorption_hf_dirty_bit; if(dst_i.flHFReference != dst_d.flHFReference) mEaxDf |= eax_hf_reference_dirty_bit; if(dst_i.flMacroFXFactor != dst_d.flMacroFXFactor) mEaxDf |= eax_macro_fx_factor_dirty_bit; } void ALCcontext::eax5_defer(const EaxCall& call, Eax5State& state) { switch(call.get_property_id()) { case EAXCONTEXT_ALLPARAMETERS: eax5_defer_all(call, state); break; case EAXCONTEXT_PRIMARYFXSLOTID: eax_defer( call, state, &EAX50CONTEXTPROPERTIES::guidPrimaryFXSlotID); break; case EAXCONTEXT_DISTANCEFACTOR: eax_defer( call, state, &EAX50CONTEXTPROPERTIES::flDistanceFactor); break; case EAXCONTEXT_AIRABSORPTIONHF: eax_defer( call, state, &EAX50CONTEXTPROPERTIES::flAirAbsorptionHF); break; case EAXCONTEXT_HFREFERENCE: eax_defer( call, state, &EAX50CONTEXTPROPERTIES::flHFReference); break; case EAXCONTEXT_MACROFXFACTOR: eax_defer( call, state, &EAX50CONTEXTPROPERTIES::flMacroFXFactor); break; default: eax_set_misc(call); break; } } void ALCcontext::eax_set(const EaxCall& call) { const auto version = call.get_version(); switch(version) { case 4: eax4_defer(call, mEax4); break; case 5: eax5_defer(call, mEax5); break; default: eax_fail_unknown_version(); } if(version != mEaxVersion) mEaxDf = ~EaxDirtyFlags(); mEaxVersion = version; } void ALCcontext::eax4_context_commit(Eax4State& state, EaxDirtyFlags& dst_df) { if(mEaxDf == EaxDirtyFlags{}) return; eax_context_commit_property( state, dst_df, &EAX40CONTEXTPROPERTIES::guidPrimaryFXSlotID); eax_context_commit_property( state, dst_df, &EAX40CONTEXTPROPERTIES::flDistanceFactor); eax_context_commit_property( state, dst_df, &EAX40CONTEXTPROPERTIES::flAirAbsorptionHF); eax_context_commit_property( state, dst_df, &EAX40CONTEXTPROPERTIES::flHFReference); mEaxDf = EaxDirtyFlags{}; } void ALCcontext::eax5_context_commit(Eax5State& state, EaxDirtyFlags& dst_df) { if(mEaxDf == EaxDirtyFlags{}) return; eax_context_commit_property( state, dst_df, &EAX50CONTEXTPROPERTIES::guidPrimaryFXSlotID); eax_context_commit_property( state, dst_df, &EAX50CONTEXTPROPERTIES::flDistanceFactor); eax_context_commit_property( state, dst_df, &EAX50CONTEXTPROPERTIES::flAirAbsorptionHF); eax_context_commit_property( state, dst_df, &EAX50CONTEXTPROPERTIES::flHFReference); eax_context_commit_property( state, dst_df, &EAX50CONTEXTPROPERTIES::flMacroFXFactor); mEaxDf = EaxDirtyFlags{}; } void ALCcontext::eax_context_commit() { auto dst_df = EaxDirtyFlags{}; switch(mEaxVersion) { case 1: case 2: case 3: eax5_context_commit(mEax123, dst_df); break; case 4: eax4_context_commit(mEax4, dst_df); break; case 5: eax5_context_commit(mEax5, dst_df); break; } if(dst_df == EaxDirtyFlags{}) return; if((dst_df & eax_primary_fx_slot_id_dirty_bit) != EaxDirtyFlags{}) eax_context_commit_primary_fx_slot_id(); if((dst_df & eax_distance_factor_dirty_bit) != EaxDirtyFlags{}) eax_context_commit_distance_factor(); if((dst_df & eax_air_absorption_hf_dirty_bit) != EaxDirtyFlags{}) eax_context_commit_air_absorbtion_hf(); if((dst_df & eax_hf_reference_dirty_bit) != EaxDirtyFlags{}) eax_context_commit_hf_reference(); if((dst_df & eax_macro_fx_factor_dirty_bit) != EaxDirtyFlags{}) eax_context_commit_macro_fx_factor(); if((dst_df & eax_primary_fx_slot_id_dirty_bit) != EaxDirtyFlags{}) eax_update_sources(); } void ALCcontext::eaxCommit() { mEaxNeedsCommit = false; eax_context_commit(); eaxCommitFxSlots(); eax_update_sources(); } FORCE_ALIGN auto AL_APIENTRY EAXSet(const GUID *property_set_id, ALuint property_id, ALuint source_id, ALvoid *value, ALuint value_size) noexcept -> ALenum { auto context = GetContextRef(); if(!context) UNLIKELY return AL_INVALID_OPERATION; return EAXSetDirect(context.get(), property_set_id, property_id, source_id, value, value_size); } FORCE_ALIGN auto AL_APIENTRY EAXSetDirect(ALCcontext *context, const GUID *property_set_id, ALuint property_id, ALuint source_id, ALvoid *value, ALuint value_size) noexcept -> ALenum try { std::lock_guard prop_lock{context->mPropLock}; return context->eax_eax_set(property_set_id, property_id, source_id, value, value_size); } catch(...) { context->eaxSetLastError(); eax_log_exception(std::data(__func__)); return AL_INVALID_OPERATION; } FORCE_ALIGN auto AL_APIENTRY EAXGet(const GUID *property_set_id, ALuint property_id, ALuint source_id, ALvoid *value, ALuint value_size) noexcept -> ALenum { auto context = GetContextRef(); if(!context) UNLIKELY return AL_INVALID_OPERATION; return EAXGetDirect(context.get(), property_set_id, property_id, source_id, value, value_size); } FORCE_ALIGN auto AL_APIENTRY EAXGetDirect(ALCcontext *context, const GUID *property_set_id, ALuint property_id, ALuint source_id, ALvoid *value, ALuint value_size) noexcept -> ALenum try { std::lock_guard prop_lock{context->mPropLock}; return context->eax_eax_get(property_set_id, property_id, source_id, value, value_size); } catch(...) { context->eaxSetLastError(); eax_log_exception(std::data(__func__)); return AL_INVALID_OPERATION; } #endif // ALSOFT_EAX openal-soft-1.24.2/alc/context.h000066400000000000000000000436101474041540300164310ustar00rootroot00000000000000#ifndef ALC_CONTEXT_H #define ALC_CONTEXT_H #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include "AL/al.h" #include "AL/alc.h" #include "AL/alext.h" #include "al/listener.h" #include "althreads.h" #include "core/context.h" #include "fmt/core.h" #include "intrusive_ptr.h" #include "opthelpers.h" #if ALSOFT_EAX #include "al/eax/api.h" #include "al/eax/exception.h" #include "al/eax/fx_slot_index.h" #include "al/eax/fx_slots.h" #include "al/eax/utils.h" class EaxCall; #endif // ALSOFT_EAX struct ALeffect; struct ALeffectslot; struct DebugGroup; struct EffectSlotSubList; struct SourceSubList; enum class DebugSource : std::uint8_t; enum class DebugType : std::uint8_t; enum class DebugSeverity : std::uint8_t; using uint = unsigned int; enum ContextFlags { DebugBit = 0, /* ALC_CONTEXT_DEBUG_BIT_EXT */ }; using ContextFlagBitset = std::bitset; struct DebugLogEntry { const DebugSource mSource; const DebugType mType; const DebugSeverity mSeverity; const uint mId; std::string mMessage; template DebugLogEntry(DebugSource source, DebugType type, uint id, DebugSeverity severity, T&& message) : mSource{source}, mType{type}, mSeverity{severity}, mId{id} , mMessage{std::forward(message)} { } DebugLogEntry(const DebugLogEntry&) = default; DebugLogEntry(DebugLogEntry&&) = default; }; namespace al { struct Device; } // namespace al struct ALCcontext final : public al::intrusive_ref, ContextBase { const al::intrusive_ptr mALDevice; bool mPropsDirty{true}; bool mDeferUpdates{false}; std::mutex mPropLock; al::tss mLastThreadError{AL_NO_ERROR}; const ContextFlagBitset mContextFlags; std::atomic mDebugEnabled{false}; DistanceModel mDistanceModel{DistanceModel::Default}; bool mSourceDistanceModel{false}; float mDopplerFactor{1.0f}; float mDopplerVelocity{1.0f}; float mSpeedOfSound{SpeedOfSoundMetersPerSec}; float mAirAbsorptionGainHF{AirAbsorbGainHF}; std::mutex mEventCbLock; ALEVENTPROCSOFT mEventCb{}; void *mEventParam{nullptr}; std::mutex mDebugCbLock; ALDEBUGPROCEXT mDebugCb{}; void *mDebugParam{nullptr}; std::vector mDebugGroups; std::deque mDebugLog; ALlistener mListener{}; std::vector mSourceList; ALuint mNumSources{0}; std::mutex mSourceLock; std::vector mEffectSlotList; ALuint mNumEffectSlots{0u}; std::mutex mEffectSlotLock; /* Default effect slot */ std::unique_ptr mDefaultSlot; std::vector mExtensions; std::string mExtensionsString; std::unordered_map mSourceNames; std::unordered_map mEffectSlotNames; ALCcontext(al::intrusive_ptr device, ContextFlagBitset flags); ALCcontext(const ALCcontext&) = delete; ALCcontext& operator=(const ALCcontext&) = delete; ~ALCcontext() final; void init(); /** * Removes the context from its device and removes it from being current on * the running thread or globally. Stops device playback if this was the * last context on its device. */ void deinit(); /** * Defers/suspends updates for the given context's listener and sources. * This does *NOT* stop mixing, but rather prevents certain property * changes from taking effect. mPropLock must be held when called. */ void deferUpdates() noexcept { mDeferUpdates = true; } /** * Resumes update processing after being deferred. mPropLock must be held * when called. */ void processUpdates() { if(std::exchange(mDeferUpdates, false)) applyAllUpdates(); } /** * Applies all pending updates for the context, listener, effect slots, and * sources. */ void applyAllUpdates(); void setErrorImpl(ALenum errorCode, const fmt::string_view fmt, fmt::format_args args); template void setError(ALenum errorCode, fmt::format_string msg, Args&& ...args) { setErrorImpl(errorCode, msg, fmt::make_format_args(args...)); } [[noreturn]] void throw_error_impl(ALenum errorCode, const fmt::string_view fmt, fmt::format_args args); template [[noreturn]] void throw_error(ALenum errorCode, fmt::format_string fmt, Args&&... args) { throw_error_impl(errorCode, fmt, fmt::make_format_args(args...)); } void sendDebugMessage(std::unique_lock &debuglock, DebugSource source, DebugType type, ALuint id, DebugSeverity severity, std::string_view message); void debugMessage(DebugSource source, DebugType type, ALuint id, DebugSeverity severity, std::string_view message) { if(!mDebugEnabled.load(std::memory_order_relaxed)) LIKELY return; std::unique_lock debuglock{mDebugCbLock}; sendDebugMessage(debuglock, source, type, id, severity, message); } /* Process-wide current context */ static std::atomic sGlobalContextLock; static std::atomic sGlobalContext; private: /* Thread-local current context. */ static inline thread_local ALCcontext *sLocalContext{}; /* Thread-local context handling. This handles attempting to release the * context which may have been left current when the thread is destroyed. */ class ThreadCtx { public: ThreadCtx() = default; ThreadCtx(const ThreadCtx&) = delete; auto operator=(const ThreadCtx&) -> ThreadCtx& = delete; ~ThreadCtx(); /* NOLINTBEGIN(readability-convert-member-functions-to-static) * This should be non-static to invoke construction of the thread-local * sThreadContext, so that it's destructor gets run at thread exit to * clear sLocalContext (which isn't a member variable to make read * access efficient). */ void set(ALCcontext *ctx) const noexcept { sLocalContext = ctx; } /* NOLINTEND(readability-convert-member-functions-to-static) */ }; static thread_local ThreadCtx sThreadContext; public: static ALCcontext *getThreadContext() noexcept { return sLocalContext; } static void setThreadContext(ALCcontext *context) noexcept { sThreadContext.set(context); } /* Default effect that applies to sources that don't have an effect on send 0. */ static ALeffect sDefaultEffect; #if ALSOFT_EAX bool hasEax() const noexcept { return mEaxIsInitialized; } bool eaxIsCapable() const noexcept; void eaxUninitialize() noexcept; ALenum eax_eax_set( const GUID* property_set_id, ALuint property_id, ALuint property_source_id, ALvoid* property_value, ALuint property_value_size); ALenum eax_eax_get( const GUID* property_set_id, ALuint property_id, ALuint property_source_id, ALvoid* property_value, ALuint property_value_size); void eaxSetLastError() noexcept; [[nodiscard]] auto eaxGetDistanceFactor() const noexcept -> float { return mEax.flDistanceFactor; } [[nodiscard]] auto eaxGetPrimaryFxSlotIndex() const noexcept -> EaxFxSlotIndex { return mEaxPrimaryFxSlotIndex; } const ALeffectslot& eaxGetFxSlot(EaxFxSlotIndexValue fx_slot_index) const { return mEaxFxSlots.get(fx_slot_index); } ALeffectslot& eaxGetFxSlot(EaxFxSlotIndexValue fx_slot_index) { return mEaxFxSlots.get(fx_slot_index); } bool eaxNeedsCommit() const noexcept { return mEaxNeedsCommit; } void eaxCommit(); void eaxCommitFxSlots() { mEaxFxSlots.commit(); } private: static constexpr auto eax_primary_fx_slot_id_dirty_bit = EaxDirtyFlags{1} << 0; static constexpr auto eax_distance_factor_dirty_bit = EaxDirtyFlags{1} << 1; static constexpr auto eax_air_absorption_hf_dirty_bit = EaxDirtyFlags{1} << 2; static constexpr auto eax_hf_reference_dirty_bit = EaxDirtyFlags{1} << 3; static constexpr auto eax_macro_fx_factor_dirty_bit = EaxDirtyFlags{1} << 4; using Eax4Props = EAX40CONTEXTPROPERTIES; struct Eax4State { Eax4Props i; // Immediate. Eax4Props d; // Deferred. }; using Eax5Props = EAX50CONTEXTPROPERTIES; struct Eax5State { Eax5Props i; // Immediate. Eax5Props d; // Deferred. }; class ContextException final : public EaxException { public: explicit ContextException(const char *message) : EaxException{"EAX_CONTEXT", message} { } }; struct Eax4PrimaryFxSlotIdValidator { void operator()(const GUID& guidPrimaryFXSlotID) const { if(guidPrimaryFXSlotID != EAX_NULL_GUID && guidPrimaryFXSlotID != EAXPROPERTYID_EAX40_FXSlot0 && guidPrimaryFXSlotID != EAXPROPERTYID_EAX40_FXSlot1 && guidPrimaryFXSlotID != EAXPROPERTYID_EAX40_FXSlot2 && guidPrimaryFXSlotID != EAXPROPERTYID_EAX40_FXSlot3) { eax_fail_unknown_primary_fx_slot_id(); } } }; struct Eax4DistanceFactorValidator { void operator()(float flDistanceFactor) const { eax_validate_range( "Distance Factor", flDistanceFactor, EAXCONTEXT_MINDISTANCEFACTOR, EAXCONTEXT_MAXDISTANCEFACTOR); } }; struct Eax4AirAbsorptionHfValidator { void operator()(float flAirAbsorptionHF) const { eax_validate_range( "Air Absorption HF", flAirAbsorptionHF, EAXCONTEXT_MINAIRABSORPTIONHF, EAXCONTEXT_MAXAIRABSORPTIONHF); } }; struct Eax4HfReferenceValidator { void operator()(float flHFReference) const { eax_validate_range( "HF Reference", flHFReference, EAXCONTEXT_MINHFREFERENCE, EAXCONTEXT_MAXHFREFERENCE); } }; struct Eax4AllValidator { void operator()(const EAX40CONTEXTPROPERTIES& all) const { Eax4PrimaryFxSlotIdValidator{}(all.guidPrimaryFXSlotID); Eax4DistanceFactorValidator{}(all.flDistanceFactor); Eax4AirAbsorptionHfValidator{}(all.flAirAbsorptionHF); Eax4HfReferenceValidator{}(all.flHFReference); } }; struct Eax5PrimaryFxSlotIdValidator { void operator()(const GUID& guidPrimaryFXSlotID) const { if(guidPrimaryFXSlotID != EAX_NULL_GUID && guidPrimaryFXSlotID != EAXPROPERTYID_EAX50_FXSlot0 && guidPrimaryFXSlotID != EAXPROPERTYID_EAX50_FXSlot1 && guidPrimaryFXSlotID != EAXPROPERTYID_EAX50_FXSlot2 && guidPrimaryFXSlotID != EAXPROPERTYID_EAX50_FXSlot3) { eax_fail_unknown_primary_fx_slot_id(); } } }; struct Eax5MacroFxFactorValidator { void operator()(float flMacroFXFactor) const { eax_validate_range( "Macro FX Factor", flMacroFXFactor, EAXCONTEXT_MINMACROFXFACTOR, EAXCONTEXT_MAXMACROFXFACTOR); } }; struct Eax5AllValidator { void operator()(const EAX50CONTEXTPROPERTIES& all) const { Eax5PrimaryFxSlotIdValidator{}(all.guidPrimaryFXSlotID); Eax4DistanceFactorValidator{}(all.flDistanceFactor); Eax4AirAbsorptionHfValidator{}(all.flAirAbsorptionHF); Eax4HfReferenceValidator{}(all.flHFReference); Eax5MacroFxFactorValidator{}(all.flMacroFXFactor); } }; struct Eax5EaxVersionValidator { void operator()(unsigned long ulEAXVersion) const { eax_validate_range( "EAX version", ulEAXVersion, EAXCONTEXT_MINEAXSESSION, EAXCONTEXT_MAXEAXSESSION); } }; struct Eax5MaxActiveSendsValidator { void operator()(unsigned long ulMaxActiveSends) const { eax_validate_range( "Max Active Sends", ulMaxActiveSends, EAXCONTEXT_MINMAXACTIVESENDS, EAXCONTEXT_MAXMAXACTIVESENDS); } }; struct Eax5SessionAllValidator { void operator()(const EAXSESSIONPROPERTIES& all) const { Eax5EaxVersionValidator{}(all.ulEAXVersion); Eax5MaxActiveSendsValidator{}(all.ulMaxActiveSends); } }; struct Eax5SpeakerConfigValidator { void operator()(unsigned long ulSpeakerConfig) const { eax_validate_range( "Speaker Config", ulSpeakerConfig, EAXCONTEXT_MINSPEAKERCONFIG, EAXCONTEXT_MAXSPEAKERCONFIG); } }; bool mEaxIsInitialized{}; bool mEaxIsTried{}; long mEaxLastError{}; unsigned long mEaxSpeakerConfig{}; EaxFxSlotIndex mEaxPrimaryFxSlotIndex{}; EaxFxSlots mEaxFxSlots{}; int mEaxVersion{}; // Current EAX version. bool mEaxNeedsCommit{}; EaxDirtyFlags mEaxDf{}; // Dirty flags for the current EAX version. Eax5State mEax123{}; // EAX1/EAX2/EAX3 state. Eax4State mEax4{}; // EAX4 state. Eax5State mEax5{}; // EAX5 state. Eax5Props mEax{}; // Current EAX state. EAXSESSIONPROPERTIES mEaxSession{}; [[noreturn]] static void eax_fail(const char* message); [[noreturn]] static void eax_fail_unknown_property_set_id(); [[noreturn]] static void eax_fail_unknown_primary_fx_slot_id(); [[noreturn]] static void eax_fail_unknown_property_id(); [[noreturn]] static void eax_fail_unknown_version(); // Gets a value from EAX call, // validates it, // and updates the current value. template static void eax_set(const EaxCall& call, TProperty& property) { const auto& value = call.get_value(); TValidator{}(value); property = value; } // Gets a new value from EAX call, // validates it, // updates the deferred value, // updates a dirty flag. template< typename TValidator, EaxDirtyFlags TDirtyBit, typename TMemberResult, typename TProps, typename TState> void eax_defer(const EaxCall& call, TState& state, TMemberResult TProps::*member) { const auto& src = call.get_value(); TValidator{}(src); const auto& dst_i = state.i.*member; auto& dst_d = state.d.*member; dst_d = src; if(dst_i != dst_d) mEaxDf |= TDirtyBit; } template< EaxDirtyFlags TDirtyBit, typename TMemberResult, typename TProps, typename TState> void eax_context_commit_property(TState& state, EaxDirtyFlags& dst_df, TMemberResult TProps::*member) noexcept { if((mEaxDf & TDirtyBit) != EaxDirtyFlags{}) { dst_df |= TDirtyBit; const auto& src_d = state.d.*member; state.i.*member = src_d; mEax.*member = src_d; } } void eax_initialize_extensions(); void eax_initialize(); bool eax_has_no_default_effect_slot() const noexcept; void eax_ensure_no_default_effect_slot() const; bool eax_has_enough_aux_sends() const noexcept; void eax_ensure_enough_aux_sends() const; void eax_ensure_compatibility(); unsigned long eax_detect_speaker_configuration() const; void eax_update_speaker_configuration(); void eax_set_last_error_defaults() noexcept; void eax_session_set_defaults() noexcept; static void eax4_context_set_defaults(Eax4Props& props) noexcept; static void eax4_context_set_defaults(Eax4State& state) noexcept; static void eax5_context_set_defaults(Eax5Props& props) noexcept; static void eax5_context_set_defaults(Eax5State& state) noexcept; void eax_context_set_defaults(); void eax_set_defaults(); void eax_dispatch_fx_slot(const EaxCall& call); void eax_dispatch_source(const EaxCall& call); void eax_get_misc(const EaxCall& call); void eax4_get(const EaxCall& call, const Eax4Props& props); void eax5_get(const EaxCall& call, const Eax5Props& props); void eax_get(const EaxCall& call); void eax_context_commit_primary_fx_slot_id(); void eax_context_commit_distance_factor(); void eax_context_commit_air_absorbtion_hf(); void eax_context_commit_hf_reference(); void eax_context_commit_macro_fx_factor(); void eax_initialize_fx_slots(); void eax_update_sources(); void eax_set_misc(const EaxCall& call); void eax4_defer_all(const EaxCall& call, Eax4State& state); void eax4_defer(const EaxCall& call, Eax4State& state); void eax5_defer_all(const EaxCall& call, Eax5State& state); void eax5_defer(const EaxCall& call, Eax5State& state); void eax_set(const EaxCall& call); void eax4_context_commit(Eax4State& state, EaxDirtyFlags& dst_df); void eax5_context_commit(Eax5State& state, EaxDirtyFlags& dst_df); void eax_context_commit(); #endif // ALSOFT_EAX }; using ContextRef = al::intrusive_ptr; ContextRef GetContextRef() noexcept; void UpdateContextProps(ALCcontext *context); inline bool TrapALError{false}; #if ALSOFT_EAX auto AL_APIENTRY EAXSet(const GUID *property_set_id, ALuint property_id, ALuint source_id, ALvoid *value, ALuint value_size) noexcept -> ALenum; auto AL_APIENTRY EAXGet(const GUID *property_set_id, ALuint property_id, ALuint source_id, ALvoid *value, ALuint value_size) noexcept -> ALenum; #endif // ALSOFT_EAX #endif /* ALC_CONTEXT_H */ openal-soft-1.24.2/alc/device.cpp000066400000000000000000000053351474041540300165410ustar00rootroot00000000000000 #include "config.h" #include "device.h" #include #include #include #include "al/buffer.h" #include "al/effect.h" #include "al/filter.h" #include "albit.h" #include "alnumeric.h" #include "atomic.h" #include "backends/base.h" #include "core/devformat.h" #include "core/hrtf.h" #include "core/logging.h" #include "core/mastering.h" #include "flexarray.h" namespace { using voidp = void*; } // namespace namespace al { Device::Device(DeviceType type) : DeviceBase{type} { } Device::~Device() { TRACE("Freeing device {}", voidp{this}); Backend = nullptr; size_t count{std::accumulate(BufferList.cbegin(), BufferList.cend(), 0_uz, [](size_t cur, const BufferSubList &sublist) noexcept -> size_t { return cur + static_cast(al::popcount(~sublist.FreeMask)); })}; if(count > 0) WARN("{} Buffer{} not deleted", count, (count==1)?"":"s"); count = std::accumulate(EffectList.cbegin(), EffectList.cend(), 0_uz, [](size_t cur, const EffectSubList &sublist) noexcept -> size_t { return cur + static_cast(al::popcount(~sublist.FreeMask)); }); if(count > 0) WARN("{} Effect{} not deleted", count, (count==1)?"":"s"); count = std::accumulate(FilterList.cbegin(), FilterList.cend(), 0_uz, [](size_t cur, const FilterSubList &sublist) noexcept -> size_t { return cur + static_cast(al::popcount(~sublist.FreeMask)); }); if(count > 0) WARN("{} Filter{} not deleted", count, (count==1)?"":"s"); } void Device::enumerateHrtfs() { mHrtfList = EnumerateHrtf(configValue({}, "hrtf-paths")); if(auto defhrtfopt = configValue({}, "default-hrtf")) { auto iter = std::find(mHrtfList.begin(), mHrtfList.end(), *defhrtfopt); if(iter == mHrtfList.end()) WARN("Failed to find default HRTF \"{}\"", *defhrtfopt); else if(iter != mHrtfList.begin()) std::rotate(mHrtfList.begin(), iter, iter+1); } } auto Device::getOutputMode1() const noexcept -> OutputMode1 { if(mContexts.load(std::memory_order_relaxed)->empty()) return OutputMode1::Any; switch(FmtChans) { case DevFmtMono: return OutputMode1::Mono; case DevFmtStereo: if(mHrtf) return OutputMode1::Hrtf; else if(mUhjEncoder) return OutputMode1::Uhj2; return OutputMode1::StereoBasic; case DevFmtQuad: return OutputMode1::Quad; case DevFmtX51: return OutputMode1::X51; case DevFmtX61: return OutputMode1::X61; case DevFmtX71: return OutputMode1::X71; case DevFmtX714: case DevFmtX7144: case DevFmtX3D71: case DevFmtAmbi3D: break; } return OutputMode1::Any; } } // namespace al openal-soft-1.24.2/alc/device.h000066400000000000000000000075651474041540300162150ustar00rootroot00000000000000#ifndef ALC_DEVICE_H #define ALC_DEVICE_H #include "config.h" #include #include #include #include #include #include #include #include #include "AL/al.h" #include "AL/alc.h" #include "AL/alext.h" #include "alconfig.h" #include "core/device.h" #include "intrusive_ptr.h" #if ALSOFT_EAX #include "al/eax/x_ram.h" #endif // ALSOFT_EAX struct BackendBase; struct BufferSubList; struct EffectSubList; struct FilterSubList; using uint = unsigned int; struct ALCdevice { virtual ~ALCdevice() = default; }; namespace al { struct Device final : public ALCdevice, al::intrusive_ref, DeviceBase { /* This lock protects the device state (format, update size, etc) from * being from being changed in multiple threads, or being accessed while * being changed. It's also used to serialize calls to the backend. */ std::mutex StateLock; std::unique_ptr Backend; ALCuint NumMonoSources{}; ALCuint NumStereoSources{}; // Maximum number of sources that can be created uint SourcesMax{}; // Maximum number of slots that can be created uint AuxiliaryEffectSlotMax{}; std::string mHrtfName; std::vector mHrtfList; ALCenum mHrtfStatus{ALC_FALSE}; enum class OutputMode1 : ALCenum { Any = ALC_ANY_SOFT, Mono = ALC_MONO_SOFT, Stereo = ALC_STEREO_SOFT, StereoBasic = ALC_STEREO_BASIC_SOFT, Uhj2 = ALC_STEREO_UHJ_SOFT, Hrtf = ALC_STEREO_HRTF_SOFT, Quad = ALC_QUAD_SOFT, X51 = ALC_SURROUND_5_1_SOFT, X61 = ALC_SURROUND_6_1_SOFT, X71 = ALC_SURROUND_7_1_SOFT }; OutputMode1 getOutputMode1() const noexcept; using OutputMode = OutputMode1; std::atomic LastError{ALC_NO_ERROR}; // Map of Buffers for this device std::mutex BufferLock; std::vector BufferList; // Map of Effects for this device std::mutex EffectLock; std::vector EffectList; // Map of Filters for this device std::mutex FilterLock; std::vector FilterList; #if ALSOFT_EAX ALuint eax_x_ram_free_size{eax_x_ram_max_size}; #endif // ALSOFT_EAX std::unordered_map mBufferNames; std::unordered_map mEffectNames; std::unordered_map mFilterNames; std::string mVendorOverride; std::string mVersionOverride; std::string mRendererOverride; explicit Device(DeviceType type); ~Device() final; void enumerateHrtfs(); bool getConfigValueBool(const std::string_view block, const std::string_view key, bool def) { return GetConfigValueBool(mDeviceName, block, key, def); } template auto configValue(const std::string_view block, const std::string_view key) -> std::optional = delete; }; template<> inline auto Device::configValue(const std::string_view block, const std::string_view key) -> std::optional { return ConfigValueStr(mDeviceName, block, key); } template<> inline auto Device::configValue(const std::string_view block, const std::string_view key) -> std::optional { return ConfigValueInt(mDeviceName, block, key); } template<> inline auto Device::configValue(const std::string_view block, const std::string_view key) -> std::optional { return ConfigValueUInt(mDeviceName, block, key); } template<> inline auto Device::configValue(const std::string_view block, const std::string_view key) -> std::optional { return ConfigValueFloat(mDeviceName, block, key); } template<> inline auto Device::configValue(const std::string_view block, const std::string_view key) -> std::optional { return ConfigValueBool(mDeviceName, block, key); } } // namespace al /** Stores the latest ALC device error. */ void alcSetError(al::Device *device, ALCenum errorCode); #endif openal-soft-1.24.2/alc/effects/000077500000000000000000000000001474041540300162075ustar00rootroot00000000000000openal-soft-1.24.2/alc/effects/autowah.cpp000066400000000000000000000166761474041540300204030ustar00rootroot00000000000000/** * OpenAL cross platform audio library * Copyright (C) 2018 by Raul Herraiz. * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * Or go to http://www.gnu.org/copyleft/lgpl.html */ #include "config.h" #include #include #include #include #include #include "alc/effects/base.h" #include "alnumbers.h" #include "alnumeric.h" #include "alspan.h" #include "core/ambidefs.h" #include "core/bufferline.h" #include "core/context.h" #include "core/device.h" #include "core/effects/base.h" #include "core/effectslot.h" #include "core/mixer.h" #include "intrusive_ptr.h" struct BufferStorage; namespace { constexpr float GainScale{31621.0f}; constexpr float MinFreq{20.0f}; constexpr float MaxFreq{2500.0f}; constexpr float QFactor{5.0f}; struct AutowahState final : public EffectState { /* Effect parameters */ float mAttackRate{}; float mReleaseRate{}; float mResonanceGain{}; float mPeakGain{}; float mFreqMinNorm{}; float mBandwidthNorm{}; float mEnvDelay{}; /* Filter components derived from the envelope. */ struct FilterParam { float cos_w0{}; float alpha{}; }; std::array mEnv; struct ChannelData { uint mTargetChannel{InvalidChannelIndex}; struct FilterHistory { float z1{}, z2{}; }; FilterHistory mFilter; /* Effect gains for each output channel */ float mCurrentGain{}; float mTargetGain{}; }; std::array mChans; /* Effects buffers */ alignas(16) FloatBufferLine mBufferOut{}; void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) override; void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props, const EffectTarget target) override; void process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) override; }; void AutowahState::deviceUpdate(const DeviceBase*, const BufferStorage*) { /* (Re-)initializing parameters and clear the buffers. */ mAttackRate = 1.0f; mReleaseRate = 1.0f; mResonanceGain = 10.0f; mPeakGain = 4.5f; mFreqMinNorm = 4.5e-4f; mBandwidthNorm = 0.05f; mEnvDelay = 0.0f; for(auto &e : mEnv) { e.cos_w0 = 0.0f; e.alpha = 0.0f; } for(auto &chan : mChans) { chan.mTargetChannel = InvalidChannelIndex; chan.mFilter.z1 = 0.0f; chan.mFilter.z2 = 0.0f; chan.mCurrentGain = 0.0f; } } void AutowahState::update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props_, const EffectTarget target) { auto &props = std::get(*props_); const DeviceBase *device{context->mDevice}; const auto frequency = static_cast(device->mSampleRate); const float ReleaseTime{std::clamp(props.ReleaseTime, 0.001f, 1.0f)}; mAttackRate = std::exp(-1.0f / (props.AttackTime*frequency)); mReleaseRate = std::exp(-1.0f / (ReleaseTime*frequency)); /* 0-20dB Resonance Peak gain */ mResonanceGain = std::sqrt(std::log10(props.Resonance)*10.0f / 3.0f); mPeakGain = 1.0f - std::log10(props.PeakGain / GainScale); mFreqMinNorm = MinFreq / frequency; mBandwidthNorm = (MaxFreq-MinFreq) / frequency; mOutTarget = target.Main->Buffer; auto set_channel = [this](size_t idx, uint outchan, float outgain) { mChans[idx].mTargetChannel = outchan; mChans[idx].mTargetGain = outgain; }; target.Main->setAmbiMixParams(slot->Wet, slot->Gain, set_channel); } void AutowahState::process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) { const float attack_rate{mAttackRate}; const float release_rate{mReleaseRate}; const float res_gain{mResonanceGain}; const float peak_gain{mPeakGain}; const float freq_min{mFreqMinNorm}; const float bandwidth{mBandwidthNorm}; float env_delay{mEnvDelay}; for(size_t i{0u};i < samplesToDo;i++) { /* Envelope follower described on the book: Audio Effects, Theory, * Implementation and Application. */ const float sample{peak_gain * std::fabs(samplesIn[0][i])}; const float a{(sample > env_delay) ? attack_rate : release_rate}; env_delay = lerpf(sample, env_delay, a); /* Calculate the cos and alpha components for this sample's filter. */ const float w0{std::min(bandwidth*env_delay + freq_min, 0.46f) * (al::numbers::pi_v*2.0f)}; mEnv[i].cos_w0 = std::cos(w0); mEnv[i].alpha = std::sin(w0)/(2.0f * QFactor); } mEnvDelay = env_delay; auto chandata = mChans.begin(); for(const auto &insamples : samplesIn) { const size_t outidx{chandata->mTargetChannel}; if(outidx == InvalidChannelIndex) { ++chandata; continue; } /* This effectively inlines BiquadFilter_setParams for a peaking * filter and BiquadFilter_processC. The alpha and cosine components * for the filter coefficients were previously calculated with the * envelope. Because the filter changes for each sample, the * coefficients are transient and don't need to be held. */ float z1{chandata->mFilter.z1}; float z2{chandata->mFilter.z2}; for(size_t i{0u};i < samplesToDo;i++) { const float alpha{mEnv[i].alpha}; const float cos_w0{mEnv[i].cos_w0}; const std::array b{ 1.0f + alpha*res_gain, -2.0f * cos_w0, 1.0f - alpha*res_gain}; const std::array a{ 1.0f + alpha/res_gain, -2.0f * cos_w0, 1.0f - alpha/res_gain}; const float input{insamples[i]}; const float output{input*(b[0]/a[0]) + z1}; z1 = input*(b[1]/a[0]) - output*(a[1]/a[0]) + z2; z2 = input*(b[2]/a[0]) - output*(a[2]/a[0]); mBufferOut[i] = output; } chandata->mFilter.z1 = z1; chandata->mFilter.z2 = z2; /* Now, mix the processed sound data to the output. */ MixSamples(al::span{mBufferOut}.first(samplesToDo), samplesOut[outidx], chandata->mCurrentGain, chandata->mTargetGain, samplesToDo); ++chandata; } } struct AutowahStateFactory final : public EffectStateFactory { al::intrusive_ptr create() override { return al::intrusive_ptr{new AutowahState{}}; } }; } // namespace EffectStateFactory *AutowahStateFactory_getFactory() { static AutowahStateFactory AutowahFactory{}; return &AutowahFactory; } openal-soft-1.24.2/alc/effects/base.h000066400000000000000000000017561474041540300173030ustar00rootroot00000000000000#ifndef EFFECTS_BASE_H #define EFFECTS_BASE_H #include "core/effects/base.h" /* This is a user config option for modifying the overall output of the reverb * effect. */ inline float ReverbBoost{1.0f}; EffectStateFactory *NullStateFactory_getFactory(); EffectStateFactory *ReverbStateFactory_getFactory(); EffectStateFactory *ChorusStateFactory_getFactory(); EffectStateFactory *AutowahStateFactory_getFactory(); EffectStateFactory *CompressorStateFactory_getFactory(); EffectStateFactory *DistortionStateFactory_getFactory(); EffectStateFactory *EchoStateFactory_getFactory(); EffectStateFactory *EqualizerStateFactory_getFactory(); EffectStateFactory *FshifterStateFactory_getFactory(); EffectStateFactory *ModulatorStateFactory_getFactory(); EffectStateFactory *PshifterStateFactory_getFactory(); EffectStateFactory* VmorpherStateFactory_getFactory(); EffectStateFactory *DedicatedStateFactory_getFactory(); EffectStateFactory *ConvolutionStateFactory_getFactory(); #endif /* EFFECTS_BASE_H */ openal-soft-1.24.2/alc/effects/chorus.cpp000066400000000000000000000271651474041540300202310ustar00rootroot00000000000000/** * OpenAL cross platform audio library * Copyright (C) 2013 by Mike Gorchak * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * Or go to http://www.gnu.org/copyleft/lgpl.html */ #include "config.h" #include #include #include #include #include #include #include #include "alc/effects/base.h" #include "alnumbers.h" #include "alnumeric.h" #include "alspan.h" #include "core/ambidefs.h" #include "core/bufferline.h" #include "core/context.h" #include "core/cubic_tables.h" #include "core/device.h" #include "core/effects/base.h" #include "core/effectslot.h" #include "core/mixer.h" #include "core/mixer/defs.h" #include "core/resampler_limits.h" #include "intrusive_ptr.h" #include "opthelpers.h" struct BufferStorage; namespace { using uint = unsigned int; constexpr auto inv_sqrt2 = static_cast(1.0 / al::numbers::sqrt2); constexpr auto lcoeffs_pw = CalcDirectionCoeffs(std::array{-1.0f, 0.0f, 0.0f}); constexpr auto rcoeffs_pw = CalcDirectionCoeffs(std::array{ 1.0f, 0.0f, 0.0f}); constexpr auto lcoeffs_nrml = CalcDirectionCoeffs(std::array{-inv_sqrt2, 0.0f, inv_sqrt2}); constexpr auto rcoeffs_nrml = CalcDirectionCoeffs(std::array{ inv_sqrt2, 0.0f, inv_sqrt2}); struct ChorusState final : public EffectState { std::vector mDelayBuffer; uint mOffset{0}; uint mLfoOffset{0}; uint mLfoRange{1}; float mLfoScale{0.0f}; uint mLfoDisp{0}; /* Calculated delays to apply to the left and right outputs. */ std::array,2> mModDelays{}; /* Temp storage for the modulated left and right outputs. */ alignas(16) std::array mBuffer{}; /* Gains for left and right outputs. */ struct OutGains { std::array Current{}; std::array Target{}; }; std::array mGains; /* effect parameters */ ChorusWaveform mWaveform{}; int mDelay{0}; float mDepth{0.0f}; float mFeedback{0.0f}; void calcTriangleDelays(const size_t todo); void calcSinusoidDelays(const size_t todo); void deviceUpdate(const DeviceBase *device, const float MaxDelay); void update(const ContextBase *context, const EffectSlot *slot, const ChorusWaveform waveform, const float delay, const float depth, const float feedback, const float rate, int phase, const EffectTarget target); void deviceUpdate(const DeviceBase *device, const BufferStorage*) final; void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props_, const EffectTarget target) final; void process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) final; }; void ChorusState::deviceUpdate(const DeviceBase *Device, const BufferStorage*) { constexpr auto MaxDelay = std::max(ChorusMaxDelay, FlangerMaxDelay); const auto frequency = static_cast(Device->mSampleRate); const size_t maxlen{NextPowerOf2(float2uint(MaxDelay*2.0f*frequency) + 1u)}; if(maxlen != mDelayBuffer.size()) decltype(mDelayBuffer)(maxlen).swap(mDelayBuffer); std::fill(mDelayBuffer.begin(), mDelayBuffer.end(), 0.0f); for(auto &e : mGains) { e.Current.fill(0.0f); e.Target.fill(0.0f); } } void ChorusState::update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props_, const EffectTarget target) { static constexpr int mindelay{MaxResamplerEdge << gCubicTable.sTableBits}; auto &props = std::get(*props_); /* The LFO depth is scaled to be relative to the sample delay. Clamp the * delay and depth to allow enough padding for resampling. */ const DeviceBase *device{context->mDevice}; const auto frequency = static_cast(device->mSampleRate); mWaveform = props.Waveform; const auto stepscale = float{frequency * gCubicTable.sTableSteps}; mDelay = std::max(float2int(std::round(props.Delay * stepscale)), mindelay); mDepth = std::min(static_cast(mDelay) * props.Depth, static_cast(mDelay - mindelay)); mFeedback = props.Feedback; /* Gains for left and right sides */ const bool ispairwise{device->mRenderMode == RenderMode::Pairwise}; const auto lcoeffs = (!ispairwise) ? al::span{lcoeffs_nrml} : al::span{lcoeffs_pw}; const auto rcoeffs = (!ispairwise) ? al::span{rcoeffs_nrml} : al::span{rcoeffs_pw}; /* Attenuate the outputs by -3dB, since we duplicate a single mono input to * separate left/right outputs. */ const auto gain = slot->Gain * (1.0f/al::numbers::sqrt2_v); mOutTarget = target.Main->Buffer; ComputePanGains(target.Main, lcoeffs, gain, mGains[0].Target); ComputePanGains(target.Main, rcoeffs, gain, mGains[1].Target); if(!(props.Rate > 0.0f)) { mLfoOffset = 0; mLfoRange = 1; mLfoScale = 0.0f; mLfoDisp = 0; } else { /* Calculate LFO coefficient (number of samples per cycle). Limit the * max range to avoid overflow when calculating the displacement. */ static constexpr int range_limit{std::numeric_limits::max()/360 - 180}; const auto range = std::round(frequency / props.Rate); const uint lfo_range{float2uint(std::min(range, float{range_limit}))}; mLfoOffset = mLfoOffset * lfo_range / mLfoRange; mLfoRange = lfo_range; switch(mWaveform) { case ChorusWaveform::Triangle: mLfoScale = 4.0f / static_cast(mLfoRange); break; case ChorusWaveform::Sinusoid: mLfoScale = al::numbers::pi_v*2.0f / static_cast(mLfoRange); break; } /* Calculate lfo phase displacement */ auto phase = props.Phase; if(phase < 0) phase += 360; mLfoDisp = (mLfoRange*static_cast(phase) + 180) / 360; } } void ChorusState::calcTriangleDelays(const size_t todo) { const uint lfo_range{mLfoRange}; const float lfo_scale{mLfoScale}; const float depth{mDepth}; const int delay{mDelay}; auto gen_lfo = [lfo_scale,depth,delay](const uint offset) -> uint { const float offset_norm{static_cast(offset) * lfo_scale}; return static_cast(fastf2i((1.0f-std::abs(2.0f-offset_norm)) * depth) + delay); }; uint offset{mLfoOffset}; ASSUME(lfo_range > offset); auto ldelays = mModDelays[0].begin(); for(size_t i{0};i < todo;) { const size_t rem{std::min(todo-i, size_t{lfo_range-offset})}; ldelays = std::generate_n(ldelays, rem, [&offset,gen_lfo] { return gen_lfo(offset++); }); if(offset == lfo_range) offset = 0; i += rem; } offset = (mLfoOffset+mLfoDisp) % lfo_range; auto rdelays = mModDelays[1].begin(); for(size_t i{0};i < todo;) { const size_t rem{std::min(todo-i, size_t{lfo_range-offset})}; rdelays = std::generate_n(rdelays, rem, [&offset,gen_lfo] { return gen_lfo(offset++); }); if(offset == lfo_range) offset = 0; i += rem; } mLfoOffset = static_cast(mLfoOffset+todo) % lfo_range; } void ChorusState::calcSinusoidDelays(const size_t todo) { const uint lfo_range{mLfoRange}; const float lfo_scale{mLfoScale}; const float depth{mDepth}; const int delay{mDelay}; auto gen_lfo = [lfo_scale,depth,delay](const uint offset) -> uint { const float offset_norm{static_cast(offset) * lfo_scale}; return static_cast(fastf2i(std::sin(offset_norm)*depth) + delay); }; uint offset{mLfoOffset}; ASSUME(lfo_range > offset); auto ldelays = mModDelays[0].begin(); for(size_t i{0};i < todo;) { const size_t rem{std::min(todo-i, size_t{lfo_range-offset})}; ldelays = std::generate_n(ldelays, rem, [&offset,gen_lfo] { return gen_lfo(offset++); }); if(offset == lfo_range) offset = 0; i += rem; } offset = (mLfoOffset+mLfoDisp) % lfo_range; auto rdelays = mModDelays[1].begin(); for(size_t i{0};i < todo;) { const size_t rem{std::min(todo-i, size_t{lfo_range-offset})}; rdelays = std::generate_n(rdelays, rem, [&offset,gen_lfo] { return gen_lfo(offset++); }); if(offset == lfo_range) offset = 0; i += rem; } mLfoOffset = static_cast(mLfoOffset+todo) % lfo_range; } void ChorusState::process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) { const auto delaybuf = al::span{mDelayBuffer}; const size_t bufmask{delaybuf.size()-1}; const float feedback{mFeedback}; const uint avgdelay{(static_cast(mDelay) + MixerFracHalf) >> MixerFracBits}; uint offset{mOffset}; if(mWaveform == ChorusWaveform::Sinusoid) calcSinusoidDelays(samplesToDo); else /*if(mWaveform == ChorusWaveform::Triangle)*/ calcTriangleDelays(samplesToDo); const auto ldelays = al::span{mModDelays[0]}; const auto rdelays = al::span{mModDelays[1]}; const auto lbuffer = al::span{mBuffer[0]}; const auto rbuffer = al::span{mBuffer[1]}; for(size_t i{0u};i < samplesToDo;++i) { // Feed the buffer's input first (necessary for delays < 1). delaybuf[offset&bufmask] = samplesIn[0][i]; // Tap for the left output. size_t delay{offset - (ldelays[i] >> gCubicTable.sTableBits)}; size_t phase{ldelays[i] & gCubicTable.sTableMask}; lbuffer[i] = delaybuf[(delay+1) & bufmask]*gCubicTable.getCoeff0(phase) + delaybuf[(delay ) & bufmask]*gCubicTable.getCoeff1(phase) + delaybuf[(delay-1) & bufmask]*gCubicTable.getCoeff2(phase) + delaybuf[(delay-2) & bufmask]*gCubicTable.getCoeff3(phase); // Tap for the right output. delay = offset - (rdelays[i] >> gCubicTable.sTableBits); phase = rdelays[i] & gCubicTable.sTableMask; rbuffer[i] = delaybuf[(delay+1) & bufmask]*gCubicTable.getCoeff0(phase) + delaybuf[(delay ) & bufmask]*gCubicTable.getCoeff1(phase) + delaybuf[(delay-1) & bufmask]*gCubicTable.getCoeff2(phase) + delaybuf[(delay-2) & bufmask]*gCubicTable.getCoeff3(phase); // Accumulate feedback from the average delay of the taps. delaybuf[offset&bufmask] += delaybuf[(offset-avgdelay) & bufmask] * feedback; ++offset; } MixSamples(lbuffer.first(samplesToDo), samplesOut, mGains[0].Current, mGains[0].Target, samplesToDo, 0); MixSamples(rbuffer.first(samplesToDo), samplesOut, mGains[1].Current, mGains[1].Target, samplesToDo, 0); mOffset = offset; } struct ChorusStateFactory final : public EffectStateFactory { al::intrusive_ptr create() override { return al::intrusive_ptr{new ChorusState{}}; } }; } // namespace EffectStateFactory *ChorusStateFactory_getFactory() { static ChorusStateFactory ChorusFactory{}; return &ChorusFactory; } openal-soft-1.24.2/alc/effects/compressor.cpp000066400000000000000000000147511474041540300211170ustar00rootroot00000000000000/** * This file is part of the OpenAL Soft cross platform audio library * * Copyright (C) 2013 by Anis A. Hireche * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * * Neither the name of Spherical-Harmonic-Transform nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include #include #include #include #include #include "alc/effects/base.h" #include "alspan.h" #include "core/ambidefs.h" #include "core/bufferline.h" #include "core/device.h" #include "core/effects/base.h" #include "core/effectslot.h" #include "core/mixer/defs.h" #include "intrusive_ptr.h" struct BufferStorage; struct ContextBase; namespace { constexpr float AmpEnvelopeMin{0.5f}; constexpr float AmpEnvelopeMax{2.0f}; constexpr float AttackTime{0.1f}; /* 100ms to rise from min to max */ constexpr float ReleaseTime{0.2f}; /* 200ms to drop from max to min */ struct CompressorState final : public EffectState { /* Effect gains for each channel */ struct TargetGain { uint mTarget{InvalidChannelIndex}; float mGain{1.0f}; }; std::array mChans; /* Effect parameters */ bool mEnabled{true}; float mAttackMult{1.0f}; float mReleaseMult{1.0f}; float mEnvFollower{1.0f}; alignas(16) FloatBufferLine mGains{}; void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) override; void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props, const EffectTarget target) override; void process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) override; }; void CompressorState::deviceUpdate(const DeviceBase *device, const BufferStorage*) { /* Number of samples to do a full attack and release (non-integer sample * counts are okay). */ const float attackCount{static_cast(device->mSampleRate) * AttackTime}; const float releaseCount{static_cast(device->mSampleRate) * ReleaseTime}; /* Calculate per-sample multipliers to attack and release at the desired * rates. */ mAttackMult = std::pow(AmpEnvelopeMax/AmpEnvelopeMin, 1.0f/attackCount); mReleaseMult = std::pow(AmpEnvelopeMin/AmpEnvelopeMax, 1.0f/releaseCount); } void CompressorState::update(const ContextBase*, const EffectSlot *slot, const EffectProps *props, const EffectTarget target) { mEnabled = std::get(*props).OnOff; mOutTarget = target.Main->Buffer; auto set_channel = [this](size_t idx, uint outchan, float outgain) { mChans[idx].mTarget = outchan; mChans[idx].mGain = outgain; }; target.Main->setAmbiMixParams(slot->Wet, slot->Gain, set_channel); } void CompressorState::process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) { /* Generate the per-sample gains from the signal envelope. */ float env{mEnvFollower}; if(mEnabled) { for(size_t i{0u};i < samplesToDo;++i) { /* Clamp the absolute amplitude to the defined envelope limits, * then attack or release the envelope to reach it. */ const float amplitude{std::clamp(std::fabs(samplesIn[0][i]), AmpEnvelopeMin, AmpEnvelopeMax)}; if(amplitude > env) env = std::min(env*mAttackMult, amplitude); else if(amplitude < env) env = std::max(env*mReleaseMult, amplitude); /* Apply the reciprocal of the envelope to normalize the volume * (compress the dynamic range). */ mGains[i] = 1.0f / env; } } else { /* Same as above, except the amplitude is forced to 1. This helps * ensure smooth gain changes when the compressor is turned on and off. */ for(size_t i{0u};i < samplesToDo;++i) { const float amplitude{1.0f}; if(amplitude > env) env = std::min(env*mAttackMult, amplitude); else if(amplitude < env) env = std::max(env*mReleaseMult, amplitude); mGains[i] = 1.0f / env; } } mEnvFollower = env; /* Now compress the signal amplitude to output. */ auto chan = mChans.cbegin(); for(const auto &input : samplesIn) { const size_t outidx{chan->mTarget}; if(outidx != InvalidChannelIndex) { const auto dst = al::span{samplesOut[outidx]}; const float gain{chan->mGain}; if(!(std::fabs(gain) > GainSilenceThreshold)) { for(size_t i{0u};i < samplesToDo;++i) dst[i] += input[i] * mGains[i] * gain; } } ++chan; } } struct CompressorStateFactory final : public EffectStateFactory { al::intrusive_ptr create() override { return al::intrusive_ptr{new CompressorState{}}; } }; } // namespace EffectStateFactory *CompressorStateFactory_getFactory() { static CompressorStateFactory CompressorFactory{}; return &CompressorFactory; } openal-soft-1.24.2/alc/effects/convolution.cpp000066400000000000000000000667661474041540300213170ustar00rootroot00000000000000 #include "config.h" #include "config_simd.h" #include #include #include #include #include #include #include #include #include #include #if HAVE_SSE_INTRINSICS #include #elif HAVE_NEON #include #endif #include "alcomplex.h" #include "almalloc.h" #include "alnumbers.h" #include "alnumeric.h" #include "alspan.h" #include "base.h" #include "core/ambidefs.h" #include "core/bufferline.h" #include "core/buffer_storage.h" #include "core/context.h" #include "core/devformat.h" #include "core/device.h" #include "core/effects/base.h" #include "core/effectslot.h" #include "core/filters/splitter.h" #include "core/fmt_traits.h" #include "core/mixer.h" #include "core/uhjfilter.h" #include "intrusive_ptr.h" #include "opthelpers.h" #include "pffft.h" #include "polyphase_resampler.h" #include "vecmat.h" #include "vector.h" namespace { /* Convolution is implemented using a segmented overlap-add method. The impulse * response is split into multiple segments of 128 samples, and each segment * has an FFT applied with a 256-sample buffer (the latter half left silent) to * get its frequency-domain response. The resulting response has its positive/ * non-mirrored frequencies saved (129 bins) in each segment. Note that since * the 0- and half-frequency bins are real for a real signal, their imaginary * components are always 0 and can be dropped, allowing their real components * to be combined so only 128 complex values are stored for the 129 bins. * * Input samples are similarly broken up into 128-sample segments, with a 256- * sample FFT applied to each new incoming segment to get its 129 bins. A * history of FFT'd input segments is maintained, equal to the number of * impulse response segments. * * To apply the convolution, each impulse response segment is convolved with * its paired input segment (using complex multiplies, far cheaper than FIRs), * accumulating into a 129-bin FFT buffer. The input history is then shifted to * align with later impulse response segments for the next input segment. * * An inverse FFT is then applied to the accumulated FFT buffer to get a 256- * sample time-domain response for output, which is split in two halves. The * first half is the 128-sample output, and the second half is a 128-sample * (really, 127) delayed extension, which gets added to the output next time. * Convolving two time-domain responses of length N results in a time-domain * signal of length N*2 - 1, and this holds true regardless of the convolution * being applied in the frequency domain, so these "overflow" samples need to * be accounted for. * * To avoid a delay with gathering enough input samples for the FFT, the first * segment is applied directly in the time-domain as the samples come in. Once * enough have been retrieved, the FFT is applied on the input and it's paired * with the remaining (FFT'd) filter segments for processing. */ template inline void LoadSampleArray(const al::span dst, const std::byte *src, const std::size_t channel, const std::size_t srcstep) noexcept { using TypeTraits = al::FmtTypeTraits; using SampleType = typename TypeTraits::Type; const auto converter = TypeTraits{}; assert(channel < srcstep); const auto srcspan = al::span{reinterpret_cast(src), dst.size()*srcstep}; auto ssrc = srcspan.cbegin(); std::generate(dst.begin(), dst.end(), [converter,channel,srcstep,&ssrc] { const auto ret = converter(ssrc[channel]); ssrc += ptrdiff_t(srcstep); return ret; }); } void LoadSamples(const al::span dst, const std::byte *src, const size_t channel, const size_t srcstep, const FmtType srctype) noexcept { #define HANDLE_FMT(T) case T: LoadSampleArray(dst, src, channel, srcstep); break switch(srctype) { HANDLE_FMT(FmtUByte); HANDLE_FMT(FmtShort); HANDLE_FMT(FmtInt); HANDLE_FMT(FmtFloat); HANDLE_FMT(FmtDouble); HANDLE_FMT(FmtMulaw); HANDLE_FMT(FmtAlaw); /* FIXME: Handle ADPCM decoding here. */ case FmtIMA4: case FmtMSADPCM: std::fill(dst.begin(), dst.end(), 0.0f); break; } #undef HANDLE_FMT } constexpr auto GetAmbiScales(AmbiScaling scaletype) noexcept { switch(scaletype) { case AmbiScaling::FuMa: return al::span{AmbiScale::FromFuMa}; case AmbiScaling::SN3D: return al::span{AmbiScale::FromSN3D}; case AmbiScaling::UHJ: return al::span{AmbiScale::FromUHJ}; case AmbiScaling::N3D: break; } return al::span{AmbiScale::FromN3D}; } constexpr auto GetAmbiLayout(AmbiLayout layouttype) noexcept { if(layouttype == AmbiLayout::FuMa) return al::span{AmbiIndex::FromFuMa}; return al::span{AmbiIndex::FromACN}; } constexpr auto GetAmbi2DLayout(AmbiLayout layouttype) noexcept { if(layouttype == AmbiLayout::FuMa) return al::span{AmbiIndex::FromFuMa2D}; return al::span{AmbiIndex::FromACN2D}; } constexpr float sin30{0.5f}; constexpr float cos30{0.866025403785f}; constexpr float sin45{al::numbers::sqrt2_v*0.5f}; constexpr float cos45{al::numbers::sqrt2_v*0.5f}; constexpr float sin110{ 0.939692620786f}; constexpr float cos110{-0.342020143326f}; struct ChanPosMap { Channel channel; std::array pos; }; using complex_f = std::complex; constexpr size_t ConvolveUpdateSize{256}; constexpr size_t ConvolveUpdateSamples{ConvolveUpdateSize / 2}; void apply_fir(al::span dst, const al::span input, const al::span filter) { auto src = input.begin(); #if HAVE_SSE_INTRINSICS std::generate(dst.begin(), dst.end(), [&src,filter] { __m128 r4{_mm_setzero_ps()}; for(size_t j{0};j < ConvolveUpdateSamples;j+=4) { const __m128 coeffs{_mm_load_ps(&filter[j])}; const __m128 s{_mm_loadu_ps(&src[j])}; r4 = _mm_add_ps(r4, _mm_mul_ps(s, coeffs)); } ++src; r4 = _mm_add_ps(r4, _mm_shuffle_ps(r4, r4, _MM_SHUFFLE(0, 1, 2, 3))); r4 = _mm_add_ps(r4, _mm_movehl_ps(r4, r4)); return _mm_cvtss_f32(r4); }); #elif HAVE_NEON std::generate(dst.begin(), dst.end(), [&src,filter] { float32x4_t r4{vdupq_n_f32(0.0f)}; for(size_t j{0};j < ConvolveUpdateSamples;j+=4) r4 = vmlaq_f32(r4, vld1q_f32(&src[j]), vld1q_f32(&filter[j])); ++src; r4 = vaddq_f32(r4, vrev64q_f32(r4)); return vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0); }); #else std::generate(dst.begin(), dst.end(), [&src,filter] { float ret{0.0f}; for(size_t j{0};j < ConvolveUpdateSamples;++j) ret += src[j] * filter[j]; ++src; return ret; }); #endif } struct ConvolutionState final : public EffectState { FmtChannels mChannels{}; AmbiLayout mAmbiLayout{}; AmbiScaling mAmbiScaling{}; uint mAmbiOrder{}; size_t mFifoPos{0}; alignas(16) std::array mInput{}; al::vector,16> mFilter; al::vector,16> mOutput; PFFFTSetup mFft; alignas(16) std::array mFftBuffer{}; alignas(16) std::array mFftWorkBuffer{}; size_t mCurrentSegment{0}; size_t mNumConvolveSegs{0}; struct ChannelData { alignas(16) FloatBufferLine mBuffer{}; float mHfScale{}, mLfScale{}; BandSplitter mFilter; std::array Current{}; std::array Target{}; }; std::vector mChans; al::vector mComplexData; ConvolutionState() = default; ~ConvolutionState() override = default; void NormalMix(const al::span samplesOut, const size_t samplesToDo); void UpsampleMix(const al::span samplesOut, const size_t samplesToDo); void (ConvolutionState::*mMix)(const al::span,const size_t) {&ConvolutionState::NormalMix}; void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) override; void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props, const EffectTarget target) override; void process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) override; }; void ConvolutionState::NormalMix(const al::span samplesOut, const size_t samplesToDo) { for(auto &chan : mChans) MixSamples(al::span{chan.mBuffer}.first(samplesToDo), samplesOut, chan.Current, chan.Target, samplesToDo, 0); } void ConvolutionState::UpsampleMix(const al::span samplesOut, const size_t samplesToDo) { for(auto &chan : mChans) { const auto src = al::span{chan.mBuffer}.first(samplesToDo); chan.mFilter.processScale(src, chan.mHfScale, chan.mLfScale); MixSamples(src, samplesOut, chan.Current, chan.Target, samplesToDo, 0); } } void ConvolutionState::deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) { using UhjDecoderType = UhjDecoder<512>; static constexpr auto DecoderPadding = UhjDecoderType::sInputPadding; static constexpr uint MaxConvolveAmbiOrder{1u}; if(!mFft) mFft = PFFFTSetup{ConvolveUpdateSize, PFFFT_REAL}; mFifoPos = 0; mInput.fill(0.0f); decltype(mFilter){}.swap(mFilter); decltype(mOutput){}.swap(mOutput); mFftBuffer.fill(0.0f); mFftWorkBuffer.fill(0.0f); mCurrentSegment = 0; mNumConvolveSegs = 0; decltype(mChans){}.swap(mChans); decltype(mComplexData){}.swap(mComplexData); /* An empty buffer doesn't need a convolution filter. */ if(!buffer || buffer->mSampleLen < 1) return; mChannels = buffer->mChannels; mAmbiLayout = IsUHJ(mChannels) ? AmbiLayout::FuMa : buffer->mAmbiLayout; mAmbiScaling = IsUHJ(mChannels) ? AmbiScaling::UHJ : buffer->mAmbiScaling; mAmbiOrder = std::min(buffer->mAmbiOrder, MaxConvolveAmbiOrder); const auto realChannels = buffer->channelsFromFmt(); const auto numChannels = (mChannels == FmtUHJ2) ? 3u : ChannelsFromFmt(mChannels, mAmbiOrder); mChans.resize(numChannels); /* The impulse response needs to have the same sample rate as the input and * output. The bsinc24 resampler is decent, but there is high-frequency * attenuation that some people may be able to pick up on. Since this is * called very infrequently, go ahead and use the polyphase resampler. */ PPhaseResampler resampler; if(device->mSampleRate != buffer->mSampleRate) resampler.init(buffer->mSampleRate, device->mSampleRate); const auto resampledCount = static_cast( (uint64_t{buffer->mSampleLen}*device->mSampleRate+(buffer->mSampleRate-1)) / buffer->mSampleRate); const BandSplitter splitter{device->mXOverFreq / static_cast(device->mSampleRate)}; for(auto &e : mChans) e.mFilter = splitter; mFilter.resize(numChannels, {}); mOutput.resize(numChannels, {}); /* Calculate the number of segments needed to hold the impulse response and * the input history (rounded up), and allocate them. Exclude one segment * which gets applied as a time-domain FIR filter. Make sure at least one * segment is allocated to simplify handling. */ mNumConvolveSegs = (resampledCount+(ConvolveUpdateSamples-1)) / ConvolveUpdateSamples; mNumConvolveSegs = std::max(mNumConvolveSegs, 2_uz) - 1_uz; const size_t complex_length{mNumConvolveSegs * ConvolveUpdateSize * (numChannels+1)}; mComplexData.resize(complex_length, 0.0f); /* Load the samples from the buffer. */ const size_t srclinelength{RoundUp(buffer->mSampleLen+DecoderPadding, 16)}; auto srcsamples = std::vector(srclinelength * numChannels); std::fill(srcsamples.begin(), srcsamples.end(), 0.0f); for(size_t c{0};c < numChannels && c < realChannels;++c) LoadSamples(al::span{srcsamples}.subspan(srclinelength*c, buffer->mSampleLen), buffer->mData.data(), c, realChannels, buffer->mType); if(IsUHJ(mChannels)) { auto decoder = std::make_unique(); std::array samples{}; for(size_t c{0};c < numChannels;++c) samples[c] = al::to_address(srcsamples.begin() + ptrdiff_t(srclinelength*c)); decoder->decode({samples.data(), numChannels}, buffer->mSampleLen, buffer->mSampleLen); } auto ressamples = std::vector(buffer->mSampleLen + (resampler ? resampledCount : 0)); auto ffttmp = al::vector(ConvolveUpdateSize); auto fftbuffer = std::vector>(ConvolveUpdateSize); auto filteriter = mComplexData.begin() + ptrdiff_t(mNumConvolveSegs*ConvolveUpdateSize); for(size_t c{0};c < numChannels;++c) { auto bufsamples = al::span{srcsamples}.subspan(srclinelength*c, buffer->mSampleLen); /* Resample to match the device. */ if(resampler) { auto restmp = al::span{ressamples}.subspan(resampledCount, buffer->mSampleLen); std::copy(bufsamples.cbegin(), bufsamples.cend(), restmp.begin()); resampler.process(restmp, al::span{ressamples}.first(resampledCount)); } else std::copy(bufsamples.cbegin(), bufsamples.cend(), ressamples.begin()); /* Store the first segment's samples in reverse in the time-domain, to * apply as a FIR filter. */ const size_t first_size{std::min(size_t{resampledCount}, ConvolveUpdateSamples)}; auto sampleseg = al::span{ressamples.cbegin(), first_size}; std::transform(sampleseg.cbegin(), sampleseg.cend(), mFilter[c].rbegin(), [](const double d) noexcept -> float { return static_cast(d); }); size_t done{first_size}; for(size_t s{0};s < mNumConvolveSegs;++s) { const size_t todo{std::min(resampledCount-done, ConvolveUpdateSamples)}; sampleseg = al::span{ressamples}.subspan(done, todo); /* Apply a double-precision forward FFT for more precise frequency * measurements. */ auto iter = std::copy(sampleseg.cbegin(), sampleseg.cend(), fftbuffer.begin()); done += todo; std::fill(iter, fftbuffer.end(), std::complex{}); forward_fft(al::span{fftbuffer}); /* Convert to, and pack in, a float buffer for PFFFT. Note that the * first bin stores the real component of the half-frequency bin in * the imaginary component. Also scale the FFT by its length so the * iFFT'd output will be normalized. */ static constexpr float fftscale{1.0f / float{ConvolveUpdateSize}}; for(size_t i{0};i < ConvolveUpdateSamples;++i) { ffttmp[i*2 ] = static_cast(fftbuffer[i].real()) * fftscale; ffttmp[i*2 + 1] = static_cast((i == 0) ? fftbuffer[ConvolveUpdateSamples].real() : fftbuffer[i].imag()) * fftscale; } /* Reorder backward to make it suitable for pffft_zconvolve and the * subsequent pffft_transform(..., PFFFT_BACKWARD). */ mFft.zreorder(ffttmp.data(), al::to_address(filteriter), PFFFT_BACKWARD); filteriter += ConvolveUpdateSize; } } } void ConvolutionState::update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props_, const EffectTarget target) { /* TODO: LFE is not mixed to output. This will require each buffer channel * to have its own output target since the main mixing buffer won't have an * LFE channel (due to being B-Format). */ static constexpr std::array MonoMap{ ChanPosMap{FrontCenter, std::array{0.0f, 0.0f, -1.0f}} }; static constexpr std::array StereoMap{ ChanPosMap{FrontLeft, std::array{-sin30, 0.0f, -cos30}}, ChanPosMap{FrontRight, std::array{ sin30, 0.0f, -cos30}}, }; static constexpr std::array RearMap{ ChanPosMap{BackLeft, std::array{-sin30, 0.0f, cos30}}, ChanPosMap{BackRight, std::array{ sin30, 0.0f, cos30}}, }; static constexpr std::array QuadMap{ ChanPosMap{FrontLeft, std::array{-sin45, 0.0f, -cos45}}, ChanPosMap{FrontRight, std::array{ sin45, 0.0f, -cos45}}, ChanPosMap{BackLeft, std::array{-sin45, 0.0f, cos45}}, ChanPosMap{BackRight, std::array{ sin45, 0.0f, cos45}}, }; static constexpr std::array X51Map{ ChanPosMap{FrontLeft, std::array{-sin30, 0.0f, -cos30}}, ChanPosMap{FrontRight, std::array{ sin30, 0.0f, -cos30}}, ChanPosMap{FrontCenter, std::array{ 0.0f, 0.0f, -1.0f}}, ChanPosMap{LFE, {}}, ChanPosMap{SideLeft, std::array{-sin110, 0.0f, -cos110}}, ChanPosMap{SideRight, std::array{ sin110, 0.0f, -cos110}}, }; static constexpr std::array X61Map{ ChanPosMap{FrontLeft, std::array{-sin30, 0.0f, -cos30}}, ChanPosMap{FrontRight, std::array{ sin30, 0.0f, -cos30}}, ChanPosMap{FrontCenter, std::array{ 0.0f, 0.0f, -1.0f}}, ChanPosMap{LFE, {}}, ChanPosMap{BackCenter, std::array{ 0.0f, 0.0f, 1.0f} }, ChanPosMap{SideLeft, std::array{-1.0f, 0.0f, 0.0f} }, ChanPosMap{SideRight, std::array{ 1.0f, 0.0f, 0.0f} }, }; static constexpr std::array X71Map{ ChanPosMap{FrontLeft, std::array{-sin30, 0.0f, -cos30}}, ChanPosMap{FrontRight, std::array{ sin30, 0.0f, -cos30}}, ChanPosMap{FrontCenter, std::array{ 0.0f, 0.0f, -1.0f}}, ChanPosMap{LFE, {}}, ChanPosMap{BackLeft, std::array{-sin30, 0.0f, cos30}}, ChanPosMap{BackRight, std::array{ sin30, 0.0f, cos30}}, ChanPosMap{SideLeft, std::array{ -1.0f, 0.0f, 0.0f}}, ChanPosMap{SideRight, std::array{ 1.0f, 0.0f, 0.0f}}, }; if(mNumConvolveSegs < 1) UNLIKELY return; auto &props = std::get(*props_); mMix = &ConvolutionState::NormalMix; for(auto &chan : mChans) std::fill(chan.Target.begin(), chan.Target.end(), 0.0f); const float gain{slot->Gain}; if(IsAmbisonic(mChannels)) { DeviceBase *device{context->mDevice}; if(mChannels == FmtUHJ2 && !device->mUhjEncoder) { mMix = &ConvolutionState::UpsampleMix; mChans[0].mHfScale = 1.0f; mChans[0].mLfScale = DecoderBase::sWLFScale; mChans[1].mHfScale = 1.0f; mChans[1].mLfScale = DecoderBase::sXYLFScale; mChans[2].mHfScale = 1.0f; mChans[2].mLfScale = DecoderBase::sXYLFScale; } else if(device->mAmbiOrder > mAmbiOrder) { mMix = &ConvolutionState::UpsampleMix; const auto scales = AmbiScale::GetHFOrderScales(mAmbiOrder, device->mAmbiOrder, device->m2DMixing); mChans[0].mHfScale = scales[0]; mChans[0].mLfScale = 1.0f; for(size_t i{1};i < mChans.size();++i) { mChans[i].mHfScale = scales[1]; mChans[i].mLfScale = 1.0f; } } mOutTarget = target.Main->Buffer; alu::Vector N{props.OrientAt[0], props.OrientAt[1], props.OrientAt[2], 0.0f}; N.normalize(); alu::Vector V{props.OrientUp[0], props.OrientUp[1], props.OrientUp[2], 0.0f}; V.normalize(); /* Build and normalize right-vector */ alu::Vector U{N.cross_product(V)}; U.normalize(); const std::array mixmatrix{ std::array{1.0f, 0.0f, 0.0f, 0.0f}, std::array{0.0f, U[0], -U[1], U[2]}, std::array{0.0f, -V[0], V[1], -V[2]}, std::array{0.0f, -N[0], N[1], -N[2]}, }; const auto scales = GetAmbiScales(mAmbiScaling); const auto index_map = Is2DAmbisonic(mChannels) ? al::span{GetAmbi2DLayout(mAmbiLayout)}.subspan(0) : al::span{GetAmbiLayout(mAmbiLayout)}.subspan(0); std::array coeffs{}; for(size_t c{0u};c < mChans.size();++c) { const size_t acn{index_map[c]}; const float scale{scales[acn]}; std::transform(mixmatrix[acn].cbegin(), mixmatrix[acn].cend(), coeffs.begin(), [scale](const float in) noexcept -> float { return in * scale; }); ComputePanGains(target.Main, coeffs, gain, mChans[c].Target); } } else { DeviceBase *device{context->mDevice}; al::span chanmap{}; switch(mChannels) { case FmtMono: chanmap = MonoMap; break; case FmtMonoDup: chanmap = MonoMap; break; case FmtSuperStereo: case FmtStereo: chanmap = StereoMap; break; case FmtRear: chanmap = RearMap; break; case FmtQuad: chanmap = QuadMap; break; case FmtX51: chanmap = X51Map; break; case FmtX61: chanmap = X61Map; break; case FmtX71: chanmap = X71Map; break; case FmtBFormat2D: case FmtBFormat3D: case FmtUHJ2: case FmtUHJ3: case FmtUHJ4: break; } mOutTarget = target.Main->Buffer; if(device->mRenderMode == RenderMode::Pairwise) { /* Scales the azimuth of the given vector by 3 if it's in front. * Effectively scales +/-30 degrees to +/-90 degrees, leaving > +90 * and < -90 alone. */ auto ScaleAzimuthFront = [](std::array pos) -> std::array { if(pos[2] < 0.0f) { /* Normalize the length of the x,z components for a 2D * vector of the azimuth angle. Negate Z since {0,0,-1} is * angle 0. */ const float len2d{std::sqrt(pos[0]*pos[0] + pos[2]*pos[2])}; float x{pos[0] / len2d}; float z{-pos[2] / len2d}; /* Z > cos(pi/6) = -30 < azimuth < 30 degrees. */ if(z > cos30) { /* Triple the angle represented by x,z. */ x = x*3.0f - x*x*x*4.0f; z = z*z*z*4.0f - z*3.0f; /* Scale the vector back to fit in 3D. */ pos[0] = x * len2d; pos[2] = -z * len2d; } else { /* If azimuth >= 30 degrees, clamp to 90 degrees. */ pos[0] = std::copysign(len2d, pos[0]); pos[2] = 0.0f; } } return pos; }; for(size_t i{0};i < chanmap.size();++i) { if(chanmap[i].channel == LFE) continue; const auto coeffs = CalcDirectionCoeffs(ScaleAzimuthFront(chanmap[i].pos), 0.0f); ComputePanGains(target.Main, coeffs, gain, mChans[i].Target); } } else for(size_t i{0};i < chanmap.size();++i) { if(chanmap[i].channel == LFE) continue; const auto coeffs = CalcDirectionCoeffs(chanmap[i].pos, 0.0f); ComputePanGains(target.Main, coeffs, gain, mChans[i].Target); } } } void ConvolutionState::process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) { if(mNumConvolveSegs < 1) UNLIKELY return; size_t curseg{mCurrentSegment}; for(size_t base{0u};base < samplesToDo;) { const size_t todo{std::min(ConvolveUpdateSamples-mFifoPos, samplesToDo-base)}; std::copy_n(samplesIn[0].begin() + ptrdiff_t(base), todo, mInput.begin()+ptrdiff_t(ConvolveUpdateSamples+mFifoPos)); /* Apply the FIR for the newly retrieved input samples, and combine it * with the inverse FFT'd output samples. */ for(size_t c{0};c < mChans.size();++c) { auto outspan = al::span{mChans[c].mBuffer}.subspan(base, todo); apply_fir(outspan, al::span{mInput}.subspan(1+mFifoPos), mFilter[c]); auto fifospan = al::span{mOutput[c]}.subspan(mFifoPos, todo); std::transform(fifospan.cbegin(), fifospan.cend(), outspan.cbegin(), outspan.begin(), std::plus{}); } mFifoPos += todo; base += todo; /* Check whether the input buffer is filled with new samples. */ if(mFifoPos < ConvolveUpdateSamples) break; mFifoPos = 0; /* Move the newest input to the front for the next iteration's history. */ std::copy(mInput.cbegin()+ConvolveUpdateSamples, mInput.cend(), mInput.begin()); std::fill(mInput.begin()+ConvolveUpdateSamples, mInput.end(), 0.0f); /* Calculate the frequency-domain response and add the relevant * frequency bins to the FFT history. */ mFft.transform(mInput.data(), &mComplexData[curseg*ConvolveUpdateSize], mFftWorkBuffer.data(), PFFFT_FORWARD); auto filter = mComplexData.cbegin() + ptrdiff_t(mNumConvolveSegs*ConvolveUpdateSize); for(size_t c{0};c < mChans.size();++c) { /* Convolve each input segment with its IR filter counterpart * (aligned in time). */ mFftBuffer.fill(0.0f); auto input = mComplexData.cbegin() + ptrdiff_t(curseg*ConvolveUpdateSize); for(size_t s{curseg};s < mNumConvolveSegs;++s) { mFft.zconvolve_accumulate(al::to_address(input), al::to_address(filter), mFftBuffer.data()); input += ConvolveUpdateSize; filter += ConvolveUpdateSize; } input = mComplexData.cbegin(); for(size_t s{0};s < curseg;++s) { mFft.zconvolve_accumulate(al::to_address(input), al::to_address(filter), mFftBuffer.data()); input += ConvolveUpdateSize; filter += ConvolveUpdateSize; } /* Apply iFFT to get the 256 (really 255) samples for output. The * 128 output samples are combined with the last output's 127 * second-half samples (and this output's second half is * subsequently saved for next time). */ mFft.transform(mFftBuffer.data(), mFftBuffer.data(), mFftWorkBuffer.data(), PFFFT_BACKWARD); /* The filter was attenuated, so the response is already scaled. */ std::transform(mFftBuffer.cbegin(), mFftBuffer.cbegin()+ConvolveUpdateSamples, mOutput[c].cbegin()+ConvolveUpdateSamples, mOutput[c].begin(), std::plus{}); std::copy(mFftBuffer.cbegin()+ConvolveUpdateSamples, mFftBuffer.cend(), mOutput[c].begin()+ConvolveUpdateSamples); } /* Shift the input history. */ curseg = curseg ? (curseg-1) : (mNumConvolveSegs-1); } mCurrentSegment = curseg; /* Finally, mix to the output. */ (this->*mMix)(samplesOut, samplesToDo); } struct ConvolutionStateFactory final : public EffectStateFactory { al::intrusive_ptr create() override { return al::intrusive_ptr{new ConvolutionState{}}; } }; } // namespace EffectStateFactory *ConvolutionStateFactory_getFactory() { static ConvolutionStateFactory ConvolutionFactory{}; return &ConvolutionFactory; } openal-soft-1.24.2/alc/effects/dedicated.cpp000066400000000000000000000100361474041540300206210ustar00rootroot00000000000000/** * OpenAL cross platform audio library * Copyright (C) 2011 by Chris Robinson. * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * Or go to http://www.gnu.org/copyleft/lgpl.html */ #include "config.h" #include #include #include #include #include "alc/effects/base.h" #include "alspan.h" #include "core/bufferline.h" #include "core/devformat.h" #include "core/device.h" #include "core/effects/base.h" #include "core/effectslot.h" #include "core/mixer.h" #include "intrusive_ptr.h" struct BufferStorage; struct ContextBase; namespace { using uint = unsigned int; struct DedicatedState final : public EffectState { /* The "dedicated" effect can output to the real output, so should have * gains for all possible output channels and not just the main ambisonic * buffer. */ std::array mCurrentGains{}; std::array mTargetGains{}; void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) final; void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props_, const EffectTarget target) final; void process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) final; }; void DedicatedState::deviceUpdate(const DeviceBase*, const BufferStorage*) { std::fill(mCurrentGains.begin(), mCurrentGains.end(), 0.0f); } void DedicatedState::update(const ContextBase*, const EffectSlot *slot, const EffectProps *props_, const EffectTarget target) { std::fill(mTargetGains.begin(), mTargetGains.end(), 0.0f); auto &props = std::get(*props_); const float Gain{slot->Gain * props.Gain}; if(props.Target == DedicatedProps::Dialog) { /* Dialog goes to the front-center speaker if it exists, otherwise it * plays from the front-center location. */ const size_t idx{target.RealOut ? target.RealOut->ChannelIndex[FrontCenter] : InvalidChannelIndex}; if(idx != InvalidChannelIndex) { mOutTarget = target.RealOut->Buffer; mTargetGains[idx] = Gain; } else { static constexpr auto coeffs = CalcDirectionCoeffs(std::array{0.0f, 0.0f, -1.0f}); mOutTarget = target.Main->Buffer; ComputePanGains(target.Main, coeffs, Gain, mTargetGains); } } else if(props.Target == DedicatedProps::Lfe) { const size_t idx{target.RealOut ? target.RealOut->ChannelIndex[LFE] : InvalidChannelIndex}; if(idx != InvalidChannelIndex) { mOutTarget = target.RealOut->Buffer; mTargetGains[idx] = Gain; } } } void DedicatedState::process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) { MixSamples(al::span{samplesIn[0]}.first(samplesToDo), samplesOut, mCurrentGains, mTargetGains, samplesToDo, 0); } struct DedicatedStateFactory final : public EffectStateFactory { al::intrusive_ptr create() override { return al::intrusive_ptr{new DedicatedState{}}; } }; } // namespace EffectStateFactory *DedicatedStateFactory_getFactory() { static DedicatedStateFactory DedicatedFactory{}; return &DedicatedFactory; } openal-soft-1.24.2/alc/effects/distortion.cpp000066400000000000000000000152651474041540300211220ustar00rootroot00000000000000/** * OpenAL cross platform audio library * Copyright (C) 2013 by Mike Gorchak * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * Or go to http://www.gnu.org/copyleft/lgpl.html */ #include "config.h" #include #include #include #include #include #include "alc/effects/base.h" #include "alnumbers.h" #include "alnumeric.h" #include "alspan.h" #include "core/ambidefs.h" #include "core/bufferline.h" #include "core/context.h" #include "core/device.h" #include "core/effects/base.h" #include "core/effectslot.h" #include "core/filters/biquad.h" #include "core/mixer.h" #include "core/mixer/defs.h" #include "intrusive_ptr.h" struct BufferStorage; namespace { struct DistortionState final : public EffectState { /* Effect gains for each channel */ std::array mGain{}; /* Effect parameters */ BiquadFilter mLowpass; BiquadFilter mBandpass; float mAttenuation{}; float mEdgeCoeff{}; alignas(16) std::array mBuffer{}; void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) override; void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props, const EffectTarget target) override; void process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) override; }; void DistortionState::deviceUpdate(const DeviceBase*, const BufferStorage*) { mLowpass.clear(); mBandpass.clear(); } void DistortionState::update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props_, const EffectTarget target) { auto &props = std::get(*props_); const DeviceBase *device{context->mDevice}; /* Store waveshaper edge settings. */ const float edge{std::min(std::sin(al::numbers::pi_v*0.5f * props.Edge), 0.99f)}; mEdgeCoeff = 2.0f * edge / (1.0f-edge); float cutoff{props.LowpassCutoff}; /* Bandwidth value is constant in octaves. */ float bandwidth{(cutoff / 2.0f) / (cutoff * 0.67f)}; /* Divide normalized frequency by the amount of oversampling done during * processing. */ auto frequency = static_cast(device->mSampleRate); mLowpass.setParamsFromBandwidth(BiquadType::LowPass, cutoff/frequency/4.0f, 1.0f, bandwidth); cutoff = props.EQCenter; /* Convert bandwidth in Hz to octaves. */ bandwidth = props.EQBandwidth / (cutoff * 0.67f); mBandpass.setParamsFromBandwidth(BiquadType::BandPass, cutoff/frequency/4.0f, 1.0f, bandwidth); static constexpr auto coeffs = CalcDirectionCoeffs(std::array{0.0f, 0.0f, -1.0f}); mOutTarget = target.Main->Buffer; ComputePanGains(target.Main, coeffs, slot->Gain*props.Gain, mGain); } void DistortionState::process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) { const float fc{mEdgeCoeff}; for(size_t base{0u};base < samplesToDo;) { /* Perform 4x oversampling to avoid aliasing. Oversampling greatly * improves distortion quality and allows to implement lowpass and * bandpass filters using high frequencies, at which classic IIR * filters became unstable. */ size_t todo{std::min(BufferLineSize, (samplesToDo-base) * 4_uz)}; /* Fill oversample buffer using zero stuffing. Multiply the sample by * the amount of oversampling to maintain the signal's power. */ for(size_t i{0u};i < todo;i++) mBuffer[0][i] = !(i&3) ? samplesIn[0][(i>>2)+base] * 4.0f : 0.0f; /* First step, do lowpass filtering of original signal. Additionally * perform buffer interpolation and lowpass cutoff for oversampling * (which is fortunately first step of distortion). So combine three * operations into the one. */ mLowpass.process(al::span{mBuffer[0]}.first(todo), mBuffer[1]); /* Second step, do distortion using waveshaper function to emulate * signal processing during tube overdriving. Three steps of * waveshaping are intended to modify waveform without boost/clipping/ * attenuation process. */ auto proc_sample = [fc](float smp) -> float { smp = (1.0f + fc) * smp/(1.0f + fc*std::fabs(smp)); smp = (1.0f + fc) * smp/(1.0f + fc*std::fabs(smp)) * -1.0f; smp = (1.0f + fc) * smp/(1.0f + fc*std::fabs(smp)); return smp; }; std::transform(mBuffer[1].begin(), mBuffer[1].begin()+todo, mBuffer[0].begin(), proc_sample); /* Third step, do bandpass filtering of distorted signal. */ mBandpass.process(al::span{mBuffer[0]}.first(todo), mBuffer[1]); todo >>= 2; auto outgains = mGain.cbegin(); auto proc_bufline = [this,base,todo,&outgains](FloatBufferSpan output) { /* Fourth step, final, do attenuation and perform decimation, * storing only one sample out of four. */ const float gain{*(outgains++)}; if(!(std::fabs(gain) > GainSilenceThreshold)) return; auto src = mBuffer[1].cbegin(); const auto dst = al::span{output}.subspan(base, todo); auto dec_sample = [gain,&src](float sample) noexcept -> float { sample += *src * gain; src += 4; return sample; }; std::transform(dst.begin(), dst.end(), dst.begin(), dec_sample); }; std::for_each(samplesOut.begin(), samplesOut.end(), proc_bufline); base += todo; } } struct DistortionStateFactory final : public EffectStateFactory { al::intrusive_ptr create() override { return al::intrusive_ptr{new DistortionState{}}; } }; } // namespace EffectStateFactory *DistortionStateFactory_getFactory() { static DistortionStateFactory DistortionFactory{}; return &DistortionFactory; } openal-soft-1.24.2/alc/effects/echo.cpp000066400000000000000000000140051474041540300176310ustar00rootroot00000000000000/** * OpenAL cross platform audio library * Copyright (C) 2009 by Chris Robinson. * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * Or go to http://www.gnu.org/copyleft/lgpl.html */ #include "config.h" #include #include #include #include #include #include #include "alc/effects/base.h" #include "alnumeric.h" #include "alspan.h" #include "core/ambidefs.h" #include "core/bufferline.h" #include "core/context.h" #include "core/device.h" #include "core/effects/base.h" #include "core/effectslot.h" #include "core/filters/biquad.h" #include "core/mixer.h" #include "intrusive_ptr.h" #include "opthelpers.h" struct BufferStorage; namespace { using uint = unsigned int; constexpr float LowpassFreqRef{5000.0f}; struct EchoState final : public EffectState { std::vector mSampleBuffer; // The echo is two tap. The delay is the number of samples from before the // current offset std::array mDelayTap{}; size_t mOffset{0u}; /* The panning gains for the two taps */ struct OutGains { std::array Current{}; std::array Target{}; }; std::array mGains; BiquadFilter mFilter; float mFeedGain{0.0f}; alignas(16) std::array mTempBuffer{}; void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) override; void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props, const EffectTarget target) override; void process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) override; }; void EchoState::deviceUpdate(const DeviceBase *Device, const BufferStorage*) { const auto frequency = static_cast(Device->mSampleRate); // Use the next power of 2 for the buffer length, so the tap offsets can be // wrapped using a mask instead of a modulo const uint maxlen{NextPowerOf2(float2uint(EchoMaxDelay*frequency + 0.5f) + float2uint(EchoMaxLRDelay*frequency + 0.5f))}; if(maxlen != mSampleBuffer.size()) decltype(mSampleBuffer)(maxlen).swap(mSampleBuffer); std::fill(mSampleBuffer.begin(), mSampleBuffer.end(), 0.0f); for(auto &e : mGains) { std::fill(e.Current.begin(), e.Current.end(), 0.0f); std::fill(e.Target.begin(), e.Target.end(), 0.0f); } } void EchoState::update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props_, const EffectTarget target) { auto &props = std::get(*props_); const DeviceBase *device{context->mDevice}; const auto frequency = static_cast(device->mSampleRate); mDelayTap[0] = std::max(float2uint(std::round(props.Delay*frequency)), 1u); mDelayTap[1] = float2uint(std::round(props.LRDelay*frequency)) + mDelayTap[0]; const float gainhf{std::max(1.0f - props.Damping, 0.0625f)}; /* Limit -24dB */ mFilter.setParamsFromSlope(BiquadType::HighShelf, LowpassFreqRef/frequency, gainhf, 1.0f); mFeedGain = props.Feedback; /* Convert echo spread (where 0 = center, +/-1 = sides) to a 2D vector. */ const float x{props.Spread}; /* +x = left */ const float z{std::sqrt(1.0f - x*x)}; const auto coeffs0 = CalcAmbiCoeffs( x, 0.0f, z, 0.0f); const auto coeffs1 = CalcAmbiCoeffs(-x, 0.0f, z, 0.0f); mOutTarget = target.Main->Buffer; ComputePanGains(target.Main, coeffs0, slot->Gain, mGains[0].Target); ComputePanGains(target.Main, coeffs1, slot->Gain, mGains[1].Target); } void EchoState::process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) { const auto delaybuf = al::span{mSampleBuffer}; const size_t mask{delaybuf.size()-1}; size_t offset{mOffset}; size_t tap1{offset - mDelayTap[0]}; size_t tap2{offset - mDelayTap[1]}; ASSUME(samplesToDo > 0); const BiquadFilter filter{mFilter}; auto [z1, z2] = mFilter.getComponents(); for(size_t i{0u};i < samplesToDo;) { offset &= mask; tap1 &= mask; tap2 &= mask; size_t td{std::min(mask+1 - std::max(offset, std::max(tap1, tap2)), samplesToDo-i)}; do { /* Feed the delay buffer's input first. */ delaybuf[offset] = samplesIn[0][i]; /* Get delayed output from the first and second taps. Use the * second tap for feedback. */ mTempBuffer[0][i] = delaybuf[tap1++]; mTempBuffer[1][i] = delaybuf[tap2++]; const float feedb{mTempBuffer[1][i++]}; /* Add feedback to the delay buffer with damping and attenuation. */ delaybuf[offset++] += filter.processOne(feedb, z1, z2) * mFeedGain; } while(--td); } mFilter.setComponents(z1, z2); mOffset = offset; for(size_t c{0};c < 2;c++) MixSamples(al::span{mTempBuffer[c]}.first(samplesToDo), samplesOut, mGains[c].Current, mGains[c].Target, samplesToDo, 0); } struct EchoStateFactory final : public EffectStateFactory { al::intrusive_ptr create() override { return al::intrusive_ptr{new EchoState{}}; } }; } // namespace EffectStateFactory *EchoStateFactory_getFactory() { static EchoStateFactory EchoFactory{}; return &EchoFactory; } openal-soft-1.24.2/alc/effects/equalizer.cpp000066400000000000000000000207411474041540300207200ustar00rootroot00000000000000/** * OpenAL cross platform audio library * Copyright (C) 2013 by Mike Gorchak * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * Or go to http://www.gnu.org/copyleft/lgpl.html */ #include "config.h" #include #include #include #include #include #include #include "alc/effects/base.h" #include "alspan.h" #include "core/ambidefs.h" #include "core/bufferline.h" #include "core/context.h" #include "core/device.h" #include "core/effects/base.h" #include "core/effectslot.h" #include "core/filters/biquad.h" #include "core/mixer.h" #include "intrusive_ptr.h" struct BufferStorage; namespace { /* The document "Effects Extension Guide.pdf" says that low and high * * frequencies are cutoff frequencies. This is not fully correct, they * * are corner frequencies for low and high shelf filters. If they were * * just cutoff frequencies, there would be no need in cutoff frequency * * gains, which are present. Documentation for "Creative Proteus X2" * * software describes 4-band equalizer functionality in a much better * * way. This equalizer seems to be a predecessor of OpenAL 4-band * * equalizer. With low and high shelf filters we are able to cutoff * * frequencies below and/or above corner frequencies using attenuation * * gains (below 1.0) and amplify all low and/or high frequencies using * * gains above 1.0. * * * * Low-shelf Low Mid Band High Mid Band High-shelf * * corner center center corner * * frequency frequency frequency frequency * * 50Hz..800Hz 200Hz..3000Hz 1000Hz..8000Hz 4000Hz..16000Hz * * * * | | | | * * | | | | * * B -----+ /--+--\ /--+--\ +----- * * O |\ | | | | | | /| * * O | \ - | - - | - / | * * S + | \ | | | | | | / | * * T | | | | | | | | | | * * ---------+---------------+------------------+---------------+-------- * * C | | | | | | | | | | * * U - | / | | | | | | \ | * * T | / - | - - | - \ | * * O |/ | | | | | | \| * * F -----+ \--+--/ \--+--/ +----- * * F | | | | * * | | | | * * * * Gains vary from 0.126 up to 7.943, which means from -18dB attenuation * * up to +18dB amplification. Band width varies from 0.01 up to 1.0 in * * octaves for two mid bands. * * * * Implementation is based on the "Cookbook formulae for audio EQ biquad * * filter coefficients" by Robert Bristow-Johnson * * http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt */ struct EqualizerState final : public EffectState { struct OutParams { uint mTargetChannel{InvalidChannelIndex}; /* Effect parameters */ std::array mFilter; /* Effect gains for each channel */ float mCurrentGain{}; float mTargetGain{}; }; std::array mChans; alignas(16) FloatBufferLine mSampleBuffer{}; void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) override; void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props, const EffectTarget target) override; void process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) override; }; void EqualizerState::deviceUpdate(const DeviceBase*, const BufferStorage*) { for(auto &e : mChans) { e.mTargetChannel = InvalidChannelIndex; std::for_each(e.mFilter.begin(), e.mFilter.end(), std::mem_fn(&BiquadFilter::clear)); e.mCurrentGain = 0.0f; } } void EqualizerState::update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props_, const EffectTarget target) { auto &props = std::get(*props_); const DeviceBase *device{context->mDevice}; auto frequency = static_cast(device->mSampleRate); /* Calculate coefficients for the each type of filter. Note that the shelf * and peaking filters' gain is for the centerpoint of the transition band, * while the effect property gains are for the shelf/peak itself. So the * property gains need their dB halved (sqrt of linear gain) for the * shelf/peak to reach the provided gain. */ float gain{std::sqrt(props.LowGain)}; float f0norm{props.LowCutoff / frequency}; mChans[0].mFilter[0].setParamsFromSlope(BiquadType::LowShelf, f0norm, gain, 0.75f); gain = std::sqrt(props.Mid1Gain); f0norm = props.Mid1Center / frequency; mChans[0].mFilter[1].setParamsFromBandwidth(BiquadType::Peaking, f0norm, gain, props.Mid1Width); gain = std::sqrt(props.Mid2Gain); f0norm = props.Mid2Center / frequency; mChans[0].mFilter[2].setParamsFromBandwidth(BiquadType::Peaking, f0norm, gain, props.Mid2Width); gain = std::sqrt(props.HighGain); f0norm = props.HighCutoff / frequency; mChans[0].mFilter[3].setParamsFromSlope(BiquadType::HighShelf, f0norm, gain, 0.75f); /* Copy the filter coefficients for the other input channels. */ for(size_t i{1u};i < slot->Wet.Buffer.size();++i) { mChans[i].mFilter[0].copyParamsFrom(mChans[0].mFilter[0]); mChans[i].mFilter[1].copyParamsFrom(mChans[0].mFilter[1]); mChans[i].mFilter[2].copyParamsFrom(mChans[0].mFilter[2]); mChans[i].mFilter[3].copyParamsFrom(mChans[0].mFilter[3]); } mOutTarget = target.Main->Buffer; auto set_channel = [this](size_t idx, uint outchan, float outgain) { mChans[idx].mTargetChannel = outchan; mChans[idx].mTargetGain = outgain; }; target.Main->setAmbiMixParams(slot->Wet, slot->Gain, set_channel); } void EqualizerState::process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) { const auto buffer = al::span{mSampleBuffer}.first(samplesToDo); auto chan = mChans.begin(); for(const auto &input : samplesIn) { if(const size_t outidx{chan->mTargetChannel}; outidx != InvalidChannelIndex) { const auto inbuf = al::span{input}.first(samplesToDo); DualBiquad{chan->mFilter[0], chan->mFilter[1]}.process(inbuf, buffer); DualBiquad{chan->mFilter[2], chan->mFilter[3]}.process(buffer, buffer); MixSamples(buffer, samplesOut[outidx], chan->mCurrentGain, chan->mTargetGain, samplesToDo); } ++chan; } } struct EqualizerStateFactory final : public EffectStateFactory { al::intrusive_ptr create() override { return al::intrusive_ptr{new EqualizerState{}}; } }; } // namespace EffectStateFactory *EqualizerStateFactory_getFactory() { static EqualizerStateFactory EqualizerFactory{}; return &EqualizerFactory; } openal-soft-1.24.2/alc/effects/fshifter.cpp000066400000000000000000000212631474041540300205310ustar00rootroot00000000000000/** * OpenAL cross platform audio library * Copyright (C) 2018 by Raul Herraiz. * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * Or go to http://www.gnu.org/copyleft/lgpl.html */ #include "config.h" #include #include #include #include #include #include #include "alc/effects/base.h" #include "alcomplex.h" #include "alnumbers.h" #include "alnumeric.h" #include "alspan.h" #include "core/ambidefs.h" #include "core/bufferline.h" #include "core/context.h" #include "core/device.h" #include "core/effects/base.h" #include "core/effectslot.h" #include "core/mixer.h" #include "core/mixer/defs.h" #include "intrusive_ptr.h" #include "opthelpers.h" struct BufferStorage; namespace { using uint = unsigned int; using complex_d = std::complex; constexpr size_t HilSize{1024}; constexpr size_t HilHalfSize{HilSize >> 1}; constexpr size_t OversampleFactor{4}; static_assert(HilSize%OversampleFactor == 0, "Factor must be a clean divisor of the size"); constexpr size_t HilStep{HilSize / OversampleFactor}; /* Define a Hann window, used to filter the HIL input and output. */ struct Windower { alignas(16) std::array mData{}; Windower() { /* Create lookup table of the Hann window for the desired size. */ for(size_t i{0};i < HilHalfSize;i++) { constexpr double scale{al::numbers::pi / double{HilSize}}; const double val{std::sin((static_cast(i)+0.5) * scale)}; mData[i] = mData[HilSize-1-i] = val * val; } } }; const Windower gWindow{}; struct FshifterState final : public EffectState { /* Effect parameters */ size_t mCount{}; size_t mPos{}; std::array mPhaseStep{}; std::array mPhase{}; std::array mSign{}; /* Effects buffers */ std::array mInFIFO{}; std::array mOutFIFO{}; std::array mOutputAccum{}; std::array mAnalytic{}; std::array mOutdata{}; alignas(16) FloatBufferLine mBufferOut{}; /* Effect gains for each output channel */ struct OutGains { std::array Current{}; std::array Target{}; }; std::array mGains; void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) override; void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props, const EffectTarget target) override; void process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) override; }; void FshifterState::deviceUpdate(const DeviceBase*, const BufferStorage*) { /* (Re-)initializing parameters and clear the buffers. */ mCount = 0; mPos = HilSize - HilStep; mPhaseStep.fill(0u); mPhase.fill(0u); mSign.fill(1.0); mInFIFO.fill(0.0); mOutFIFO.fill(complex_d{}); mOutputAccum.fill(complex_d{}); mAnalytic.fill(complex_d{}); for(auto &gain : mGains) { gain.Current.fill(0.0f); gain.Target.fill(0.0f); } } void FshifterState::update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props_, const EffectTarget target) { auto &props = std::get(*props_); const DeviceBase *device{context->mDevice}; const float step{props.Frequency / static_cast(device->mSampleRate)}; mPhaseStep[0] = mPhaseStep[1] = fastf2u(std::min(step, 1.0f) * MixerFracOne); switch(props.LeftDirection) { case FShifterDirection::Down: mSign[0] = -1.0; break; case FShifterDirection::Up: mSign[0] = 1.0; break; case FShifterDirection::Off: mPhase[0] = 0; mPhaseStep[0] = 0; break; } switch(props.RightDirection) { case FShifterDirection::Down: mSign[1] = -1.0; break; case FShifterDirection::Up: mSign[1] = 1.0; break; case FShifterDirection::Off: mPhase[1] = 0; mPhaseStep[1] = 0; break; } static constexpr auto inv_sqrt2 = static_cast(1.0 / al::numbers::sqrt2); static constexpr auto lcoeffs_pw = CalcDirectionCoeffs(std::array{-1.0f, 0.0f, 0.0f}); static constexpr auto rcoeffs_pw = CalcDirectionCoeffs(std::array{ 1.0f, 0.0f, 0.0f}); static constexpr auto lcoeffs_nrml = CalcDirectionCoeffs(std::array{-inv_sqrt2, 0.0f, inv_sqrt2}); static constexpr auto rcoeffs_nrml = CalcDirectionCoeffs(std::array{ inv_sqrt2, 0.0f, inv_sqrt2}); auto &lcoeffs = (device->mRenderMode != RenderMode::Pairwise) ? lcoeffs_nrml : lcoeffs_pw; auto &rcoeffs = (device->mRenderMode != RenderMode::Pairwise) ? rcoeffs_nrml : rcoeffs_pw; mOutTarget = target.Main->Buffer; ComputePanGains(target.Main, lcoeffs, slot->Gain, mGains[0].Target); ComputePanGains(target.Main, rcoeffs, slot->Gain, mGains[1].Target); } void FshifterState::process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) { for(size_t base{0u};base < samplesToDo;) { size_t todo{std::min(HilStep-mCount, samplesToDo-base)}; /* Fill FIFO buffer with samples data */ const size_t pos{mPos}; size_t count{mCount}; do { mInFIFO[pos+count] = samplesIn[0][base]; mOutdata[base] = mOutFIFO[count]; ++base; ++count; } while(--todo); mCount = count; /* Check whether FIFO buffer is filled */ if(mCount < HilStep) break; mCount = 0; mPos = (mPos+HilStep) & (HilSize-1); /* Real signal windowing and store in Analytic buffer */ for(size_t src{mPos}, k{0u};src < HilSize;++src,++k) mAnalytic[k] = mInFIFO[src]*gWindow.mData[k]; for(size_t src{0u}, k{HilSize-mPos};src < mPos;++src,++k) mAnalytic[k] = mInFIFO[src]*gWindow.mData[k]; /* Processing signal by Discrete Hilbert Transform (analytical signal). */ complex_hilbert(mAnalytic); /* Windowing and add to output accumulator */ for(size_t dst{mPos}, k{0u};dst < HilSize;++dst,++k) mOutputAccum[dst] += 2.0/OversampleFactor*gWindow.mData[k]*mAnalytic[k]; for(size_t dst{0u}, k{HilSize-mPos};dst < mPos;++dst,++k) mOutputAccum[dst] += 2.0/OversampleFactor*gWindow.mData[k]*mAnalytic[k]; /* Copy out the accumulated result, then clear for the next iteration. */ std::copy_n(mOutputAccum.cbegin() + mPos, HilStep, mOutFIFO.begin()); std::fill_n(mOutputAccum.begin() + mPos, HilStep, complex_d{}); } /* Process frequency shifter using the analytic signal obtained. */ for(size_t c{0};c < 2;++c) { const double sign{mSign[c]}; const uint phase_step{mPhaseStep[c]}; uint phase_idx{mPhase[c]}; std::transform(mOutdata.cbegin(), mOutdata.cbegin()+samplesToDo, mBufferOut.begin(), [&phase_idx,phase_step,sign](const complex_d &in) -> float { const double phase{phase_idx * (al::numbers::pi*2.0 / MixerFracOne)}; const auto out = static_cast(in.real()*std::cos(phase) + in.imag()*std::sin(phase)*sign); phase_idx += phase_step; phase_idx &= MixerFracMask; return out; }); mPhase[c] = phase_idx; /* Now, mix the processed sound data to the output. */ MixSamples(al::span{mBufferOut}.first(samplesToDo), samplesOut, mGains[c].Current, mGains[c].Target, std::max(samplesToDo, 512_uz), 0); } } struct FshifterStateFactory final : public EffectStateFactory { al::intrusive_ptr create() override { return al::intrusive_ptr{new FshifterState{}}; } }; } // namespace EffectStateFactory *FshifterStateFactory_getFactory() { static FshifterStateFactory FshifterFactory{}; return &FshifterFactory; } openal-soft-1.24.2/alc/effects/modulator.cpp000066400000000000000000000164541474041540300207330ustar00rootroot00000000000000/** * OpenAL cross platform audio library * Copyright (C) 2009 by Chris Robinson. * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * Or go to http://www.gnu.org/copyleft/lgpl.html */ #include "config.h" #include #include #include #include #include #include #include #include "alc/effects/base.h" #include "alnumbers.h" #include "alnumeric.h" #include "alspan.h" #include "core/ambidefs.h" #include "core/bufferline.h" #include "core/context.h" #include "core/device.h" #include "core/effects/base.h" #include "core/effectslot.h" #include "core/filters/biquad.h" #include "core/mixer.h" #include "intrusive_ptr.h" #include "opthelpers.h" struct BufferStorage; namespace { using uint = unsigned int; struct SinFunc { static auto Get(uint index, float scale) noexcept(noexcept(std::sin(0.0f))) -> float { return std::sin(static_cast(index) * scale); } }; struct SawFunc { static constexpr auto Get(uint index, float scale) noexcept -> float { return static_cast(index)*scale - 1.0f; } }; struct SquareFunc { static constexpr auto Get(uint index, float scale) noexcept -> float { return float(static_cast(index)*scale < 0.5f)*2.0f - 1.0f; } }; struct OneFunc { static constexpr auto Get(uint, float) noexcept -> float { return 1.0f; } }; struct ModulatorState final : public EffectState { std::variant mSampleGen; uint mIndex{0}; uint mRange{1}; float mIndexScale{0.0f}; alignas(16) FloatBufferLine mModSamples{}; alignas(16) FloatBufferLine mBuffer{}; struct OutParams { uint mTargetChannel{InvalidChannelIndex}; BiquadFilter mFilter; float mCurrentGain{}; float mTargetGain{}; }; std::array mChans; void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) override; void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props, const EffectTarget target) override; void process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) override; }; void ModulatorState::deviceUpdate(const DeviceBase*, const BufferStorage*) { for(auto &e : mChans) { e.mTargetChannel = InvalidChannelIndex; e.mFilter.clear(); e.mCurrentGain = 0.0f; } } void ModulatorState::update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props_, const EffectTarget target) { auto &props = std::get(*props_); const DeviceBase *device{context->mDevice}; /* The effective frequency will be adjusted to have a whole number of * samples per cycle (at 48khz, that allows 8000, 6857.14, 6000, 5333.33, * 4800, etc). We could do better by using fixed-point stepping over a sin * function, with additive synthesis for the square and sawtooth waveforms, * but that may need a more efficient sin function since it needs to do * many iterations per sample. */ const float samplesPerCycle{props.Frequency > 0.0f ? static_cast(device->mSampleRate)/props.Frequency + 0.5f : 1.0f}; const uint range{static_cast(std::clamp(samplesPerCycle, 1.0f, static_cast(device->mSampleRate)))}; mIndex = static_cast(uint64_t{mIndex} * range / mRange); mRange = range; if(mRange == 1) { mIndexScale = 0.0f; mSampleGen.emplace(); } else if(props.Waveform == ModulatorWaveform::Sinusoid) { mIndexScale = al::numbers::pi_v*2.0f / static_cast(mRange); mSampleGen.emplace(); } else if(props.Waveform == ModulatorWaveform::Sawtooth) { mIndexScale = 2.0f / static_cast(mRange-1); mSampleGen.emplace(); } else if(props.Waveform == ModulatorWaveform::Square) { /* For square wave, the range should be even (there should be an equal * number of high and low samples). An odd number of samples per cycle * would need a more complex value generator. */ mRange = (mRange+1) & ~1u; mIndexScale = 1.0f / static_cast(mRange-1); mSampleGen.emplace(); } float f0norm{props.HighPassCutoff / static_cast(device->mSampleRate)}; f0norm = std::clamp(f0norm, 1.0f/512.0f, 0.49f); /* Bandwidth value is constant in octaves. */ mChans[0].mFilter.setParamsFromBandwidth(BiquadType::HighPass, f0norm, 1.0f, 0.75f); for(size_t i{1u};i < slot->Wet.Buffer.size();++i) mChans[i].mFilter.copyParamsFrom(mChans[0].mFilter); mOutTarget = target.Main->Buffer; auto set_channel = [this](size_t idx, uint outchan, float outgain) { mChans[idx].mTargetChannel = outchan; mChans[idx].mTargetGain = outgain; }; target.Main->setAmbiMixParams(slot->Wet, slot->Gain, set_channel); } void ModulatorState::process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) { ASSUME(samplesToDo > 0); std::visit([this,samplesToDo](auto&& type) { const uint range{mRange}; const float scale{mIndexScale}; uint index{mIndex}; ASSUME(range > 1); for(size_t i{0};i < samplesToDo;) { size_t rem{std::min(samplesToDo-i, size_t{range-index})}; do { mModSamples[i++] = type.Get(index++, scale); } while(--rem); if(index == range) index = 0; } mIndex = index; }, mSampleGen); auto chandata = mChans.begin(); for(const auto &input : samplesIn) { if(const size_t outidx{chandata->mTargetChannel}; outidx != InvalidChannelIndex) { chandata->mFilter.process(al::span{input}.first(samplesToDo), mBuffer); std::transform(mBuffer.cbegin(), mBuffer.cbegin()+samplesToDo, mModSamples.cbegin(), mBuffer.begin(), std::multiplies<>{}); MixSamples(al::span{mBuffer}.first(samplesToDo), samplesOut[outidx], chandata->mCurrentGain, chandata->mTargetGain, std::min(samplesToDo, 64_uz)); } ++chandata; } } struct ModulatorStateFactory final : public EffectStateFactory { al::intrusive_ptr create() override { return al::intrusive_ptr{new ModulatorState{}}; } }; } // namespace EffectStateFactory *ModulatorStateFactory_getFactory() { static ModulatorStateFactory ModulatorFactory{}; return &ModulatorFactory; } openal-soft-1.24.2/alc/effects/null.cpp000066400000000000000000000045641474041540300176760ustar00rootroot00000000000000 #include "config.h" #include #include "alspan.h" #include "base.h" #include "core/bufferline.h" #include "core/effects/base.h" #include "intrusive_ptr.h" struct BufferStorage; struct ContextBase; struct DeviceBase; struct EffectSlot; namespace { struct NullState final : public EffectState { NullState(); ~NullState() override; void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) override; void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props, const EffectTarget target) override; void process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) override; }; /* This constructs the effect state. It's called when the object is first * created. */ NullState::NullState() = default; /* This destructs the effect state. It's called only when the effect instance * is no longer used. */ NullState::~NullState() = default; /* This updates the device-dependant effect state. This is called on state * initialization and any time the device parameters (e.g. playback frequency, * format) have been changed. Will always be followed by a call to the update * method, if successful. */ void NullState::deviceUpdate(const DeviceBase* /*device*/, const BufferStorage* /*buffer*/) { } /* This updates the effect state with new properties. This is called any time * the effect is (re)loaded into a slot. */ void NullState::update(const ContextBase* /*context*/, const EffectSlot* /*slot*/, const EffectProps* /*props*/, const EffectTarget /*target*/) { } /* This processes the effect state, for the given number of samples from the * input to the output buffer. The result should be added to the output buffer, * not replace it. */ void NullState::process(const size_t/*samplesToDo*/, const al::span /*samplesIn*/, const al::span /*samplesOut*/) { } struct NullStateFactory final : public EffectStateFactory { al::intrusive_ptr create() override; }; /* Creates EffectState objects of the appropriate type. */ al::intrusive_ptr NullStateFactory::create() { return al::intrusive_ptr{new NullState{}}; } } // namespace EffectStateFactory *NullStateFactory_getFactory() { static NullStateFactory NullFactory{}; return &NullFactory; } openal-soft-1.24.2/alc/effects/pshifter.cpp000066400000000000000000000276201474041540300205460ustar00rootroot00000000000000/** * OpenAL cross platform audio library * Copyright (C) 2018 by Raul Herraiz. * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * Or go to http://www.gnu.org/copyleft/lgpl.html */ #include "config.h" #include #include #include #include #include #include #include "alc/effects/base.h" #include "alnumbers.h" #include "alnumeric.h" #include "alspan.h" #include "core/ambidefs.h" #include "core/bufferline.h" #include "core/device.h" #include "core/effects/base.h" #include "core/effectslot.h" #include "core/mixer.h" #include "core/mixer/defs.h" #include "intrusive_ptr.h" #include "pffft.h" struct BufferStorage; struct ContextBase; namespace { using uint = unsigned int; using complex_f = std::complex; constexpr size_t StftSize{1024}; constexpr size_t StftHalfSize{StftSize >> 1}; constexpr size_t OversampleFactor{8}; static_assert(StftSize%OversampleFactor == 0, "Factor must be a clean divisor of the size"); constexpr size_t StftStep{StftSize / OversampleFactor}; /* Define a Hann window, used to filter the STFT input and output. */ struct Windower { alignas(16) std::array mData{}; Windower() { /* Create lookup table of the Hann window for the desired size. */ for(size_t i{0};i < StftHalfSize;i++) { constexpr double scale{al::numbers::pi / double{StftSize}}; const double val{std::sin((static_cast(i)+0.5) * scale)}; mData[i] = mData[StftSize-1-i] = static_cast(val * val); } } }; const Windower gWindow{}; struct FrequencyBin { float Magnitude; float FreqBin; }; struct PshifterState final : public EffectState { /* Effect parameters */ size_t mCount{}; size_t mPos{}; uint mPitchShiftI{}; float mPitchShift{}; /* Effects buffers */ std::array mFIFO{}; std::array mLastPhase{}; std::array mSumPhase{}; std::array mOutputAccum{}; PFFFTSetup mFft; alignas(16) std::array mFftBuffer{}; alignas(16) std::array mFftWorkBuffer{}; std::array mAnalysisBuffer{}; std::array mSynthesisBuffer{}; alignas(16) FloatBufferLine mBufferOut{}; /* Effect gains for each output channel */ std::array mCurrentGains{}; std::array mTargetGains{}; void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) override; void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props, const EffectTarget target) override; void process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) override; }; void PshifterState::deviceUpdate(const DeviceBase*, const BufferStorage*) { /* (Re-)initializing parameters and clear the buffers. */ mCount = 0; mPos = StftSize - StftStep; mPitchShiftI = MixerFracOne; mPitchShift = 1.0f; mFIFO.fill(0.0f); mLastPhase.fill(0.0f); mSumPhase.fill(0.0f); mOutputAccum.fill(0.0f); mFftBuffer.fill(0.0f); mAnalysisBuffer.fill(FrequencyBin{}); mSynthesisBuffer.fill(FrequencyBin{}); mCurrentGains.fill(0.0f); mTargetGains.fill(0.0f); if(!mFft) mFft = PFFFTSetup{StftSize, PFFFT_REAL}; } void PshifterState::update(const ContextBase*, const EffectSlot *slot, const EffectProps *props_, const EffectTarget target) { auto &props = std::get(*props_); const int tune{props.CoarseTune*100 + props.FineTune}; const float pitch{std::pow(2.0f, static_cast(tune) / 1200.0f)}; mPitchShiftI = std::clamp(fastf2u(pitch*MixerFracOne), uint{MixerFracHalf}, uint{MixerFracOne}*2u); mPitchShift = static_cast(mPitchShiftI) * float{1.0f/MixerFracOne}; static constexpr auto coeffs = CalcDirectionCoeffs(std::array{0.0f, 0.0f, -1.0f}); mOutTarget = target.Main->Buffer; ComputePanGains(target.Main, coeffs, slot->Gain, mTargetGains); } void PshifterState::process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) { /* Pitch shifter engine based on the work of Stephan Bernsee. * http://blogs.zynaptiq.com/bernsee/pitch-shifting-using-the-ft/ */ /* Cycle offset per update expected of each frequency bin (bin 0 is none, * bin 1 is x1, bin 2 is x2, etc). */ constexpr float expected_cycles{al::numbers::pi_v*2.0f / OversampleFactor}; for(size_t base{0u};base < samplesToDo;) { const size_t todo{std::min(StftStep-mCount, samplesToDo-base)}; /* Retrieve the output samples from the FIFO and fill in the new input * samples. */ auto fifo_iter = mFIFO.begin()+mPos + mCount; std::copy_n(fifo_iter, todo, mBufferOut.begin()+base); std::copy_n(samplesIn[0].begin()+base, todo, fifo_iter); mCount += todo; base += todo; /* Check whether FIFO buffer is filled with new samples. */ if(mCount < StftStep) break; mCount = 0; mPos = (mPos+StftStep) & (mFIFO.size()-1); /* Time-domain signal windowing, store in FftBuffer, and apply a * forward FFT to get the frequency-domain signal. */ for(size_t src{mPos}, k{0u};src < StftSize;++src,++k) mFftBuffer[k] = mFIFO[src] * gWindow.mData[k]; for(size_t src{0u}, k{StftSize-mPos};src < mPos;++src,++k) mFftBuffer[k] = mFIFO[src] * gWindow.mData[k]; mFft.transform_ordered(mFftBuffer.data(), mFftBuffer.data(), mFftWorkBuffer.data(), PFFFT_FORWARD); /* Analyze the obtained data. Since the real FFT is symmetric, only * StftHalfSize+1 samples are needed. */ for(size_t k{0u};k < StftHalfSize+1;++k) { const auto cplx = (k == 0) ? complex_f{mFftBuffer[0]} : (k == StftHalfSize) ? complex_f{mFftBuffer[1]} : complex_f{mFftBuffer[k*2], mFftBuffer[k*2 + 1]}; const float magnitude{std::abs(cplx)}; const float phase{std::arg(cplx)}; /* Compute the phase difference from the last update and subtract * the expected phase difference for this bin. * * When oversampling, the expected per-update offset increments by * 1/OversampleFactor for every frequency bin. So, the offset wraps * every 'OversampleFactor' bin. */ const auto bin_offset = static_cast(k % OversampleFactor); float tmp{(phase - mLastPhase[k]) - bin_offset*expected_cycles}; /* Store the actual phase for the next update. */ mLastPhase[k] = phase; /* Normalize from pi, and wrap the delta between -1 and +1. */ tmp *= al::numbers::inv_pi_v; int qpd{float2int(tmp)}; tmp -= static_cast(qpd + (qpd%2)); /* Get deviation from bin frequency (-0.5 to +0.5), and account for * oversampling. */ tmp *= 0.5f * OversampleFactor; /* Compute the k-th partials' frequency bin target and store the * magnitude and frequency bin in the analysis buffer. We don't * need the "true frequency" since it's a linear relationship with * the bin. */ mAnalysisBuffer[k].Magnitude = magnitude; mAnalysisBuffer[k].FreqBin = static_cast(k) + tmp; } /* Shift the frequency bins according to the pitch adjustment, * accumulating the magnitudes of overlapping frequency bins. */ std::fill(mSynthesisBuffer.begin(), mSynthesisBuffer.end(), FrequencyBin{}); static constexpr size_t bin_limit{((StftHalfSize+1)<> MixerFracBits}; /* If more than two bins end up together, use the target frequency * bin for the one with the dominant magnitude. There might be a * better way to handle this, but it's better than last-index-wins. */ if(mAnalysisBuffer[k].Magnitude > mSynthesisBuffer[j].Magnitude) mSynthesisBuffer[j].FreqBin = mAnalysisBuffer[k].FreqBin * mPitchShift; mSynthesisBuffer[j].Magnitude += mAnalysisBuffer[k].Magnitude; } /* Reconstruct the frequency-domain signal from the adjusted frequency * bins. */ for(size_t k{0u};k < StftHalfSize+1;k++) { /* Calculate the actual delta phase for this bin's target frequency * bin, and accumulate it to get the actual bin phase. */ float tmp{mSumPhase[k] + mSynthesisBuffer[k].FreqBin*expected_cycles}; /* Wrap between -pi and +pi for the sum. If mSumPhase is left to * grow indefinitely, it will lose precision and produce less exact * phase over time. */ tmp *= al::numbers::inv_pi_v; int qpd{float2int(tmp)}; tmp -= static_cast(qpd + (qpd%2)); mSumPhase[k] = tmp * al::numbers::pi_v; const complex_f cplx{std::polar(mSynthesisBuffer[k].Magnitude, mSumPhase[k])}; if(k == 0) mFftBuffer[0] = cplx.real(); else if(k == StftHalfSize) mFftBuffer[1] = cplx.real(); else { mFftBuffer[k*2 + 0] = cplx.real(); mFftBuffer[k*2 + 1] = cplx.imag(); } } /* Apply an inverse FFT to get the time-domain signal, and accumulate * for the output with windowing. */ mFft.transform_ordered(mFftBuffer.data(), mFftBuffer.data(), mFftWorkBuffer.data(), PFFFT_BACKWARD); static constexpr float scale{3.0f / OversampleFactor / StftSize}; for(size_t dst{mPos}, k{0u};dst < StftSize;++dst,++k) mOutputAccum[dst] += gWindow.mData[k]*mFftBuffer[k] * scale; for(size_t dst{0u}, k{StftSize-mPos};dst < mPos;++dst,++k) mOutputAccum[dst] += gWindow.mData[k]*mFftBuffer[k] * scale; /* Copy out the accumulated result, then clear for the next iteration. */ std::copy_n(mOutputAccum.begin() + mPos, StftStep, mFIFO.begin() + mPos); std::fill_n(mOutputAccum.begin() + mPos, StftStep, 0.0f); } /* Now, mix the processed sound data to the output. */ MixSamples(al::span{mBufferOut}.first(samplesToDo), samplesOut, mCurrentGains, mTargetGains, std::max(samplesToDo, 512_uz), 0); } struct PshifterStateFactory final : public EffectStateFactory { al::intrusive_ptr create() override { return al::intrusive_ptr{new PshifterState{}}; } }; } // namespace EffectStateFactory *PshifterStateFactory_getFactory() { static PshifterStateFactory PshifterFactory{}; return &PshifterFactory; } openal-soft-1.24.2/alc/effects/reverb.cpp000066400000000000000000002125561474041540300202130ustar00rootroot00000000000000/** * Ambisonic reverb engine for the OpenAL cross platform audio library * Copyright (C) 2008-2017 by Chris Robinson and Christopher Fitzgerald. * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * Or go to http://www.gnu.org/copyleft/lgpl.html */ #include "config.h" #include #include #include #include #include #include #include #include #include "alc/effects/base.h" #include "alnumbers.h" #include "alnumeric.h" #include "alspan.h" #include "core/ambidefs.h" #include "core/bufferline.h" #include "core/context.h" #include "core/cubic_tables.h" #include "core/device.h" #include "core/effects/base.h" #include "core/effectslot.h" #include "core/filters/biquad.h" #include "core/filters/splitter.h" #include "core/mixer.h" #include "core/mixer/defs.h" #include "intrusive_ptr.h" #include "opthelpers.h" #include "vector.h" struct BufferStorage; namespace { using uint = unsigned int; constexpr float MaxModulationTime{4.0f}; constexpr float DefaultModulationTime{0.25f}; #define MOD_FRACBITS 24 #define MOD_FRACONE (1<,NUM_LINES> B2A{{ /* W Y Z X */ {{ 0.5f, 0.5f, 0.5f, 0.5f }}, /* A0 */ {{ 0.5f, -0.5f, -0.5f, 0.5f }}, /* A1 */ {{ 0.5f, 0.5f, -0.5f, -0.5f }}, /* A2 */ {{ 0.5f, -0.5f, 0.5f, -0.5f }} /* A3 */ }}; /* Converts (W-normalized) A-Format to B-Format for early reflections (scaled * by 1/sqrt(3) to compensate for the boost in the B2A matrix). */ alignas(16) constexpr std::array,NUM_LINES> EarlyA2B{{ /* A0 A1 A2 A3 */ {{ 0.5f, 0.5f, 0.5f, 0.5f }}, /* W */ {{ 0.5f, -0.5f, 0.5f, -0.5f }}, /* Y */ {{ 0.5f, -0.5f, -0.5f, 0.5f }}, /* Z */ {{ 0.5f, 0.5f, -0.5f, -0.5f }} /* X */ }}; /* Converts (W-normalized) A-Format to B-Format for late reverb (scaled * by 1/sqrt(3) to compensate for the boost in the B2A matrix). The response * is rotated around Z (ambisonic X) so that the front lines are placed * horizontally in front, and the rear lines are placed vertically in back. */ constexpr auto InvSqrt2 = static_cast(1.0/al::numbers::sqrt2); alignas(16) constexpr std::array,NUM_LINES> LateA2B{{ /* A0 A1 A2 A3 */ {{ 0.5f, 0.5f, 0.5f, 0.5f }}, /* W */ {{ InvSqrt2, -InvSqrt2, 0.0f, 0.0f }}, /* Y */ {{ 0.0f, 0.0f, -InvSqrt2, InvSqrt2 }}, /* Z */ {{ 0.5f, 0.5f, -0.5f, -0.5f }} /* X */ }}; /* The all-pass and delay lines have a variable length dependent on the * effect's density parameter, which helps alter the perceived environment * size. The size-to-density conversion is a cubed scale: * * density = min(1.0, pow(size, 3.0) / DENSITY_SCALE); * * The line lengths scale linearly with room size, so the inverse density * conversion is needed, taking the cube root of the re-scaled density to * calculate the line length multiplier: * * length_mult = max(5.0, cbrt(density*DENSITY_SCALE)); * * The density scale below will result in a max line multiplier of 50, for an * effective size range of 5m to 50m. */ constexpr float DENSITY_SCALE{125000.0f}; /* All delay line lengths are specified in seconds. * * To approximate early reflections, we break them up into primary (those * arriving from the same direction as the source) and secondary (those * arriving from the opposite direction). * * The early taps decorrelate the 4-channel signal to approximate an average * room response for the primary reflections after the initial early delay. * * Given an average room dimension (d_a) and the speed of sound (c) we can * calculate the average reflection delay (r_a) regardless of listener and * source positions as: * * r_a = d_a / c * c = 343.3 * * This can extended to finding the average difference (r_d) between the * maximum (r_1) and minimum (r_0) reflection delays: * * r_0 = 2 / 3 r_a * = r_a - r_d / 2 * = r_d * r_1 = 4 / 3 r_a * = r_a + r_d / 2 * = 2 r_d * r_d = 2 / 3 r_a * = r_1 - r_0 * * As can be determined by integrating the 1D model with a source (s) and * listener (l) positioned across the dimension of length (d_a): * * r_d = int_(l=0)^d_a (int_(s=0)^d_a |2 d_a - 2 (l + s)| ds) dl / c * * The initial taps (T_(i=0)^N) are then specified by taking a power series * that ranges between r_0 and half of r_1 less r_0: * * R_i = 2^(i / (2 N - 1)) r_d * = r_0 + (2^(i / (2 N - 1)) - 1) r_d * = r_0 + T_i * T_i = R_i - r_0 * = (2^(i / (2 N - 1)) - 1) r_d * * Assuming an average of 1m, we get the following taps: */ constexpr std::array EARLY_TAP_LENGTHS{{ 0.0000000e+0f, 2.0213520e-4f, 4.2531060e-4f, 6.7171600e-4f }}; /* The early all-pass filter lengths are based on the early tap lengths: * * A_i = R_i / a * * Where a is the approximate maximum all-pass cycle limit (20). */ constexpr std::array EARLY_ALLPASS_LENGTHS{{ 9.7096800e-5f, 1.0720356e-4f, 1.1836234e-4f, 1.3068260e-4f }}; /* The early delay lines are used to transform the primary reflections into * the secondary reflections. The A-format is arranged in such a way that * the channels/lines are spatially opposite: * * C_i is opposite C_(N-i-1) * * The delays of the two opposing reflections (R_i and O_i) from a source * anywhere along a particular dimension always sum to twice its full delay: * * 2 r_a = R_i + O_i * * With that in mind we can determine the delay between the two reflections * and thus specify our early line lengths (L_(i=0)^N) using: * * O_i = 2 r_a - R_(N-i-1) * L_i = O_i - R_(N-i-1) * = 2 (r_a - R_(N-i-1)) * = 2 (r_a - T_(N-i-1) - r_0) * = 2 r_a (1 - (2 / 3) 2^((N - i - 1) / (2 N - 1))) * * Using an average dimension of 1m, we get: */ constexpr std::array EARLY_LINE_LENGTHS{{ 0.0000000e+0f, 4.9281100e-4f, 9.3916180e-4f, 1.3434322e-3f }}; /* The late all-pass filter lengths are based on the late line lengths: * * A_i = (5 / 3) L_i / r_1 */ constexpr std::array LATE_ALLPASS_LENGTHS{{ 1.6182800e-4f, 2.0389060e-4f, 2.8159360e-4f, 3.2365600e-4f }}; /* The late lines are used to approximate the decaying cycle of recursive * late reflections. * * Splitting the lines in half, we start with the shortest reflection paths * (L_(i=0)^(N/2)): * * L_i = 2^(i / (N - 1)) r_d * * Then for the opposite (longest) reflection paths (L_(i=N/2)^N): * * L_i = 2 r_a - L_(i-N/2) * = 2 r_a - 2^((i - N / 2) / (N - 1)) r_d * * For our 1m average room, we get: */ constexpr std::array LATE_LINE_LENGTHS{{ 1.9419362e-3f, 2.4466860e-3f, 3.3791220e-3f, 3.8838720e-3f }}; using ReverbUpdateLine = std::array; struct DelayLineI { /* The delay lines use interleaved samples, with the lengths being powers * of 2 to allow the use of bit-masking instead of a modulus for wrapping. */ al::span mLine; /* Given the allocated sample buffer, this function updates each delay line * offset. */ void realizeLineOffset(al::span sampleBuffer) noexcept { mLine = sampleBuffer; } /* Calculate the length of a delay line and store its mask and offset. */ static auto calcLineLength(const float length, const float frequency, const uint extra) -> size_t { /* All line lengths are powers of 2, calculated from their lengths in * seconds, rounded up. */ uint samples{float2uint(std::ceil(length*frequency))}; samples = NextPowerOf2(samples + extra); /* Return the sample count for accumulation. */ return samples*NUM_LINES; } }; struct DelayLineU { al::span mLine; void realizeLineOffset(al::span sampleBuffer) noexcept { assert(sampleBuffer.size() > 4 && !(sampleBuffer.size() & (sampleBuffer.size()-1))); mLine = sampleBuffer; } static auto calcLineLength(const float length, const float frequency, const uint extra) -> size_t { uint samples{float2uint(std::ceil(length*frequency))}; samples = NextPowerOf2(samples + extra); return samples*NUM_LINES; } [[nodiscard]] auto get(size_t chan) const noexcept { const size_t stride{mLine.size() / NUM_LINES}; return mLine.subspan(chan*stride, stride); } void write(size_t offset, const size_t c, al::span in) const noexcept { const size_t stride{mLine.size() / NUM_LINES}; const auto output = mLine.subspan(c*stride); while(!in.empty()) { offset &= stride-1; const size_t td{std::min(stride - offset, in.size())}; std::copy_n(in.begin(), td, output.begin() + ptrdiff_t(offset)); offset += td; in = in.subspan(td); } } /* Writes the given input lines to the delay buffer, applying a geometric * reflection. This effectively applies the matrix * * [ +1/2 -1/2 -1/2 -1/2 ] * [ -1/2 +1/2 -1/2 -1/2 ] * [ -1/2 -1/2 +1/2 -1/2 ] * [ -1/2 -1/2 -1/2 +1/2 ] * * to the four input lines when writing to the delay buffer. The effect on * the B-Format signal is negating W, applying a 180-degree phase shift and * moving each response to its spatially opposite location. */ void writeReflected(size_t offset, const al::span in, const size_t count) const noexcept { const size_t stride{mLine.size() / NUM_LINES}; for(size_t i{0u};i < count;) { offset &= stride-1; size_t td{std::min(stride - offset, count - i)}; do { const std::array src{in[0][i], in[1][i], in[2][i], in[3][i]}; ++i; const std::array f{ (src[0] - src[1] - src[2] - src[3]) * 0.5f, (src[1] - src[0] - src[2] - src[3]) * 0.5f, (src[2] - src[0] - src[1] - src[3]) * 0.5f, (src[3] - src[0] - src[1] - src[2] ) * 0.5f }; mLine[0*stride + offset] = f[0]; mLine[1*stride + offset] = f[1]; mLine[2*stride + offset] = f[2]; mLine[3*stride + offset] = f[3]; ++offset; } while(--td); } } }; struct VecAllpass { DelayLineI Delay; float Coeff{0.0f}; std::array Offset{}; void process(const al::span samples, size_t offset, const float xCoeff, const float yCoeff, const size_t todo) const noexcept; }; struct Allpass4 { DelayLineU Delay; float Coeff{0.0f}; std::array Offset{}; void process(const al::span samples, const size_t offset, const size_t todo) const noexcept; }; struct T60Filter { /* Two filters are used to adjust the signal. One to control the low * frequencies, and one to control the high frequencies. */ float MidGain{0.0f}; BiquadFilter HFFilter, LFFilter; void calcCoeffs(const float length, const float lfDecayTime, const float mfDecayTime, const float hfDecayTime, const float lf0norm, const float hf0norm); /* Applies the two T60 damping filter sections. */ void process(const al::span samples) { DualBiquad{HFFilter, LFFilter}.process(samples, samples); } void clear() noexcept { HFFilter.clear(); LFFilter.clear(); } }; struct EarlyReflections { Allpass4 VecAp; /* An echo line is used to complete the second half of the early * reflections. */ DelayLineU Delay; std::array Offset{}; float Coeff{}; /* The gain for each output channel based on 3D panning. */ struct OutGains { std::array Current{}; std::array Target{}; void clear() { Current.fill(0.0f); Target.fill(0.0); } }; std::array Gains{}; void updateLines(const float density_mult, const float diffusion, const float decayTime, const float frequency); void clear() { std::for_each(Gains.begin(), Gains.end(), std::mem_fn(&OutGains::clear)); } }; struct Modulation { /* The vibrato time is tracked with an index over a (MOD_FRACONE) * normalized range. */ uint Index{0u}, Step{1u}; /* The depth of frequency change, in samples. */ float Depth{0.0f}; std::array ModDelays{}; void updateModulator(float modTime, float modDepth, float frequency); auto calcDelays(size_t todo) -> al::span; void clear() noexcept { Index = 0u; Step = 1u; Depth = 0.0f; } }; struct LateReverb { /* A recursive delay line is used fill in the reverb tail. */ DelayLineU Delay; std::array Offset{}; /* Attenuation to compensate for the modal density and decay rate of the * late lines. */ float DensityGain{0.0f}; /* T60 decay filters are used to simulate absorption. */ std::array T60; Modulation Mod; /* A Gerzon vector all-pass filter is used to simulate diffusion. */ VecAllpass VecAp; /* The gain for each output channel based on 3D panning. */ struct OutGains { std::array Current{}; std::array Target{}; void clear() { Current.fill(0.0f); Target.fill(0.0); } }; std::array Gains{}; void updateLines(const float density_mult, const float diffusion, const float lfDecayTime, const float mfDecayTime, const float hfDecayTime, const float lf0norm, const float hf0norm, const float frequency); void clear() { std::for_each(T60.begin(), T60.end(), std::mem_fn(&T60Filter::clear)); Mod.clear(); std::for_each(Gains.begin(), Gains.end(), std::mem_fn(&OutGains::clear)); } }; struct ReverbPipeline { /* Master effect filters */ struct FilterPair { BiquadFilter Lp; BiquadFilter Hp; void clear() noexcept { Lp.clear(); Hp.clear(); } }; std::array mFilter; /* Late reverb input delay line (early reflections feed this, and late * reverb taps from it). */ DelayLineU mLateDelayIn; /* Tap points for early reflection input delay. */ std::array,NUM_LINES> mEarlyDelayTap{}; std::array mEarlyDelayCoeff{}; /* Tap points for late reverb feed and delay. */ std::array,NUM_LINES> mLateDelayTap{}; /* Coefficients for the all-pass and line scattering matrices. */ float mMixX{1.0f}; float mMixY{0.0f}; EarlyReflections mEarly; LateReverb mLate; std::array,2> mAmbiSplitter; size_t mFadeSampleCount{1}; void updateDelayLine(const float gain, const float earlyDelay, const float lateDelay, const float density_mult, const float frequency); void update3DPanning(const al::span ReflectionsPan, const al::span LateReverbPan, const float earlyGain, const float lateGain, const bool doUpmix, const MixParams *mainMix); void processEarly(const DelayLineU &main_delay, size_t offset, const size_t samplesToDo, const al::span tempSamples, const al::span outSamples); void processLate(size_t offset, const size_t samplesToDo, const al::span tempSamples, const al::span outSamples); void clear() noexcept { std::for_each(mFilter.begin(), mFilter.end(), std::mem_fn(&FilterPair::clear)); mEarlyDelayTap = {}; mEarlyDelayCoeff = {}; mLateDelayTap = {}; mEarly.clear(); mLate.clear(); auto clear_filters = [](const al::span filters) { std::for_each(filters.begin(), filters.end(), std::mem_fn(&BandSplitter::clear)); }; std::for_each(mAmbiSplitter.begin(), mAmbiSplitter.end(), clear_filters); } }; struct ReverbState final : public EffectState { /* All delay lines are allocated as a single buffer to reduce memory * fragmentation and management code. */ al::vector mSampleBuffer; struct Params { /* Calculated parameters which indicate if cross-fading is needed after * an update. */ float Density{1.0f}; float Diffusion{1.0f}; float DecayTime{1.49f}; float HFDecayTime{0.83f * 1.49f}; float LFDecayTime{1.0f * 1.49f}; float ModulationTime{0.25f}; float ModulationDepth{0.0f}; float HFReference{5000.0f}; float LFReference{250.0f}; }; Params mParams; enum PipelineState : uint8_t { DeviceClear, StartFade, Fading, Cleanup, Normal, }; PipelineState mPipelineState{DeviceClear}; bool mCurrentPipeline{false}; /* Core delay line (early reflections tap from this). */ DelayLineU mMainDelay; std::array mPipelines; /* The current write offset for all delay lines. */ size_t mOffset{}; /* Temporary storage used when processing. */ alignas(16) FloatBufferLine mTempLine{}; alignas(16) std::array mTempSamples{}; alignas(16) std::array mEarlySamples{}; alignas(16) std::array mLateSamples{}; std::array mOrderScales{}; bool mUpmixOutput{false}; void MixOutPlain(ReverbPipeline &pipeline, const al::span samplesOut, const size_t todo) const { /* When not upsampling, the panning gains convert to B-Format and pan * at the same time. */ auto inBuffer = mEarlySamples.cbegin(); for(auto &gains : pipeline.mEarly.Gains) { MixSamples(al::span{*inBuffer++}.first(todo), samplesOut, gains.Current, gains.Target, todo, 0); } inBuffer = mLateSamples.cbegin(); for(auto &gains : pipeline.mLate.Gains) { MixSamples(al::span{*inBuffer++}.first(todo), samplesOut, gains.Current, gains.Target, todo, 0); } } void MixOutAmbiUp(ReverbPipeline &pipeline, const al::span samplesOut, const size_t todo) { auto DoMixRow = [](const al::span OutBuffer, const al::span Gains, const al::span InSamples) { auto inBuffer = InSamples.cbegin(); std::fill(OutBuffer.begin(), OutBuffer.end(), 0.0f); for(const float gain : Gains) { if(std::fabs(gain) > GainSilenceThreshold) { auto mix_sample = [gain](const float sample, const float in) noexcept -> float { return sample + in*gain; }; std::transform(OutBuffer.begin(), OutBuffer.end(), inBuffer->cbegin(), OutBuffer.begin(), mix_sample); } ++inBuffer; } }; /* When upsampling, the B-Format conversion needs to be done separately * so the proper HF scaling can be applied to each B-Format channel. * The panning gains then pan and upsample the B-Format channels. */ const auto tmpspan = al::span{mTempLine}.first(todo); auto hfscale = float{mOrderScales[0]}; auto splitter = pipeline.mAmbiSplitter[0].begin(); auto a2bcoeffs = EarlyA2B.cbegin(); for(auto &gains : pipeline.mEarly.Gains) { DoMixRow(tmpspan, *(a2bcoeffs++), mEarlySamples); /* Apply scaling to the B-Format's HF response to "upsample" it to * higher-order output. */ (splitter++)->processHfScale(tmpspan, hfscale); hfscale = mOrderScales[1]; MixSamples(tmpspan, samplesOut, gains.Current, gains.Target, todo, 0); } hfscale = mOrderScales[0]; splitter = pipeline.mAmbiSplitter[1].begin(); a2bcoeffs = LateA2B.cbegin(); for(auto &gains : pipeline.mLate.Gains) { DoMixRow(tmpspan, *(a2bcoeffs++), mLateSamples); (splitter++)->processHfScale(tmpspan, hfscale); hfscale = mOrderScales[1]; MixSamples(tmpspan, samplesOut, gains.Current, gains.Target, todo, 0); } } void mixOut(ReverbPipeline &pipeline, const al::span samplesOut, const size_t todo) { if(mUpmixOutput) MixOutAmbiUp(pipeline, samplesOut, todo); else MixOutPlain(pipeline, samplesOut, todo); } void allocLines(const float frequency); void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) override; void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props, const EffectTarget target) override; void process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) override; }; /************************************** * Device Update * **************************************/ inline float CalcDelayLengthMult(float density) { return std::max(5.0f, std::cbrt(density*DENSITY_SCALE)); } /* Calculates the delay line metrics and allocates the shared sample buffer * for all lines given the sample rate (frequency). */ void ReverbState::allocLines(const float frequency) { /* Multiplier for the maximum density value, i.e. density=1, which is * actually the least density... */ const float multiplier{CalcDelayLengthMult(1.0f)}; /* The modulator's line length is calculated from the maximum modulation * time and depth coefficient, and halfed for the low-to-high frequency * swing. */ static constexpr float max_mod_delay{MaxModulationTime*MODULATION_DEPTH_COEFF / 2.0f}; std::array linelengths{}; size_t oidx{0}; size_t totalSamples{0u}; /* The main delay length includes the maximum early reflection delay and * the largest early tap width. It must also be extended by the update size * (BufferLineSize) for block processing. */ float length{ReverbMaxReflectionsDelay + EARLY_TAP_LENGTHS.back()*multiplier}; size_t count{mMainDelay.calcLineLength(length, frequency, BufferLineSize)}; linelengths[oidx++] = count; totalSamples += count; for(auto &pipeline : mPipelines) { static constexpr float LateDiffAvg{(LATE_LINE_LENGTHS.back()-LATE_LINE_LENGTHS.front()) / float{NUM_LINES}}; length = ReverbMaxLateReverbDelay + LateDiffAvg*multiplier; count = pipeline.mLateDelayIn.calcLineLength(length, frequency, BufferLineSize); linelengths[oidx++] = count; totalSamples += count; /* The early vector all-pass line. */ length = EARLY_ALLPASS_LENGTHS.back() * multiplier; count = pipeline.mEarly.VecAp.Delay.calcLineLength(length, frequency, 0); linelengths[oidx++] = count; totalSamples += count; /* The early reflection line. */ length = EARLY_LINE_LENGTHS.back() * multiplier; count = pipeline.mEarly.Delay.calcLineLength(length, frequency, MAX_UPDATE_SAMPLES); linelengths[oidx++] = count; totalSamples += count; /* The late vector all-pass line. */ length = LATE_ALLPASS_LENGTHS.back() * multiplier; count = pipeline.mLate.VecAp.Delay.calcLineLength(length, frequency, 0); linelengths[oidx++] = count; totalSamples += count; /* The late delay lines are calculated from the largest maximum density * line length, and the maximum modulation delay. Four additional * samples are needed for resampling the modulator delay. */ length = LATE_LINE_LENGTHS.back()*multiplier + max_mod_delay; count = pipeline.mLate.Delay.calcLineLength(length, frequency, 4); linelengths[oidx++] = count; totalSamples += count; } assert(oidx == linelengths.size()); if(totalSamples != mSampleBuffer.size()) decltype(mSampleBuffer)(totalSamples).swap(mSampleBuffer); /* Clear the sample buffer. */ std::fill(mSampleBuffer.begin(), mSampleBuffer.end(), 0.0f); /* Update all delays to reflect the new sample buffer. */ auto bufferspan = al::span{mSampleBuffer}; oidx = 0; mMainDelay.realizeLineOffset(bufferspan.first(linelengths[oidx])); bufferspan = bufferspan.subspan(linelengths[oidx++]); for(auto &pipeline : mPipelines) { pipeline.mLateDelayIn.realizeLineOffset(bufferspan.first(linelengths[oidx])); bufferspan = bufferspan.subspan(linelengths[oidx++]); pipeline.mEarly.VecAp.Delay.realizeLineOffset(bufferspan.first(linelengths[oidx])); bufferspan = bufferspan.subspan(linelengths[oidx++]); pipeline.mEarly.Delay.realizeLineOffset(bufferspan.first(linelengths[oidx])); bufferspan = bufferspan.subspan(linelengths[oidx++]); pipeline.mLate.VecAp.Delay.realizeLineOffset(bufferspan.first(linelengths[oidx])); bufferspan = bufferspan.subspan(linelengths[oidx++]); pipeline.mLate.Delay.realizeLineOffset(bufferspan.first(linelengths[oidx])); bufferspan = bufferspan.subspan(linelengths[oidx++]); } assert(oidx == linelengths.size()); } void ReverbState::deviceUpdate(const DeviceBase *device, const BufferStorage*) { const auto frequency = static_cast(device->mSampleRate); /* Allocate the delay lines. */ allocLines(frequency); std::for_each(mPipelines.begin(), mPipelines.end(), std::mem_fn(&ReverbPipeline::clear)); mPipelineState = DeviceClear; /* Reset offset base. */ mOffset = 0; if(device->mAmbiOrder > 1) { mUpmixOutput = true; mOrderScales = AmbiScale::GetHFOrderScales(1, device->mAmbiOrder, device->m2DMixing); } else { mUpmixOutput = false; mOrderScales.fill(1.0f); } auto splitter = BandSplitter{device->mXOverFreq / frequency}; auto set_splitters = [&splitter](ReverbPipeline &pipeline) { std::fill(pipeline.mAmbiSplitter[0].begin(), pipeline.mAmbiSplitter[0].end(), splitter); std::fill(pipeline.mAmbiSplitter[1].begin(), pipeline.mAmbiSplitter[1].end(), splitter); }; std::for_each(mPipelines.begin(), mPipelines.end(), set_splitters); } /************************************** * Effect Update * **************************************/ /* Calculate a decay coefficient given the length of each cycle and the time * until the decay reaches -60 dB. */ inline float CalcDecayCoeff(const float length, const float decayTime) { return std::pow(ReverbDecayGain, length/decayTime); } /* Calculate a decay length from a coefficient and the time until the decay * reaches -60 dB. */ inline float CalcDecayLength(const float coeff, const float decayTime) { constexpr float log10_decaygain{-3.0f/*std::log10(ReverbDecayGain)*/}; return std::log10(coeff) * decayTime / log10_decaygain; } /* Calculate an attenuation to be applied to the input of any echo models to * compensate for modal density and decay time. */ inline float CalcDensityGain(const float a) { /* The energy of a signal can be obtained by finding the area under the * squared signal. This takes the form of Sum(x_n^2), where x is the * amplitude for the sample n. * * Decaying feedback matches exponential decay of the form Sum(a^n), * where a is the attenuation coefficient, and n is the sample. The area * under this decay curve can be calculated as: 1 / (1 - a). * * Modifying the above equation to find the area under the squared curve * (for energy) yields: 1 / (1 - a^2). Input attenuation can then be * calculated by inverting the square root of this approximation, * yielding: 1 / sqrt(1 / (1 - a^2)), simplified to: sqrt(1 - a^2). */ return std::sqrt(1.0f - a*a); } /* Calculate the scattering matrix coefficients given a diffusion factor. */ inline void CalcMatrixCoeffs(const float diffusion, float *x, float *y) { /* The matrix is of order 4, so n is sqrt(4 - 1). */ constexpr float n{al::numbers::sqrt3_v}; const float t{diffusion * std::atan(n)}; /* Calculate the first mixing matrix coefficient. */ *x = std::cos(t); /* Calculate the second mixing matrix coefficient. */ *y = std::sin(t) / n; } /* Calculate the limited HF ratio for use with the late reverb low-pass * filters. */ float CalcLimitedHfRatio(const float hfRatio, const float airAbsorptionGainHF, const float decayTime) { /* Find the attenuation due to air absorption in dB (converting delay * time to meters using the speed of sound). Then reversing the decay * equation, solve for HF ratio. The delay length is cancelled out of * the equation, so it can be calculated once for all lines. */ float limitRatio{1.0f / SpeedOfSoundMetersPerSec / CalcDecayLength(airAbsorptionGainHF, decayTime)}; /* Using the limit calculated above, apply the upper bound to the HF ratio. */ return std::min(limitRatio, hfRatio); } /* Calculates the 3-band T60 damping coefficients for a particular delay line * of specified length, using a combination of two shelf filter sections given * decay times for each band split at two reference frequencies. */ void T60Filter::calcCoeffs(const float length, const float lfDecayTime, const float mfDecayTime, const float hfDecayTime, const float lf0norm, const float hf0norm) { const float mfGain{CalcDecayCoeff(length, mfDecayTime)}; const float lfGain{CalcDecayCoeff(length, lfDecayTime) / mfGain}; const float hfGain{CalcDecayCoeff(length, hfDecayTime) / mfGain}; MidGain = mfGain; LFFilter.setParamsFromSlope(BiquadType::LowShelf, lf0norm, lfGain, 1.0f); HFFilter.setParamsFromSlope(BiquadType::HighShelf, hf0norm, hfGain, 1.0f); } /* Update the early reflection line lengths and gain coefficients. */ void EarlyReflections::updateLines(const float density_mult, const float diffusion, const float decayTime, const float frequency) { /* Calculate the all-pass feed-back/forward coefficient. */ VecAp.Coeff = diffusion*diffusion * InvSqrt2; for(size_t i{0u};i < NUM_LINES;i++) { /* Calculate the delay length of each all-pass line. */ float length{EARLY_ALLPASS_LENGTHS[i] * density_mult}; VecAp.Offset[i] = float2uint(length * frequency); /* Calculate the delay length of each delay line. */ length = EARLY_LINE_LENGTHS[i] * density_mult; Offset[i] = float2uint(length * frequency); } /* Calculate the gain (coefficient) for the secondary reflections based on * the average delay and decay time. */ const auto length = std::reduce(EARLY_LINE_LENGTHS.begin(), EARLY_LINE_LENGTHS.end(), 0.0f) / float{EARLY_LINE_LENGTHS.size()} * density_mult; Coeff = CalcDecayCoeff(length, decayTime); } /* Update the EAX modulation step and depth. Keep in mind that this kind of * vibrato is additive and not multiplicative as one may expect. The downswing * will sound stronger than the upswing. */ void Modulation::updateModulator(float modTime, float modDepth, float frequency) { /* Modulation is calculated in two parts. * * The modulation time effects the sinus rate, altering the speed of * frequency changes. An index is incremented for each sample with an * appropriate step size to generate an LFO, which will vary the feedback * delay over time. */ Step = std::max(fastf2u(MOD_FRACONE / (frequency * modTime)), 1u); /* The modulation depth effects the amount of frequency change over the * range of the sinus. It needs to be scaled by the modulation time so that * a given depth produces a consistent change in frequency over all ranges * of time. Since the depth is applied to a sinus value, it needs to be * halved once for the sinus range and again for the sinus swing in time * (half of it is spent decreasing the frequency, half is spent increasing * it). */ if(modTime >= DefaultModulationTime) { /* To cancel the effects of a long period modulation on the late * reverberation, the amount of pitch should be varied (decreased) * according to the modulation time. The natural form is varying * inversely, in fact resulting in an invariant. */ Depth = MODULATION_DEPTH_COEFF / 4.0f * DefaultModulationTime * modDepth * frequency; } else Depth = MODULATION_DEPTH_COEFF / 4.0f * modTime * modDepth * frequency; } /* Update the late reverb line lengths and T60 coefficients. */ void LateReverb::updateLines(const float density_mult, const float diffusion, const float lfDecayTime, const float mfDecayTime, const float hfDecayTime, const float lf0norm, const float hf0norm, const float frequency) { /* Scaling factor to convert the normalized reference frequencies from * representing 0...freq to 0...max_reference. */ constexpr float MaxHFReference{20000.0f}; const float norm_weight_factor{frequency / MaxHFReference}; const float late_allpass_avg{ std::accumulate(LATE_ALLPASS_LENGTHS.begin(), LATE_ALLPASS_LENGTHS.end(), 0.0f) / float{NUM_LINES}}; /* To compensate for changes in modal density and decay time of the late * reverb signal, the input is attenuated based on the maximal energy of * the outgoing signal. This approximation is used to keep the apparent * energy of the signal equal for all ranges of density and decay time. * * The average length of the delay lines is used to calculate the * attenuation coefficient. */ float length{std::accumulate(LATE_LINE_LENGTHS.begin(), LATE_LINE_LENGTHS.end(), 0.0f) / float{NUM_LINES} + late_allpass_avg}; length *= density_mult; /* The density gain calculation uses an average decay time weighted by * approximate bandwidth. This attempts to compensate for losses of energy * that reduce decay time due to scattering into highly attenuated bands. */ const float decayTimeWeighted{ lf0norm*norm_weight_factor*lfDecayTime + (hf0norm - lf0norm)*norm_weight_factor*mfDecayTime + (1.0f - hf0norm*norm_weight_factor)*hfDecayTime}; DensityGain = CalcDensityGain(CalcDecayCoeff(length, decayTimeWeighted)); /* Calculate the all-pass feed-back/forward coefficient. */ VecAp.Coeff = diffusion*diffusion * InvSqrt2; for(size_t i{0u};i < NUM_LINES;i++) { /* Calculate the delay length of each all-pass line. */ length = LATE_ALLPASS_LENGTHS[i] * density_mult; VecAp.Offset[i] = float2uint(length * frequency); /* Calculate the delay length of each feedback delay line. A cubic * resampler is used for modulation on the feedback delay, which * includes one sample of delay. Reduce by one to compensate. */ length = LATE_LINE_LENGTHS[i] * density_mult; Offset[i] = std::max(float2uint(length*frequency + 0.5f), 1u) - 1u; /* Approximate the absorption that the vector all-pass would exhibit * given the current diffusion so we don't have to process a full T60 * filter for each of its four lines. Also include the average * modulation delay (depth is half the max delay in samples). */ length += lerpf(LATE_ALLPASS_LENGTHS[i], late_allpass_avg, diffusion)*density_mult + Mod.Depth/frequency; /* Calculate the T60 damping coefficients for each line. */ T60[i].calcCoeffs(length, lfDecayTime, mfDecayTime, hfDecayTime, lf0norm, hf0norm); } } /* Update the offsets for the main effect delay line. */ void ReverbPipeline::updateDelayLine(const float gain, const float earlyDelay, const float lateDelay, const float density_mult, const float frequency) { /* Early reflection taps are decorrelated by means of an average room * reflection approximation described above the definition of the taps. * This approximation is linear and so the above density multiplier can * be applied to adjust the width of the taps. A single-band decay * coefficient is applied to simulate initial attenuation and absorption. * * Late reverb taps are based on the late line lengths to allow a zero- * delay path and offsets that would continue the propagation naturally * into the late lines. */ mEarlyDelayCoeff[1] = gain; for(size_t i{0u};i < NUM_LINES;i++) { float length{EARLY_TAP_LENGTHS[i]*density_mult}; mEarlyDelayTap[i][1] = float2uint((earlyDelay+length) * frequency); /* Reduce the late delay tap by the shortest early delay line length to * compensate for the late line input being fed by the delayed early * output. */ length = (LATE_LINE_LENGTHS[i] - LATE_LINE_LENGTHS.front())/float{NUM_LINES}*density_mult + lateDelay; mLateDelayTap[i][1] = float2uint(length * frequency); } } /* Creates a transform matrix given a reverb vector. The vector pans the reverb * reflections toward the given direction, using its magnitude (up to 1) as a * focal strength. This function results in a B-Format transformation matrix * that spatially focuses the signal in the desired direction. */ std::array,4> GetTransformFromVector(const al::span vec) { /* Normalize the panning vector according to the N3D scale, which has an * extra sqrt(3) term on the directional components. Converting from OpenAL * to B-Format also requires negating X (ACN 1) and Z (ACN 3). Note however * that the reverb panning vectors use left-handed coordinates, unlike the * rest of OpenAL which use right-handed. This is fixed by negating Z, * which cancels out with the B-Format Z negation. */ std::array norm{{vec[0], vec[1], vec[2]}}; float mag{std::sqrt(vec[0]*vec[0] + vec[1]*vec[1] + vec[2]*vec[2])}; if(mag > 1.0f) { const float scale{al::numbers::sqrt3_v / mag}; norm[0] *= -scale; norm[1] *= scale; norm[2] *= scale; mag = 1.0f; } else { /* If the magnitude is less than or equal to 1, just apply the sqrt(3) * term. There's no need to renormalize the magnitude since it would * just be reapplied in the matrix. */ norm[0] *= -al::numbers::sqrt3_v; norm[1] *= al::numbers::sqrt3_v; norm[2] *= al::numbers::sqrt3_v; } return std::array,4>{{ {{1.0f, 0.0f, 0.0f, 0.0f}}, {{norm[0], 1.0f-mag, 0.0f, 0.0f}}, {{norm[1], 0.0f, 1.0f-mag, 0.0f}}, {{norm[2], 0.0f, 0.0f, 1.0f-mag}} }}; } /* Update the early and late 3D panning gains. */ void ReverbPipeline::update3DPanning(const al::span ReflectionsPan, const al::span LateReverbPan, const float earlyGain, const float lateGain, const bool doUpmix, const MixParams *mainMix) { /* Create matrices that transform a B-Format signal according to the * panning vectors. */ const auto earlymat = GetTransformFromVector(ReflectionsPan); const auto latemat = GetTransformFromVector(LateReverbPan); const auto get_coeffs = [&] { if(doUpmix) { /* When upsampling, combine the early and late transforms with the * first-order upsample matrix. This results in panning gains that * apply the panning transform to first-order B-Format, which is * then upsampled. */ auto mult_matrix = [](const al::span,4> mtx1) { std::array,NUM_LINES> res{}; const auto mtx2 = al::span{AmbiScale::FirstOrderUp}; for(size_t i{0};i < mtx1[0].size();++i) { const al::span dst{res[i]}; static_assert(dst.size() >= std::tuple_size_v); for(size_t k{0};k < mtx1.size();++k) { const float a{mtx1[k][i]}; std::transform(mtx2[k].begin(), mtx2[k].end(), dst.begin(), dst.begin(), [a](const float in, const float out) noexcept -> float { return a*in + out; }); } } return res; }; return std::array{mult_matrix(earlymat), mult_matrix(latemat)}; } /* When not upsampling, combine the early and late A-to-B-Format * conversions with their respective transform. This results panning * gains that convert A-Format to B-Format, which is then panned. */ auto mult_matrix = [](const al::span,4> mtx1, const al::span,4> mtx2) { std::array,NUM_LINES> res{}; for(size_t i{0};i < mtx1[0].size();++i) { const al::span dst{res[i]}; static_assert(dst.size() >= std::tuple_size_v); for(size_t k{0};k < mtx1.size();++k) { const float a{mtx1[k][i]}; std::transform(mtx2[k].begin(), mtx2[k].end(), dst.begin(), dst.begin(), [a](const float in, const float out) noexcept -> float { return a*in + out; }); } } return res; }; return std::array{mult_matrix(EarlyA2B, earlymat), mult_matrix(LateA2B, latemat)}; }; const auto [earlycoeffs, latecoeffs] = get_coeffs(); auto earlygains = mEarly.Gains.begin(); for(auto &coeffs : earlycoeffs) ComputePanGains(mainMix, coeffs, earlyGain, (earlygains++)->Target); auto lategains = mLate.Gains.begin(); for(auto &coeffs : latecoeffs) ComputePanGains(mainMix, coeffs, lateGain, (lategains++)->Target); } void ReverbState::update(const ContextBase *Context, const EffectSlot *Slot, const EffectProps *props_, const EffectTarget target) { auto &props = std::get(*props_); const DeviceBase *Device{Context->mDevice}; const auto frequency = static_cast(Device->mSampleRate); /* If the HF limit parameter is flagged, calculate an appropriate limit * based on the air absorption parameter. */ float hfRatio{props.DecayHFRatio}; if(props.DecayHFLimit && props.AirAbsorptionGainHF < 1.0f) hfRatio = CalcLimitedHfRatio(hfRatio, props.AirAbsorptionGainHF, props.DecayTime); /* Calculate the LF/HF decay times. */ constexpr float MinDecayTime{0.1f}, MaxDecayTime{20.0f}; const float lfDecayTime{std::clamp(props.DecayTime*props.DecayLFRatio, MinDecayTime, MaxDecayTime)}; const float hfDecayTime{std::clamp(props.DecayTime*hfRatio, MinDecayTime, MaxDecayTime)}; /* Determine if a full update is required. */ const bool fullUpdate{mPipelineState == DeviceClear || /* Density is essentially a master control for the feedback delays, so * changes the offsets of many delay lines. */ mParams.Density != props.Density || /* Diffusion and decay times influences the decay rate (gain) of the * late reverb T60 filter. */ mParams.Diffusion != props.Diffusion || mParams.DecayTime != props.DecayTime || mParams.HFDecayTime != hfDecayTime || mParams.LFDecayTime != lfDecayTime || /* Modulation time and depth both require fading the modulation delay. */ mParams.ModulationTime != props.ModulationTime || mParams.ModulationDepth != props.ModulationDepth || /* HF/LF References control the weighting used to calculate the density * gain. */ mParams.HFReference != props.HFReference || mParams.LFReference != props.LFReference}; if(fullUpdate) { mParams.Density = props.Density; mParams.Diffusion = props.Diffusion; mParams.DecayTime = props.DecayTime; mParams.HFDecayTime = hfDecayTime; mParams.LFDecayTime = lfDecayTime; mParams.ModulationTime = props.ModulationTime; mParams.ModulationDepth = props.ModulationDepth; mParams.HFReference = props.HFReference; mParams.LFReference = props.LFReference; mPipelineState = (mPipelineState != DeviceClear) ? StartFade : Normal; mCurrentPipeline = !mCurrentPipeline; auto &oldpipeline = mPipelines[!mCurrentPipeline]; oldpipeline.mEarlyDelayCoeff[1] = 0.0f; } auto &pipeline = mPipelines[mCurrentPipeline]; /* The density-based room size (delay length) multiplier. */ const float density_mult{CalcDelayLengthMult(props.Density)}; /* Update the main effect delay and associated taps. */ pipeline.updateDelayLine(props.Gain, props.ReflectionsDelay, props.LateReverbDelay, density_mult, frequency); /* Update early and late 3D panning. */ mOutTarget = target.Main->Buffer; const float gain{Slot->Gain * ReverbBoost}; pipeline.update3DPanning(props.ReflectionsPan, props.LateReverbPan, props.ReflectionsGain*gain, props.LateReverbGain*gain, mUpmixOutput, target.Main); /* Calculate the master filters */ float hf0norm{std::min(props.HFReference/frequency, 0.49f)}; pipeline.mFilter[0].Lp.setParamsFromSlope(BiquadType::HighShelf, hf0norm, props.GainHF, 1.0f); float lf0norm{std::min(props.LFReference/frequency, 0.49f)}; pipeline.mFilter[0].Hp.setParamsFromSlope(BiquadType::LowShelf, lf0norm, props.GainLF, 1.0f); for(size_t i{1u};i < NUM_LINES;i++) { pipeline.mFilter[i].Lp.copyParamsFrom(pipeline.mFilter[0].Lp); pipeline.mFilter[i].Hp.copyParamsFrom(pipeline.mFilter[0].Hp); } if(fullUpdate) { /* Update the early lines. */ pipeline.mEarly.updateLines(density_mult, props.Diffusion, props.DecayTime, frequency); /* Get the mixing matrix coefficients. */ CalcMatrixCoeffs(props.Diffusion, &pipeline.mMixX, &pipeline.mMixY); /* Update the modulator rate and depth. */ pipeline.mLate.Mod.updateModulator(props.ModulationTime, props.ModulationDepth, frequency); /* Update the late lines. */ pipeline.mLate.updateLines(density_mult, props.Diffusion, lfDecayTime, props.DecayTime, hfDecayTime, lf0norm, hf0norm, frequency); } /* Calculate the gain at the start of the late reverb stage, and the gain * difference from the decay target (0.001, or -60dB). */ const float decayBase{props.ReflectionsGain * props.LateReverbGain}; const float decayDiff{ReverbDecayGain / decayBase}; /* Given the DecayTime (the amount of time for the late reverb to decay by * -60dB), calculate the time to decay to -60dB from the start of the late * reverb. * * Otherwise, if the late reverb already starts at -60dB or less, only * include the time to get to the late reverb. */ const float diffTime{!(decayDiff < 1.0f) ? 0.0f : (std::log10(decayDiff)*(20.0f / -60.0f) * props.DecayTime)}; const float decaySamples{(props.ReflectionsDelay+props.LateReverbDelay+diffTime) * frequency}; /* Limit to 100,000 samples (a touch over 2 seconds at 48khz) to avoid * excessive double-processing. */ pipeline.mFadeSampleCount = static_cast(std::min(decaySamples, 100'000.0f)); } /************************************** * Effect Processing * **************************************/ /* Applies a scattering matrix to the 4-line (vector) input. This is used * for both the below vector all-pass model and to perform modal feed-back * delay network (FDN) mixing. * * The matrix is derived from a skew-symmetric matrix to form a 4D rotation * matrix with a single unitary rotational parameter: * * [ d, a, b, c ] 1 = a^2 + b^2 + c^2 + d^2 * [ -a, d, c, -b ] * [ -b, -c, d, a ] * [ -c, b, -a, d ] * * The rotation is constructed from the effect's diffusion parameter, * yielding: * * 1 = x^2 + 3 y^2 * * Where a, b, and c are the coefficient y with differing signs, and d is the * coefficient x. The final matrix is thus: * * [ x, y, -y, y ] n = sqrt(matrix_order - 1) * [ -y, x, y, y ] t = diffusion_parameter * atan(n) * [ y, -y, x, y ] x = cos(t) * [ -y, -y, -y, x ] y = sin(t) / n * * Any square orthogonal matrix with an order that is a power of two will * work (where ^T is transpose, ^-1 is inverse): * * M^T = M^-1 * * Using that knowledge, finding an appropriate matrix can be accomplished * naively by searching all combinations of: * * M = D + S - S^T * * Where D is a diagonal matrix (of x), and S is a triangular matrix (of y) * whose combination of signs are being iterated. */ inline auto VectorPartialScatter(const std::array &in, const float xCoeff, const float yCoeff) noexcept -> std::array { return std::array{ xCoeff*in[0] + yCoeff*( in[1] + -in[2] + in[3]), xCoeff*in[1] + yCoeff*(-in[0] + in[2] + in[3]), xCoeff*in[2] + yCoeff*( in[0] + -in[1] + in[3]), xCoeff*in[3] + yCoeff*(-in[0] + -in[1] + -in[2] ) }; } /* Utilizes the above, but also applies a line-based reflection on the input * channels (swapping 0<->3 and 1<->2). */ void VectorScatterRev(const float xCoeff, const float yCoeff, const al::span samples, const size_t count) noexcept { ASSUME(count > 0); for(size_t i{0u};i < count;++i) { std::array src{samples[0][i], samples[1][i], samples[2][i], samples[3][i]}; src = VectorPartialScatter(std::array{src[3], src[2], src[1], src[0]}, xCoeff, yCoeff); samples[0][i] = src[0]; samples[1][i] = src[1]; samples[2][i] = src[2]; samples[3][i] = src[3]; } } /* This applies a Gerzon multiple-in/multiple-out (MIMO) vector all-pass * filter to the 4-line input. * * It works by vectorizing a regular all-pass filter and replacing the delay * element with a scattering matrix (like the one above) and a diagonal * matrix of delay elements. */ void VecAllpass::process(const al::span samples, size_t main_offset, const float xCoeff, const float yCoeff, const size_t todo) const noexcept { const auto linelen = size_t{Delay.mLine.size()/NUM_LINES}; const float feedCoeff{Coeff}; ASSUME(todo > 0); for(size_t i{0u};i < todo;) { std::array vap_offset{}; std::transform(Offset.cbegin(), Offset.cend(), vap_offset.begin(), [main_offset,mask=linelen-1](const size_t delay) noexcept -> size_t { return (main_offset-delay) & mask; }); main_offset &= linelen-1; const auto maxoff = std::accumulate(vap_offset.cbegin(), vap_offset.cend(), main_offset, [](const size_t offset, const size_t apoffset) { return std::max(offset, apoffset); }); size_t td{std::min(linelen - maxoff, todo - i)}; auto delayIn = Delay.mLine.begin(); auto delayOut = Delay.mLine.begin() + ptrdiff_t(main_offset*NUM_LINES); main_offset += td; do { std::array f{}; for(size_t j{0u};j < NUM_LINES;j++) { const float input{samples[j][i]}; const float out{delayIn[vap_offset[j]*NUM_LINES + j] - feedCoeff*input}; f[j] = input + feedCoeff*out; samples[j][i] = out; } delayIn += NUM_LINES; ++i; f = VectorPartialScatter(f, xCoeff, yCoeff); delayOut = std::copy_n(f.cbegin(), f.size(), delayOut); } while(--td); } } /* This applies a more typical all-pass to each line, without the scattering * matrix. */ void Allpass4::process(const al::span samples, const size_t offset, const size_t todo) const noexcept { const DelayLineU delay{Delay}; const float feedCoeff{Coeff}; ASSUME(todo > 0); for(size_t j{0u};j < NUM_LINES;j++) { auto smpiter = samples[j].begin(); const auto buffer = delay.get(j); size_t dstoffset{offset}; size_t vap_offset{offset - Offset[j]}; for(size_t i{0u};i < todo;) { vap_offset &= buffer.size()-1; dstoffset &= buffer.size()-1; const size_t maxoff{std::max(dstoffset, vap_offset)}; const size_t td{std::min(buffer.size() - maxoff, todo - i)}; auto proc_sample = [buffer,feedCoeff,&vap_offset,&dstoffset](const float x) -> float { const float y{buffer[vap_offset++] - feedCoeff*x}; buffer[dstoffset++] = x + feedCoeff*y; return y; }; smpiter = std::transform(smpiter, smpiter+td, smpiter, proc_sample); i += td; } } } /* This generates early reflections. * * This is done by obtaining the primary reflections (those arriving from the * same direction as the source) from the main delay line. These are * attenuated and all-pass filtered (based on the diffusion parameter). * * The early lines are then reflected about the origin to create the secondary * reflections (those arriving from the opposite direction as the source). * * The early response is then completed by combining the primary reflections * with the delayed and attenuated output from the early lines. * * Finally, the early response is reflected, scattered (based on diffusion), * and fed into the late reverb section of the main delay line. */ void ReverbPipeline::processEarly(const DelayLineU &main_delay, size_t offset, const size_t samplesToDo, const al::span tempSamples, const al::span outSamples) { const DelayLineU early_delay{mEarly.Delay}; const DelayLineU in_delay{main_delay}; const float mixX{mMixX}; const float mixY{mMixY}; ASSUME(samplesToDo <= BufferLineSize); for(size_t base{0};base < samplesToDo;) { const size_t todo{std::min(samplesToDo-base, MAX_UPDATE_SAMPLES)}; /* First, load decorrelated samples from the main delay line as the * primary reflections. */ const auto fadeStep = 1.0f / static_cast(todo); const auto earlycoeff0 = float{mEarlyDelayCoeff[0]}; const auto earlycoeff1 = float{mEarlyDelayCoeff[1]}; mEarlyDelayCoeff[0] = mEarlyDelayCoeff[1]; for(size_t j{0_uz};j < NUM_LINES;j++) { const auto input = in_delay.get(j); auto early_delay_tap0 = size_t{offset - mEarlyDelayTap[j][0]}; auto early_delay_tap1 = size_t{offset - mEarlyDelayTap[j][1]}; mEarlyDelayTap[j][0] = mEarlyDelayTap[j][1]; auto fadeCount = 0.0f; auto tmp = tempSamples[j].begin(); for(size_t i{0_uz};i < todo;) { early_delay_tap0 &= input.size()-1; early_delay_tap1 &= input.size()-1; const auto max_tap = size_t{std::max(early_delay_tap0, early_delay_tap1)}; const auto td = size_t{std::min(input.size()-max_tap, todo-i)}; const auto intap0 = input.subspan(early_delay_tap0, td); const auto intap1 = input.subspan(early_delay_tap1, td); auto do_blend = [earlycoeff0,earlycoeff1,fadeStep,&fadeCount](const float in0, const float in1) noexcept -> float { const auto ret = lerpf(in0*earlycoeff0, in1*earlycoeff1, fadeStep*fadeCount); fadeCount += 1.0f; return ret; }; tmp = std::transform(intap0.begin(), intap0.end(), intap1.begin(), tmp, do_blend); early_delay_tap0 += td; early_delay_tap1 += td; i += td; } /* Band-pass the incoming samples. */ auto&& filter = DualBiquad{mFilter[j].Lp, mFilter[j].Hp}; filter.process(al::span{tempSamples[j]}.first(todo), tempSamples[j]); } /* Apply an all-pass, to help color the initial reflections. */ mEarly.VecAp.process(tempSamples, offset, todo); /* Apply a delay and bounce to generate secondary reflections. */ early_delay.writeReflected(offset, tempSamples, todo); const auto feedb_coeff = mEarly.Coeff; for(size_t j{0_uz};j < NUM_LINES;j++) { const auto input = early_delay.get(j); auto feedb_tap = size_t{offset - mEarly.Offset[j]}; auto out = outSamples[j].begin() + base; auto tmp = tempSamples[j].begin(); for(size_t i{0_uz};i < todo;) { feedb_tap &= input.size()-1; const auto td = size_t{std::min(input.size() - feedb_tap, todo - i)}; const auto delaySrc = input.subspan(feedb_tap, td); /* Combine the main input with the attenuated delayed echo for * the early output. */ out = std::transform(delaySrc.begin(), delaySrc.end(), tmp, out, [feedb_coeff](const float delayspl, const float mainspl) noexcept -> float { return delayspl*feedb_coeff + mainspl; }); /* Move the (non-attenuated) delayed echo to the temp buffer * for feeding the late reverb. */ tmp = std::copy_n(delaySrc.begin(), delaySrc.size(), tmp); feedb_tap += td; i += td; } } /* Finally, apply a scatter and bounce to improve the initial diffusion * in the late reverb, writing the result to the late delay line input. */ VectorScatterRev(mixX, mixY, tempSamples, todo); for(size_t j{0_uz};j < NUM_LINES;j++) mLateDelayIn.write(offset, j, al::span{tempSamples[j]}.first(todo)); base += todo; offset += todo; } } auto Modulation::calcDelays(size_t todo) -> al::span { auto idx = Index; const auto step = Step; const auto depth = Depth * float{gCubicTable.sTableSteps}; const auto delays = al::span{ModDelays}.first(todo); std::generate(delays.begin(), delays.end(), [step,depth,&idx] { idx += step; const auto x = static_cast(idx&MOD_FRACMASK) * (1.0f/MOD_FRACONE); /* Approximate sin(x*2pi). As long as it roughly fits a sinusoid shape * and stays within [-1...+1], it needn't be perfect. */ const auto lfo = !(idx&(MOD_FRACONE>>1)) ? ((-16.0f * x * x) + (8.0f * x)) : ((16.0f * x * x) + (-8.0f * x) + (-16.0f * x) + 8.0f); return float2uint((lfo+1.0f) * depth); }); Index = idx; return delays; } /* This generates the reverb tail using a modified feed-back delay network * (FDN). * * Results from the early reflections are mixed with the output from the * modulated late delay lines. * * The late response is then completed by T60 and all-pass filtering the mix. * * Finally, the lines are reversed (so they feed their opposite directions) * and scattered with the FDN matrix before re-feeding the delay lines. */ void ReverbPipeline::processLate(size_t offset, const size_t samplesToDo, const al::span tempSamples, const al::span outSamples) { const DelayLineU late_delay{mLate.Delay}; const DelayLineU in_delay{mLateDelayIn}; const float mixX{mMixX}; const float mixY{mMixY}; ASSUME(samplesToDo <= BufferLineSize); for(size_t base{0};base < samplesToDo;) { const size_t todo{std::min(std::min(mLate.Offset[0], MAX_UPDATE_SAMPLES), samplesToDo-base)}; ASSUME(todo > 0); /* First, calculate the modulated delays for the late feedback. */ const auto delays = mLate.Mod.calcDelays(todo); /* Now load samples from the feedback delay lines. Filter the signal to * apply its frequency-dependent decay. */ for(size_t j{0_uz};j < NUM_LINES;++j) { const auto input = late_delay.get(j); const auto midGain = mLate.T60[j].MidGain; auto late_feedb_tap = size_t{offset - mLate.Offset[j]}; auto proc_sample = [input,midGain,&late_feedb_tap](const size_t idelay) -> float { /* Calculate the read sample offset and sub-sample offset * between it and the next sample. */ const auto delay = late_feedb_tap - (idelay>>gCubicTable.sTableBits); const auto delayoffset = size_t{idelay & gCubicTable.sTableMask}; ++late_feedb_tap; /* Get the samples around the delayed offset, interpolated for * output. */ const auto out0 = float{input[(delay ) & (input.size()-1)]}; const auto out1 = float{input[(delay-1) & (input.size()-1)]}; const auto out2 = float{input[(delay-2) & (input.size()-1)]}; const auto out3 = float{input[(delay-3) & (input.size()-1)]}; const auto out = out0*gCubicTable.getCoeff0(delayoffset) + out1*gCubicTable.getCoeff1(delayoffset) + out2*gCubicTable.getCoeff2(delayoffset) + out3*gCubicTable.getCoeff3(delayoffset); return out * midGain; }; std::transform(delays.begin(), delays.end(), tempSamples[j].begin(), proc_sample); mLate.T60[j].process(al::span{tempSamples[j]}.first(todo)); } /* Next load decorrelated samples from the main delay lines. */ const float fadeStep{1.0f / static_cast(todo)}; for(size_t j{0_uz};j < NUM_LINES;++j) { const auto input = in_delay.get(j); auto late_delay_tap0 = size_t{offset - mLateDelayTap[j][0]}; auto late_delay_tap1 = size_t{offset - mLateDelayTap[j][1]}; mLateDelayTap[j][0] = mLateDelayTap[j][1]; const auto densityGain = mLate.DensityGain; const auto densityStep = late_delay_tap0 != late_delay_tap1 ? densityGain*fadeStep : 0.0f; auto fadeCount = 0.0f; auto samples = tempSamples[j].begin(); for(size_t i{0u};i < todo;) { late_delay_tap0 &= input.size()-1; late_delay_tap1 &= input.size()-1; const auto td = size_t{std::min(todo - i, input.size() - std::max(late_delay_tap0, late_delay_tap1))}; auto proc_sample = [input,densityGain,densityStep,&late_delay_tap0, &late_delay_tap1,&fadeCount](const float sample) noexcept -> float { const auto fade0 = float{densityGain - densityStep*fadeCount}; const auto fade1 = float{densityStep*fadeCount}; fadeCount += 1.0f; return input[late_delay_tap0++]*fade0 + input[late_delay_tap1++]*fade1 + sample; }; samples = std::transform(samples, samples+ptrdiff_t(td), samples, proc_sample); i += td; } } /* Apply a vector all-pass to improve micro-surface diffusion, and * write out the results for mixing. */ mLate.VecAp.process(tempSamples, offset, mixX, mixY, todo); for(size_t j{0_uz};j < NUM_LINES;++j) std::copy_n(tempSamples[j].begin(), todo, outSamples[j].begin()+base); /* Finally, scatter and bounce the results to refeed the feedback buffer. */ VectorScatterRev(mixX, mixY, tempSamples, todo); for(size_t j{0_uz};j < NUM_LINES;++j) late_delay.write(offset, j, al::span{tempSamples[j]}.first(todo)); base += todo; offset += todo; } } void ReverbState::process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) { const size_t offset{mOffset}; ASSUME(samplesToDo <= BufferLineSize); auto &oldpipeline = mPipelines[!mCurrentPipeline]; auto &pipeline = mPipelines[mCurrentPipeline]; /* Convert B-Format to A-Format for processing. */ const size_t numInput{std::min(samplesIn.size(), NUM_LINES)}; const al::span tmpspan{al::assume_aligned<16>(mTempLine.data()), samplesToDo}; for(size_t c{0u};c < NUM_LINES;++c) { std::fill(tmpspan.begin(), tmpspan.end(), 0.0f); for(size_t i{0};i < numInput;++i) { const float gain{B2A[c][i]}; auto mix_sample = [gain](const float sample, const float in) noexcept -> float { return sample + in*gain; }; std::transform(tmpspan.begin(), tmpspan.end(), samplesIn[i].begin(), tmpspan.begin(), mix_sample); } mMainDelay.write(offset, c, tmpspan); } mPipelineState = std::max(Fading, mPipelineState); /* Process reverb for these samples. and mix them to the output. */ pipeline.processEarly(mMainDelay, offset, samplesToDo, mTempSamples, mEarlySamples); pipeline.processLate(offset, samplesToDo, mTempSamples, mLateSamples); mixOut(pipeline, samplesOut, samplesToDo); if(mPipelineState != Normal) { if(mPipelineState == Cleanup) { size_t numSamples{mSampleBuffer.size()/2}; const auto bufferspan = al::span{mSampleBuffer}.subspan(numSamples * !mCurrentPipeline, numSamples); std::fill_n(bufferspan.begin(), bufferspan.size(), 0.0f); oldpipeline.clear(); mPipelineState = Normal; } else { /* If this is the final mix for this old pipeline, set the target * gains to 0 to ensure a complete fade out, and set the state to * Cleanup so the next invocation cleans up the delay buffers and * filters. */ if(samplesToDo >= oldpipeline.mFadeSampleCount) { for(auto &gains : oldpipeline.mEarly.Gains) std::fill(gains.Target.begin(), gains.Target.end(), 0.0f); for(auto &gains : oldpipeline.mLate.Gains) std::fill(gains.Target.begin(), gains.Target.end(), 0.0f); oldpipeline.mFadeSampleCount = 0; mPipelineState = Cleanup; } else oldpipeline.mFadeSampleCount -= samplesToDo; /* Process the old reverb for these samples. */ oldpipeline.processEarly(mMainDelay, offset, samplesToDo, mTempSamples, mEarlySamples); oldpipeline.processLate(offset, samplesToDo, mTempSamples, mLateSamples); mixOut(oldpipeline, samplesOut, samplesToDo); } } mOffset = offset + samplesToDo; } struct ReverbStateFactory final : public EffectStateFactory { al::intrusive_ptr create() override { return al::intrusive_ptr{new ReverbState{}}; } }; } // namespace EffectStateFactory *ReverbStateFactory_getFactory() { static ReverbStateFactory ReverbFactory{}; return &ReverbFactory; } openal-soft-1.24.2/alc/effects/vmorpher.cpp000066400000000000000000000320541474041540300205610ustar00rootroot00000000000000/** * This file is part of the OpenAL Soft cross platform audio library * * Copyright (C) 2019 by Anis A. Hireche * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * * Neither the name of Spherical-Harmonic-Transform nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include #include #include #include #include #include #include "alc/effects/base.h" #include "alnumbers.h" #include "alnumeric.h" #include "alspan.h" #include "core/ambidefs.h" #include "core/bufferline.h" #include "core/context.h" #include "core/device.h" #include "core/effects/base.h" #include "core/effectslot.h" #include "core/mixer.h" #include "intrusive_ptr.h" struct BufferStorage; namespace { using uint = unsigned int; constexpr size_t MaxUpdateSamples{256}; constexpr size_t NumFormants{4}; constexpr float RcpQFactor{1.0f / 5.0f}; enum : size_t { VowelAIndex, VowelBIndex, NumFilters }; constexpr size_t WaveformFracBits{24}; constexpr size_t WaveformFracOne{1<*2.0f / float{WaveformFracOne}}; return std::sin(static_cast(index) * scale)*0.5f + 0.5f; } inline float Saw(uint index) { return static_cast(index) / float{WaveformFracOne}; } inline float Triangle(uint index) { return std::fabs(static_cast(index)*(2.0f/WaveformFracOne) - 1.0f); } inline float Half(uint) { return 0.5f; } template void Oscillate(const al::span dst, uint index, const uint step) { std::generate(dst.begin(), dst.end(), [&index,step] { index += step; index &= WaveformFracMask; return func(index); }); } struct FormantFilter { float mCoeff{0.0f}; float mGain{1.0f}; float mS1{0.0f}; float mS2{0.0f}; FormantFilter() = default; FormantFilter(float f0norm, float gain) : mCoeff{std::tan(al::numbers::pi_v * f0norm)}, mGain{gain} { } void process(const float *samplesIn, float *samplesOut, const size_t numInput) noexcept { /* A state variable filter from a topology-preserving transform. * Based on a talk given by Ivan Cohen: https://www.youtube.com/watch?v=esjHXGPyrhg */ const float g{mCoeff}; const float gain{mGain}; const float h{1.0f / (1.0f + (g*RcpQFactor) + (g*g))}; const float coeff{RcpQFactor + g}; float s1{mS1}; float s2{mS2}; const auto input = al::span{samplesIn, numInput}; const auto output = al::span{samplesOut, numInput}; std::transform(input.cbegin(), input.cend(), output.cbegin(), output.begin(), [g,gain,h,coeff,&s1,&s2](const float in, const float out) noexcept -> float { const float H{(in - coeff*s1 - s2)*h}; const float B{g*H + s1}; const float L{g*B + s2}; s1 = g*H + B; s2 = g*B + L; // Apply peak and accumulate samples. return out + B*gain; }); mS1 = s1; mS2 = s2; } void clear() noexcept { mS1 = 0.0f; mS2 = 0.0f; } }; struct VmorpherState final : public EffectState { struct OutParams { uint mTargetChannel{InvalidChannelIndex}; /* Effect parameters */ std::array,NumFilters> mFormants; /* Effect gains for each channel */ float mCurrentGain{}; float mTargetGain{}; }; std::array mChans; void (*mGetSamples)(const al::span dst, uint index, const uint step){}; uint mIndex{0}; uint mStep{1}; /* Effects buffers */ alignas(16) std::array mSampleBufferA{}; alignas(16) std::array mSampleBufferB{}; alignas(16) std::array mLfo{}; void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) override; void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props, const EffectTarget target) override; void process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) override; static std::array getFiltersByPhoneme(VMorpherPhenome phoneme, float frequency, float pitch) noexcept; }; std::array VmorpherState::getFiltersByPhoneme(VMorpherPhenome phoneme, float frequency, float pitch) noexcept { /* Using soprano formant set of values to * better match mid-range frequency space. * * See: https://www.classes.cs.uchicago.edu/archive/1999/spring/CS295/Computing_Resources/Csound/CsManual3.48b1.HTML/Appendices/table3.html */ switch(phoneme) { case VMorpherPhenome::A: return {{ {( 800 * pitch) / frequency, 1.000000f}, /* std::pow(10.0f, 0 / 20.0f); */ {(1150 * pitch) / frequency, 0.501187f}, /* std::pow(10.0f, -6 / 20.0f); */ {(2900 * pitch) / frequency, 0.025118f}, /* std::pow(10.0f, -32 / 20.0f); */ {(3900 * pitch) / frequency, 0.100000f} /* std::pow(10.0f, -20 / 20.0f); */ }}; case VMorpherPhenome::E: return {{ {( 350 * pitch) / frequency, 1.000000f}, /* std::pow(10.0f, 0 / 20.0f); */ {(2000 * pitch) / frequency, 0.100000f}, /* std::pow(10.0f, -20 / 20.0f); */ {(2800 * pitch) / frequency, 0.177827f}, /* std::pow(10.0f, -15 / 20.0f); */ {(3600 * pitch) / frequency, 0.009999f} /* std::pow(10.0f, -40 / 20.0f); */ }}; case VMorpherPhenome::I: return {{ {( 270 * pitch) / frequency, 1.000000f}, /* std::pow(10.0f, 0 / 20.0f); */ {(2140 * pitch) / frequency, 0.251188f}, /* std::pow(10.0f, -12 / 20.0f); */ {(2950 * pitch) / frequency, 0.050118f}, /* std::pow(10.0f, -26 / 20.0f); */ {(3900 * pitch) / frequency, 0.050118f} /* std::pow(10.0f, -26 / 20.0f); */ }}; case VMorpherPhenome::O: return {{ {( 450 * pitch) / frequency, 1.000000f}, /* std::pow(10.0f, 0 / 20.0f); */ {( 800 * pitch) / frequency, 0.281838f}, /* std::pow(10.0f, -11 / 20.0f); */ {(2830 * pitch) / frequency, 0.079432f}, /* std::pow(10.0f, -22 / 20.0f); */ {(3800 * pitch) / frequency, 0.079432f} /* std::pow(10.0f, -22 / 20.0f); */ }}; case VMorpherPhenome::U: return {{ {( 325 * pitch) / frequency, 1.000000f}, /* std::pow(10.0f, 0 / 20.0f); */ {( 700 * pitch) / frequency, 0.158489f}, /* std::pow(10.0f, -16 / 20.0f); */ {(2700 * pitch) / frequency, 0.017782f}, /* std::pow(10.0f, -35 / 20.0f); */ {(3800 * pitch) / frequency, 0.009999f} /* std::pow(10.0f, -40 / 20.0f); */ }}; default: break; } return {}; } void VmorpherState::deviceUpdate(const DeviceBase*, const BufferStorage*) { for(auto &e : mChans) { e.mTargetChannel = InvalidChannelIndex; std::for_each(e.mFormants[VowelAIndex].begin(), e.mFormants[VowelAIndex].end(), std::mem_fn(&FormantFilter::clear)); std::for_each(e.mFormants[VowelBIndex].begin(), e.mFormants[VowelBIndex].end(), std::mem_fn(&FormantFilter::clear)); e.mCurrentGain = 0.0f; } } void VmorpherState::update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props_, const EffectTarget target) { auto &props = std::get(*props_); const DeviceBase *device{context->mDevice}; const float frequency{static_cast(device->mSampleRate)}; const float step{props.Rate / frequency}; mStep = fastf2u(std::clamp(step*WaveformFracOne, 0.0f, WaveformFracOne-1.0f)); if(mStep == 0) mGetSamples = Oscillate; else if(props.Waveform == VMorpherWaveform::Sinusoid) mGetSamples = Oscillate; else if(props.Waveform == VMorpherWaveform::Triangle) mGetSamples = Oscillate; else /*if(props.Waveform == VMorpherWaveform::Sawtooth)*/ mGetSamples = Oscillate; const float pitchA{std::pow(2.0f, static_cast(props.PhonemeACoarseTuning) / 12.0f)}; const float pitchB{std::pow(2.0f, static_cast(props.PhonemeBCoarseTuning) / 12.0f)}; auto vowelA = getFiltersByPhoneme(props.PhonemeA, frequency, pitchA); auto vowelB = getFiltersByPhoneme(props.PhonemeB, frequency, pitchB); /* Copy the filter coefficients to the input channels. */ for(size_t i{0u};i < slot->Wet.Buffer.size();++i) { std::copy(vowelA.begin(), vowelA.end(), mChans[i].mFormants[VowelAIndex].begin()); std::copy(vowelB.begin(), vowelB.end(), mChans[i].mFormants[VowelBIndex].begin()); } mOutTarget = target.Main->Buffer; auto set_channel = [this](size_t idx, uint outchan, float outgain) { mChans[idx].mTargetChannel = outchan; mChans[idx].mTargetGain = outgain; }; target.Main->setAmbiMixParams(slot->Wet, slot->Gain, set_channel); } void VmorpherState::process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) { alignas(16) std::array blended{}; /* Following the EFX specification for a conformant implementation which describes * the effect as a pair of 4-band formant filters blended together using an LFO. */ for(size_t base{0u};base < samplesToDo;) { const size_t td{std::min(MaxUpdateSamples, samplesToDo-base)}; mGetSamples(al::span{mLfo}.first(td), mIndex, mStep); mIndex += static_cast(mStep * td); mIndex &= WaveformFracMask; auto chandata = mChans.begin(); for(const auto &input : samplesIn) { const size_t outidx{chandata->mTargetChannel}; if(outidx == InvalidChannelIndex) { ++chandata; continue; } const auto vowelA = al::span{chandata->mFormants[VowelAIndex]}; const auto vowelB = al::span{chandata->mFormants[VowelBIndex]}; /* Process first vowel. */ std::fill_n(mSampleBufferA.begin(), td, 0.0f); vowelA[0].process(&input[base], mSampleBufferA.data(), td); vowelA[1].process(&input[base], mSampleBufferA.data(), td); vowelA[2].process(&input[base], mSampleBufferA.data(), td); vowelA[3].process(&input[base], mSampleBufferA.data(), td); /* Process second vowel. */ std::fill_n(mSampleBufferB.begin(), td, 0.0f); vowelB[0].process(&input[base], mSampleBufferB.data(), td); vowelB[1].process(&input[base], mSampleBufferB.data(), td); vowelB[2].process(&input[base], mSampleBufferB.data(), td); vowelB[3].process(&input[base], mSampleBufferB.data(), td); for(size_t i{0u};i < td;i++) blended[i] = lerpf(mSampleBufferA[i], mSampleBufferB[i], mLfo[i]); /* Now, mix the processed sound data to the output. */ MixSamples(al::span{blended}.first(td), al::span{samplesOut[outidx]}.subspan(base), chandata->mCurrentGain, chandata->mTargetGain, samplesToDo-base); ++chandata; } base += td; } } struct VmorpherStateFactory final : public EffectStateFactory { al::intrusive_ptr create() override { return al::intrusive_ptr{new VmorpherState{}}; } }; } // namespace EffectStateFactory *VmorpherStateFactory_getFactory() { static VmorpherStateFactory VmorpherFactory{}; return &VmorpherFactory; } openal-soft-1.24.2/alc/events.cpp000066400000000000000000000054061474041540300166050ustar00rootroot00000000000000 #include "config.h" #include "events.h" #include "alnumeric.h" #include "alspan.h" #include "core/logging.h" #include "device.h" #include "fmt/core.h" namespace { ALCenum EnumFromEventType(const alc::EventType type) { switch(type) { case alc::EventType::DefaultDeviceChanged: return ALC_EVENT_TYPE_DEFAULT_DEVICE_CHANGED_SOFT; case alc::EventType::DeviceAdded: return ALC_EVENT_TYPE_DEVICE_ADDED_SOFT; case alc::EventType::DeviceRemoved: return ALC_EVENT_TYPE_DEVICE_REMOVED_SOFT; case alc::EventType::Count: break; } throw std::runtime_error{fmt::format("Invalid EventType: {}", int{al::to_underlying(type)})}; } } // namespace namespace alc { std::optional GetEventType(ALCenum type) { switch(type) { case ALC_EVENT_TYPE_DEFAULT_DEVICE_CHANGED_SOFT: return alc::EventType::DefaultDeviceChanged; case ALC_EVENT_TYPE_DEVICE_ADDED_SOFT: return alc::EventType::DeviceAdded; case ALC_EVENT_TYPE_DEVICE_REMOVED_SOFT: return alc::EventType::DeviceRemoved; } return std::nullopt; } void Event(EventType eventType, DeviceType deviceType, ALCdevice *device, std::string_view message) noexcept { auto eventlock = std::unique_lock{EventMutex}; if(EventCallback && EventsEnabled.test(al::to_underlying(eventType))) EventCallback(EnumFromEventType(eventType), al::to_underlying(deviceType), device, static_cast(message.length()), message.data(), EventUserPtr); } } // namespace alc FORCE_ALIGN ALCboolean ALC_APIENTRY alcEventControlSOFT(ALCsizei count, const ALCenum *events, ALCboolean enable) noexcept { if(enable != ALC_FALSE && enable != ALC_TRUE) { alcSetError(nullptr, ALC_INVALID_ENUM); return ALC_FALSE; } if(count < 0) { alcSetError(nullptr, ALC_INVALID_VALUE); return ALC_FALSE; } if(count == 0) return ALC_TRUE; if(!events) { alcSetError(nullptr, ALC_INVALID_VALUE); return ALC_FALSE; } alc::EventBitSet eventSet{0}; for(ALCenum type : al::span{events, static_cast(count)}) { auto etype = alc::GetEventType(type); if(!etype) { WARN("Invalid event type: {:#04x}", as_unsigned(type)); alcSetError(nullptr, ALC_INVALID_ENUM); return ALC_FALSE; } eventSet.set(al::to_underlying(*etype)); } auto eventlock = std::unique_lock{alc::EventMutex}; if(enable) alc::EventsEnabled |= eventSet; else alc::EventsEnabled &= ~eventSet; return ALC_TRUE; } FORCE_ALIGN void ALC_APIENTRY alcEventCallbackSOFT(ALCEVENTPROCTYPESOFT callback, void *userParam) noexcept { auto eventlock = std::unique_lock{alc::EventMutex}; alc::EventCallback = callback; alc::EventUserPtr = userParam; } openal-soft-1.24.2/alc/events.h000066400000000000000000000021461474041540300162500ustar00rootroot00000000000000#ifndef ALC_EVENTS_H #define ALC_EVENTS_H #include "inprogext.h" #include "opthelpers.h" #include #include #include #include namespace alc { enum class EventType : uint8_t { DefaultDeviceChanged, DeviceAdded, DeviceRemoved, Count }; std::optional GetEventType(ALCenum type); enum class EventSupport : ALCenum { FullSupport = ALC_EVENT_SUPPORTED_SOFT, NoSupport = ALC_EVENT_NOT_SUPPORTED_SOFT, }; enum class DeviceType : ALCenum { Playback = ALC_PLAYBACK_DEVICE_SOFT, Capture = ALC_CAPTURE_DEVICE_SOFT, }; using EventBitSet = std::bitset; inline EventBitSet EventsEnabled{0}; inline std::mutex EventMutex; inline ALCEVENTPROCTYPESOFT EventCallback{}; inline void *EventUserPtr{}; void Event(EventType eventType, DeviceType deviceType, ALCdevice *device, std::string_view message) noexcept; inline void Event(EventType eventType, DeviceType deviceType, std::string_view message) noexcept { Event(eventType, deviceType, nullptr, message); } } // namespace alc #endif /* ALC_EVENTS_H */ openal-soft-1.24.2/alc/export_list.h000066400000000000000000000620471474041540300173260ustar00rootroot00000000000000#ifndef ALC_EXPORT_LIST_H #define ALC_EXPORT_LIST_H #include "config.h" #include "AL/alc.h" #include "AL/al.h" #include "AL/alext.h" #include "inprogext.h" #if ALSOFT_EAX #include "context.h" #include "al/eax/x_ram.h" #endif struct FuncExport { const char *funcName; void *address; }; #define DECL(x) FuncExport{#x, reinterpret_cast(x)} /* NOLINTNEXTLINE(*-avoid-c-arrays) Too large for std::array auto-deduction :( */ inline const FuncExport alcFunctions[]{ DECL(alcCreateContext), DECL(alcMakeContextCurrent), DECL(alcProcessContext), DECL(alcSuspendContext), DECL(alcDestroyContext), DECL(alcGetCurrentContext), DECL(alcGetContextsDevice), DECL(alcOpenDevice), DECL(alcCloseDevice), DECL(alcGetError), DECL(alcIsExtensionPresent), DECL(alcGetProcAddress), DECL(alcGetEnumValue), DECL(alcGetString), DECL(alcGetIntegerv), DECL(alcCaptureOpenDevice), DECL(alcCaptureCloseDevice), DECL(alcCaptureStart), DECL(alcCaptureStop), DECL(alcCaptureSamples), DECL(alcSetThreadContext), DECL(alcGetThreadContext), DECL(alcLoopbackOpenDeviceSOFT), DECL(alcIsRenderFormatSupportedSOFT), DECL(alcRenderSamplesSOFT), DECL(alcDevicePauseSOFT), DECL(alcDeviceResumeSOFT), DECL(alcGetStringiSOFT), DECL(alcResetDeviceSOFT), DECL(alcGetInteger64vSOFT), DECL(alcReopenDeviceSOFT), DECL(alcEventIsSupportedSOFT), DECL(alcEventControlSOFT), DECL(alcEventCallbackSOFT), DECL(alEnable), DECL(alDisable), DECL(alIsEnabled), DECL(alGetString), DECL(alGetBooleanv), DECL(alGetIntegerv), DECL(alGetFloatv), DECL(alGetDoublev), DECL(alGetBoolean), DECL(alGetInteger), DECL(alGetFloat), DECL(alGetDouble), DECL(alGetError), DECL(alIsExtensionPresent), DECL(alGetProcAddress), DECL(alGetEnumValue), DECL(alListenerf), DECL(alListener3f), DECL(alListenerfv), DECL(alListeneri), DECL(alListener3i), DECL(alListeneriv), DECL(alGetListenerf), DECL(alGetListener3f), DECL(alGetListenerfv), DECL(alGetListeneri), DECL(alGetListener3i), DECL(alGetListeneriv), DECL(alGenSources), DECL(alDeleteSources), DECL(alIsSource), DECL(alSourcef), DECL(alSource3f), DECL(alSourcefv), DECL(alSourcei), DECL(alSource3i), DECL(alSourceiv), DECL(alGetSourcef), DECL(alGetSource3f), DECL(alGetSourcefv), DECL(alGetSourcei), DECL(alGetSource3i), DECL(alGetSourceiv), DECL(alSourcePlayv), DECL(alSourceStopv), DECL(alSourceRewindv), DECL(alSourcePausev), DECL(alSourcePlay), DECL(alSourceStop), DECL(alSourceRewind), DECL(alSourcePause), DECL(alSourceQueueBuffers), DECL(alSourceUnqueueBuffers), DECL(alGenBuffers), DECL(alDeleteBuffers), DECL(alIsBuffer), DECL(alBufferData), DECL(alBufferf), DECL(alBuffer3f), DECL(alBufferfv), DECL(alBufferi), DECL(alBuffer3i), DECL(alBufferiv), DECL(alGetBufferf), DECL(alGetBuffer3f), DECL(alGetBufferfv), DECL(alGetBufferi), DECL(alGetBuffer3i), DECL(alGetBufferiv), DECL(alDopplerFactor), DECL(alDopplerVelocity), DECL(alSpeedOfSound), DECL(alDistanceModel), DECL(alGenFilters), DECL(alDeleteFilters), DECL(alIsFilter), DECL(alFilteri), DECL(alFilteriv), DECL(alFilterf), DECL(alFilterfv), DECL(alGetFilteri), DECL(alGetFilteriv), DECL(alGetFilterf), DECL(alGetFilterfv), DECL(alGenEffects), DECL(alDeleteEffects), DECL(alIsEffect), DECL(alEffecti), DECL(alEffectiv), DECL(alEffectf), DECL(alEffectfv), DECL(alGetEffecti), DECL(alGetEffectiv), DECL(alGetEffectf), DECL(alGetEffectfv), DECL(alGenAuxiliaryEffectSlots), DECL(alDeleteAuxiliaryEffectSlots), DECL(alIsAuxiliaryEffectSlot), DECL(alAuxiliaryEffectSloti), DECL(alAuxiliaryEffectSlotiv), DECL(alAuxiliaryEffectSlotf), DECL(alAuxiliaryEffectSlotfv), DECL(alGetAuxiliaryEffectSloti), DECL(alGetAuxiliaryEffectSlotiv), DECL(alGetAuxiliaryEffectSlotf), DECL(alGetAuxiliaryEffectSlotfv), DECL(alDeferUpdatesSOFT), DECL(alProcessUpdatesSOFT), DECL(alSourcedSOFT), DECL(alSource3dSOFT), DECL(alSourcedvSOFT), DECL(alGetSourcedSOFT), DECL(alGetSource3dSOFT), DECL(alGetSourcedvSOFT), DECL(alSourcei64SOFT), DECL(alSource3i64SOFT), DECL(alSourcei64vSOFT), DECL(alGetSourcei64SOFT), DECL(alGetSource3i64SOFT), DECL(alGetSourcei64vSOFT), DECL(alGetStringiSOFT), DECL(alBufferStorageSOFT), DECL(alMapBufferSOFT), DECL(alUnmapBufferSOFT), DECL(alFlushMappedBufferSOFT), DECL(alEventControlSOFT), DECL(alEventCallbackSOFT), DECL(alGetPointerSOFT), DECL(alGetPointervSOFT), DECL(alBufferCallbackSOFT), DECL(alGetBufferPtrSOFT), DECL(alGetBuffer3PtrSOFT), DECL(alGetBufferPtrvSOFT), DECL(alSourcePlayAtTimeSOFT), DECL(alSourcePlayAtTimevSOFT), DECL(alBufferSubDataSOFT), DECL(alBufferDataStatic), DECL(alDebugMessageCallbackEXT), DECL(alDebugMessageInsertEXT), DECL(alDebugMessageControlEXT), DECL(alPushDebugGroupEXT), DECL(alPopDebugGroupEXT), DECL(alGetDebugMessageLogEXT), DECL(alObjectLabelEXT), DECL(alGetObjectLabelEXT), DECL(alGetPointerEXT), DECL(alGetPointervEXT), /* Direct Context functions */ DECL(alcGetProcAddress2), DECL(alEnableDirect), DECL(alDisableDirect), DECL(alIsEnabledDirect), DECL(alDopplerFactorDirect), DECL(alSpeedOfSoundDirect), DECL(alDistanceModelDirect), DECL(alGetStringDirect), DECL(alGetBooleanvDirect), DECL(alGetIntegervDirect), DECL(alGetFloatvDirect), DECL(alGetDoublevDirect), DECL(alGetBooleanDirect), DECL(alGetIntegerDirect), DECL(alGetFloatDirect), DECL(alGetDoubleDirect), DECL(alGetErrorDirect), DECL(alIsExtensionPresentDirect), DECL(alGetProcAddress), DECL(alGetEnumValueDirect), DECL(alListeneriDirect), DECL(alListener3iDirect), DECL(alListenerivDirect), DECL(alListenerfDirect), DECL(alListener3fDirect), DECL(alListenerfvDirect), DECL(alGetListeneriDirect), DECL(alGetListener3iDirect), DECL(alGetListenerivDirect), DECL(alGetListenerfDirect), DECL(alGetListener3fDirect), DECL(alGetListenerfvDirect), DECL(alGenBuffersDirect), DECL(alDeleteBuffersDirect), DECL(alIsBufferDirect), DECL(alBufferDataDirect), DECL(alBufferiDirect), DECL(alBuffer3iDirect), DECL(alBufferivDirect), DECL(alBufferfDirect), DECL(alBuffer3fDirect), DECL(alBufferfvDirect), DECL(alGetBufferiDirect), DECL(alGetBuffer3iDirect), DECL(alGetBufferivDirect), DECL(alGetBufferfDirect), DECL(alGetBuffer3fDirect), DECL(alGetBufferfvDirect), DECL(alGenSourcesDirect), DECL(alDeleteSourcesDirect), DECL(alIsSourceDirect), DECL(alSourcePlayDirect), DECL(alSourceStopDirect), DECL(alSourcePauseDirect), DECL(alSourceRewindDirect), DECL(alSourcePlayvDirect), DECL(alSourceStopvDirect), DECL(alSourcePausevDirect), DECL(alSourceRewindvDirect), DECL(alSourceiDirect), DECL(alSource3iDirect), DECL(alSourceivDirect), DECL(alSourcefDirect), DECL(alSource3fDirect), DECL(alSourcefvDirect), DECL(alGetSourceiDirect), DECL(alGetSource3iDirect), DECL(alGetSourceivDirect), DECL(alGetSourcefDirect), DECL(alGetSource3fDirect), DECL(alGetSourcefvDirect), DECL(alSourceQueueBuffersDirect), DECL(alSourceUnqueueBuffersDirect), DECL(alGenFiltersDirect), DECL(alDeleteFiltersDirect), DECL(alIsFilterDirect), DECL(alFilteriDirect), DECL(alFilterivDirect), DECL(alFilterfDirect), DECL(alFilterfvDirect), DECL(alGetFilteriDirect), DECL(alGetFilterivDirect), DECL(alGetFilterfDirect), DECL(alGetFilterfvDirect), DECL(alGenEffectsDirect), DECL(alDeleteEffectsDirect), DECL(alIsEffectDirect), DECL(alEffectiDirect), DECL(alEffectivDirect), DECL(alEffectfDirect), DECL(alEffectfvDirect), DECL(alGetEffectiDirect), DECL(alGetEffectivDirect), DECL(alGetEffectfDirect), DECL(alGetEffectfvDirect), DECL(alGenAuxiliaryEffectSlotsDirect), DECL(alDeleteAuxiliaryEffectSlotsDirect), DECL(alIsAuxiliaryEffectSlotDirect), DECL(alAuxiliaryEffectSlotiDirect), DECL(alAuxiliaryEffectSlotivDirect), DECL(alAuxiliaryEffectSlotfDirect), DECL(alAuxiliaryEffectSlotfvDirect), DECL(alGetAuxiliaryEffectSlotiDirect), DECL(alGetAuxiliaryEffectSlotivDirect), DECL(alGetAuxiliaryEffectSlotfDirect), DECL(alGetAuxiliaryEffectSlotfvDirect), DECL(alDeferUpdatesDirectSOFT), DECL(alProcessUpdatesDirectSOFT), DECL(alGetStringiDirectSOFT), DECL(alBufferDataStaticDirect), DECL(alBufferCallbackDirectSOFT), DECL(alBufferSubDataDirectSOFT), DECL(alBufferStorageDirectSOFT), DECL(alMapBufferDirectSOFT), DECL(alUnmapBufferDirectSOFT), DECL(alFlushMappedBufferDirectSOFT), DECL(alSourcei64DirectSOFT), DECL(alSource3i64DirectSOFT), DECL(alSourcei64vDirectSOFT), DECL(alSourcedDirectSOFT), DECL(alSource3dDirectSOFT), DECL(alSourcedvDirectSOFT), DECL(alGetSourcei64DirectSOFT), DECL(alGetSource3i64DirectSOFT), DECL(alGetSourcei64vDirectSOFT), DECL(alGetSourcedDirectSOFT), DECL(alGetSource3dDirectSOFT), DECL(alGetSourcedvDirectSOFT), DECL(alSourcePlayAtTimeDirectSOFT), DECL(alSourcePlayAtTimevDirectSOFT), DECL(alEventControlDirectSOFT), DECL(alEventCallbackDirectSOFT), DECL(alDebugMessageCallbackDirectEXT), DECL(alDebugMessageInsertDirectEXT), DECL(alDebugMessageControlDirectEXT), DECL(alPushDebugGroupDirectEXT), DECL(alPopDebugGroupDirectEXT), DECL(alGetDebugMessageLogDirectEXT), DECL(alObjectLabelDirectEXT), DECL(alGetObjectLabelDirectEXT), DECL(alGetPointerDirectEXT), DECL(alGetPointervDirectEXT), /* Extra functions */ DECL(alsoft_set_log_callback), }; #if ALSOFT_EAX inline const std::array eaxFunctions{ DECL(EAXGet), DECL(EAXSet), DECL(EAXGetBufferMode), DECL(EAXSetBufferMode), DECL(EAXGetDirect), DECL(EAXSetDirect), DECL(EAXGetBufferModeDirect), DECL(EAXSetBufferModeDirect), }; #endif #undef DECL struct EnumExport { const char *enumName; int value; }; #define DECL(x) EnumExport{#x, (x)} /* NOLINTNEXTLINE(*-avoid-c-arrays) Too large for std::array auto-deduction :( */ inline const EnumExport alcEnumerations[]{ DECL(ALC_INVALID), DECL(ALC_FALSE), DECL(ALC_TRUE), DECL(ALC_MAJOR_VERSION), DECL(ALC_MINOR_VERSION), DECL(ALC_ATTRIBUTES_SIZE), DECL(ALC_ALL_ATTRIBUTES), DECL(ALC_DEFAULT_DEVICE_SPECIFIER), DECL(ALC_DEVICE_SPECIFIER), DECL(ALC_ALL_DEVICES_SPECIFIER), DECL(ALC_DEFAULT_ALL_DEVICES_SPECIFIER), DECL(ALC_EXTENSIONS), DECL(ALC_FREQUENCY), DECL(ALC_REFRESH), DECL(ALC_SYNC), DECL(ALC_MONO_SOURCES), DECL(ALC_STEREO_SOURCES), DECL(ALC_CAPTURE_DEVICE_SPECIFIER), DECL(ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER), DECL(ALC_CAPTURE_SAMPLES), DECL(ALC_CONNECTED), DECL(ALC_EFX_MAJOR_VERSION), DECL(ALC_EFX_MINOR_VERSION), DECL(ALC_MAX_AUXILIARY_SENDS), DECL(ALC_FORMAT_CHANNELS_SOFT), DECL(ALC_FORMAT_TYPE_SOFT), DECL(ALC_MONO_SOFT), DECL(ALC_STEREO_SOFT), DECL(ALC_QUAD_SOFT), DECL(ALC_5POINT1_SOFT), DECL(ALC_6POINT1_SOFT), DECL(ALC_7POINT1_SOFT), DECL(ALC_BFORMAT3D_SOFT), DECL(ALC_BYTE_SOFT), DECL(ALC_UNSIGNED_BYTE_SOFT), DECL(ALC_SHORT_SOFT), DECL(ALC_UNSIGNED_SHORT_SOFT), DECL(ALC_INT_SOFT), DECL(ALC_UNSIGNED_INT_SOFT), DECL(ALC_FLOAT_SOFT), DECL(ALC_HRTF_SOFT), DECL(ALC_DONT_CARE_SOFT), DECL(ALC_HRTF_STATUS_SOFT), DECL(ALC_HRTF_DISABLED_SOFT), DECL(ALC_HRTF_ENABLED_SOFT), DECL(ALC_HRTF_DENIED_SOFT), DECL(ALC_HRTF_REQUIRED_SOFT), DECL(ALC_HRTF_HEADPHONES_DETECTED_SOFT), DECL(ALC_HRTF_UNSUPPORTED_FORMAT_SOFT), DECL(ALC_NUM_HRTF_SPECIFIERS_SOFT), DECL(ALC_HRTF_SPECIFIER_SOFT), DECL(ALC_HRTF_ID_SOFT), DECL(ALC_AMBISONIC_LAYOUT_SOFT), DECL(ALC_AMBISONIC_SCALING_SOFT), DECL(ALC_AMBISONIC_ORDER_SOFT), DECL(ALC_ACN_SOFT), DECL(ALC_FUMA_SOFT), DECL(ALC_N3D_SOFT), DECL(ALC_SN3D_SOFT), DECL(ALC_OUTPUT_LIMITER_SOFT), DECL(ALC_DEVICE_CLOCK_SOFT), DECL(ALC_DEVICE_LATENCY_SOFT), DECL(ALC_DEVICE_CLOCK_LATENCY_SOFT), DECL(AL_SAMPLE_OFFSET_CLOCK_SOFT), DECL(AL_SEC_OFFSET_CLOCK_SOFT), DECL(ALC_OUTPUT_MODE_SOFT), DECL(ALC_ANY_SOFT), DECL(ALC_STEREO_BASIC_SOFT), DECL(ALC_STEREO_UHJ_SOFT), DECL(ALC_STEREO_HRTF_SOFT), DECL(ALC_SURROUND_5_1_SOFT), DECL(ALC_SURROUND_6_1_SOFT), DECL(ALC_SURROUND_7_1_SOFT), DECL(ALC_NO_ERROR), DECL(ALC_INVALID_DEVICE), DECL(ALC_INVALID_CONTEXT), DECL(ALC_INVALID_ENUM), DECL(ALC_INVALID_VALUE), DECL(ALC_OUT_OF_MEMORY), DECL(ALC_CONTEXT_FLAGS_EXT), DECL(ALC_CONTEXT_DEBUG_BIT_EXT), DECL(ALC_PLAYBACK_DEVICE_SOFT), DECL(ALC_CAPTURE_DEVICE_SOFT), DECL(ALC_EVENT_TYPE_DEFAULT_DEVICE_CHANGED_SOFT), DECL(ALC_EVENT_TYPE_DEVICE_ADDED_SOFT), DECL(ALC_EVENT_TYPE_DEVICE_REMOVED_SOFT), DECL(AL_INVALID), DECL(AL_NONE), DECL(AL_FALSE), DECL(AL_TRUE), DECL(AL_SOURCE_RELATIVE), DECL(AL_CONE_INNER_ANGLE), DECL(AL_CONE_OUTER_ANGLE), DECL(AL_PITCH), DECL(AL_POSITION), DECL(AL_DIRECTION), DECL(AL_VELOCITY), DECL(AL_LOOPING), DECL(AL_BUFFER), DECL(AL_GAIN), DECL(AL_MIN_GAIN), DECL(AL_MAX_GAIN), DECL(AL_ORIENTATION), DECL(AL_REFERENCE_DISTANCE), DECL(AL_ROLLOFF_FACTOR), DECL(AL_CONE_OUTER_GAIN), DECL(AL_MAX_DISTANCE), DECL(AL_SEC_OFFSET), DECL(AL_SAMPLE_OFFSET), DECL(AL_BYTE_OFFSET), DECL(AL_SOURCE_TYPE), DECL(AL_STATIC), DECL(AL_STREAMING), DECL(AL_UNDETERMINED), DECL(AL_METERS_PER_UNIT), DECL(AL_LOOP_POINTS_SOFT), DECL(AL_DIRECT_CHANNELS_SOFT), DECL(AL_DIRECT_FILTER), DECL(AL_AUXILIARY_SEND_FILTER), DECL(AL_AIR_ABSORPTION_FACTOR), DECL(AL_ROOM_ROLLOFF_FACTOR), DECL(AL_CONE_OUTER_GAINHF), DECL(AL_DIRECT_FILTER_GAINHF_AUTO), DECL(AL_AUXILIARY_SEND_FILTER_GAIN_AUTO), DECL(AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO), DECL(AL_SOURCE_STATE), DECL(AL_INITIAL), DECL(AL_PLAYING), DECL(AL_PAUSED), DECL(AL_STOPPED), DECL(AL_BUFFERS_QUEUED), DECL(AL_BUFFERS_PROCESSED), DECL(AL_FORMAT_MONO8), DECL(AL_FORMAT_MONO16), DECL(AL_FORMAT_MONO_FLOAT32), DECL(AL_FORMAT_MONO_DOUBLE_EXT), DECL(AL_FORMAT_STEREO8), DECL(AL_FORMAT_STEREO16), DECL(AL_FORMAT_STEREO_FLOAT32), DECL(AL_FORMAT_STEREO_DOUBLE_EXT), DECL(AL_FORMAT_MONO_IMA4), DECL(AL_FORMAT_STEREO_IMA4), DECL(AL_FORMAT_MONO_MSADPCM_SOFT), DECL(AL_FORMAT_STEREO_MSADPCM_SOFT), DECL(AL_FORMAT_QUAD8_LOKI), DECL(AL_FORMAT_QUAD16_LOKI), DECL(AL_FORMAT_QUAD8), DECL(AL_FORMAT_QUAD16), DECL(AL_FORMAT_QUAD32), DECL(AL_FORMAT_51CHN8), DECL(AL_FORMAT_51CHN16), DECL(AL_FORMAT_51CHN32), DECL(AL_FORMAT_61CHN8), DECL(AL_FORMAT_61CHN16), DECL(AL_FORMAT_61CHN32), DECL(AL_FORMAT_71CHN8), DECL(AL_FORMAT_71CHN16), DECL(AL_FORMAT_71CHN32), DECL(AL_FORMAT_REAR8), DECL(AL_FORMAT_REAR16), DECL(AL_FORMAT_REAR32), DECL(AL_FORMAT_MONO_MULAW), DECL(AL_FORMAT_MONO_MULAW_EXT), DECL(AL_FORMAT_STEREO_MULAW), DECL(AL_FORMAT_STEREO_MULAW_EXT), DECL(AL_FORMAT_QUAD_MULAW), DECL(AL_FORMAT_51CHN_MULAW), DECL(AL_FORMAT_61CHN_MULAW), DECL(AL_FORMAT_71CHN_MULAW), DECL(AL_FORMAT_REAR_MULAW), DECL(AL_FORMAT_MONO_ALAW_EXT), DECL(AL_FORMAT_STEREO_ALAW_EXT), DECL(AL_FORMAT_BFORMAT2D_8), DECL(AL_FORMAT_BFORMAT2D_16), DECL(AL_FORMAT_BFORMAT2D_FLOAT32), DECL(AL_FORMAT_BFORMAT2D_MULAW), DECL(AL_FORMAT_BFORMAT3D_8), DECL(AL_FORMAT_BFORMAT3D_16), DECL(AL_FORMAT_BFORMAT3D_FLOAT32), DECL(AL_FORMAT_BFORMAT3D_MULAW), DECL(AL_FORMAT_UHJ2CHN8_SOFT), DECL(AL_FORMAT_UHJ2CHN16_SOFT), DECL(AL_FORMAT_UHJ2CHN_FLOAT32_SOFT), DECL(AL_FORMAT_UHJ3CHN8_SOFT), DECL(AL_FORMAT_UHJ3CHN16_SOFT), DECL(AL_FORMAT_UHJ3CHN_FLOAT32_SOFT), DECL(AL_FORMAT_UHJ4CHN8_SOFT), DECL(AL_FORMAT_UHJ4CHN16_SOFT), DECL(AL_FORMAT_UHJ4CHN_FLOAT32_SOFT), DECL(AL_STEREO_MODE_SOFT), DECL(AL_NORMAL_SOFT), DECL(AL_SUPER_STEREO_SOFT), DECL(AL_SUPER_STEREO_WIDTH_SOFT), DECL(AL_FORMAT_UHJ2CHN_MULAW_SOFT), DECL(AL_FORMAT_UHJ2CHN_ALAW_SOFT), DECL(AL_FORMAT_UHJ2CHN_IMA4_SOFT), DECL(AL_FORMAT_UHJ2CHN_MSADPCM_SOFT), DECL(AL_FORMAT_UHJ3CHN_MULAW_SOFT), DECL(AL_FORMAT_UHJ3CHN_ALAW_SOFT), DECL(AL_FORMAT_UHJ4CHN_MULAW_SOFT), DECL(AL_FORMAT_UHJ4CHN_ALAW_SOFT), DECL(AL_FORMAT_MONO_I32), DECL(AL_FORMAT_STEREO_I32), DECL(AL_FORMAT_REAR_I32), DECL(AL_FORMAT_QUAD_I32), DECL(AL_FORMAT_51CHN_I32), DECL(AL_FORMAT_61CHN_I32), DECL(AL_FORMAT_71CHN_I32), DECL(AL_FORMAT_BFORMAT2D_I32), DECL(AL_FORMAT_BFORMAT3D_I32), DECL(AL_FORMAT_UHJ2CHN_I32_SOFT), DECL(AL_FORMAT_UHJ3CHN_I32_SOFT), DECL(AL_FORMAT_UHJ4CHN_I32_SOFT), DECL(AL_FORMAT_REAR_FLOAT32), DECL(AL_FORMAT_QUAD_FLOAT32), DECL(AL_FORMAT_51CHN_FLOAT32), DECL(AL_FORMAT_61CHN_FLOAT32), DECL(AL_FORMAT_71CHN_FLOAT32), DECL(AL_FREQUENCY), DECL(AL_BITS), DECL(AL_CHANNELS), DECL(AL_SIZE), DECL(AL_UNPACK_BLOCK_ALIGNMENT_SOFT), DECL(AL_PACK_BLOCK_ALIGNMENT_SOFT), DECL(AL_SOURCE_RADIUS), DECL(AL_SAMPLE_OFFSET_LATENCY_SOFT), DECL(AL_SEC_OFFSET_LATENCY_SOFT), DECL(AL_STEREO_ANGLES), DECL(AL_UNUSED), DECL(AL_PENDING), DECL(AL_PROCESSED), DECL(AL_NO_ERROR), DECL(AL_INVALID_NAME), DECL(AL_INVALID_ENUM), DECL(AL_INVALID_VALUE), DECL(AL_INVALID_OPERATION), DECL(AL_OUT_OF_MEMORY), DECL(AL_VENDOR), DECL(AL_VERSION), DECL(AL_RENDERER), DECL(AL_EXTENSIONS), DECL(AL_DOPPLER_FACTOR), DECL(AL_DOPPLER_VELOCITY), DECL(AL_DISTANCE_MODEL), DECL(AL_SPEED_OF_SOUND), DECL(AL_SOURCE_DISTANCE_MODEL), DECL(AL_DEFERRED_UPDATES_SOFT), DECL(AL_GAIN_LIMIT_SOFT), DECL(AL_INVERSE_DISTANCE), DECL(AL_INVERSE_DISTANCE_CLAMPED), DECL(AL_LINEAR_DISTANCE), DECL(AL_LINEAR_DISTANCE_CLAMPED), DECL(AL_EXPONENT_DISTANCE), DECL(AL_EXPONENT_DISTANCE_CLAMPED), DECL(AL_FILTER_TYPE), DECL(AL_FILTER_NULL), DECL(AL_FILTER_LOWPASS), DECL(AL_FILTER_HIGHPASS), DECL(AL_FILTER_BANDPASS), DECL(AL_LOWPASS_GAIN), DECL(AL_LOWPASS_GAINHF), DECL(AL_HIGHPASS_GAIN), DECL(AL_HIGHPASS_GAINLF), DECL(AL_BANDPASS_GAIN), DECL(AL_BANDPASS_GAINHF), DECL(AL_BANDPASS_GAINLF), DECL(AL_EFFECT_TYPE), DECL(AL_EFFECT_NULL), DECL(AL_EFFECT_REVERB), DECL(AL_EFFECT_EAXREVERB), DECL(AL_EFFECT_CHORUS), DECL(AL_EFFECT_DISTORTION), DECL(AL_EFFECT_ECHO), DECL(AL_EFFECT_FLANGER), DECL(AL_EFFECT_PITCH_SHIFTER), DECL(AL_EFFECT_FREQUENCY_SHIFTER), DECL(AL_EFFECT_VOCAL_MORPHER), DECL(AL_EFFECT_RING_MODULATOR), DECL(AL_EFFECT_AUTOWAH), DECL(AL_EFFECT_COMPRESSOR), DECL(AL_EFFECT_EQUALIZER), DECL(AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT), DECL(AL_EFFECT_DEDICATED_DIALOGUE), DECL(AL_EFFECTSLOT_EFFECT), DECL(AL_EFFECTSLOT_GAIN), DECL(AL_EFFECTSLOT_AUXILIARY_SEND_AUTO), DECL(AL_EFFECTSLOT_NULL), DECL(AL_EAXREVERB_DENSITY), DECL(AL_EAXREVERB_DIFFUSION), DECL(AL_EAXREVERB_GAIN), DECL(AL_EAXREVERB_GAINHF), DECL(AL_EAXREVERB_GAINLF), DECL(AL_EAXREVERB_DECAY_TIME), DECL(AL_EAXREVERB_DECAY_HFRATIO), DECL(AL_EAXREVERB_DECAY_LFRATIO), DECL(AL_EAXREVERB_REFLECTIONS_GAIN), DECL(AL_EAXREVERB_REFLECTIONS_DELAY), DECL(AL_EAXREVERB_REFLECTIONS_PAN), DECL(AL_EAXREVERB_LATE_REVERB_GAIN), DECL(AL_EAXREVERB_LATE_REVERB_DELAY), DECL(AL_EAXREVERB_LATE_REVERB_PAN), DECL(AL_EAXREVERB_ECHO_TIME), DECL(AL_EAXREVERB_ECHO_DEPTH), DECL(AL_EAXREVERB_MODULATION_TIME), DECL(AL_EAXREVERB_MODULATION_DEPTH), DECL(AL_EAXREVERB_AIR_ABSORPTION_GAINHF), DECL(AL_EAXREVERB_HFREFERENCE), DECL(AL_EAXREVERB_LFREFERENCE), DECL(AL_EAXREVERB_ROOM_ROLLOFF_FACTOR), DECL(AL_EAXREVERB_DECAY_HFLIMIT), DECL(AL_REVERB_DENSITY), DECL(AL_REVERB_DIFFUSION), DECL(AL_REVERB_GAIN), DECL(AL_REVERB_GAINHF), DECL(AL_REVERB_DECAY_TIME), DECL(AL_REVERB_DECAY_HFRATIO), DECL(AL_REVERB_REFLECTIONS_GAIN), DECL(AL_REVERB_REFLECTIONS_DELAY), DECL(AL_REVERB_LATE_REVERB_GAIN), DECL(AL_REVERB_LATE_REVERB_DELAY), DECL(AL_REVERB_AIR_ABSORPTION_GAINHF), DECL(AL_REVERB_ROOM_ROLLOFF_FACTOR), DECL(AL_REVERB_DECAY_HFLIMIT), DECL(AL_CHORUS_WAVEFORM), DECL(AL_CHORUS_PHASE), DECL(AL_CHORUS_RATE), DECL(AL_CHORUS_DEPTH), DECL(AL_CHORUS_FEEDBACK), DECL(AL_CHORUS_DELAY), DECL(AL_DISTORTION_EDGE), DECL(AL_DISTORTION_GAIN), DECL(AL_DISTORTION_LOWPASS_CUTOFF), DECL(AL_DISTORTION_EQCENTER), DECL(AL_DISTORTION_EQBANDWIDTH), DECL(AL_ECHO_DELAY), DECL(AL_ECHO_LRDELAY), DECL(AL_ECHO_DAMPING), DECL(AL_ECHO_FEEDBACK), DECL(AL_ECHO_SPREAD), DECL(AL_FLANGER_WAVEFORM), DECL(AL_FLANGER_PHASE), DECL(AL_FLANGER_RATE), DECL(AL_FLANGER_DEPTH), DECL(AL_FLANGER_FEEDBACK), DECL(AL_FLANGER_DELAY), DECL(AL_FREQUENCY_SHIFTER_FREQUENCY), DECL(AL_FREQUENCY_SHIFTER_LEFT_DIRECTION), DECL(AL_FREQUENCY_SHIFTER_RIGHT_DIRECTION), DECL(AL_RING_MODULATOR_FREQUENCY), DECL(AL_RING_MODULATOR_HIGHPASS_CUTOFF), DECL(AL_RING_MODULATOR_WAVEFORM), DECL(AL_PITCH_SHIFTER_COARSE_TUNE), DECL(AL_PITCH_SHIFTER_FINE_TUNE), DECL(AL_COMPRESSOR_ONOFF), DECL(AL_EQUALIZER_LOW_GAIN), DECL(AL_EQUALIZER_LOW_CUTOFF), DECL(AL_EQUALIZER_MID1_GAIN), DECL(AL_EQUALIZER_MID1_CENTER), DECL(AL_EQUALIZER_MID1_WIDTH), DECL(AL_EQUALIZER_MID2_GAIN), DECL(AL_EQUALIZER_MID2_CENTER), DECL(AL_EQUALIZER_MID2_WIDTH), DECL(AL_EQUALIZER_HIGH_GAIN), DECL(AL_EQUALIZER_HIGH_CUTOFF), DECL(AL_DEDICATED_GAIN), DECL(AL_AUTOWAH_ATTACK_TIME), DECL(AL_AUTOWAH_RELEASE_TIME), DECL(AL_AUTOWAH_RESONANCE), DECL(AL_AUTOWAH_PEAK_GAIN), DECL(AL_VOCAL_MORPHER_PHONEMEA), DECL(AL_VOCAL_MORPHER_PHONEMEB_COARSE_TUNING), DECL(AL_VOCAL_MORPHER_PHONEMEB), DECL(AL_VOCAL_MORPHER_PHONEMEB_COARSE_TUNING), DECL(AL_VOCAL_MORPHER_WAVEFORM), DECL(AL_VOCAL_MORPHER_RATE), DECL(AL_EFFECTSLOT_TARGET_SOFT), DECL(AL_NUM_RESAMPLERS_SOFT), DECL(AL_DEFAULT_RESAMPLER_SOFT), DECL(AL_SOURCE_RESAMPLER_SOFT), DECL(AL_RESAMPLER_NAME_SOFT), DECL(AL_SOURCE_SPATIALIZE_SOFT), DECL(AL_AUTO_SOFT), DECL(AL_MAP_READ_BIT_SOFT), DECL(AL_MAP_WRITE_BIT_SOFT), DECL(AL_MAP_PERSISTENT_BIT_SOFT), DECL(AL_PRESERVE_DATA_BIT_SOFT), DECL(AL_EVENT_CALLBACK_FUNCTION_SOFT), DECL(AL_EVENT_CALLBACK_USER_PARAM_SOFT), DECL(AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT), DECL(AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT), DECL(AL_EVENT_TYPE_DISCONNECTED_SOFT), DECL(AL_DROP_UNMATCHED_SOFT), DECL(AL_REMIX_UNMATCHED_SOFT), DECL(AL_AMBISONIC_LAYOUT_SOFT), DECL(AL_AMBISONIC_SCALING_SOFT), DECL(AL_FUMA_SOFT), DECL(AL_ACN_SOFT), DECL(AL_SN3D_SOFT), DECL(AL_N3D_SOFT), DECL(AL_BUFFER_CALLBACK_FUNCTION_SOFT), DECL(AL_BUFFER_CALLBACK_USER_PARAM_SOFT), DECL(AL_UNPACK_AMBISONIC_ORDER_SOFT), DECL(AL_EFFECT_CONVOLUTION_SOFT), DECL(AL_EFFECTSLOT_STATE_SOFT), DECL(AL_DONT_CARE_EXT), DECL(AL_DEBUG_OUTPUT_EXT), DECL(AL_DEBUG_CALLBACK_FUNCTION_EXT), DECL(AL_DEBUG_CALLBACK_USER_PARAM_EXT), DECL(AL_DEBUG_SOURCE_API_EXT), DECL(AL_DEBUG_SOURCE_AUDIO_SYSTEM_EXT), DECL(AL_DEBUG_SOURCE_THIRD_PARTY_EXT), DECL(AL_DEBUG_SOURCE_APPLICATION_EXT), DECL(AL_DEBUG_SOURCE_OTHER_EXT), DECL(AL_DEBUG_TYPE_ERROR_EXT), DECL(AL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_EXT), DECL(AL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_EXT), DECL(AL_DEBUG_TYPE_PORTABILITY_EXT), DECL(AL_DEBUG_TYPE_PERFORMANCE_EXT), DECL(AL_DEBUG_TYPE_MARKER_EXT), DECL(AL_DEBUG_TYPE_PUSH_GROUP_EXT), DECL(AL_DEBUG_TYPE_POP_GROUP_EXT), DECL(AL_DEBUG_TYPE_OTHER_EXT), DECL(AL_DEBUG_SEVERITY_HIGH_EXT), DECL(AL_DEBUG_SEVERITY_MEDIUM_EXT), DECL(AL_DEBUG_SEVERITY_LOW_EXT), DECL(AL_DEBUG_SEVERITY_NOTIFICATION_EXT), DECL(AL_DEBUG_LOGGED_MESSAGES_EXT), DECL(AL_DEBUG_NEXT_LOGGED_MESSAGE_LENGTH_EXT), DECL(AL_MAX_DEBUG_MESSAGE_LENGTH_EXT), DECL(AL_MAX_DEBUG_LOGGED_MESSAGES_EXT), DECL(AL_MAX_DEBUG_GROUP_STACK_DEPTH_EXT), DECL(AL_MAX_LABEL_LENGTH_EXT), DECL(AL_STACK_OVERFLOW_EXT), DECL(AL_STACK_UNDERFLOW_EXT), DECL(AL_BUFFER_EXT), DECL(AL_SOURCE_EXT), DECL(AL_FILTER_EXT), DECL(AL_EFFECT_EXT), DECL(AL_AUXILIARY_EFFECT_SLOT_EXT), DECL(AL_PANNING_ENABLED_SOFT), DECL(AL_PAN_SOFT), DECL(AL_STOP_SOURCES_ON_DISCONNECT_SOFT), }; #if ALSOFT_EAX inline const std::array eaxEnumerations{ DECL(AL_EAX_RAM_SIZE), DECL(AL_EAX_RAM_FREE), DECL(AL_STORAGE_AUTOMATIC), DECL(AL_STORAGE_HARDWARE), DECL(AL_STORAGE_ACCESSIBLE), }; #endif #undef DECL #endif /* ALC_EXPORT_LIST_H */ openal-soft-1.24.2/alc/inprogext.h000066400000000000000000000134661474041540300167720ustar00rootroot00000000000000#ifndef INPROGEXT_H #define INPROGEXT_H /* NOLINTBEGIN */ #include "AL/al.h" #include "AL/alc.h" #include "AL/alext.h" #ifdef __cplusplus extern "C" { #endif #ifndef AL_SOFT_map_buffer #define AL_SOFT_map_buffer 1 typedef unsigned int ALbitfieldSOFT; #define AL_MAP_READ_BIT_SOFT 0x00000001 #define AL_MAP_WRITE_BIT_SOFT 0x00000002 #define AL_MAP_PERSISTENT_BIT_SOFT 0x00000004 #define AL_PRESERVE_DATA_BIT_SOFT 0x00000008 typedef void (AL_APIENTRY*LPALBUFFERSTORAGESOFT)(ALuint buffer, ALenum format, const ALvoid *data, ALsizei size, ALsizei freq, ALbitfieldSOFT flags) AL_API_NOEXCEPT17; typedef void* (AL_APIENTRY*LPALMAPBUFFERSOFT)(ALuint buffer, ALsizei offset, ALsizei length, ALbitfieldSOFT access) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY*LPALUNMAPBUFFERSOFT)(ALuint buffer) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY*LPALFLUSHMAPPEDBUFFERSOFT)(ALuint buffer, ALsizei offset, ALsizei length) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY*LPALBUFFERSTORAGEDIRECTSOFT)(ALCcontext *context, ALuint buffer, ALenum format, const ALvoid *data, ALsizei size, ALsizei freq, ALbitfieldSOFT flags) AL_API_NOEXCEPT17; typedef void* (AL_APIENTRY*LPALMAPBUFFERDIRECTSOFT)(ALCcontext *context, ALuint buffer, ALsizei offset, ALsizei length, ALbitfieldSOFT access) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY*LPALUNMAPBUFFERDIRECTSOFT)(ALCcontext *context, ALuint buffer) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY*LPALFLUSHMAPPEDBUFFERDIRECTSOFT)(ALCcontext *context, ALuint buffer, ALsizei offset, ALsizei length) AL_API_NOEXCEPT17; #ifdef AL_ALEXT_PROTOTYPES AL_API void AL_APIENTRY alBufferStorageSOFT(ALuint buffer, ALenum format, const ALvoid *data, ALsizei size, ALsizei freq, ALbitfieldSOFT flags) AL_API_NOEXCEPT; AL_API void* AL_APIENTRY alMapBufferSOFT(ALuint buffer, ALsizei offset, ALsizei length, ALbitfieldSOFT access) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alUnmapBufferSOFT(ALuint buffer) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alFlushMappedBufferSOFT(ALuint buffer, ALsizei offset, ALsizei length) AL_API_NOEXCEPT; void AL_APIENTRY alBufferStorageDirectSOFT(ALCcontext *context, ALuint buffer, ALenum format, const ALvoid *data, ALsizei size, ALsizei freq, ALbitfieldSOFT flags) AL_API_NOEXCEPT; void* AL_APIENTRY alMapBufferDirectSOFT(ALCcontext *context, ALuint buffer, ALsizei offset, ALsizei length, ALbitfieldSOFT access) AL_API_NOEXCEPT; void AL_APIENTRY alUnmapBufferDirectSOFT(ALCcontext *context, ALuint buffer) AL_API_NOEXCEPT; void AL_APIENTRY alFlushMappedBufferDirectSOFT(ALCcontext *context, ALuint buffer, ALsizei offset, ALsizei length) AL_API_NOEXCEPT; #endif #endif #ifndef AL_SOFT_convolution_effect #define AL_SOFT_convolution_effect #define AL_EFFECT_CONVOLUTION_SOFT 0xA000 #define AL_CONVOLUTION_ORIENTATION_SOFT 0x100F /* same as AL_ORIENTATION */ #define AL_EFFECTSLOT_STATE_SOFT 0x199E #endif #ifndef AL_SOFT_hold_on_disconnect #define AL_SOFT_hold_on_disconnect #define AL_STOP_SOURCES_ON_DISCONNECT_SOFT 0x19AB #endif #ifndef AL_EXT_32bit_formats #define AL_EXT_32bit_formats #define AL_FORMAT_MONO_I32 0x19DB #define AL_FORMAT_STEREO_I32 0x19DC #define AL_FORMAT_REAR_I32 0x19DD #define AL_FORMAT_REAR_FLOAT32 0x19DE #define AL_FORMAT_QUAD_I32 0x19DF #define AL_FORMAT_QUAD_FLOAT32 0x19E0 #define AL_FORMAT_51CHN_I32 0x19E1 #define AL_FORMAT_51CHN_FLOAT32 0x19E2 #define AL_FORMAT_61CHN_I32 0x19E3 #define AL_FORMAT_61CHN_FLOAT32 0x19E4 #define AL_FORMAT_71CHN_I32 0x19E5 #define AL_FORMAT_71CHN_FLOAT32 0x19E6 #define AL_FORMAT_BFORMAT2D_I32 0x19E7 #define AL_FORMAT_BFORMAT3D_I32 0x19E8 #define AL_FORMAT_UHJ2CHN_I32_SOFT 0x19E9 #define AL_FORMAT_UHJ3CHN_I32_SOFT 0x19EA #define AL_FORMAT_UHJ4CHN_I32_SOFT 0x19EB #endif #ifndef AL_SOFT_source_panning #define AL_SOFT_source_panning #define AL_PANNING_ENABLED_SOFT 0x19EC #define AL_PAN_SOFT 0x19ED #endif /* Non-standard exports. Not part of any extension. */ AL_API const ALchar* AL_APIENTRY alsoft_get_version(void) noexcept; typedef void (ALC_APIENTRY*LPALSOFTLOGCALLBACK)(void *userptr, char level, const char *message, int length) noexcept; void ALC_APIENTRY alsoft_set_log_callback(LPALSOFTLOGCALLBACK callback, void *userptr) noexcept; /* Functions from abandoned extensions. Only here for binary compatibility. */ AL_API void AL_APIENTRY alSourceQueueBufferLayersSOFT(ALuint src, ALsizei nb, const ALuint *buffers) noexcept; AL_API void AL_APIENTRY alAuxiliaryEffectSlotPlaySOFT(ALuint slotid) noexcept; AL_API void AL_APIENTRY alAuxiliaryEffectSlotPlayvSOFT(ALsizei n, const ALuint *slotids) noexcept; AL_API void AL_APIENTRY alAuxiliaryEffectSlotStopSOFT(ALuint slotid) noexcept; AL_API void AL_APIENTRY alAuxiliaryEffectSlotStopvSOFT(ALsizei n, const ALuint *slotids) noexcept; AL_API ALint64SOFT AL_APIENTRY alGetInteger64SOFT(ALenum pname) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alGetInteger64vSOFT(ALenum pname, ALint64SOFT *values) AL_API_NOEXCEPT; ALint64SOFT AL_APIENTRY alGetInteger64DirectSOFT(ALCcontext *context, ALenum pname) AL_API_NOEXCEPT; void AL_APIENTRY alGetInteger64vDirectSOFT(ALCcontext *context, ALenum pname, ALint64SOFT *values) AL_API_NOEXCEPT; /* Not included in the public headers or export list, as a precaution for apps * that check these to determine the behavior of the multi-channel *32 formats. */ #define AL_FORMAT_MONO32 0x1202 #define AL_FORMAT_STEREO32 0x1203 #ifdef __cplusplus } /* extern "C" */ #endif /* NOLINTEND */ #endif /* INPROGEXT_H */ openal-soft-1.24.2/alc/panning.cpp000066400000000000000000001642561474041540300167440ustar00rootroot00000000000000/** * OpenAL cross platform audio library * Copyright (C) 1999-2010 by authors. * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * Or go to http://www.gnu.org/copyleft/lgpl.html */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "AL/alc.h" #include "AL/alext.h" #include "alc/context.h" #include "alnumbers.h" #include "alnumeric.h" #include "alspan.h" #include "alstring.h" #include "alu.h" #include "core/ambdec.h" #include "core/ambidefs.h" #include "core/bformatdec.h" #include "core/bufferline.h" #include "core/bs2b.h" #include "core/context.h" #include "core/devformat.h" #include "core/device.h" #include "core/effectslot.h" #include "core/filters/nfc.h" #include "core/filters/splitter.h" #include "core/front_stablizer.h" #include "core/hrtf.h" #include "core/logging.h" #include "core/mixer/hrtfdefs.h" #include "core/uhjfilter.h" #include "device.h" #include "flexarray.h" #include "intrusive_ptr.h" #include "opthelpers.h" #include "vector.h" namespace { using namespace std::string_view_literals; using std::chrono::seconds; using std::chrono::nanoseconds; [[nodiscard]] auto GetLabelFromChannel(Channel channel) -> std::string_view { switch(channel) { case FrontLeft: return "front-left"sv; case FrontRight: return "front-right"sv; case FrontCenter: return "front-center"sv; case LFE: return "lfe"sv; case BackLeft: return "back-left"sv; case BackRight: return "back-right"sv; case BackCenter: return "back-center"sv; case SideLeft: return "side-left"sv; case SideRight: return "side-right"sv; case TopFrontLeft: return "top-front-left"sv; case TopFrontCenter: return "top-front-center"sv; case TopFrontRight: return "top-front-right"sv; case TopCenter: return "top-center"sv; case TopBackLeft: return "top-back-left"sv; case TopBackCenter: return "top-back-center"sv; case TopBackRight: return "top-back-right"sv; case BottomFrontLeft: return "bottom-front-left"sv; case BottomFrontRight: return "bottom-front-right"sv; case BottomBackLeft: return "bottom-back-left"sv; case BottomBackRight: return "bottom-back-right"sv; case Aux0: return "Aux0"sv; case Aux1: return "Aux1"sv; case Aux2: return "Aux2"sv; case Aux3: return "Aux3"sv; case Aux4: return "Aux4"sv; case Aux5: return "Aux5"sv; case Aux6: return "Aux6"sv; case Aux7: return "Aux7"sv; case Aux8: return "Aux8"sv; case Aux9: return "Aux9"sv; case Aux10: return "Aux10"sv; case Aux11: return "Aux11"sv; case Aux12: return "Aux12"sv; case Aux13: return "Aux13"sv; case Aux14: return "Aux14"sv; case Aux15: return "Aux15"sv; case MaxChannels: break; } return "(unknown)"sv; } [[nodiscard]] auto GetLayoutName(DevAmbiLayout layout) noexcept -> std::string_view { switch(layout) { case DevAmbiLayout::FuMa: return "FuMa"sv; case DevAmbiLayout::ACN: return "ACN"sv; } return ""sv; } [[nodiscard]] auto GetScalingName(DevAmbiScaling scaling) noexcept -> std::string_view { switch(scaling) { case DevAmbiScaling::FuMa: return "FuMa"sv; case DevAmbiScaling::SN3D: return "SN3D"sv; case DevAmbiScaling::N3D: return "N3D"sv; } return ""sv; } std::unique_ptr CreateStablizer(const size_t outchans, const uint srate) { auto stablizer = FrontStablizer::Create(outchans); /* Initialize band-splitting filter for the mid signal, with a crossover at * 5khz (could be higher). */ stablizer->MidFilter.init(5000.0f / static_cast(srate)); for(auto &filter : stablizer->ChannelFilters) filter = stablizer->MidFilter; return stablizer; } void AllocChannels(al::Device *device, const size_t main_chans, const size_t real_chans) { TRACE("Channel config, Main: {}, Real: {}", main_chans, real_chans); /* Allocate extra channels for any post-filter output. */ const size_t num_chans{main_chans + real_chans}; TRACE("Allocating {} channels, {} bytes", num_chans, num_chans*sizeof(device->MixBuffer[0])); device->MixBuffer.resize(num_chans); al::span buffer{device->MixBuffer}; device->Dry.Buffer = buffer.first(main_chans); buffer = buffer.subspan(main_chans); if(real_chans != 0) { device->RealOut.Buffer = buffer.first(real_chans); buffer = buffer.subspan(real_chans); } else device->RealOut.Buffer = device->Dry.Buffer; } using ChannelCoeffs = std::array; enum DecoderMode : bool { SingleBand = false, DualBand = true }; template struct DecoderConfig; template struct DecoderConfig { uint8_t mOrder{}; bool mIs3D{}; std::array mChannels{}; DevAmbiScaling mScaling{}; std::array mOrderGain{}; std::array mCoeffs{}; }; template struct DecoderConfig { uint8_t mOrder{}; bool mIs3D{}; std::array mChannels{}; DevAmbiScaling mScaling{}; std::array mOrderGain{}; std::array mCoeffs{}; std::array mOrderGainLF{}; std::array mCoeffsLF{}; }; template<> struct DecoderConfig { uint8_t mOrder{}; bool mIs3D{}; al::span mChannels; DevAmbiScaling mScaling{}; al::span mOrderGain; al::span mCoeffs; al::span mOrderGainLF; al::span mCoeffsLF; template DecoderConfig& operator=(const DecoderConfig &rhs) noexcept { mOrder = rhs.mOrder; mIs3D = rhs.mIs3D; mChannels = rhs.mChannels; mScaling = rhs.mScaling; mOrderGain = rhs.mOrderGain; mCoeffs = rhs.mCoeffs; mOrderGainLF = {}; mCoeffsLF = {}; return *this; } template DecoderConfig& operator=(const DecoderConfig &rhs) noexcept { mOrder = rhs.mOrder; mIs3D = rhs.mIs3D; mChannels = rhs.mChannels; mScaling = rhs.mScaling; mOrderGain = rhs.mOrderGain; mCoeffs = rhs.mCoeffs; mOrderGainLF = rhs.mOrderGainLF; mCoeffsLF = rhs.mCoeffsLF; return *this; } explicit operator bool() const noexcept { return !mChannels.empty(); } }; using DecoderView = DecoderConfig; void InitNearFieldCtrl(al::Device *device, const float ctrl_dist, const uint order, const bool is3d) { static const std::array chans_per_order2d{{1, 2, 2, 2}}; static const std::array chans_per_order3d{{1, 3, 5, 7}}; /* NFC is only used when AvgSpeakerDist is greater than 0. */ if(!device->getConfigValueBool("decoder", "nfc", false) || !(ctrl_dist > 0.0f)) return; device->AvgSpeakerDist = std::clamp(ctrl_dist, 0.1f, 10.0f); TRACE("Using near-field reference distance: {:.2f} meters", device->AvgSpeakerDist); const float w1{SpeedOfSoundMetersPerSec / (device->AvgSpeakerDist * static_cast(device->mSampleRate))}; device->mNFCtrlFilter.init(w1); auto iter = std::copy_n(is3d ? chans_per_order3d.begin() : chans_per_order2d.begin(), order+1u, device->NumChannelsPerOrder.begin()); std::fill(iter, device->NumChannelsPerOrder.end(), 0u); } void InitDistanceComp(al::Device *device, const al::span channels, const al::span dists) { const float maxdist{std::accumulate(dists.begin(), dists.end(), 0.0f, [](const float a, const float b) noexcept -> float { return std::max(a, b); })}; if(!device->getConfigValueBool("decoder", "distance-comp", true) || !(maxdist > 0.0f)) return; const auto distSampleScale = static_cast(device->mSampleRate)/SpeedOfSoundMetersPerSec; struct DistCoeffs { uint Length{}; float Gain{}; }; std::vector ChanDelay; ChanDelay.reserve(device->RealOut.Buffer.size()); size_t total{0u}; for(size_t chidx{0};chidx < channels.size();++chidx) { const Channel ch{channels[chidx]}; const size_t idx{device->RealOut.ChannelIndex[ch]}; if(idx == InvalidChannelIndex) continue; const float distance{dists[chidx]}; /* Distance compensation only delays in steps of the sample rate. This * is a bit less accurate since the delay time falls to the nearest * sample time, but it's far simpler as it doesn't have to deal with * phase offsets. This means at 48khz, for instance, the distance delay * will be in steps of about 7 millimeters. */ float delay{std::floor((maxdist - distance)*distSampleScale + 0.5f)}; if(delay > float{DistanceComp::MaxDelay-1}) { ERR("Delay for channel {} ({}) exceeds buffer length ({:f} > {})", idx, GetLabelFromChannel(ch), delay, DistanceComp::MaxDelay-1); delay = float{DistanceComp::MaxDelay-1}; } ChanDelay.resize(std::max(ChanDelay.size(), idx+1_uz)); ChanDelay[idx].Length = static_cast(delay); ChanDelay[idx].Gain = distance / maxdist; TRACE("Channel {} distance comp: {} samples, {:f} gain", GetLabelFromChannel(ch), ChanDelay[idx].Length, ChanDelay[idx].Gain); /* Round up to the next 4th sample, so each channel buffer starts * 16-byte aligned. */ total += RoundUp(ChanDelay[idx].Length, 4); } if(total > 0) { auto chandelays = DistanceComp::Create(total); auto chanbuffer = chandelays->mSamples.begin(); auto set_bufptr = [&chanbuffer](const DistCoeffs &data) { DistanceComp::ChanData ret{}; ret.Buffer = al::span{chanbuffer, data.Length}; ret.Gain = data.Gain; chanbuffer += ptrdiff_t(RoundUp(data.Length, 4)); return ret; }; std::transform(ChanDelay.begin(), ChanDelay.end(), chandelays->mChannels.begin(), set_bufptr); device->ChannelDelays = std::move(chandelays); } } constexpr auto GetAmbiScales(DevAmbiScaling scaletype) noexcept { if(scaletype == DevAmbiScaling::FuMa) return al::span{AmbiScale::FromFuMa}; if(scaletype == DevAmbiScaling::SN3D) return al::span{AmbiScale::FromSN3D}; return al::span{AmbiScale::FromN3D}; } constexpr auto GetAmbiLayout(DevAmbiLayout layouttype) noexcept { if(layouttype == DevAmbiLayout::FuMa) return al::span{AmbiIndex::FromFuMa}; return al::span{AmbiIndex::FromACN}; } auto MakeDecoderView(al::Device *device, const AmbDecConf *conf, DecoderConfig &decoder) -> DecoderView { DecoderView ret{}; decoder.mOrder = (conf->ChanMask > Ambi3OrderMask) ? uint8_t{4} : (conf->ChanMask > Ambi2OrderMask) ? uint8_t{3} : (conf->ChanMask > Ambi1OrderMask) ? uint8_t{2} : uint8_t{1}; decoder.mIs3D = (conf->ChanMask&AmbiPeriphonicMask) != 0; switch(conf->CoeffScale) { case AmbDecScale::Unset: ASSUME(false); break; case AmbDecScale::N3D: decoder.mScaling = DevAmbiScaling::N3D; break; case AmbDecScale::SN3D: decoder.mScaling = DevAmbiScaling::SN3D; break; case AmbDecScale::FuMa: decoder.mScaling = DevAmbiScaling::FuMa; break; } const auto hfordermin = std::min(conf->HFOrderGain.size(), decoder.mOrderGain.size()); std::copy_n(conf->HFOrderGain.begin(), hfordermin, decoder.mOrderGain.begin()); const auto lfordermin = std::min(conf->LFOrderGain.size(), decoder.mOrderGainLF.size()); std::copy_n(conf->LFOrderGain.begin(), lfordermin, decoder.mOrderGainLF.begin()); const auto num_coeffs = decoder.mIs3D ? AmbiChannelsFromOrder(decoder.mOrder) : Ambi2DChannelsFromOrder(decoder.mOrder); const auto idx_map = decoder.mIs3D ? al::span{AmbiIndex::FromACN} : al::span{AmbiIndex::FromACN2D}; const auto hfmatrix = conf->HFMatrix; const auto lfmatrix = conf->LFMatrix; uint chan_count{0}; for(auto &speaker : al::span{std::as_const(conf->Speakers)}) { /* NOTE: AmbDec does not define any standard speaker names, however * for this to work we have to by able to find the output channel * the speaker definition corresponds to. Therefore, OpenAL Soft * requires these channel labels to be recognized: * * LF = Front left * RF = Front right * LS = Side left * RS = Side right * LB = Back left * RB = Back right * CE = Front center * CB = Back center * LFT = Top front left * RFT = Top front right * LBT = Top back left * RBT = Top back right * LFB = Bottom front left * RFB = Bottom front right * LBB = Bottom back left * RBB = Bottom back right * * Additionally, surround51 will acknowledge back speakers for side * channels, to avoid issues with an ambdec expecting 5.1 to use the * back channels. */ Channel ch{}; if(speaker.Name == "LF"sv) ch = FrontLeft; else if(speaker.Name == "RF"sv) ch = FrontRight; else if(speaker.Name == "CE"sv) ch = FrontCenter; else if(speaker.Name == "LS"sv) ch = SideLeft; else if(speaker.Name == "RS"sv) ch = SideRight; else if(speaker.Name == "LB"sv) ch = (device->FmtChans == DevFmtX51) ? SideLeft : BackLeft; else if(speaker.Name == "RB"sv) ch = (device->FmtChans == DevFmtX51) ? SideRight : BackRight; else if(speaker.Name == "CB"sv) ch = BackCenter; else if(speaker.Name == "LFT"sv) ch = TopFrontLeft; else if(speaker.Name == "RFT"sv) ch = TopFrontRight; else if(speaker.Name == "LBT"sv) ch = TopBackLeft; else if(speaker.Name == "RBT"sv) ch = TopBackRight; else if(speaker.Name == "LFB"sv) ch = BottomFrontLeft; else if(speaker.Name == "RFB"sv) ch = BottomFrontRight; else if(speaker.Name == "LBB"sv) ch = BottomBackLeft; else if(speaker.Name == "RBB"sv) ch = BottomBackRight; else { int idx{}; char c{}; /* NOLINTNEXTLINE(cert-err34-c,cppcoreguidelines-pro-type-vararg) */ if(sscanf(speaker.Name.c_str(), "AUX%d%c", &idx, &c) != 1 || idx < 0 || idx >= MaxChannels-Aux0) { ERR("AmbDec speaker label \"{}\" not recognized", speaker.Name); continue; } ch = static_cast(Aux0+idx); } decoder.mChannels[chan_count] = ch; for(size_t dst{0};dst < num_coeffs;++dst) { const size_t src{idx_map[dst]}; decoder.mCoeffs[chan_count][dst] = hfmatrix[chan_count][src]; } if(conf->FreqBands > 1) { for(size_t dst{0};dst < num_coeffs;++dst) { const size_t src{idx_map[dst]}; decoder.mCoeffsLF[chan_count][dst] = lfmatrix[chan_count][src]; } } ++chan_count; } if(chan_count > 0) { ret.mOrder = decoder.mOrder; ret.mIs3D = decoder.mIs3D; ret.mScaling = decoder.mScaling; ret.mChannels = al::span{decoder.mChannels}.first(chan_count); ret.mOrderGain = decoder.mOrderGain; ret.mCoeffs = al::span{decoder.mCoeffs}.first(chan_count); if(conf->FreqBands > 1) { ret.mOrderGainLF = decoder.mOrderGainLF; ret.mCoeffsLF = al::span{decoder.mCoeffsLF}.first(chan_count); } } return ret; } constexpr DecoderConfig MonoConfig{ 0, false, {{FrontCenter}}, DevAmbiScaling::N3D, {{1.0f}}, {{ {{1.0f}} }} }; constexpr DecoderConfig StereoConfig{ 1, false, {{FrontLeft, FrontRight}}, DevAmbiScaling::N3D, {{1.0f, 1.0f}}, {{ {{5.00000000e-1f, 2.88675135e-1f, 5.52305643e-2f}}, {{5.00000000e-1f, -2.88675135e-1f, 5.52305643e-2f}}, }} }; constexpr DecoderConfig QuadConfig{ 1, false, {{BackLeft, FrontLeft, FrontRight, BackRight}}, DevAmbiScaling::N3D, /*HF*/{{1.41421356e+0f, 1.00000000e+0f}}, {{ {{2.50000000e-1f, 2.04124145e-1f, -2.04124145e-1f}}, {{2.50000000e-1f, 2.04124145e-1f, 2.04124145e-1f}}, {{2.50000000e-1f, -2.04124145e-1f, 2.04124145e-1f}}, {{2.50000000e-1f, -2.04124145e-1f, -2.04124145e-1f}}, }}, /*LF*/{{1.00000000e+0f, 1.00000000e+0f}}, {{ {{2.50000000e-1f, 2.04124145e-1f, -2.04124145e-1f}}, {{2.50000000e-1f, 2.04124145e-1f, 2.04124145e-1f}}, {{2.50000000e-1f, -2.04124145e-1f, 2.04124145e-1f}}, {{2.50000000e-1f, -2.04124145e-1f, -2.04124145e-1f}}, }} }; constexpr DecoderConfig X51Config{ 2, false, {{SideLeft, FrontLeft, FrontCenter, FrontRight, SideRight}}, DevAmbiScaling::FuMa, /*HF*/{{1.00000000e+0f, 1.00000000e+0f, 1.00000000e+0f}}, {{ {{5.67316000e-1f, 4.22920000e-1f, -3.15495000e-1f, -6.34490000e-2f, -2.92380000e-2f}}, {{3.68584000e-1f, 2.72349000e-1f, 3.21616000e-1f, 1.92645000e-1f, 4.82600000e-2f}}, {{1.83579000e-1f, 0.00000000e+0f, 1.99588000e-1f, 0.00000000e+0f, 9.62820000e-2f}}, {{3.68584000e-1f, -2.72349000e-1f, 3.21616000e-1f, -1.92645000e-1f, 4.82600000e-2f}}, {{5.67316000e-1f, -4.22920000e-1f, -3.15495000e-1f, 6.34490000e-2f, -2.92380000e-2f}}, }}, /*LF*/{{1.00000000e+0f, 1.00000000e+0f, 1.00000000e+0f}}, {{ {{4.90109850e-1f, 3.77305010e-1f, -3.73106990e-1f, -1.25914530e-1f, 1.45133000e-2f}}, {{1.49085730e-1f, 3.03561680e-1f, 1.53290060e-1f, 2.45112480e-1f, -1.50753130e-1f}}, {{1.37654920e-1f, 0.00000000e+0f, 4.49417940e-1f, 0.00000000e+0f, 2.57844070e-1f}}, {{1.49085730e-1f, -3.03561680e-1f, 1.53290060e-1f, -2.45112480e-1f, -1.50753130e-1f}}, {{4.90109850e-1f, -3.77305010e-1f, -3.73106990e-1f, 1.25914530e-1f, 1.45133000e-2f}}, }} }; constexpr DecoderConfig X61Config{ 2, false, {{SideLeft, FrontLeft, FrontRight, SideRight, BackCenter}}, DevAmbiScaling::N3D, {{1.0f, 1.0f, 1.0f}}, {{ {{2.04460341e-1f, 2.17177926e-1f, -4.39996780e-2f, -2.60790269e-2f, -6.87239792e-2f}}, {{1.58923161e-1f, 9.21772680e-2f, 1.59658796e-1f, 6.66278083e-2f, 3.84686854e-2f}}, {{1.58923161e-1f, -9.21772680e-2f, 1.59658796e-1f, -6.66278083e-2f, 3.84686854e-2f}}, {{2.04460341e-1f, -2.17177926e-1f, -4.39996780e-2f, 2.60790269e-2f, -6.87239792e-2f}}, {{2.50001688e-1f, 0.00000000e+0f, -2.50000094e-1f, 0.00000000e+0f, 6.05133395e-2f}}, }} }; constexpr DecoderConfig X71Config{ 2, false, {{BackLeft, SideLeft, FrontLeft, FrontRight, SideRight, BackRight}}, DevAmbiScaling::N3D, /*HF*/{{1.41421356e+0f, 1.22474487e+0f, 7.07106781e-1f}}, {{ {{1.66666667e-1f, 9.62250449e-2f, -1.66666667e-1f, -1.49071198e-1f, 8.60662966e-2f}}, {{1.66666667e-1f, 1.92450090e-1f, 0.00000000e+0f, 0.00000000e+0f, -1.72132593e-1f}}, {{1.66666667e-1f, 9.62250449e-2f, 1.66666667e-1f, 1.49071198e-1f, 8.60662966e-2f}}, {{1.66666667e-1f, -9.62250449e-2f, 1.66666667e-1f, -1.49071198e-1f, 8.60662966e-2f}}, {{1.66666667e-1f, -1.92450090e-1f, 0.00000000e+0f, 0.00000000e+0f, -1.72132593e-1f}}, {{1.66666667e-1f, -9.62250449e-2f, -1.66666667e-1f, 1.49071198e-1f, 8.60662966e-2f}}, }}, /*LF*/{{1.00000000e+0f, 1.00000000e+0f, 1.00000000e+0f}}, {{ {{1.66666667e-1f, 9.62250449e-2f, -1.66666667e-1f, -1.49071198e-1f, 8.60662966e-2f}}, {{1.66666667e-1f, 1.92450090e-1f, 0.00000000e+0f, 0.00000000e+0f, -1.72132593e-1f}}, {{1.66666667e-1f, 9.62250449e-2f, 1.66666667e-1f, 1.49071198e-1f, 8.60662966e-2f}}, {{1.66666667e-1f, -9.62250449e-2f, 1.66666667e-1f, -1.49071198e-1f, 8.60662966e-2f}}, {{1.66666667e-1f, -1.92450090e-1f, 0.00000000e+0f, 0.00000000e+0f, -1.72132593e-1f}}, {{1.66666667e-1f, -9.62250449e-2f, -1.66666667e-1f, 1.49071198e-1f, 8.60662966e-2f}}, }} }; constexpr DecoderConfig X3D71Config{ 1, true, {{Aux0, SideLeft, FrontLeft, FrontRight, SideRight, Aux1}}, DevAmbiScaling::N3D, /*HF*/{{1.73205081e+0f, 1.00000000e+0f}}, {{ {{1.666666667e-01f, 0.000000000e+00f, 2.356640879e-01f, -1.667265410e-01f}}, {{1.666666667e-01f, 2.033043281e-01f, -1.175581508e-01f, -1.678904388e-01f}}, {{1.666666667e-01f, 2.033043281e-01f, 1.175581508e-01f, 1.678904388e-01f}}, {{1.666666667e-01f, -2.033043281e-01f, 1.175581508e-01f, 1.678904388e-01f}}, {{1.666666667e-01f, -2.033043281e-01f, -1.175581508e-01f, -1.678904388e-01f}}, {{1.666666667e-01f, 0.000000000e+00f, -2.356640879e-01f, 1.667265410e-01f}}, }}, /*LF*/{{1.00000000e+0f, 1.00000000e+0f}}, {{ {{1.666666667e-01f, 0.000000000e+00f, 2.356640879e-01f, -1.667265410e-01f}}, {{1.666666667e-01f, 2.033043281e-01f, -1.175581508e-01f, -1.678904388e-01f}}, {{1.666666667e-01f, 2.033043281e-01f, 1.175581508e-01f, 1.678904388e-01f}}, {{1.666666667e-01f, -2.033043281e-01f, 1.175581508e-01f, 1.678904388e-01f}}, {{1.666666667e-01f, -2.033043281e-01f, -1.175581508e-01f, -1.678904388e-01f}}, {{1.666666667e-01f, 0.000000000e+00f, -2.356640879e-01f, 1.667265410e-01f}}, }} }; constexpr DecoderConfig X714Config{ 1, true, {{FrontLeft, FrontRight, SideLeft, SideRight, BackLeft, BackRight, TopFrontLeft, TopFrontRight, TopBackLeft, TopBackRight }}, DevAmbiScaling::N3D, {{1.00000000e+0f, 1.00000000e+0f, 1.00000000e+0f}}, {{ {{1.27149251e-01f, 7.63047539e-02f, -3.64373750e-02f, 1.59700680e-01f}}, {{1.07005418e-01f, -7.67638760e-02f, -4.92129762e-02f, 1.29012797e-01f}}, {{1.26400196e-01f, 1.77494694e-01f, -3.71203389e-02f, 0.00000000e+00f}}, {{1.26396516e-01f, -1.77488059e-01f, -3.71297878e-02f, 0.00000000e+00f}}, {{1.06996956e-01f, 7.67615256e-02f, -4.92166307e-02f, -1.29001640e-01f}}, {{1.27145671e-01f, -7.63003471e-02f, -3.64353304e-02f, -1.59697510e-01f}}, {{8.80919747e-02f, 7.48940670e-02f, 9.08786244e-02f, 6.22527183e-02f}}, {{1.57880745e-01f, -7.28755272e-02f, 1.82364187e-01f, 8.74240284e-02f}}, {{1.57892225e-01f, 7.28944768e-02f, 1.82363474e-01f, -8.74301086e-02f}}, {{8.80892603e-02f, -7.48948724e-02f, 9.08779842e-02f, -6.22480443e-02f}}, }} }; constexpr DecoderConfig X7144Config{ 1, true, {{BackLeft, SideLeft, FrontLeft, FrontRight, SideRight, BackRight, TopBackLeft, TopFrontLeft, TopFrontRight, TopBackRight, BottomBackLeft, BottomFrontLeft, BottomFrontRight, BottomBackRight}}, DevAmbiScaling::N3D, /*HF*/{{2.64575131e+0f, 1.52752523e+0f}}, {{ {{7.14285714e-02f, 5.09426708e-02f, 0.00000000e+00f, -8.82352941e-02f}}, {{7.14285714e-02f, 1.01885342e-01f, 0.00000000e+00f, 0.00000000e+00f}}, {{7.14285714e-02f, 5.09426708e-02f, 0.00000000e+00f, 8.82352941e-02f}}, {{7.14285714e-02f, -5.09426708e-02f, 0.00000000e+00f, 8.82352941e-02f}}, {{7.14285714e-02f, -1.01885342e-01f, 0.00000000e+00f, 0.00000000e+00f}}, {{7.14285714e-02f, -5.09426708e-02f, 0.00000000e+00f, -8.82352941e-02f}}, {{7.14285714e-02f, 5.88235294e-02f, 1.25000000e-01f, -5.88235294e-02f}}, {{7.14285714e-02f, 5.88235294e-02f, 1.25000000e-01f, 5.88235294e-02f}}, {{7.14285714e-02f, -5.88235294e-02f, 1.25000000e-01f, 5.88235294e-02f}}, {{7.14285714e-02f, -5.88235294e-02f, 1.25000000e-01f, -5.88235294e-02f}}, {{7.14285714e-02f, 5.88235294e-02f, -1.25000000e-01f, -5.88235294e-02f}}, {{7.14285714e-02f, 5.88235294e-02f, -1.25000000e-01f, 5.88235294e-02f}}, {{7.14285714e-02f, -5.88235294e-02f, -1.25000000e-01f, 5.88235294e-02f}}, {{7.14285714e-02f, -5.88235294e-02f, -1.25000000e-01f, -5.88235294e-02f}}, }}, /*LF*/{{1.00000000e+0f, 1.00000000e+0f}}, {{ {{7.14285714e-02f, 5.09426708e-02f, 0.00000000e+00f, -8.82352941e-02f}}, {{7.14285714e-02f, 1.01885342e-01f, 0.00000000e+00f, 0.00000000e+00f}}, {{7.14285714e-02f, 5.09426708e-02f, 0.00000000e+00f, 8.82352941e-02f}}, {{7.14285714e-02f, -5.09426708e-02f, 0.00000000e+00f, 8.82352941e-02f}}, {{7.14285714e-02f, -1.01885342e-01f, 0.00000000e+00f, 0.00000000e+00f}}, {{7.14285714e-02f, -5.09426708e-02f, 0.00000000e+00f, -8.82352941e-02f}}, {{7.14285714e-02f, 5.88235294e-02f, 1.25000000e-01f, -5.88235294e-02f}}, {{7.14285714e-02f, 5.88235294e-02f, 1.25000000e-01f, 5.88235294e-02f}}, {{7.14285714e-02f, -5.88235294e-02f, 1.25000000e-01f, 5.88235294e-02f}}, {{7.14285714e-02f, -5.88235294e-02f, 1.25000000e-01f, -5.88235294e-02f}}, {{7.14285714e-02f, 5.88235294e-02f, -1.25000000e-01f, -5.88235294e-02f}}, {{7.14285714e-02f, 5.88235294e-02f, -1.25000000e-01f, 5.88235294e-02f}}, {{7.14285714e-02f, -5.88235294e-02f, -1.25000000e-01f, 5.88235294e-02f}}, {{7.14285714e-02f, -5.88235294e-02f, -1.25000000e-01f, -5.88235294e-02f}}, }} }; void InitPanning(al::Device *device, const bool hqdec=false, const bool stablize=false, DecoderView decoder={}) { if(!decoder) { switch(device->FmtChans) { case DevFmtMono: decoder = MonoConfig; break; case DevFmtStereo: decoder = StereoConfig; break; case DevFmtQuad: decoder = QuadConfig; break; case DevFmtX51: decoder = X51Config; break; case DevFmtX61: decoder = X61Config; break; case DevFmtX71: decoder = X71Config; break; case DevFmtX714: decoder = X714Config; break; case DevFmtX7144: decoder = X7144Config; break; case DevFmtX3D71: decoder = X3D71Config; break; case DevFmtAmbi3D: /* For DevFmtAmbi3D, the ambisonic order is already set. */ const size_t count{AmbiChannelsFromOrder(device->mAmbiOrder)}; const auto acnmap = GetAmbiLayout(device->mAmbiLayout).first(count); const auto n3dscale = GetAmbiScales(device->mAmbiScale); std::transform(acnmap.cbegin(), acnmap.cend(), device->Dry.AmbiMap.begin(), [n3dscale](const uint8_t &acn) noexcept -> BFChannelConfig { return BFChannelConfig{1.0f/n3dscale[acn], acn}; }); AllocChannels(device, count, 0); device->m2DMixing = false; float avg_dist{}; if(auto distopt = device->configValue("decoder", "speaker-dist")) avg_dist = *distopt; else if(auto delayopt = device->configValue("decoder", "nfc-ref-delay")) { WARN("nfc-ref-delay is deprecated, use speaker-dist instead"); avg_dist = *delayopt * SpeedOfSoundMetersPerSec; } TRACE("{}{} order ambisonic output ({} layout, {} scaling)", device->mAmbiOrder, GetCounterSuffix(device->mAmbiOrder), GetLayoutName(device->mAmbiLayout), GetScalingName(device->mAmbiScale)); InitNearFieldCtrl(device, avg_dist, device->mAmbiOrder, true); return; } } const size_t ambicount{decoder.mIs3D ? AmbiChannelsFromOrder(decoder.mOrder) : Ambi2DChannelsFromOrder(decoder.mOrder)}; const bool dual_band{hqdec && !decoder.mCoeffsLF.empty()}; std::vector chancoeffs, chancoeffslf; for(size_t i{0u};i < decoder.mChannels.size();++i) { const size_t idx{device->channelIdxByName(decoder.mChannels[i])}; if(idx == InvalidChannelIndex) { ERR("Failed to find {} channel in device", GetLabelFromChannel(decoder.mChannels[i])); continue; } auto ordermap = decoder.mIs3D ? al::span{AmbiIndex::OrderFromChannel} : al::span{AmbiIndex::OrderFrom2DChannel}; chancoeffs.resize(std::max(chancoeffs.size(), idx+1_zu), ChannelDec{}); al::span src{decoder.mCoeffs[i]}; al::span dst{chancoeffs[idx]}; for(size_t ambichan{0};ambichan < ambicount;++ambichan) dst[ambichan] = src[ambichan] * decoder.mOrderGain[ordermap[ambichan]]; if(!dual_band) continue; chancoeffslf.resize(std::max(chancoeffslf.size(), idx+1_zu), ChannelDec{}); src = decoder.mCoeffsLF[i]; dst = chancoeffslf[idx]; for(size_t ambichan{0};ambichan < ambicount;++ambichan) dst[ambichan] = src[ambichan] * decoder.mOrderGainLF[ordermap[ambichan]]; } /* For non-DevFmtAmbi3D, set the ambisonic order. */ device->mAmbiOrder = decoder.mOrder; device->m2DMixing = !decoder.mIs3D; const auto acnmap = decoder.mIs3D ? al::span{AmbiIndex::FromACN}.first(ambicount) : al::span{AmbiIndex::FromACN2D}.first(ambicount); const auto coeffscale = GetAmbiScales(decoder.mScaling); std::transform(acnmap.begin(), acnmap.end(), device->Dry.AmbiMap.begin(), [coeffscale](const uint8_t &acn) noexcept { return BFChannelConfig{1.0f/coeffscale[acn], acn}; }); AllocChannels(device, ambicount, device->channelsFromFmt()); std::unique_ptr stablizer; if(stablize) { /* Only enable the stablizer if the decoder does not output to the * front-center channel. */ const size_t cidx{device->RealOut.ChannelIndex[FrontCenter]}; bool hasfc{false}; if(cidx < chancoeffs.size()) { for(const auto &coeff : chancoeffs[cidx]) hasfc |= coeff != 0.0f; } if(!hasfc && cidx < chancoeffslf.size()) { for(const auto &coeff : chancoeffslf[cidx]) hasfc |= coeff != 0.0f; } if(!hasfc) { stablizer = CreateStablizer(device->channelsFromFmt(), device->mSampleRate); TRACE("Front stablizer enabled"); } } TRACE("Enabling {}-band {}-order{} ambisonic decoder", !dual_band ? "single" : "dual", (decoder.mOrder > 3) ? "fourth" : (decoder.mOrder > 2) ? "third" : (decoder.mOrder > 1) ? "second" : "first", decoder.mIs3D ? " periphonic" : ""); device->AmbiDecoder = BFormatDec::Create(ambicount, chancoeffs, chancoeffslf, device->mXOverFreq/static_cast(device->mSampleRate), std::move(stablizer)); } void InitHrtfPanning(al::Device *device) { static constexpr float Deg180{al::numbers::pi_v}; static constexpr float Deg_90{Deg180 / 2.0f /* 90 degrees*/}; static constexpr float Deg_45{Deg_90 / 2.0f /* 45 degrees*/}; static constexpr float Deg135{Deg_45 * 3.0f /*135 degrees*/}; static constexpr float Deg_21{3.648638281e-01f /* 20~ 21 degrees*/}; static constexpr float Deg_32{5.535743589e-01f /* 31~ 32 degrees*/}; static constexpr float Deg_35{6.154797087e-01f /* 35~ 36 degrees*/}; static constexpr float Deg_58{1.017221968e+00f /* 58~ 59 degrees*/}; static constexpr float Deg_69{1.205932499e+00f /* 69~ 70 degrees*/}; static constexpr float Deg111{1.935660155e+00f /*110~111 degrees*/}; static constexpr float Deg122{2.124370686e+00f /*121~122 degrees*/}; static constexpr std::array AmbiPoints1O{ AngularPoint{EvRadians{ Deg_35}, AzRadians{-Deg_45}}, AngularPoint{EvRadians{ Deg_35}, AzRadians{-Deg135}}, AngularPoint{EvRadians{ Deg_35}, AzRadians{ Deg_45}}, AngularPoint{EvRadians{ Deg_35}, AzRadians{ Deg135}}, AngularPoint{EvRadians{-Deg_35}, AzRadians{-Deg_45}}, AngularPoint{EvRadians{-Deg_35}, AzRadians{-Deg135}}, AngularPoint{EvRadians{-Deg_35}, AzRadians{ Deg_45}}, AngularPoint{EvRadians{-Deg_35}, AzRadians{ Deg135}}, }; static constexpr std::array AmbiPoints2O{ AngularPoint{EvRadians{-Deg_32}, AzRadians{ 0.0f}}, AngularPoint{EvRadians{ 0.0f}, AzRadians{ Deg_58}}, AngularPoint{EvRadians{ Deg_58}, AzRadians{ Deg_90}}, AngularPoint{EvRadians{ Deg_32}, AzRadians{ 0.0f}}, AngularPoint{EvRadians{ 0.0f}, AzRadians{ Deg122}}, AngularPoint{EvRadians{-Deg_58}, AzRadians{-Deg_90}}, AngularPoint{EvRadians{-Deg_32}, AzRadians{ Deg180}}, AngularPoint{EvRadians{ 0.0f}, AzRadians{-Deg122}}, AngularPoint{EvRadians{ Deg_58}, AzRadians{-Deg_90}}, AngularPoint{EvRadians{ Deg_32}, AzRadians{ Deg180}}, AngularPoint{EvRadians{ 0.0f}, AzRadians{-Deg_58}}, AngularPoint{EvRadians{-Deg_58}, AzRadians{ Deg_90}}, }; static constexpr std::array AmbiPoints3O{ AngularPoint{EvRadians{ Deg_69}, AzRadians{-Deg_90}}, AngularPoint{EvRadians{ Deg_69}, AzRadians{ Deg_90}}, AngularPoint{EvRadians{-Deg_69}, AzRadians{-Deg_90}}, AngularPoint{EvRadians{-Deg_69}, AzRadians{ Deg_90}}, AngularPoint{EvRadians{ 0.0f}, AzRadians{-Deg_69}}, AngularPoint{EvRadians{ 0.0f}, AzRadians{-Deg111}}, AngularPoint{EvRadians{ 0.0f}, AzRadians{ Deg_69}}, AngularPoint{EvRadians{ 0.0f}, AzRadians{ Deg111}}, AngularPoint{EvRadians{ Deg_21}, AzRadians{ 0.0f}}, AngularPoint{EvRadians{ Deg_21}, AzRadians{ Deg180}}, AngularPoint{EvRadians{-Deg_21}, AzRadians{ 0.0f}}, AngularPoint{EvRadians{-Deg_21}, AzRadians{ Deg180}}, AngularPoint{EvRadians{ Deg_35}, AzRadians{-Deg_45}}, AngularPoint{EvRadians{ Deg_35}, AzRadians{-Deg135}}, AngularPoint{EvRadians{ Deg_35}, AzRadians{ Deg_45}}, AngularPoint{EvRadians{ Deg_35}, AzRadians{ Deg135}}, AngularPoint{EvRadians{-Deg_35}, AzRadians{-Deg_45}}, AngularPoint{EvRadians{-Deg_35}, AzRadians{-Deg135}}, AngularPoint{EvRadians{-Deg_35}, AzRadians{ Deg_45}}, AngularPoint{EvRadians{-Deg_35}, AzRadians{ Deg135}}, }; static constexpr std::array AmbiMatrix1O{ ChannelCoeffs{1.250000000e-01f, 1.250000000e-01f, 1.250000000e-01f, 1.250000000e-01f}, ChannelCoeffs{1.250000000e-01f, 1.250000000e-01f, 1.250000000e-01f, -1.250000000e-01f}, ChannelCoeffs{1.250000000e-01f, -1.250000000e-01f, 1.250000000e-01f, 1.250000000e-01f}, ChannelCoeffs{1.250000000e-01f, -1.250000000e-01f, 1.250000000e-01f, -1.250000000e-01f}, ChannelCoeffs{1.250000000e-01f, 1.250000000e-01f, -1.250000000e-01f, 1.250000000e-01f}, ChannelCoeffs{1.250000000e-01f, 1.250000000e-01f, -1.250000000e-01f, -1.250000000e-01f}, ChannelCoeffs{1.250000000e-01f, -1.250000000e-01f, -1.250000000e-01f, 1.250000000e-01f}, ChannelCoeffs{1.250000000e-01f, -1.250000000e-01f, -1.250000000e-01f, -1.250000000e-01f}, }; static constexpr std::array AmbiMatrix2O{ ChannelCoeffs{8.333333333e-02f, 0.000000000e+00f, -7.588274978e-02f, 1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.591525047e-02f, -1.443375673e-01f, 1.167715449e-01f}, ChannelCoeffs{8.333333333e-02f, -1.227808683e-01f, 0.000000000e+00f, 7.588274978e-02f, -1.443375673e-01f, 0.000000000e+00f, -9.316949906e-02f, 0.000000000e+00f, -7.216878365e-02f}, ChannelCoeffs{8.333333333e-02f, -7.588274978e-02f, 1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.443375673e-01f, 1.090847495e-01f, 0.000000000e+00f, -4.460276122e-02f}, ChannelCoeffs{8.333333333e-02f, 0.000000000e+00f, 7.588274978e-02f, 1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.591525047e-02f, 1.443375673e-01f, 1.167715449e-01f}, ChannelCoeffs{8.333333333e-02f, -1.227808683e-01f, 0.000000000e+00f, -7.588274978e-02f, 1.443375673e-01f, 0.000000000e+00f, -9.316949906e-02f, 0.000000000e+00f, -7.216878365e-02f}, ChannelCoeffs{8.333333333e-02f, 7.588274978e-02f, -1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.443375673e-01f, 1.090847495e-01f, 0.000000000e+00f, -4.460276122e-02f}, ChannelCoeffs{8.333333333e-02f, 0.000000000e+00f, -7.588274978e-02f, -1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.591525047e-02f, 1.443375673e-01f, 1.167715449e-01f}, ChannelCoeffs{8.333333333e-02f, 1.227808683e-01f, 0.000000000e+00f, -7.588274978e-02f, -1.443375673e-01f, 0.000000000e+00f, -9.316949906e-02f, 0.000000000e+00f, -7.216878365e-02f}, ChannelCoeffs{8.333333333e-02f, 7.588274978e-02f, 1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, 1.443375673e-01f, 1.090847495e-01f, 0.000000000e+00f, -4.460276122e-02f}, ChannelCoeffs{8.333333333e-02f, 0.000000000e+00f, 7.588274978e-02f, -1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.591525047e-02f, -1.443375673e-01f, 1.167715449e-01f}, ChannelCoeffs{8.333333333e-02f, 1.227808683e-01f, 0.000000000e+00f, 7.588274978e-02f, 1.443375673e-01f, 0.000000000e+00f, -9.316949906e-02f, 0.000000000e+00f, -7.216878365e-02f}, ChannelCoeffs{8.333333333e-02f, -7.588274978e-02f, -1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, 1.443375673e-01f, 1.090847495e-01f, 0.000000000e+00f, -4.460276122e-02f}, }; static constexpr std::array AmbiMatrix3O{ ChannelCoeffs{5.000000000e-02f, 3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, 6.454972244e-02f, 9.045084972e-02f, 0.000000000e+00f, -1.232790000e-02f, -1.256118221e-01f, 0.000000000e+00f, 1.126112056e-01f, 7.944389175e-02f, 0.000000000e+00f, 2.421151497e-02f, 0.000000000e+00f}, ChannelCoeffs{5.000000000e-02f, -3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -6.454972244e-02f, 9.045084972e-02f, 0.000000000e+00f, -1.232790000e-02f, 1.256118221e-01f, 0.000000000e+00f, -1.126112056e-01f, 7.944389175e-02f, 0.000000000e+00f, 2.421151497e-02f, 0.000000000e+00f}, ChannelCoeffs{5.000000000e-02f, 3.090169944e-02f, -8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -6.454972244e-02f, 9.045084972e-02f, 0.000000000e+00f, -1.232790000e-02f, -1.256118221e-01f, 0.000000000e+00f, 1.126112056e-01f, -7.944389175e-02f, 0.000000000e+00f, -2.421151497e-02f, 0.000000000e+00f}, ChannelCoeffs{5.000000000e-02f, -3.090169944e-02f, -8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, 6.454972244e-02f, 9.045084972e-02f, 0.000000000e+00f, -1.232790000e-02f, 1.256118221e-01f, 0.000000000e+00f, -1.126112056e-01f, -7.944389175e-02f, 0.000000000e+00f, -2.421151497e-02f, 0.000000000e+00f}, ChannelCoeffs{5.000000000e-02f, 8.090169944e-02f, 0.000000000e+00f, 3.090169944e-02f, 6.454972244e-02f, 0.000000000e+00f, -5.590169944e-02f, 0.000000000e+00f, -7.216878365e-02f, -7.763237543e-02f, 0.000000000e+00f, -2.950836627e-02f, 0.000000000e+00f, -1.497759251e-01f, 0.000000000e+00f, -7.763237543e-02f}, ChannelCoeffs{5.000000000e-02f, 8.090169944e-02f, 0.000000000e+00f, -3.090169944e-02f, -6.454972244e-02f, 0.000000000e+00f, -5.590169944e-02f, 0.000000000e+00f, -7.216878365e-02f, -7.763237543e-02f, 0.000000000e+00f, -2.950836627e-02f, 0.000000000e+00f, 1.497759251e-01f, 0.000000000e+00f, 7.763237543e-02f}, ChannelCoeffs{5.000000000e-02f, -8.090169944e-02f, 0.000000000e+00f, 3.090169944e-02f, -6.454972244e-02f, 0.000000000e+00f, -5.590169944e-02f, 0.000000000e+00f, -7.216878365e-02f, 7.763237543e-02f, 0.000000000e+00f, 2.950836627e-02f, 0.000000000e+00f, -1.497759251e-01f, 0.000000000e+00f, -7.763237543e-02f}, ChannelCoeffs{5.000000000e-02f, -8.090169944e-02f, 0.000000000e+00f, -3.090169944e-02f, 6.454972244e-02f, 0.000000000e+00f, -5.590169944e-02f, 0.000000000e+00f, -7.216878365e-02f, 7.763237543e-02f, 0.000000000e+00f, 2.950836627e-02f, 0.000000000e+00f, 1.497759251e-01f, 0.000000000e+00f, 7.763237543e-02f}, ChannelCoeffs{5.000000000e-02f, 0.000000000e+00f, 3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -3.454915028e-02f, 6.454972244e-02f, 8.449668365e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, 3.034486645e-02f, -6.779013272e-02f, 1.659481923e-01f, 4.797944664e-02f}, ChannelCoeffs{5.000000000e-02f, 0.000000000e+00f, 3.090169944e-02f, -8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -3.454915028e-02f, -6.454972244e-02f, 8.449668365e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, 3.034486645e-02f, 6.779013272e-02f, 1.659481923e-01f, -4.797944664e-02f}, ChannelCoeffs{5.000000000e-02f, 0.000000000e+00f, -3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -3.454915028e-02f, -6.454972244e-02f, 8.449668365e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, -3.034486645e-02f, -6.779013272e-02f, -1.659481923e-01f, 4.797944664e-02f}, ChannelCoeffs{5.000000000e-02f, 0.000000000e+00f, -3.090169944e-02f, -8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -3.454915028e-02f, 6.454972244e-02f, 8.449668365e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, -3.034486645e-02f, 6.779013272e-02f, -1.659481923e-01f, -4.797944664e-02f}, ChannelCoeffs{5.000000000e-02f, 5.000000000e-02f, 5.000000000e-02f, 5.000000000e-02f, 6.454972244e-02f, 6.454972244e-02f, 0.000000000e+00f, 6.454972244e-02f, 0.000000000e+00f, 1.016220987e-01f, 6.338656910e-02f, -1.092600649e-02f, -7.364853795e-02f, 1.011266756e-01f, -7.086833869e-02f, -1.482646439e-02f}, ChannelCoeffs{5.000000000e-02f, 5.000000000e-02f, 5.000000000e-02f, -5.000000000e-02f, -6.454972244e-02f, 6.454972244e-02f, 0.000000000e+00f, -6.454972244e-02f, 0.000000000e+00f, 1.016220987e-01f, -6.338656910e-02f, -1.092600649e-02f, -7.364853795e-02f, -1.011266756e-01f, -7.086833869e-02f, 1.482646439e-02f}, ChannelCoeffs{5.000000000e-02f, -5.000000000e-02f, 5.000000000e-02f, 5.000000000e-02f, -6.454972244e-02f, -6.454972244e-02f, 0.000000000e+00f, 6.454972244e-02f, 0.000000000e+00f, -1.016220987e-01f, -6.338656910e-02f, 1.092600649e-02f, -7.364853795e-02f, 1.011266756e-01f, -7.086833869e-02f, -1.482646439e-02f}, ChannelCoeffs{5.000000000e-02f, -5.000000000e-02f, 5.000000000e-02f, -5.000000000e-02f, 6.454972244e-02f, -6.454972244e-02f, 0.000000000e+00f, -6.454972244e-02f, 0.000000000e+00f, -1.016220987e-01f, 6.338656910e-02f, 1.092600649e-02f, -7.364853795e-02f, -1.011266756e-01f, -7.086833869e-02f, 1.482646439e-02f}, ChannelCoeffs{5.000000000e-02f, 5.000000000e-02f, -5.000000000e-02f, 5.000000000e-02f, 6.454972244e-02f, -6.454972244e-02f, 0.000000000e+00f, -6.454972244e-02f, 0.000000000e+00f, 1.016220987e-01f, -6.338656910e-02f, -1.092600649e-02f, 7.364853795e-02f, 1.011266756e-01f, 7.086833869e-02f, -1.482646439e-02f}, ChannelCoeffs{5.000000000e-02f, 5.000000000e-02f, -5.000000000e-02f, -5.000000000e-02f, -6.454972244e-02f, -6.454972244e-02f, 0.000000000e+00f, 6.454972244e-02f, 0.000000000e+00f, 1.016220987e-01f, 6.338656910e-02f, -1.092600649e-02f, 7.364853795e-02f, -1.011266756e-01f, 7.086833869e-02f, 1.482646439e-02f}, ChannelCoeffs{5.000000000e-02f, -5.000000000e-02f, -5.000000000e-02f, 5.000000000e-02f, -6.454972244e-02f, 6.454972244e-02f, 0.000000000e+00f, -6.454972244e-02f, 0.000000000e+00f, -1.016220987e-01f, 6.338656910e-02f, 1.092600649e-02f, 7.364853795e-02f, 1.011266756e-01f, 7.086833869e-02f, -1.482646439e-02f}, ChannelCoeffs{5.000000000e-02f, -5.000000000e-02f, -5.000000000e-02f, -5.000000000e-02f, 6.454972244e-02f, 6.454972244e-02f, 0.000000000e+00f, 6.454972244e-02f, 0.000000000e+00f, -1.016220987e-01f, -6.338656910e-02f, 1.092600649e-02f, 7.364853795e-02f, -1.011266756e-01f, 7.086833869e-02f, 1.482646439e-02f}, }; static constexpr std::array AmbiOrderHFGain1O{ /*ENRGY*/ 2.000000000e+00f, 1.154700538e+00f }; static constexpr std::array AmbiOrderHFGain2O{ /*ENRGY*/ 1.825741858e+00f, 1.414213562e+00f, 7.302967433e-01f /*AMP 1.000000000e+00f, 7.745966692e-01f, 4.000000000e-01f*/ /*RMS 9.128709292e-01f, 7.071067812e-01f, 3.651483717e-01f*/ }; static constexpr std::array AmbiOrderHFGain3O{ /*ENRGY 1.865086714e+00f, 1.606093894e+00f, 1.142055301e+00f, 5.683795528e-01f*/ /*AMP*/ 1.000000000e+00f, 8.611363116e-01f, 6.123336207e-01f, 3.047469850e-01f /*RMS 8.340921354e-01f, 7.182670250e-01f, 5.107426573e-01f, 2.541870634e-01f*/ }; static_assert(AmbiPoints1O.size() == AmbiMatrix1O.size(), "First-Order Ambisonic HRTF mismatch"); static_assert(AmbiPoints2O.size() == AmbiMatrix2O.size(), "Second-Order Ambisonic HRTF mismatch"); static_assert(AmbiPoints3O.size() == AmbiMatrix3O.size(), "Third-Order Ambisonic HRTF mismatch"); /* A 700hz crossover frequency provides tighter sound imaging at the sweet * spot with ambisonic decoding, as the distance between the ears is closer * to half this frequency wavelength, which is the optimal point where the * response should change between optimizing phase vs volume. Normally this * tighter imaging is at the cost of a smaller sweet spot, but since the * listener is fixed in the center of the HRTF responses for the decoder, * we don't have to worry about ever being out of the sweet spot. * * A better option here may be to have the head radius as part of the HRTF * data set and calculate the optimal crossover frequency from that. */ device->mXOverFreq = 700.0f; /* Don't bother with HOA when using full HRTF rendering. Nothing needs it, * and it eases the CPU/memory load. */ device->mRenderMode = RenderMode::Hrtf; uint ambi_order{1}; if(auto modeopt = device->configValue({}, "hrtf-mode")) { struct HrtfModeEntry { std::string_view name; RenderMode mode; uint order; }; constexpr std::array hrtf_modes{ HrtfModeEntry{"full"sv, RenderMode::Hrtf, 1}, HrtfModeEntry{"ambi1"sv, RenderMode::Normal, 1}, HrtfModeEntry{"ambi2"sv, RenderMode::Normal, 2}, HrtfModeEntry{"ambi3"sv, RenderMode::Normal, 3}, }; std::string_view mode{*modeopt}; if(al::case_compare(mode, "basic"sv) == 0) { ERR("HRTF mode \"{}\" deprecated, substituting \"{}\"", *modeopt, "ambi2"); mode = "ambi2"; } auto match_entry = [mode](const HrtfModeEntry &entry) -> bool { return al::case_compare(mode, entry.name) == 0; }; auto iter = std::find_if(hrtf_modes.begin(), hrtf_modes.end(), match_entry); if(iter == hrtf_modes.end()) ERR("Unexpected hrtf-mode: {}", *modeopt); else { device->mRenderMode = iter->mode; ambi_order = iter->order; } } TRACE("{}{} order {}HRTF rendering enabled, using \"{}\"", ambi_order, GetCounterSuffix(ambi_order), (device->mRenderMode == RenderMode::Hrtf) ? "+ Full " : "", device->mHrtfName); bool perHrirMin{false}; auto AmbiPoints = al::span{AmbiPoints1O}.subspan(0); auto AmbiMatrix = al::span{AmbiMatrix1O}.subspan(0); auto AmbiOrderHFGain = al::span{AmbiOrderHFGain1O}; if(ambi_order >= 3) { perHrirMin = true; AmbiPoints = AmbiPoints3O; AmbiMatrix = AmbiMatrix3O; AmbiOrderHFGain = AmbiOrderHFGain3O; } else if(ambi_order == 2) { AmbiPoints = AmbiPoints2O; AmbiMatrix = AmbiMatrix2O; AmbiOrderHFGain = AmbiOrderHFGain2O; } device->mAmbiOrder = ambi_order; device->m2DMixing = false; const size_t count{AmbiChannelsFromOrder(ambi_order)}; const auto acnmap = al::span{AmbiIndex::FromACN}.first(count); std::transform(acnmap.begin(), acnmap.end(), device->Dry.AmbiMap.begin(), [](const uint8_t &index) noexcept { return BFChannelConfig{1.0f, index}; }); AllocChannels(device, count, device->channelsFromFmt()); HrtfStore *Hrtf{device->mHrtf.get()}; auto hrtfstate = DirectHrtfState::Create(count); hrtfstate->build(Hrtf, device->mIrSize, perHrirMin, AmbiPoints, AmbiMatrix, device->mXOverFreq, AmbiOrderHFGain); device->mHrtfState = std::move(hrtfstate); InitNearFieldCtrl(device, Hrtf->mFields[0].distance, ambi_order, true); } void InitUhjPanning(al::Device *device) { /* UHJ is always 2D first-order. */ static constexpr size_t count{Ambi2DChannelsFromOrder(1)}; device->mAmbiOrder = 1; device->m2DMixing = true; const auto acnmap = al::span{AmbiIndex::FromFuMa2D}.first(); std::transform(acnmap.cbegin(), acnmap.cend(), device->Dry.AmbiMap.begin(), [](const uint8_t &acn) noexcept -> BFChannelConfig { return BFChannelConfig{1.0f/AmbiScale::FromUHJ[acn], acn}; }); AllocChannels(device, count, device->channelsFromFmt()); } } // namespace void aluInitRenderer(al::Device *device, int hrtf_id, std::optional stereomode) { /* Hold the HRTF the device last used, in case it's used again. */ HrtfStorePtr old_hrtf{std::move(device->mHrtf)}; device->mHrtfState = nullptr; device->mHrtf = nullptr; device->mIrSize = 0; device->mHrtfName.clear(); device->mXOverFreq = 400.0f; device->m2DMixing = false; device->mRenderMode = RenderMode::Normal; if(device->FmtChans != DevFmtStereo) { old_hrtf = nullptr; if(stereomode && *stereomode == StereoEncoding::Hrtf) device->mHrtfStatus = ALC_HRTF_UNSUPPORTED_FORMAT_SOFT; const char *layout{nullptr}; switch(device->FmtChans) { case DevFmtQuad: layout = "quad"; break; case DevFmtX51: layout = "surround51"; break; case DevFmtX61: layout = "surround61"; break; case DevFmtX71: layout = "surround71"; break; case DevFmtX714: layout = "surround714"; break; case DevFmtX7144: layout = "surround7144"; break; case DevFmtX3D71: layout = "surround3d71"; break; /* Mono, Stereo, and Ambisonics output don't use custom decoders. */ case DevFmtMono: case DevFmtStereo: case DevFmtAmbi3D: break; } std::unique_ptr> decoder_store; DecoderView decoder{}; std::array speakerdists{}; auto load_config = [device,&decoder_store,&decoder,&speakerdists](const char *config) { AmbDecConf conf{}; if(auto err = conf.load(config)) { ERR("Failed to load layout file {}", config); ERR(" {}", *err); return false; } if(conf.Speakers.size() > MaxOutputChannels) { ERR("Unsupported decoder speaker count {} (max {})", conf.Speakers.size(), MaxOutputChannels); return false; } if(conf.ChanMask > Ambi3OrderMask) { ERR("Unsupported decoder channel mask {:#x} (max {:#x})", conf.ChanMask, Ambi3OrderMask); return false; } TRACE("Using {} decoder: \"{}\"", DevFmtChannelsString(device->FmtChans), conf.Description); device->mXOverFreq = std::clamp(conf.XOverFreq, 100.0f, 1000.0f); decoder_store = std::make_unique>(); decoder = MakeDecoderView(device, &conf, *decoder_store); const auto confspeakers = al::span{std::as_const(conf.Speakers)} .first(decoder.mChannels.size()); std::transform(confspeakers.cbegin(), confspeakers.cend(), speakerdists.begin(), std::mem_fn(&AmbDecConf::SpeakerConf::Distance)); return true; }; bool usingCustom{false}; if(layout) { if(auto decopt = device->configValue("decoder", layout)) usingCustom = load_config(decopt->c_str()); } if(!usingCustom && device->FmtChans != DevFmtAmbi3D) TRACE("Using built-in {} decoder", DevFmtChannelsString(device->FmtChans)); /* Enable the stablizer only for formats that have front-left, front- * right, and front-center outputs. */ const bool stablize{device->RealOut.ChannelIndex[FrontCenter] != InvalidChannelIndex && device->RealOut.ChannelIndex[FrontLeft] != InvalidChannelIndex && device->RealOut.ChannelIndex[FrontRight] != InvalidChannelIndex && device->getConfigValueBool({}, "front-stablizer", false)}; const bool hqdec{device->getConfigValueBool("decoder", "hq-mode", true)}; InitPanning(device, hqdec, stablize, decoder); if(decoder) { float accum_dist{0.0f}, spkr_count{0.0f}; for(auto dist : speakerdists) { if(dist > 0.0f) { accum_dist += dist; spkr_count += 1.0f; } } const float avg_dist{(accum_dist > 0.0f && spkr_count > 0) ? accum_dist/spkr_count : device->configValue("decoder", "speaker-dist").value_or(1.0f)}; InitNearFieldCtrl(device, avg_dist, decoder.mOrder, decoder.mIs3D); if(spkr_count > 0) InitDistanceComp(device, decoder.mChannels, speakerdists); } if(auto *ambidec{device->AmbiDecoder.get()}) { device->PostProcess = ambidec->hasStablizer() ? &al::Device::ProcessAmbiDecStablized : &al::Device::ProcessAmbiDec; } return; } /* If HRTF is explicitly requested, or if there's no explicit request and * the device is headphones, try to enable it. */ if(stereomode.value_or(StereoEncoding::Default) == StereoEncoding::Hrtf || (!stereomode && device->Flags.test(DirectEar))) { if(device->mHrtfList.empty()) device->enumerateHrtfs(); if(hrtf_id >= 0 && static_cast(hrtf_id) < device->mHrtfList.size()) { const std::string_view hrtfname{device->mHrtfList[static_cast(hrtf_id)]}; if(HrtfStorePtr hrtf{GetLoadedHrtf(hrtfname, device->mSampleRate)}) { device->mHrtf = std::move(hrtf); device->mHrtfName = hrtfname; } } if(!device->mHrtf) { for(const std::string_view hrtfname : device->mHrtfList) { if(HrtfStorePtr hrtf{GetLoadedHrtf(hrtfname, device->mSampleRate)}) { device->mHrtf = std::move(hrtf); device->mHrtfName = hrtfname; break; } } } if(device->mHrtf) { old_hrtf = nullptr; HrtfStore *hrtf{device->mHrtf.get()}; device->mIrSize = hrtf->mIrSize; if(auto hrtfsizeopt = device->configValue({}, "hrtf-size")) { if(*hrtfsizeopt > 0 && *hrtfsizeopt < device->mIrSize) device->mIrSize = std::max(*hrtfsizeopt, MinIrLength); } InitHrtfPanning(device); device->PostProcess = &al::Device::ProcessHrtf; device->mHrtfStatus = ALC_HRTF_ENABLED_SOFT; return; } } old_hrtf = nullptr; if(stereomode.value_or(StereoEncoding::Default) == StereoEncoding::Uhj) { auto ftype = std::string_view{}; switch(UhjEncodeQuality) { case UhjQualityType::IIR: device->mUhjEncoder = std::make_unique(); ftype = "IIR"sv; break; case UhjQualityType::FIR256: device->mUhjEncoder = std::make_unique>(); ftype = "FIR-256"sv; break; case UhjQualityType::FIR512: device->mUhjEncoder = std::make_unique>(); ftype = "FIR-512"sv; break; } assert(device->mUhjEncoder != nullptr); TRACE("UHJ enabled ({} encoder)", ftype); InitUhjPanning(device); device->PostProcess = &al::Device::ProcessUhj; return; } device->mRenderMode = RenderMode::Pairwise; if(device->Type != DeviceType::Loopback) { if(auto cflevopt = device->configValue({}, "cf_level")) { if(*cflevopt > 0 && *cflevopt <= 6) { auto bs2b = std::make_unique(); bs2b->set_params(*cflevopt, static_cast(device->mSampleRate)); device->Bs2b = std::move(bs2b); TRACE("BS2B enabled"); InitPanning(device); device->PostProcess = &al::Device::ProcessBs2b; return; } } } TRACE("Stereo rendering"); InitPanning(device); device->PostProcess = &al::Device::ProcessAmbiDec; } void aluInitEffectPanning(EffectSlot *slot, ALCcontext *context) { DeviceBase *device{context->mDevice}; const size_t count{AmbiChannelsFromOrder(device->mAmbiOrder)}; slot->mWetBuffer.resize(count); const auto acnmap = al::span{AmbiIndex::FromACN}.first(count); const auto iter = std::transform(acnmap.cbegin(), acnmap.cend(), slot->Wet.AmbiMap.begin(), [](const uint8_t &acn) noexcept -> BFChannelConfig { return BFChannelConfig{1.0f, acn}; }); std::fill(iter, slot->Wet.AmbiMap.end(), BFChannelConfig{}); slot->Wet.Buffer = slot->mWetBuffer; } openal-soft-1.24.2/alsoftrc.sample000066400000000000000000000657441474041540300170710ustar00rootroot00000000000000# OpenAL config file. # # Option blocks may appear multiple times, and duplicated options will take the # last value specified. Environment variables may be specified within option # values, and are automatically substituted when the config file is loaded. # Environment variable names may only contain alpha-numeric characters (a-z, # A-Z, 0-9) and underscores (_), and are prefixed with $. For example, # specifying "$HOME/file.ext" would typically result in something like # "/home/user/file.ext". To specify an actual "$" character, use "$$". # # Device-specific values may be specified by including the device name in the # block name, with "general" replaced by the device name. That is, general # options for the device "Name of Device" would be in the [Name of Device] # block, while ALSA options would be in the [alsa/Name of Device] block. # Options marked as "(global)" are not influenced by the device. # # The system-wide settings can be put in /etc/xdg/alsoft.conf (as determined by # the XDG_CONFIG_DIRS env var list, /etc/xdg being the default if unset) and # user-specific override settings in $HOME/.config/alsoft.conf (as determined # by the XDG_CONFIG_HOME env var). # # For Windows, these settings should go into $AppData\alsoft.ini # # An additional configuration file (alsoft.ini on Windows, alsoft.conf on other # OSs) can be placed alongside the process executable for app-specific config # settings. # # Option and block names are case-senstive. The supplied values are only hints # and may not be honored (though generally it'll try to get as close as # possible). Note: options that are left unset may default to app- or system- # specified values. These are the current available settings: ## ## General stuff ## [general] ## disable-cpu-exts: (global) # Disables use of specialized methods that use specific CPU intrinsics. # Certain methods may utilize CPU extensions for improved performance, and # this option is useful for preventing some or all of those methods from being # used. The available extensions are: sse, sse2, sse3, sse4.1, and neon. # Specifying 'all' disables use of all such specialized methods. #disable-cpu-exts = ## drivers: (global) # Sets the backend driver list order, comma-seperated. Unknown backends and # duplicated names are ignored. Unlisted backends won't be considered for use # unless the list is ended with a comma (e.g. 'oss,' will try OSS first before # other backends, while 'oss' will try OSS only). Backends prepended with - # won't be considered for use (e.g. '-oss,' will try all available backends # except OSS). An empty list means to try all backends. #drivers = ## channels: # Sets the default output channel configuration. If left unspecified, one will # try to be detected from the system, with a fallback to stereo. The available # values are: mono, stereo, quad, surround51, surround61, surround71, # surround714, surround3d71, ambi1, ambi2, ambi3. Note that the ambi* # configurations output ambisonic channels of the given order (using ACN # ordering and SN3D normalization by default), which need to be decoded to # play correctly on speakers. #channels = ## sample-type: # Sets the default output sample type. Currently, all mixing is done with # 32-bit float and converted to the output sample type as needed. Available # values are: # int8 - signed 8-bit int # uint8 - unsigned 8-bit int # int16 - signed 16-bit int # uint16 - unsigned 16-bit int # int32 - signed 32-bit int # uint32 - unsigned 32-bit int # float32 - 32-bit float #sample-type = float32 ## frequency: # Sets the default output frequency. If left unspecified it will try to detect # a default from the system, otherwise it will fallback to 48000. #frequency = ## period_size: # Sets the update period size, in sample frames. This is the number of frames # needed for each mixing update. Acceptable values range between 64 and 8192. # If left unspecified it will default to 1/50th of the frequency (20ms, or 882 # for 44100, 960 for 48000, etc). #period_size = ## periods: # Sets the number of update periods. Higher values create a larger mix ahead, # which helps protect against skips when the CPU is under load, but increases # the delay between a sound getting mixed and being heard. Acceptable values # range between 2 and 16. #periods = 3 ## stereo-mode: # Specifies if stereo output is treated as being headphones or speakers. With # headphones, HRTF or crossfeed filters may be used for better audio quality. # Valid settings are auto, speakers, and headphones. #stereo-mode = auto ## stereo-encoding: # Specifies the default encoding method for stereo output. Valid values are: # basic - Standard amplitude panning (aka pair-wise, stereo pair, etc) between # -30 and +30 degrees. # uhj - Creates a stereo-compatible two-channel UHJ mix, which encodes some # surround sound information into stereo output that can be decoded with # a surround sound receiver. # hrtf - Uses filters to provide better spatialization of sounds while using # stereo headphones. # If crossfeed filters are used, basic stereo mixing is used. #stereo-encoding = basic ## ambi-format: # Specifies the channel order and normalization for the "ambi*" set of channel # configurations. Valid settings are: fuma, acn+fuma, ambix (or acn+sn3d), or # acn+n3d #ambi-format = ambix ## hrtf: # Deprecated. Consider using stereo-encoding instead. Valid values are auto, # off, and on. #hrtf = auto ## hrtf-mode: # Specifies the rendering mode for HRTF processing. Setting the mode to full # (default) applies a unique HRIR filter to each source given its relative # location, providing the clearest directional response at the cost of the # highest CPU usage. Setting the mode to ambi1, ambi2, or ambi3 will instead # mix to a first-, second-, or third-order ambisonic buffer respectively, then # decode that buffer with HRTF filters. Ambi1 has the lowest CPU usage, # replacing the per-source HRIR filter for a simple 4-channel panning mix, but # retains full 3D placement at the cost of a more diffuse response. Ambi2 and # ambi3 increasingly improve the directional clarity, at the cost of more CPU # usage (still less than "full", given some number of active sources). #hrtf-mode = full ## hrtf-size: # Specifies the impulse response size, in samples, for the HRTF filter. Larger # values increase the filter quality, while smaller values reduce processing # cost. A value of 0 (default) uses the full filter size in the dataset, and # the default dataset has a filter size of 64 samples at 48khz. #hrtf-size = 0 ## default-hrtf: # Specifies the default HRTF to use. When multiple HRTFs are available, this # determines the preferred one to use if none are specifically requested. Note # that this is the enumerated HRTF name, not necessarily the filename. #default-hrtf = ## hrtf-paths: # Specifies a comma-separated list of paths containing HRTF data sets. The # format of the files are described in docs/hrtf.txt. The files within the # directories must have the .mhr file extension to be recognized. By default, # OS-dependent data paths will be used. They will also be used if the list # ends with a comma. On Windows this is: # $AppData\openal\hrtf # And on other systems, it's (in order): # $XDG_DATA_HOME/openal/hrtf (defaults to $HOME/.local/share/openal/hrtf) # $XDG_DATA_DIRS/openal/hrtf (defaults to /usr/local/share/openal/hrtf and # /usr/share/openal/hrtf) #hrtf-paths = ## cf_level: # Sets the crossfeed level for stereo output. Valid values are: # 0 - No crossfeed # 1 - Low crossfeed # 2 - Middle crossfeed # 3 - High crossfeed (virtual speakers are closer to itself) # 4 - Low easy crossfeed # 5 - Middle easy crossfeed # 6 - High easy crossfeed # Users of headphones may want to try various settings. Has no effect on non- # stereo modes. #cf_level = 0 ## resampler: (global) # Selects the default resampler used when mixing sources. Valid values are: # point - nearest sample, no interpolation # linear - extrapolates samples using a linear slope between samples # spline - extrapolates samples using a Catmull-Rom spline # gaussian - extrapolates samples using a 4-point Gaussian filter # bsinc12 - extrapolates samples using a band-limited Sinc filter (varying # between 12 and 24 points, with anti-aliasing) # fast_bsinc12 - same as bsinc12, except without interpolation between down- # sampling scales # bsinc24 - extrapolates samples using a band-limited Sinc filter (varying # between 24 and 48 points, with anti-aliasing) # fast_bsinc24 - same as bsinc24, except without interpolation between down- # sampling scales #resampler = gaussian ## rt-prio: (global) # Sets the real-time priority value for the mixing thread. Not all drivers may # use this (eg. PortAudio) as those APIs already control the priority of the # mixing thread. 0 and negative values will disable real-time priority. Note # that this may constitute a security risk since a real-time priority thread # can indefinitely block normal-priority threads if it fails to wait. Disable # this if it turns out to be a problem. #rt-prio = 1 ## rt-time-limit: (global) # On non-Windows systems, allows reducing the process's RLIMIT_RTTIME resource # as necessary for acquiring real-time priority from RTKit. #rt-time-limit = true ## sources: # Sets the maximum number of allocatable sources. Lower values may help for # systems with apps that try to play more sounds than the CPU can handle. #sources = 256 ## slots: # Sets the maximum number of Auxiliary Effect Slots an app can create. A slot # can use a non-negligible amount of CPU time if an effect is set on it even # if no sources are feeding it, so this may help when apps use more than the # system can handle. #slots = 64 ## sends: # Limits the number of auxiliary sends allowed per source. Setting this higher # than the default has no effect. #sends = 6 ## front-stablizer: # Applies filters to "stablize" front sound imaging. A psychoacoustic method # is used to generate a front-center channel signal from the front-left and # front-right channels, improving the front response by reducing the combing # artifacts and phase errors. Consequently, it will only work with channel # configurations that include front-left, front-right, and front-center. #front-stablizer = false ## output-limiter: # Applies a gain limiter on the final mixed output. This reduces the volume # when the output samples would otherwise clamp, avoiding excessive clipping # noise. On by default for integer sample types, and off by default for # floating-point. #output-limiter = ## dither: # Applies dithering on the final mix, enabled by default for 8- and 16-bit # output. This replaces the distortion created by nearest-value quantization # with low-level whitenoise. #dither = ## dither-depth: # Quantization bit-depth for dithered output. A value of 0 (or less) will # match the output sample depth. For int32, uint32, and float32 output, 0 will # disable dithering because they're at or beyond the rendered precision. The # maximum dither depth is 24. #dither-depth = 0 ## volume-adjust: # A global volume adjustment for source output, expressed in decibels. The # value is logarithmic, so +6 will be a scale of (approximately) 2x, +12 will # be a scale of 4x, etc. Similarly, -6 will be x1/2, and -12 is about x1/4. A # value of 0 means no change. #volume-adjust = 0 ## excludefx: (global) # Sets which effects to exclude, preventing apps from using them. This can # help for apps that try to use effects which are too CPU intensive for the # system to handle. Available effects are: eaxreverb,reverb,autowah,chorus, # compressor,distortion,echo,equalizer,flanger,modulator,dedicated,pshifter, # fshifter,vmorpher. #excludefx = ## default-reverb: (global) # A reverb preset that applies by default to all sources on send 0 # (applications that set their own slots on send 0 will override this). # Available presets include: None, Generic, PaddedCell, Room, Bathroom, # Livingroom, Stoneroom, Auditorium, ConcertHall, Cave, Arena, Hangar, # CarpetedHallway, Hallway, StoneCorridor, Alley, Forest, City, Mountains, # Quarry, Plain, ParkingLot, SewerPipe, Underwater, Drugged, Dizzy, Psychotic. #default-reverb = ## trap-alc-error: (global) # Generates a SIGTRAP signal when an ALC device error is generated, on systems # that support it. This helps when debugging, while trying to find the cause # of a device error. On Windows, a breakpoint exception is generated. #trap-alc-error = false ## trap-al-error: (global) # Generates a SIGTRAP signal when an AL context error is generated, on systems # that support it. This helps when debugging, while trying to find the cause # of a context error. On Windows, a breakpoint exception is generated. #trap-al-error = false ## ## Ambisonic decoder stuff ## [decoder] ## hq-mode: # Enables a high-quality ambisonic decoder. This mode is capable of frequency- # dependent processing, creating a better reproduction of 3D sound rendering # over surround sound speakers. #hq-mode = true ## distance-comp: # Enables compensation for the speakers' relative distances to the listener. # This applies the necessary delays and attenuation to make the speakers # behave as though they are all equidistant, which is important for proper # playback of 3D sound rendering. Requires the proper distances to be # specified in the decoder configuration file. #distance-comp = true ## nfc: # Enables near-field control filters. This simulates and compensates for low- # frequency effects caused by the curvature of nearby sound-waves, which # creates a more realistic perception of sound distance with surround sound # output. Note that the effect may be stronger or weaker than intended if the # application doesn't use or specify an appropriate unit scale, or if # incorrect speaker distances are set. For HRTF output, hrtf-mode must be set # to one of the ambi* values for this to function. #nfc = false ## speaker-dist: # Specifies the speaker distance in meters, used by the near-field control # filters with surround sound output. For ambisonic output modes, this value # is the basis for the NFC-HOA Reference Delay parameter (calculated as # delay_seconds = speaker_dist/343.3). This value is not used when a decoder # configuration is set for the output mode (since they specify the per-speaker # distances, overriding this setting), or when the NFC filters are off. Valid # values range from 0.1 to 10. #speaker-dist = 1 ## quad: # Decoder configuration file for Quadraphonic channel output. See # docs/ambdec.txt for a description of the file format. #quad = ## surround51: # Decoder configuration file for 5.1 Surround (Side and Rear) channel output. # See docs/ambdec.txt for a description of the file format. #surround51 = ## surround61: # Decoder configuration file for 6.1 Surround channel output. See # docs/ambdec.txt for a description of the file format. #surround61 = ## surround71: # Decoder configuration file for 7.1 Surround channel output. See # docs/ambdec.txt for a description of the file format. #surround71 = ## surround714: # Decoder configuration file for 7.1.4 Surround channel output. See # docs/ambdec.txt for a description of the file format. #surround714 = ## surround3d71: # Decoder configuration file for 3D7.1 Surround channel output. See # docs/ambdec.txt for a description of the file format. See also # docs/3D7.1.txt for information about 3D7.1. #surround3d71 = ## ## UHJ and Super Stereo stuff ## [uhj] ## decode-filter: (global) # Specifies the all-pass filter type for UHJ decoding and Super Stereo # processing. Valid values are: # iir - utilizes dual IIR filters, providing a wide pass-band with low CPU # use, but causes additional phase shifts on the signal. # fir256 - utilizes a 256-point FIR filter, providing more stable results but # exhibiting attenuation in the lower and higher frequency bands. # fir512 - utilizes a 512-point FIR filter, providing a wider pass-band than # fir256, at the cost of more CPU use. #decode-filter = iir ## encode-filter: (global) # Specifies the all-pass filter type for UHJ output encoding. Valid values are # the same as for decode-filter. #encode-filter = iir ## ## Reverb effect stuff (includes EAX reverb) ## [reverb] ## boost: (global) # A global amplification for reverb output, expressed in decibels. The value # is logarithmic, so +6 will be a scale of (approximately) 2x, +12 will be a # scale of 4x, etc. Similarly, -6 will be about half, and -12 about 1/4th. A # value of 0 means no change. #boost = 0 ## ## PipeWire backend stuff ## [pipewire] ## assume-audio: (global) # Causes the backend to succeed initialization even if PipeWire reports no # audio support. Currently, audio support is detected by the presence of audio # source or sink nodes, although this can cause false negatives in cases where # device availability during library initialization is spotty. Future versions # of PipeWire are expected to have a more robust method to test audio support, # but in the mean time this can be set to true to assume PipeWire has audio # support even when no nodes may be reported at initialization time. #assume-audio = false ## rt-mix: # Renders samples directly in the real-time processing callback. This allows # for lower latency and less overall CPU utilization, but can increase the # risk of underruns when increasing the amount of work the mixer needs to do. #rt-mix = false ## ## PulseAudio backend stuff ## [pulse] ## spawn-server: (global) # Attempts to autospawn a PulseAudio server whenever needed (initializing the # backend, enumerating devices, etc). Setting autospawn to false in Pulse's # client.conf will still prevent autospawning even if this is set to true. #spawn-server = false ## allow-moves: (global) # Allows PulseAudio to move active streams to different devices. Note that the # device specifier (seen by applications) will not be updated when this # occurs, and neither will the AL device configuration (sample rate, format, # etc). #allow-moves = true ## fix-rate: # Specifies whether to match the playback stream's sample rate to the device's # sample rate. Enabling this forces OpenAL Soft to mix sources and effects # directly to the actual output rate, avoiding a second resample pass by the # PulseAudio server. #fix-rate = false ## adjust-latency: # Attempts to adjust the overall latency of device playback. Note that this # may have adverse effects on the resulting internal buffer sizes and mixing # updates, leading to performance problems and drop-outs. However, if the # PulseAudio server is creating a lot of latency, enabling this may help make # it more manageable. #adjust-latency = false ## ## ALSA backend stuff ## [alsa] ## device: (global) # Sets the device name for the default playback device. #device = default ## device-prefix: (global) # Sets the prefix used by the discovered (non-default) playback devices. This # will be appended with "CARD=c,DEV=d", where c is the card id and d is the # device index for the requested device name. #device-prefix = plughw: ## device-prefix-*: (global) # Card- and device-specific prefixes may be used to override the device-prefix # option. The option may specify the card id (eg, device-prefix-NVidia), or # the card id and device index (eg, device-prefix-NVidia-0). The card id is # case-sensitive. #device-prefix- = ## custom-devices: (global) # Specifies a list of enumerated playback devices and the ALSA devices they # refer to. The list pattern is "Display Name=ALSA device;...". The display # names will be returned for device enumeration, and the ALSA device is the # device name to open for each enumerated device. #custom-devices = ## capture: (global) # Sets the device name for the default capture device. #capture = default ## capture-prefix: (global) # Sets the prefix used by the discovered (non-default) capture devices. This # will be appended with "CARD=c,DEV=d", where c is the card id and d is the # device number for the requested device name. #capture-prefix = plughw: ## capture-prefix-*: (global) # Card- and device-specific prefixes may be used to override the # capture-prefix option. The option may specify the card id (eg, # capture-prefix-NVidia), or the card id and device index (eg, # capture-prefix-NVidia-0). The card id is case-sensitive. #capture-prefix- = ## custom-captures: (global) # Specifies a list of enumerated capture devices and the ALSA devices they # refer to. The list pattern is "Display Name=ALSA device;...". The display # names will be returned for device enumeration, and the ALSA device is the # device name to open for each enumerated device. #custom-captures = ## mmap: # Sets whether to try using mmap mode (helps reduce latencies and CPU # consumption). If mmap isn't available, it will automatically fall back to # non-mmap mode. True, yes, on, and non-0 values will attempt to use mmap. 0 # and anything else will force mmap off. #mmap = true ## allow-resampler: # Specifies whether to allow ALSA's built-in resampler. Enabling this will # allow the playback device to be set to a different sample rate than the # actual output, causing ALSA to apply its own resampling pass after OpenAL # Soft resamples and mixes the sources and effects for output. #allow-resampler = false ## ## OSS backend stuff ## [oss] ## device: (global) # Sets the device name for OSS output. #device = /dev/dsp ## capture: (global) # Sets the device name for OSS capture. #capture = /dev/dsp ## ## Solaris backend stuff ## [solaris] ## device: (global) # Sets the device name for Solaris output. #device = /dev/audio ## ## QSA backend stuff ## [qsa] ## ## JACK backend stuff ## [jack] ## spawn-server: (global) # Attempts to autospawn a JACK server when initializing. #spawn-server = false ## custom-devices: (global) # Specifies a list of enumerated devices and the ports they connect to. The # list pattern is "Display Name=ports regex;Display Name=ports regex;...". The # display names will be returned for device enumeration, and the ports regex # is the regular expression to identify the target ports on the server (as # given by the jack_get_ports function) for each enumerated device. #custom-devices = ## rt-mix: # Renders samples directly in the real-time processing callback. This allows # for lower latency and less overall CPU utilization, but can increase the # risk of underruns when increasing the amount of work the mixer needs to do. #rt-mix = true ## connect-ports: # Attempts to automatically connect the client ports to physical server ports. # Client ports that fail to connect will leave the remaining channels # unconnected and silent (the device format won't change to accommodate). #connect-ports = true ## buffer-size: # Sets the update buffer size, in samples, that the backend will keep buffered # to handle the server's real-time processing requests. This value must be a # power of 2, or else it will be rounded up to the next power of 2. If it is # less than JACK's buffer update size, it will be clamped. This option may # be useful in case the server's update size is too small and doesn't give the # mixer time to keep enough audio available for the processing requests. # Ignored when rt-mix is true. #buffer-size = 0 ## ## WASAPI backend stuff ## [wasapi] ## spatial-api: # Specifies whether to use a Spatial Audio stream for playback. This may # provide expanded capabilities for surround sound and with-height speaker # configurations. Very experimental. #spatial-api = false ## exclusive-mode: # Enables Exlusive mode for playback devices. This uses the device directly, # allowing lower latencies but prevents the device from being used multiple # times simultaneously. Ignores the periods setting when enabled, as WASAPI # automatically sets a buffer size based on the period size. #exclusive-mode = false ## allow-resampler: # Specifies whether to allow an extra resampler pass on the output. Enabling # this will allow the playback device to be set to a different sample rate # than the actual output can accept, causing the backend to apply its own # resampling pass after OpenAL Soft mixes the sources and processes effects # for output. #allow-resampler = true ## ## DirectSound backend stuff ## [dsound] ## ## Windows Multimedia backend stuff ## [winmm] ## ## PortAudio backend stuff ## [port] ## device: (global) # Sets the device index for output. Negative values will use the default as # given by PortAudio itself. #device = -1 ## capture: (global) # Sets the device index for capture. Negative values will use the default as # given by PortAudio itself. #capture = -1 ## ## Wave File Writer stuff ## [wave] ## file: (global) # Sets the filename of the wave file to write to. An empty name prevents the # backend from opening, even when explicitly requested. # THIS WILL OVERWRITE EXISTING FILES WITHOUT QUESTION! #file = ## bformat: (global) # Creates AMB format files using first-order ambisonics instead of a standard # single- or multi-channel .wav file. #bformat = false ## ## EAX extensions stuff ## [eax] ## enable: (global) # Sets whether to enable EAX extensions or not. #enable = true ## ## Per-game compatibility options (these should only be set in per-game config ## files, *NOT* system- or user-level!) ## [game_compat] ## default-error: (global) # An error value returned by alGetError when there's no current context. The # default value is AL_INVALID_OPERATION, which lets the caller know the # operation could not be executed. Some applications may erroneously call # alGetError without a current context and expect 0 (AL_NO_ERROR), however # that may cause other applications to think earlier AL calls succeeded when # they actually failed. #default-error = 0xA004 ## nfc-scale: (global) # A meters-per-unit distance scale applied to NFC filters. If a game doesn't # use real-world meters for in-game units, the filters may create a too-near # or too-distant effect. For instance, if the game uses 1 foot per unit, a # value of 0.3048 will correctly adjust the filters. Or if the game uses 1 # kilometer per unit, a value of 1000 will correctly adjust the filters. #nfc-scale = 1 ## enable-sub-data-ext: (global) # Enables the AL_SOFT_buffer_sub_data extension, disabling the # AL_EXT_SOURCE_RADIUS extension. These extensions are incompatible, so only # one can be available. The latter extension is more commonly used, but this # option can be enabled for older apps that want the former extension. #enable-sub-data-ext = false ## reverse-x: (global) # Reverses the local X (left-right) position of 3D sound sources. #reverse-x = false ## reverse-y: (global) # Reverses the local Y (up-down) position of 3D sound sources. #reverse-y = false ## reverse-z: (global) # Reverses the local Z (front-back) position of 3D sound sources. #reverse-z = false ## vendor-override: # Overrides the string returned by alGetString(AL_VENDOR). #vendor-override = ## version-override: # Overrides the string returned by alGetString(AL_VERSION). #version-override = ## renderer-override: # Overrides the string returned by alGetString(AL_RENDERER). #renderer-override = openal-soft-1.24.2/appveyor.yml000066400000000000000000000012211474041540300164150ustar00rootroot00000000000000version: 1.24.2.{build} environment: APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022 GEN: "Visual Studio 17 2022" matrix: - ARCH: Win32 CFG: Release - ARCH: x64 CFG: Release after_build: - 7z a ..\soft_oal.zip "%APPVEYOR_BUILD_FOLDER%\build\%CFG%\soft_oal.dll" "%APPVEYOR_BUILD_FOLDER%\README.md" "%APPVEYOR_BUILD_FOLDER%\COPYING" artifacts: - path: soft_oal.zip build_script: - cd build - cmake -G "%GEN%" -A %ARCH% -DALSOFT_BUILD_ROUTER=ON -DALSOFT_REQUIRE_WINMM=ON -DALSOFT_REQUIRE_DSOUND=ON -DALSOFT_REQUIRE_WASAPI=ON -DALSOFT_EMBED_HRTF_DATA=YES .. - cmake --build . --config %CFG% --clean-first openal-soft-1.24.2/build/000077500000000000000000000000001474041540300151305ustar00rootroot00000000000000openal-soft-1.24.2/build/.empty000066400000000000000000000000001474041540300162550ustar00rootroot00000000000000openal-soft-1.24.2/cmake/000077500000000000000000000000001474041540300151115ustar00rootroot00000000000000openal-soft-1.24.2/cmake/FindALSA.cmake000066400000000000000000000063211474041540300174360ustar00rootroot00000000000000# - Find alsa # Find the alsa libraries (asound) # # This module defines the following variables: # ALSA_FOUND - True if ALSA_INCLUDE_DIR & ALSA_LIBRARY are found # ALSA_LIBRARIES - Set when ALSA_LIBRARY is found # ALSA_INCLUDE_DIRS - Set when ALSA_INCLUDE_DIR is found # # ALSA_INCLUDE_DIR - where to find asoundlib.h, etc. # ALSA_LIBRARY - the asound library # ALSA_VERSION_STRING - the version of alsa found (since CMake 2.8.8) # #============================================================================= # Copyright 2009-2011 Kitware, Inc. # Copyright 2009-2011 Philip Lowman # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # * The names of Kitware, Inc., the Insight Consortium, or the names of # any consortium members, or of any contributors, may not be used to # endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS ``AS IS'' # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #============================================================================= find_path(ALSA_INCLUDE_DIR NAMES alsa/asoundlib.h DOC "The ALSA (asound) include directory" ) find_library(ALSA_LIBRARY NAMES asound DOC "The ALSA (asound) library" ) if(ALSA_INCLUDE_DIR AND EXISTS "${ALSA_INCLUDE_DIR}/alsa/version.h") file(STRINGS "${ALSA_INCLUDE_DIR}/alsa/version.h" alsa_version_str REGEX "^#define[\t ]+SND_LIB_VERSION_STR[\t ]+\".*\"") string(REGEX REPLACE "^.*SND_LIB_VERSION_STR[\t ]+\"([^\"]*)\".*$" "\\1" ALSA_VERSION_STRING "${alsa_version_str}") unset(alsa_version_str) endif() # handle the QUIETLY and REQUIRED arguments and set ALSA_FOUND to TRUE if # all listed variables are TRUE include(FindPackageHandleStandardArgs) find_package_handle_standard_args(ALSA REQUIRED_VARS ALSA_LIBRARY ALSA_INCLUDE_DIR VERSION_VAR ALSA_VERSION_STRING) if(ALSA_FOUND) set( ALSA_LIBRARIES ${ALSA_LIBRARY} ) set( ALSA_INCLUDE_DIRS ${ALSA_INCLUDE_DIR} ) endif() mark_as_advanced(ALSA_INCLUDE_DIR ALSA_LIBRARY) openal-soft-1.24.2/cmake/FindAudioIO.cmake000066400000000000000000000011051474041540300202020ustar00rootroot00000000000000# - Find AudioIO includes and libraries # # AUDIOIO_FOUND - True if AUDIOIO_INCLUDE_DIR is found # AUDIOIO_INCLUDE_DIRS - Set when AUDIOIO_INCLUDE_DIR is found # # AUDIOIO_INCLUDE_DIR - where to find sys/audioio.h, etc. # find_path(AUDIOIO_INCLUDE_DIR NAMES sys/audioio.h DOC "The AudioIO include directory" ) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(AudioIO REQUIRED_VARS AUDIOIO_INCLUDE_DIR) if(AUDIOIO_FOUND) set(AUDIOIO_INCLUDE_DIRS ${AUDIOIO_INCLUDE_DIR}) endif() mark_as_advanced(AUDIOIO_INCLUDE_DIR) openal-soft-1.24.2/cmake/FindFFmpeg.cmake000066400000000000000000000162721474041540300200700ustar00rootroot00000000000000# vim: ts=2 sw=2 # - Try to find the required ffmpeg components(default: AVFORMAT, AVUTIL, AVCODEC) # # Once done this will define # FFMPEG_FOUND - System has the all required components. # FFMPEG_INCLUDE_DIRS - Include directory necessary for using the required components headers. # FFMPEG_LIBRARIES - Link these to use the required ffmpeg components. # FFMPEG_DEFINITIONS - Compiler switches required for using the required ffmpeg components. # # For each of the components it will additionaly set. # - AVCODEC # - AVDEVICE # - AVFORMAT # - AVUTIL # - POSTPROC # - SWSCALE # - SWRESAMPLE # the following variables will be defined # _FOUND - System has # _INCLUDE_DIRS - Include directory necessary for using the headers # _LIBRARIES - Link these to use # _DEFINITIONS - Compiler switches required for using # _VERSION - The components version # # Copyright (c) 2006, Matthias Kretz, # Copyright (c) 2008, Alexander Neundorf, # Copyright (c) 2011, Michael Jansen, # # Redistribution and use is allowed according to the terms of the BSD license. include(FindPackageHandleStandardArgs) if(NOT FFmpeg_FIND_COMPONENTS) set(FFmpeg_FIND_COMPONENTS AVFORMAT AVCODEC AVUTIL) endif() # ### Macro: set_component_found # # Marks the given component as found if both *_LIBRARIES AND *_INCLUDE_DIRS is present. # macro(set_component_found _component) if(${_component}_LIBRARIES AND ${_component}_INCLUDE_DIRS) # message(STATUS " - ${_component} found.") set(${_component}_FOUND TRUE) else() # message(STATUS " - ${_component} not found.") endif() endmacro() # ### Macro: find_component # # Checks for the given component by invoking pkgconfig and then looking up the libraries and # include directories. # macro(find_component _component _pkgconfig _library _header) if(NOT WIN32) # use pkg-config to get the directories and then use these values # in the FIND_PATH() and FIND_LIBRARY() calls find_package(PkgConfig) if(PKG_CONFIG_FOUND) pkg_check_modules(PC_${_component} ${_pkgconfig}) endif() endif() find_path(${_component}_INCLUDE_DIRS ${_header} HINTS ${FFMPEGSDK_INC} ${PC_LIB${_component}_INCLUDEDIR} ${PC_LIB${_component}_INCLUDE_DIRS} PATH_SUFFIXES ffmpeg ) find_library(${_component}_LIBRARIES NAMES ${_library} HINTS ${FFMPEGSDK_LIB} ${PC_LIB${_component}_LIBDIR} ${PC_LIB${_component}_LIBRARY_DIRS} ) if(DEFINED ${PC_${_component}_VERSION}) set(${_component}_VERSION ${PC_${_component}_VERSION} CACHE STRING "The ${_component} version number." FORCE) else() if(EXISTS "${${_component}_INCLUDE_DIRS}/${_pkgconfig}/version_major.h") file(STRINGS "${${_component}_INCLUDE_DIRS}/${_pkgconfig}/version_major.h" majorver REGEX "^#define[ \t]+LIB${_component}_VERSION_MAJOR[ \t]+[0-9]+$") else() file(STRINGS "${${_component}_INCLUDE_DIRS}/${_pkgconfig}/version.h" majorver REGEX "^#define[ \t]+LIB${_component}_VERSION_MAJOR[ \t]+[0-9]+$") endif() file(STRINGS "${${_component}_INCLUDE_DIRS}/${_pkgconfig}/version.h" minorver REGEX "^#define[ \t]+LIB${_component}_VERSION_MINOR[ \t]+[0-9]+$") file(STRINGS "${${_component}_INCLUDE_DIRS}/${_pkgconfig}/version.h" microver REGEX "^#define[ \t]+LIB${_component}_VERSION_MICRO[ \t]+[0-9]+$") string(REGEX REPLACE "^#define[ \t]+LIB${_component}_VERSION_MAJOR[ \t]+([0-9]+)$" "\\1" majorver "${majorver}") string(REGEX REPLACE "^#define[ \t]+LIB${_component}_VERSION_MINOR[ \t]+([0-9]+)$" "\\1" minorver "${minorver}") string(REGEX REPLACE "^#define[ \t]+LIB${_component}_VERSION_MICRO[ \t]+([0-9]+)$" "\\1" microver "${microver}") set(${_component}_VERSION "${majorver}.${minorver}.${microver}" CACHE STRING "The ${_component} version number." FORCE) unset(microver) unset(minorver) unset(majorver) endif() set(${_component}_DEFINITIONS ${PC_${_component}_CFLAGS_OTHER} CACHE STRING "The ${_component} CFLAGS." FORCE) set_component_found(${_component}) mark_as_advanced( ${_component}_INCLUDE_DIRS ${_component}_LIBRARIES ${_component}_DEFINITIONS ${_component}_VERSION) endmacro() set(FFMPEGSDK $ENV{FFMPEG_HOME}) if(FFMPEGSDK) set(FFMPEGSDK_INC "${FFMPEGSDK}/include") set(FFMPEGSDK_LIB "${FFMPEGSDK}/lib") endif() # Check for all possible components. find_component(AVCODEC libavcodec avcodec libavcodec/avcodec.h) find_component(AVFORMAT libavformat avformat libavformat/avformat.h) find_component(AVDEVICE libavdevice avdevice libavdevice/avdevice.h) find_component(AVUTIL libavutil avutil libavutil/avutil.h) find_component(SWSCALE libswscale swscale libswscale/swscale.h) find_component(SWRESAMPLE libswresample swresample libswresample/swresample.h) find_component(POSTPROC libpostproc postproc libpostproc/postprocess.h) # Check if the required components were found and add their stuff to the FFMPEG_* vars. foreach(_component ${FFmpeg_FIND_COMPONENTS}) if(${_component}_FOUND) # message(STATUS "Required component ${_component} present.") set(FFMPEG_LIBRARIES ${FFMPEG_LIBRARIES} ${${_component}_LIBRARIES}) set(FFMPEG_DEFINITIONS ${FFMPEG_DEFINITIONS} ${${_component}_DEFINITIONS}) list(APPEND FFMPEG_INCLUDE_DIRS ${${_component}_INCLUDE_DIRS}) else() # message(STATUS "Required component ${_component} missing.") endif() endforeach() # Add libz if it exists (needed for static ffmpeg builds) find_library(_FFmpeg_HAVE_LIBZ NAMES z) if(_FFmpeg_HAVE_LIBZ) set(FFMPEG_LIBRARIES ${FFMPEG_LIBRARIES} ${_FFmpeg_HAVE_LIBZ}) endif() # Build the include path and library list with duplicates removed. if(FFMPEG_INCLUDE_DIRS) list(REMOVE_DUPLICATES FFMPEG_INCLUDE_DIRS) endif() if(FFMPEG_LIBRARIES) list(REMOVE_DUPLICATES FFMPEG_LIBRARIES) endif() # cache the vars. set(FFMPEG_INCLUDE_DIRS ${FFMPEG_INCLUDE_DIRS} CACHE STRING "The FFmpeg include directories." FORCE) set(FFMPEG_LIBRARIES ${FFMPEG_LIBRARIES} CACHE STRING "The FFmpeg libraries." FORCE) set(FFMPEG_DEFINITIONS ${FFMPEG_DEFINITIONS} CACHE STRING "The FFmpeg cflags." FORCE) mark_as_advanced(FFMPEG_INCLUDE_DIRS FFMPEG_LIBRARIES FFMPEG_DEFINITIONS) # Now set the noncached _FOUND vars for the components. foreach(_component AVCODEC AVDEVICE AVFORMAT AVUTIL POSTPROCESS SWRESAMPLE SWSCALE) set_component_found(${_component}) endforeach () # Compile the list of required vars set(_FFmpeg_REQUIRED_VARS FFMPEG_LIBRARIES FFMPEG_INCLUDE_DIRS) foreach(_component ${FFmpeg_FIND_COMPONENTS}) list(APPEND _FFmpeg_REQUIRED_VARS ${_component}_LIBRARIES ${_component}_INCLUDE_DIRS) endforeach() # Give a nice error message if some of the required vars are missing. find_package_handle_standard_args(FFmpeg DEFAULT_MSG ${_FFmpeg_REQUIRED_VARS}) openal-soft-1.24.2/cmake/FindJACK.cmake000066400000000000000000000050621474041540300174270ustar00rootroot00000000000000# - Find JACK # Find the JACK libraries # # This module defines the following variables: # JACK_FOUND - True if JACK_INCLUDE_DIR & JACK_LIBRARY are found # JACK_INCLUDE_DIRS - where to find jack.h, etc. # JACK_LIBRARIES - the jack library # #============================================================================= # Copyright 2009-2011 Kitware, Inc. # Copyright 2009-2011 Philip Lowman # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # * The names of Kitware, Inc., the Insight Consortium, or the names of # any consortium members, or of any contributors, may not be used to # endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS ``AS IS'' # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #============================================================================= find_path(JACK_INCLUDE_DIR NAMES jack/jack.h DOC "The JACK include directory" ) find_library(JACK_LIBRARY NAMES jack jack64 DOC "The JACK library" ) # handle the QUIETLY and REQUIRED arguments and set JACK_FOUND to TRUE if # all listed variables are TRUE include(FindPackageHandleStandardArgs) find_package_handle_standard_args(JACK REQUIRED_VARS JACK_LIBRARY JACK_INCLUDE_DIR) if(JACK_FOUND) set(JACK_LIBRARIES ${JACK_LIBRARY}) set(JACK_INCLUDE_DIRS ${JACK_INCLUDE_DIR}) endif() mark_as_advanced(JACK_INCLUDE_DIR JACK_LIBRARY) openal-soft-1.24.2/cmake/FindMySOFA.cmake000066400000000000000000000064741474041540300177650ustar00rootroot00000000000000# - Find MySOFA # Find the MySOFA libraries # # This module defines the following variables: # MYSOFA_FOUND - True if MYSOFA_INCLUDE_DIR & MYSOFA_LIBRARY are found # MYSOFA_INCLUDE_DIRS - where to find mysofa.h, etc. # MYSOFA_LIBRARIES - the MySOFA library # #============================================================================= # Copyright 2009-2011 Kitware, Inc. # Copyright 2009-2011 Philip Lowman # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # * The names of Kitware, Inc., the Insight Consortium, or the names of # any consortium members, or of any contributors, may not be used to # endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS ``AS IS'' # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #============================================================================= find_package(ZLIB) find_path(MYSOFA_INCLUDE_DIR NAMES mysofa.h DOC "The MySOFA include directory" ) find_library(MYSOFA_LIBRARY NAMES mysofa DOC "The MySOFA library" ) find_library(MYSOFA_M_LIBRARY NAMES m DOC "The math library for MySOFA" ) # handle the QUIETLY and REQUIRED arguments and set MYSOFA_FOUND to TRUE if # all listed variables are TRUE include(FindPackageHandleStandardArgs) find_package_handle_standard_args(MySOFA REQUIRED_VARS MYSOFA_LIBRARY MYSOFA_INCLUDE_DIR ZLIB_FOUND) if(MYSOFA_FOUND) set(MYSOFA_INCLUDE_DIRS ${MYSOFA_INCLUDE_DIR}) set(MYSOFA_LIBRARIES ${MYSOFA_LIBRARY}) set(MYSOFA_LIBRARIES ${MYSOFA_LIBRARIES} ZLIB::ZLIB) if(MYSOFA_M_LIBRARY) set(MYSOFA_LIBRARIES ${MYSOFA_LIBRARIES} ${MYSOFA_M_LIBRARY}) endif() add_library(MySOFA::MySOFA UNKNOWN IMPORTED) set_property(TARGET MySOFA::MySOFA PROPERTY IMPORTED_LOCATION ${MYSOFA_LIBRARY}) set_target_properties(MySOFA::MySOFA PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${MYSOFA_INCLUDE_DIRS} INTERFACE_LINK_LIBRARIES ZLIB::ZLIB) if(MYSOFA_M_LIBRARY) set_property(TARGET MySOFA::MySOFA APPEND PROPERTY INTERFACE_LINK_LIBRARIES ${MYSOFA_M_LIBRARY}) endif() endif() mark_as_advanced(MYSOFA_INCLUDE_DIR MYSOFA_LIBRARY) openal-soft-1.24.2/cmake/FindOSS.cmake000066400000000000000000000015221474041540300173600ustar00rootroot00000000000000# - Find OSS includes # # OSS_FOUND - True if OSS_INCLUDE_DIR is found # OSS_INCLUDE_DIRS - Set when OSS_INCLUDE_DIR is found # OSS_LIBRARIES - Set when OSS_LIBRARY is found # # OSS_INCLUDE_DIR - where to find sys/soundcard.h, etc. # OSS_LIBRARY - where to find libossaudio (optional). # find_path(OSS_INCLUDE_DIR NAMES sys/soundcard.h DOC "The OSS include directory" ) find_library(OSS_LIBRARY NAMES ossaudio DOC "Optional OSS library" ) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(OSS REQUIRED_VARS OSS_INCLUDE_DIR) if(OSS_FOUND) set(OSS_INCLUDE_DIRS ${OSS_INCLUDE_DIR}) if(OSS_LIBRARY) set(OSS_LIBRARIES ${OSS_LIBRARY}) else() unset(OSS_LIBRARIES) endif() endif() mark_as_advanced(OSS_INCLUDE_DIR OSS_LIBRARY) openal-soft-1.24.2/cmake/FindOboe.cmake000066400000000000000000000015641474041540300176060ustar00rootroot00000000000000# - Find Oboe # Find the Oboe library # # This module defines the following variable: # OBOE_FOUND - True if Oboe was found # # This module defines the following target: # oboe::oboe - Import target for linking Oboe to a project # find_path(OBOE_INCLUDE_DIR NAMES oboe/Oboe.h DOC "The Oboe include directory" ) find_library(OBOE_LIBRARY NAMES oboe DOC "The Oboe library" ) # handle the QUIETLY and REQUIRED arguments and set OBOE_FOUND to TRUE if # all listed variables are TRUE include(FindPackageHandleStandardArgs) find_package_handle_standard_args(Oboe REQUIRED_VARS OBOE_LIBRARY OBOE_INCLUDE_DIR) if(OBOE_FOUND) add_library(oboe::oboe UNKNOWN IMPORTED) set_target_properties(oboe::oboe PROPERTIES IMPORTED_LOCATION ${OBOE_LIBRARY} INTERFACE_INCLUDE_DIRECTORIES ${OBOE_INCLUDE_DIR}) endif() mark_as_advanced(OBOE_INCLUDE_DIR OBOE_LIBRARY) openal-soft-1.24.2/cmake/FindOpenSL.cmake000066400000000000000000000054221474041540300200570ustar00rootroot00000000000000# - Find OpenSL # Find the OpenSL libraries # # This module defines the following variables and targets: # OPENSL_FOUND - True if OPENSL was found # OPENSL_INCLUDE_DIRS - The OpenSL include paths # OPENSL_LIBRARIES - The OpenSL libraries to link # #============================================================================= # Copyright 2009-2011 Kitware, Inc. # Copyright 2009-2011 Philip Lowman # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # * The names of Kitware, Inc., the Insight Consortium, or the names of # any consortium members, or of any contributors, may not be used to # endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS ``AS IS'' # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #============================================================================= find_path(OPENSL_INCLUDE_DIR NAMES SLES/OpenSLES.h DOC "The OpenSL include directory") find_path(OPENSL_ANDROID_INCLUDE_DIR NAMES SLES/OpenSLES_Android.h DOC "The OpenSL Android include directory") find_library(OPENSL_LIBRARY NAMES OpenSLES DOC "The OpenSL library") # handle the QUIETLY and REQUIRED arguments and set OPENSL_FOUND to TRUE if # all listed variables are TRUE include(FindPackageHandleStandardArgs) find_package_handle_standard_args(OpenSL REQUIRED_VARS OPENSL_LIBRARY OPENSL_INCLUDE_DIR OPENSL_ANDROID_INCLUDE_DIR) if(OPENSL_FOUND) set(OPENSL_LIBRARIES ${OPENSL_LIBRARY}) set(OPENSL_INCLUDE_DIRS ${OPENSL_INCLUDE_DIR} ${OPENSL_ANDROID_INCLUDE_DIR}) endif() mark_as_advanced(OPENSL_INCLUDE_DIR OPENSL_ANDROID_INCLUDE_DIR OPENSL_LIBRARY) openal-soft-1.24.2/cmake/FindPortAudio.cmake000066400000000000000000000017041474041540300206240ustar00rootroot00000000000000# - Find PortAudio includes and libraries # # PORTAUDIO_FOUND - True if PORTAUDIO_INCLUDE_DIR & PORTAUDIO_LIBRARY # are found # PORTAUDIO_LIBRARIES - Set when PORTAUDIO_LIBRARY is found # PORTAUDIO_INCLUDE_DIRS - Set when PORTAUDIO_INCLUDE_DIR is found # # PORTAUDIO_INCLUDE_DIR - where to find portaudio.h, etc. # PORTAUDIO_LIBRARY - the portaudio library # find_path(PORTAUDIO_INCLUDE_DIR NAMES portaudio.h DOC "The PortAudio include directory" ) find_library(PORTAUDIO_LIBRARY NAMES portaudio DOC "The PortAudio library" ) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(PortAudio REQUIRED_VARS PORTAUDIO_LIBRARY PORTAUDIO_INCLUDE_DIR ) if(PORTAUDIO_FOUND) set(PORTAUDIO_LIBRARIES ${PORTAUDIO_LIBRARY}) set(PORTAUDIO_INCLUDE_DIRS ${PORTAUDIO_INCLUDE_DIR}) endif() mark_as_advanced(PORTAUDIO_INCLUDE_DIR PORTAUDIO_LIBRARY) openal-soft-1.24.2/cmake/FindPulseAudio.cmake000066400000000000000000000022511474041540300207660ustar00rootroot00000000000000# - Find PulseAudio includes and libraries # # PULSEAUDIO_FOUND - True if PULSEAUDIO_INCLUDE_DIR & # PULSEAUDIO_LIBRARY are found # # PULSEAUDIO_INCLUDE_DIR - where to find pulse/pulseaudio.h, etc. # PULSEAUDIO_LIBRARY - the pulse library # PULSEAUDIO_VERSION_STRING - the version of PulseAudio found # find_path(PULSEAUDIO_INCLUDE_DIR NAMES pulse/pulseaudio.h DOC "The PulseAudio include directory" ) find_library(PULSEAUDIO_LIBRARY NAMES pulse DOC "The PulseAudio library" ) if(PULSEAUDIO_INCLUDE_DIR AND EXISTS "${PULSEAUDIO_INCLUDE_DIR}/pulse/version.h") file(STRINGS "${PULSEAUDIO_INCLUDE_DIR}/pulse/version.h" pulse_version_str REGEX "^#define[\t ]+pa_get_headers_version\\(\\)[\t ]+\\(\".*\"\\)") string(REGEX REPLACE "^.*pa_get_headers_version\\(\\)[\t ]+\\(\"([^\"]*)\"\\).*$" "\\1" PULSEAUDIO_VERSION_STRING "${pulse_version_str}") unset(pulse_version_str) endif() include(FindPackageHandleStandardArgs) find_package_handle_standard_args(PulseAudio REQUIRED_VARS PULSEAUDIO_LIBRARY PULSEAUDIO_INCLUDE_DIR VERSION_VAR PULSEAUDIO_VERSION_STRING ) openal-soft-1.24.2/cmake/FindSndFile.cmake000066400000000000000000000015341474041540300202430ustar00rootroot00000000000000# - Try to find SndFile # Once done this will define # # SNDFILE_FOUND - system has SndFile # SndFile::SndFile - the SndFile target # find_path(SNDFILE_INCLUDE_DIR NAMES sndfile.h) find_library(SNDFILE_LIBRARY NAMES sndfile sndfile-1) # handle the QUIETLY and REQUIRED arguments and set SNDFILE_FOUND to TRUE if # all listed variables are TRUE include(FindPackageHandleStandardArgs) find_package_handle_standard_args(SndFile DEFAULT_MSG SNDFILE_LIBRARY SNDFILE_INCLUDE_DIR) if(SNDFILE_FOUND) add_library(SndFile::SndFile UNKNOWN IMPORTED) set_target_properties(SndFile::SndFile PROPERTIES IMPORTED_LOCATION ${SNDFILE_LIBRARY} INTERFACE_INCLUDE_DIRECTORIES ${SNDFILE_INCLUDE_DIR}) endif() # show the SNDFILE_INCLUDE_DIR and SNDFILE_LIBRARY variables only in the advanced view mark_as_advanced(SNDFILE_INCLUDE_DIR SNDFILE_LIBRARY) openal-soft-1.24.2/cmake/FindSndIO.cmake000066400000000000000000000014671474041540300177000ustar00rootroot00000000000000# - Find SndIO includes and libraries # # SNDIO_FOUND - True if SNDIO_INCLUDE_DIR & SNDIO_LIBRARY are found # SNDIO_LIBRARIES - Set when SNDIO_LIBRARY is found # SNDIO_INCLUDE_DIRS - Set when SNDIO_INCLUDE_DIR is found # # SNDIO_INCLUDE_DIR - where to find sndio.h, etc. # SNDIO_LIBRARY - the sndio library # find_path(SNDIO_INCLUDE_DIR NAMES sndio.h DOC "The SndIO include directory" ) find_library(SNDIO_LIBRARY NAMES sndio DOC "The SndIO library" ) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(SndIO REQUIRED_VARS SNDIO_LIBRARY SNDIO_INCLUDE_DIR ) if(SNDIO_FOUND) set(SNDIO_LIBRARIES ${SNDIO_LIBRARY}) set(SNDIO_INCLUDE_DIRS ${SNDIO_INCLUDE_DIR}) endif() mark_as_advanced(SNDIO_INCLUDE_DIR SNDIO_LIBRARY) openal-soft-1.24.2/cmake/bin2h.script.cmake000066400000000000000000000007321474041540300204220ustar00rootroot00000000000000# Read the input file into 'indata', converting each byte to a pair of hex # characters file(READ "${INPUT_FILE}" indata HEX) # For each pair of characters, indent them and prepend the 0x prefix, and # append a comma separateor. # TODO: Prettify this. Should group a number of bytes per line instead of one # per line. string(REGEX REPLACE "(..)" " 0x\\1,\n" output "${indata}") # Write the list of hex chars to the output file file(WRITE "${OUTPUT_FILE}" "${output}") openal-soft-1.24.2/common/000077500000000000000000000000001474041540300153215ustar00rootroot00000000000000openal-soft-1.24.2/common/alassert.cpp000066400000000000000000000021261474041540300176440ustar00rootroot00000000000000 #include "alassert.h" #include #include namespace { [[noreturn]] void throw_error(const std::string &message) { throw std::runtime_error{message}; } } /* namespace */ namespace al { [[noreturn]] void do_assert(const char *message, int linenum, const char *filename, const char *funcname) noexcept { /* Throwing an exception that tries to leave a noexcept function will * hopefully cause the system to provide info about the caught exception in * an error dialog. At least on Linux, this results in the process printing * * terminate called after throwing an instance of 'std::runtime_error' * what(): * * before terminating from a SIGABRT. Hopefully Windows and Mac will do the * appropriate things with the message to alert the user about an abnormal * termination. */ auto errstr = std::string{filename}; errstr += ':'; errstr += std::to_string(linenum); errstr += ": "; errstr += funcname; errstr += ": "; errstr += message; throw_error(errstr); } } /* namespace al */ openal-soft-1.24.2/common/alassert.h000066400000000000000000000013001474041540300173020ustar00rootroot00000000000000#ifndef AL_ASSERT_H #define AL_ASSERT_H #include #include "opthelpers.h" namespace al { [[noreturn]] void do_assert(const char *message, int linenum, const char *filename, const char *funcname) noexcept; } /* namespace al */ /* A custom assert macro that is not compiled out for Release/NDEBUG builds, * making it an appropriate replacement for assert() checks that must not be * ignored. */ #define alassert(cond) do { \ if(!(cond)) UNLIKELY \ al::do_assert("Assertion '" #cond "' failed", __LINE__, __FILE__, std::data(__func__)); \ } while(0) #endif /* AL_ASSERT_H */ openal-soft-1.24.2/common/albit.h000066400000000000000000000127561474041540300166000ustar00rootroot00000000000000#ifndef AL_BIT_H #define AL_BIT_H #include #include #ifndef __GNUC__ #include #endif #include #include #include #include #if !defined(__GNUC__) && (defined(_WIN32) || defined(_WIN64)) #include #endif namespace al { template std::enable_if_t && std::is_trivially_copyable_v, To> bit_cast(const From &src) noexcept { alignas(To) std::array dst; std::memcpy(dst.data(), &src, sizeof(To)); return *std::launder(reinterpret_cast(dst.data())); } template std::enable_if_t, T> byteswap(T value) noexcept { static_assert(std::has_unique_object_representations_v); auto bytes = al::bit_cast>(value); std::reverse(bytes.begin(), bytes.end()); return al::bit_cast(bytes); } #ifdef __BYTE_ORDER__ enum class endian { little = __ORDER_LITTLE_ENDIAN__, big = __ORDER_BIG_ENDIAN__, native = __BYTE_ORDER__ }; #else /* This doesn't support mixed-endian. */ namespace detail_ { constexpr bool IsLittleEndian() noexcept { static_assert(sizeof(char) < sizeof(int), "char is too big"); constexpr int test_val{1}; return static_cast(test_val) ? true : false; } } // namespace detail_ enum class endian { big = 0, little = 1, native = detail_::IsLittleEndian() ? little : big }; #endif /* Define popcount (population count/count 1 bits) and countr_zero (count * trailing zero bits, starting from the lsb) methods, for various integer * types. */ #ifdef __GNUC__ namespace detail_ { inline int popcount(unsigned long long val) noexcept { return __builtin_popcountll(val); } inline int popcount(unsigned long val) noexcept { return __builtin_popcountl(val); } inline int popcount(unsigned int val) noexcept { return __builtin_popcount(val); } inline int countr_zero(unsigned long long val) noexcept { return __builtin_ctzll(val); } inline int countr_zero(unsigned long val) noexcept { return __builtin_ctzl(val); } inline int countr_zero(unsigned int val) noexcept { return __builtin_ctz(val); } } // namespace detail_ template inline std::enable_if_t::value && std::is_unsigned::value, int> popcount(T v) noexcept { return detail_::popcount(v); } template inline std::enable_if_t::value && std::is_unsigned::value, int> countr_zero(T val) noexcept { return val ? detail_::countr_zero(val) : std::numeric_limits::digits; } #else /* There be black magics here. The popcount method is derived from * https://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetParallel * while the ctz-utilizing-popcount algorithm is shown here * http://www.hackersdelight.org/hdcodetxt/ntz.c.txt * as the ntz2 variant. These likely aren't the most efficient methods, but * they're good enough if the GCC built-ins aren't available. */ namespace detail_ { template::digits> struct fast_utype { }; template struct fast_utype { using type = std::uint_fast8_t; }; template struct fast_utype { using type = std::uint_fast16_t; }; template struct fast_utype { using type = std::uint_fast32_t; }; template struct fast_utype { using type = std::uint_fast64_t; }; template constexpr T repbits(unsigned char bits) noexcept { T ret{bits}; for(size_t i{1};i < sizeof(T);++i) ret = (ret<<8) | bits; return ret; } } // namespace detail_ template constexpr std::enable_if_t::value && std::is_unsigned::value, int> popcount(T val) noexcept { using fast_type = typename detail_::fast_utype::type; constexpr fast_type b01010101{detail_::repbits(0x55)}; constexpr fast_type b00110011{detail_::repbits(0x33)}; constexpr fast_type b00001111{detail_::repbits(0x0f)}; constexpr fast_type b00000001{detail_::repbits(0x01)}; fast_type v{fast_type{val} - ((fast_type{val} >> 1) & b01010101)}; v = (v & b00110011) + ((v >> 2) & b00110011); v = (v + (v >> 4)) & b00001111; return static_cast(((v * b00000001) >> ((sizeof(T)-1)*8)) & 0xff); } #ifdef _WIN32 template inline std::enable_if_t::value && std::is_unsigned::value && std::numeric_limits::digits <= 32, int> countr_zero(T v) { unsigned long idx{std::numeric_limits::digits}; _BitScanForward(&idx, static_cast(v)); return static_cast(idx); } template inline std::enable_if_t::value && std::is_unsigned::value && 32 < std::numeric_limits::digits && std::numeric_limits::digits <= 64, int> countr_zero(T v) { unsigned long idx{std::numeric_limits::digits}; #ifdef _WIN64 _BitScanForward64(&idx, v); #else if(!_BitScanForward(&idx, static_cast(v))) { if(_BitScanForward(&idx, static_cast(v>>32))) idx += 32; } #endif /* _WIN64 */ return static_cast(idx); } #else template constexpr std::enable_if_t::value && std::is_unsigned::value, int> countr_zero(T value) { return popcount(static_cast(~value & (value - 1))); } #endif #endif } // namespace al #endif /* AL_BIT_H */ openal-soft-1.24.2/common/alcomplex.cpp000066400000000000000000000170231474041540300200140ustar00rootroot00000000000000 #include "config.h" #include "alcomplex.h" #include #include #include #include #include #include #include #include "albit.h" #include "alnumbers.h" #include "alnumeric.h" #include "opthelpers.h" namespace { using ushort = unsigned short; using ushort2 = std::array; using complex_d = std::complex; constexpr std::size_t BitReverseCounter(std::size_t log2_size) noexcept { /* Some magic math that calculates the number of swaps needed for a * sequence of bit-reversed indices when index < reversed_index. */ return (1_zu<<(log2_size-1)) - (1_zu<<((log2_size-1_zu)/2_zu)); } template struct BitReverser { static_assert(N <= sizeof(ushort)*8, "Too many bits for the bit-reversal table."); std::array mData{}; constexpr BitReverser() { const std::size_t fftsize{1u << N}; std::size_t ret_i{0}; /* Bit-reversal permutation applied to a sequence of fftsize items. */ for(std::size_t idx{1u};idx < fftsize-1;++idx) { std::size_t revidx{idx}; revidx = ((revidx&0xaaaaaaaa) >> 1) | ((revidx&0x55555555) << 1); revidx = ((revidx&0xcccccccc) >> 2) | ((revidx&0x33333333) << 2); revidx = ((revidx&0xf0f0f0f0) >> 4) | ((revidx&0x0f0f0f0f) << 4); revidx = ((revidx&0xff00ff00) >> 8) | ((revidx&0x00ff00ff) << 8); revidx = (revidx >> 16) | ((revidx&0x0000ffff) << 16); revidx >>= 32-N; if(idx < revidx) { mData[ret_i][0] = static_cast(idx); mData[ret_i][1] = static_cast(revidx); ++ret_i; } } assert(ret_i == std::size(mData)); } }; /* These bit-reversal swap tables support up to 11-bit indices (2048 elements), * which is large enough for the filters and effects in OpenAL Soft. Larger FFT * requests will use a slower table-less path. */ constexpr BitReverser<2> BitReverser2{}; constexpr BitReverser<3> BitReverser3{}; constexpr BitReverser<4> BitReverser4{}; constexpr BitReverser<5> BitReverser5{}; constexpr BitReverser<6> BitReverser6{}; constexpr BitReverser<7> BitReverser7{}; constexpr BitReverser<8> BitReverser8{}; constexpr BitReverser<9> BitReverser9{}; constexpr BitReverser<10> BitReverser10{}; constexpr BitReverser<11> BitReverser11{}; constexpr std::array,12> gBitReverses{{ {}, {}, BitReverser2.mData, BitReverser3.mData, BitReverser4.mData, BitReverser5.mData, BitReverser6.mData, BitReverser7.mData, BitReverser8.mData, BitReverser9.mData, BitReverser10.mData, BitReverser11.mData }}; /* Lookup table for std::polar(1, pi / (1< constexpr std::array,gBitReverses.size()-1> gArgAngle{{ {static_cast(-1.00000000000000000e+00), static_cast(0.00000000000000000e+00)}, {static_cast( 0.00000000000000000e+00), static_cast(1.00000000000000000e+00)}, {static_cast( 7.07106781186547524e-01), static_cast(7.07106781186547524e-01)}, {static_cast( 9.23879532511286756e-01), static_cast(3.82683432365089772e-01)}, {static_cast( 9.80785280403230449e-01), static_cast(1.95090322016128268e-01)}, {static_cast( 9.95184726672196886e-01), static_cast(9.80171403295606020e-02)}, {static_cast( 9.98795456205172393e-01), static_cast(4.90676743274180143e-02)}, {static_cast( 9.99698818696204220e-01), static_cast(2.45412285229122880e-02)}, {static_cast( 9.99924701839144541e-01), static_cast(1.22715382857199261e-02)}, {static_cast( 9.99981175282601143e-01), static_cast(6.13588464915447536e-03)}, {static_cast( 9.99995293809576172e-01), static_cast(3.06795676296597627e-03)} }}; } // namespace void complex_fft(const al::span> buffer, const double sign) { const std::size_t fftsize{buffer.size()}; /* Get the number of bits used for indexing. Simplifies bit-reversal and * the main loop count. */ const std::size_t log2_size{static_cast(al::countr_zero(fftsize))}; if(log2_size < gBitReverses.size()) LIKELY { for(auto &rev : gBitReverses[log2_size]) std::swap(buffer[rev[0]], buffer[rev[1]]); /* Iterative form of Danielson-Lanczos lemma */ for(std::size_t i{0};i < log2_size;++i) { const std::size_t step2{1_uz << i}; const std::size_t step{2_uz << i}; /* The first iteration of the inner loop would have u=1, which we * can simplify to remove a number of complex multiplies. */ for(std::size_t k{0};k < fftsize;k+=step) { const complex_d temp{buffer[k+step2]}; buffer[k+step2] = buffer[k] - temp; buffer[k] += temp; } const complex_d w{gArgAngle[i].real(), gArgAngle[i].imag()*sign}; complex_d u{w}; for(std::size_t j{1};j < step2;j++) { for(std::size_t k{j};k < fftsize;k+=step) { const complex_d temp{buffer[k+step2] * u}; buffer[k+step2] = buffer[k] - temp; buffer[k] += temp; } u *= w; } } } else { assert(log2_size < 32); for(std::size_t idx{1u};idx < fftsize-1;++idx) { std::size_t revidx{idx}; revidx = ((revidx&0xaaaaaaaa) >> 1) | ((revidx&0x55555555) << 1); revidx = ((revidx&0xcccccccc) >> 2) | ((revidx&0x33333333) << 2); revidx = ((revidx&0xf0f0f0f0) >> 4) | ((revidx&0x0f0f0f0f) << 4); revidx = ((revidx&0xff00ff00) >> 8) | ((revidx&0x00ff00ff) << 8); revidx = (revidx >> 16) | ((revidx&0x0000ffff) << 16); revidx >>= 32-log2_size; if(idx < revidx) std::swap(buffer[idx], buffer[revidx]); } const double pi{al::numbers::pi * sign}; for(std::size_t i{0};i < log2_size;++i) { const std::size_t step2{1_uz << i}; const std::size_t step{2_uz << i}; for(std::size_t k{0};k < fftsize;k+=step) { const complex_d temp{buffer[k+step2]}; buffer[k+step2] = buffer[k] - temp; buffer[k] += temp; } const double arg{pi / static_cast(step2)}; const complex_d w{std::polar(1.0, arg)}; complex_d u{w}; for(std::size_t j{1};j < step2;j++) { for(std::size_t k{j};k < fftsize;k+=step) { const complex_d temp{buffer[k+step2] * u}; buffer[k+step2] = buffer[k] - temp; buffer[k] += temp; } u *= w; } } } } void complex_hilbert(const al::span> buffer) { inverse_fft(buffer); const double inverse_size = 1.0/static_cast(buffer.size()); auto bufiter = buffer.begin(); const auto halfiter = bufiter + ptrdiff_t(buffer.size()>>1); *bufiter *= inverse_size; ++bufiter; bufiter = std::transform(bufiter, halfiter, bufiter, [scale=inverse_size*2.0](std::complex d){ return d * scale; }); *bufiter *= inverse_size; ++bufiter; std::fill(bufiter, buffer.end(), std::complex{}); forward_fft(buffer); } openal-soft-1.24.2/common/alcomplex.h000066400000000000000000000024561474041540300174650ustar00rootroot00000000000000#ifndef ALCOMPLEX_H #define ALCOMPLEX_H #include #include "alspan.h" /** * Iterative implementation of 2-radix FFT (In-place algorithm). Sign = -1 is * FFT and 1 is inverse FFT. Applies the Discrete Fourier Transform (DFT) to * the data supplied in the buffer, which MUST BE power of two. */ void complex_fft(const al::span> buffer, const double sign); /** * Calculate the frequency-domain response of the time-domain signal in the * provided buffer, which MUST BE power of two. */ inline void forward_fft(const al::span> buffer) { complex_fft(buffer, -1.0); } /** * Calculate the time-domain signal of the frequency-domain response in the * provided buffer, which MUST BE power of two. */ inline void inverse_fft(const al::span> buffer) { complex_fft(buffer, +1.0); } /** * Calculate the complex helical sequence (discrete-time analytical signal) of * the given input using the discrete Hilbert transform (In-place algorithm). * Fills the buffer with the discrete-time analytical signal stored in the * buffer. The buffer is an array of complex numbers and MUST BE power of two, * and the imaginary components should be cleared to 0. */ void complex_hilbert(const al::span> buffer); #endif /* ALCOMPLEX_H */ openal-soft-1.24.2/common/almalloc.h000066400000000000000000000130231474041540300172550ustar00rootroot00000000000000#ifndef AL_MALLOC_H #define AL_MALLOC_H #include #include #include #include #include #include #include namespace gsl { template using owner = T; } #define DISABLE_ALLOC \ void *operator new(size_t) = delete; \ void *operator new[](size_t) = delete; \ void operator delete(void*) noexcept = delete; \ void operator delete[](void*) noexcept = delete; enum FamCount : size_t { }; #define DEF_FAM_NEWDEL(T, FamMem) \ static constexpr size_t Sizeof(size_t count) noexcept \ { \ static_assert(&Sizeof == &T::Sizeof, \ "Incorrect container type specified"); \ return std::max(decltype(FamMem)::Sizeof(count, offsetof(T, FamMem)), \ sizeof(T)); \ } \ \ gsl::owner operator new(size_t /*size*/, FamCount count) \ { \ const auto alignment = std::align_val_t{alignof(T)}; \ return ::operator new[](T::Sizeof(count), alignment); \ } \ void operator delete(gsl::owner block, FamCount) noexcept \ { ::operator delete[](block, std::align_val_t{alignof(T)}); } \ void operator delete(gsl::owner block) noexcept \ { ::operator delete[](block, std::align_val_t{alignof(T)}); } \ void *operator new[](size_t /*size*/) = delete; \ void operator delete[](void* /*block*/) = delete; namespace al { template struct allocator { static constexpr auto Alignment = std::max(AlignV, alignof(T)); static constexpr auto AlignVal = std::align_val_t{Alignment}; using value_type = std::remove_cv_t>; using reference = value_type&; using const_reference = const value_type&; using pointer = value_type*; using const_pointer = const value_type*; using size_type = std::size_t; using difference_type = std::ptrdiff_t; using is_always_equal = std::true_type; template = true> struct rebind { using other = allocator; }; constexpr explicit allocator() noexcept = default; template constexpr explicit allocator(const allocator&) noexcept { static_assert(Alignment == allocator::Alignment); } gsl::owner allocate(std::size_t n) { if(n > std::numeric_limits::max()/sizeof(T)) throw std::bad_alloc(); return static_cast>(::operator new[](n*sizeof(T), AlignVal)); } void deallocate(gsl::owner p, std::size_t) noexcept { ::operator delete[](gsl::owner{p}, AlignVal); } }; template constexpr bool operator==(const allocator&, const allocator&) noexcept { return allocator::Alignment == allocator::Alignment; } template constexpr bool operator!=(const allocator&, const allocator&) noexcept { return allocator::Alignment != allocator::Alignment; } #ifdef __cpp_lib_to_address using std::to_address; #else template constexpr T *to_address(T *p) noexcept { static_assert(!std::is_function::value, "Can't be a function type"); return p; } template constexpr auto to_address(const T &p) noexcept { return ::al::to_address(p.operator->()); } #endif template constexpr T* construct_at(T *ptr, Args&& ...args) noexcept(std::is_nothrow_constructible_v) { /* NOLINTBEGIN(cppcoreguidelines-owning-memory) construct_at doesn't * necessarily handle the address from an owner, while placement new * expects to. */ return ::new(static_cast(ptr)) T{std::forward(args)...}; /* NOLINTEND(cppcoreguidelines-owning-memory) */ } template class out_ptr_t { static_assert(!std::is_same_v); SP &mRes; std::variant mPtr{}; public: explicit out_ptr_t(SP &res) : mRes{res} { } ~out_ptr_t() { auto set_res = [this](auto &ptr) { mRes.reset(static_cast(ptr)); }; std::visit(set_res, mPtr); } out_ptr_t(const out_ptr_t&) = delete; out_ptr_t& operator=(const out_ptr_t&) = delete; operator PT*() noexcept /* NOLINT(google-explicit-constructor) */ { return &std::get(mPtr); } operator void**() noexcept /* NOLINT(google-explicit-constructor) */ { return &mPtr.template emplace(); } }; template auto out_ptr(SP &res) { using ptype = typename SP::element_type*; return out_ptr_t{res}; } } // namespace al #endif /* AL_MALLOC_H */ openal-soft-1.24.2/common/alnumbers.h000066400000000000000000000016471474041540300174720ustar00rootroot00000000000000#ifndef COMMON_ALNUMBERS_H #define COMMON_ALNUMBERS_H #include namespace al::numbers { namespace detail_ { template using as_fp = std::enable_if_t::value, T>; } // detail_ template inline constexpr auto pi_v = detail_::as_fp(3.141592653589793238462643383279502884L); template inline constexpr auto inv_pi_v = detail_::as_fp(0.318309886183790671537767526745028724L); template inline constexpr auto sqrt2_v = detail_::as_fp(1.414213562373095048801688724209698079L); template inline constexpr auto sqrt3_v = detail_::as_fp(1.732050807568877293527446341505872367L); inline constexpr auto pi = pi_v; inline constexpr auto inv_pi = inv_pi_v; inline constexpr auto sqrt2 = sqrt2_v; inline constexpr auto sqrt3 = sqrt3_v; } // namespace al::numbers #endif /* COMMON_ALNUMBERS_H */ openal-soft-1.24.2/common/alnumeric.h000066400000000000000000000165461474041540300174650ustar00rootroot00000000000000#ifndef AL_NUMERIC_H #define AL_NUMERIC_H #include "config_simd.h" #include #include #include #include #include #include #include #ifdef HAVE_INTRIN_H #include #endif #if HAVE_SSE_INTRINSICS #include #endif #include "albit.h" #include "altraits.h" #include "opthelpers.h" constexpr auto operator "" _i64(unsigned long long n) noexcept { return static_cast(n); } constexpr auto operator "" _u64(unsigned long long n) noexcept { return static_cast(n); } constexpr auto operator "" _z(unsigned long long n) noexcept { return static_cast>(n); } constexpr auto operator "" _uz(unsigned long long n) noexcept { return static_cast(n); } constexpr auto operator "" _zu(unsigned long long n) noexcept { return static_cast(n); } template,bool> = true> constexpr auto as_unsigned(T value) noexcept { using UT = std::make_unsigned_t; return static_cast(value); } constexpr auto GetCounterSuffix(size_t count) noexcept -> std::string_view { using namespace std::string_view_literals; return (((count%100)/10) == 1) ? "th"sv : ((count%10) == 1) ? "st"sv : ((count%10) == 2) ? "nd"sv : ((count%10) == 3) ? "rd"sv : "th"sv; } constexpr auto lerpf(float val1, float val2, float mu) noexcept -> float { return val1 + (val2-val1)*mu; } constexpr auto lerpd(double val1, double val2, double mu) noexcept -> double { return val1 + (val2-val1)*mu; } /** Find the next power-of-2 for non-power-of-2 numbers. */ inline uint32_t NextPowerOf2(uint32_t value) noexcept { if(value > 0) { value--; value |= value>>1; value |= value>>2; value |= value>>4; value |= value>>8; value |= value>>16; } return value+1; } /** * If the value is not already a multiple of r, round down to the next * multiple. */ template constexpr T RoundDown(T value, al::type_identity_t r) noexcept { return value - (value%r); } /** * If the value is not already a multiple of r, round up to the next multiple. */ template constexpr T RoundUp(T value, al::type_identity_t r) noexcept { return RoundDown(value + r-1, r); } /** * Fast float-to-int conversion. No particular rounding mode is assumed; the * IEEE-754 default is round-to-nearest with ties-to-even, though an app could * change it on its own threads. On some systems, a truncating conversion may * always be the fastest method. */ inline int fastf2i(float f) noexcept { #if HAVE_SSE_INTRINSICS return _mm_cvt_ss2si(_mm_set_ss(f)); #elif defined(_MSC_VER) && defined(_M_IX86_FP) && _M_IX86_FP == 0 int i; __asm fld f __asm fistp i return i; #elif (defined(__GNUC__) || defined(__clang__)) && (defined(__i386__) || defined(__x86_64__)) \ && !defined(__SSE_MATH__) int i; __asm__ __volatile__("fistpl %0" : "=m"(i) : "t"(f) : "st"); return i; #else return static_cast(f); #endif } inline unsigned int fastf2u(float f) noexcept { return static_cast(fastf2i(f)); } /** Converts float-to-int using standard behavior (truncation). */ inline int float2int(float f) noexcept { #if HAVE_SSE_INTRINSICS return _mm_cvtt_ss2si(_mm_set_ss(f)); #elif (defined(_MSC_VER) && defined(_M_IX86_FP) && _M_IX86_FP == 0) \ || ((defined(__GNUC__) || defined(__clang__)) && (defined(__i386__) || defined(__x86_64__)) \ && !defined(__SSE_MATH__)) const int conv_i{al::bit_cast(f)}; const int sign{(conv_i>>31) | 1}; const int shift{((conv_i>>23)&0xff) - (127+23)}; /* Over/underflow */ if(shift >= 31 || shift < -23) UNLIKELY return 0; const int mant{(conv_i&0x7fffff) | 0x800000}; if(shift < 0) LIKELY return (mant >> -shift) * sign; return (mant << shift) * sign; #else return static_cast(f); #endif } inline unsigned int float2uint(float f) noexcept { return static_cast(float2int(f)); } /** Converts double-to-int using standard behavior (truncation). */ inline int double2int(double d) noexcept { #if HAVE_SSE_INTRINSICS return _mm_cvttsd_si32(_mm_set_sd(d)); #elif (defined(_MSC_VER) && defined(_M_IX86_FP) && _M_IX86_FP < 2) \ || ((defined(__GNUC__) || defined(__clang__)) && (defined(__i386__) || defined(__x86_64__)) \ && !defined(__SSE2_MATH__)) const int64_t conv_i64{al::bit_cast(d)}; const int sign{static_cast(conv_i64 >> 63) | 1}; const int shift{(static_cast(conv_i64 >> 52) & 0x7ff) - (1023 + 52)}; /* Over/underflow */ if(shift >= 63 || shift < -52) UNLIKELY return 0; const int64_t mant{(conv_i64 & 0xfffffffffffff_i64) | 0x10000000000000_i64}; if(shift < 0) LIKELY return static_cast(mant >> -shift) * sign; return static_cast(mant << shift) * sign; #else return static_cast(d); #endif } /** * Rounds a float to the nearest integral value, according to the current * rounding mode. This is essentially an inlined version of rintf, although * makes fewer promises (e.g. -0 or -0.25 rounded to 0 may result in +0). */ inline float fast_roundf(float f) noexcept { #if (defined(__GNUC__) || defined(__clang__)) && (defined(__i386__) || defined(__x86_64__)) \ && !defined(__SSE_MATH__) float out; __asm__ __volatile__("frndint" : "=t"(out) : "0"(f)); return out; #elif (defined(__GNUC__) || defined(__clang__)) && defined(__aarch64__) float out; __asm__ volatile("frintx %s0, %s1" : "=w"(out) : "w"(f)); return out; #else /* Integral limit, where sub-integral precision is not available for * floats. */ static constexpr std::array ilim{ 8388608.0f /* 0x1.0p+23 */, -8388608.0f /* -0x1.0p+23 */ }; const unsigned int conv_i{al::bit_cast(f)}; const unsigned int sign{(conv_i>>31)&0x01}; const unsigned int expo{(conv_i>>23)&0xff}; if(expo >= 150/*+23*/) UNLIKELY { /* An exponent (base-2) of 23 or higher is incapable of sub-integral * precision, so it's already an integral value. We don't need to worry * about infinity or NaN here. */ return f; } /* Adding the integral limit to the value (with a matching sign) forces a * result that has no sub-integral precision, and is consequently forced to * round to an integral value. Removing the integral limit then restores * the initial value rounded to the integral. The compiler should not * optimize this out because of non-associative rules on floating-point * math (as long as you don't use -fassociative-math, * -funsafe-math-optimizations, -ffast-math, or -Ofast, in which case this * may break without __builtin_assoc_barrier support). */ #if HAS_BUILTIN(__builtin_assoc_barrier) return __builtin_assoc_barrier(f + ilim[sign]) - ilim[sign]; #else f += ilim[sign]; return f - ilim[sign]; #endif #endif } // Converts level (mB) to gain. inline float level_mb_to_gain(float x) { if(x <= -10'000.0f) return 0.0f; return std::pow(10.0f, x / 2'000.0f); } // Converts gain to level (mB). inline float gain_to_level_mb(float x) { if(x <= 1e-05f) return -10'000.0f; return std::max(std::log10(x) * 2'000.0f, -10'000.0f); } #endif /* AL_NUMERIC_H */ openal-soft-1.24.2/common/alsem.cpp000066400000000000000000000063231474041540300171320ustar00rootroot00000000000000/** * OpenAL cross platform audio library * Copyright (C) 1999-2007 by authors. * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * Or go to http://www.gnu.org/copyleft/lgpl.html */ #include "config.h" #include "alsem.h" #include #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #include #include namespace al { semaphore::semaphore(unsigned int initial) { if(initial > static_cast(std::numeric_limits::max())) throw std::system_error(std::make_error_code(std::errc::value_too_large)); mSem = CreateSemaphoreW(nullptr, static_cast(initial), std::numeric_limits::max(), nullptr); if(mSem == nullptr) throw std::system_error(std::make_error_code(std::errc::resource_unavailable_try_again)); } semaphore::~semaphore() { CloseHandle(mSem); } void semaphore::post() { if(!ReleaseSemaphore(static_cast(mSem), 1, nullptr)) throw std::system_error(std::make_error_code(std::errc::value_too_large)); } void semaphore::wait() noexcept { WaitForSingleObject(static_cast(mSem), INFINITE); } bool semaphore::try_wait() noexcept { return WaitForSingleObject(static_cast(mSem), 0) == WAIT_OBJECT_0; } } // namespace al #else /* Do not try using libdispatch on systems where it is absent. */ #if defined(AL_APPLE_HAVE_DISPATCH) namespace al { semaphore::semaphore(unsigned int initial) { mSem = dispatch_semaphore_create(initial); if(!mSem) throw std::system_error(std::make_error_code(std::errc::resource_unavailable_try_again)); } semaphore::~semaphore() { dispatch_release(mSem); } void semaphore::post() { dispatch_semaphore_signal(mSem); } void semaphore::wait() noexcept { dispatch_semaphore_wait(mSem, DISPATCH_TIME_FOREVER); } bool semaphore::try_wait() noexcept { return dispatch_semaphore_wait(mSem, DISPATCH_TIME_NOW) == 0; } } // namespace al #else /* !__APPLE__ */ #include namespace al { semaphore::semaphore(unsigned int initial) { if(sem_init(&mSem, 0, initial) != 0) throw std::system_error(std::make_error_code(std::errc::resource_unavailable_try_again)); } semaphore::~semaphore() { sem_destroy(&mSem); } void semaphore::post() { if(sem_post(&mSem) != 0) throw std::system_error(std::make_error_code(std::errc::value_too_large)); } void semaphore::wait() noexcept { while(sem_wait(&mSem) == -1 && errno == EINTR) { } } bool semaphore::try_wait() noexcept { return sem_trywait(&mSem) == 0; } } // namespace al #endif /* __APPLE__ */ #endif /* _WIN32 */ openal-soft-1.24.2/common/alsem.h000066400000000000000000000017221474041540300165750ustar00rootroot00000000000000#ifndef COMMON_ALSEM_H #define COMMON_ALSEM_H #if defined(__APPLE__) #include #include #if (((MAC_OS_X_VERSION_MIN_REQUIRED > 1050) && !defined(__ppc__)) || TARGET_OS_IOS || TARGET_OS_TV) #include #define AL_APPLE_HAVE_DISPATCH 1 #else #include /* Fallback option for Apple without a working libdispatch */ #endif #elif !defined(_WIN32) #include #endif namespace al { class semaphore { #ifdef _WIN32 using native_type = void*; #elif defined(AL_APPLE_HAVE_DISPATCH) using native_type = dispatch_semaphore_t; #else using native_type = sem_t; #endif native_type mSem{}; public: explicit semaphore(unsigned int initial=0); semaphore(const semaphore&) = delete; ~semaphore(); semaphore& operator=(const semaphore&) = delete; void post(); void wait() noexcept; bool try_wait() noexcept; }; } // namespace al #endif /* COMMON_ALSEM_H */ openal-soft-1.24.2/common/alspan.h000066400000000000000000000437611474041540300167630ustar00rootroot00000000000000#ifndef AL_SPAN_H #define AL_SPAN_H #include #include #include #include #include #include #include #include "alassert.h" #include "almalloc.h" #include "altraits.h" namespace al { /* This is here primarily to help ensure proper behavior for span's iterators, * being an actual object with member functions instead of a raw pointer (which * has requirements like + and - working with ptrdiff_t). This also helps * silence clang-tidy's pointer arithmetic warnings for span and FlexArray * iterators. It otherwise behaves like a plain pointer and should optimize * accordingly. * * Shouldn't be needed once we use std::span in C++20. */ template class ptr_wrapper { static_assert(std::is_pointer_v); T mPointer{}; public: using value_type = std::remove_pointer_t; using size_type = std::size_t; using difference_type = std::ptrdiff_t; using pointer = value_type*; using reference = value_type&; using iterator_category = std::random_access_iterator_tag; explicit constexpr ptr_wrapper(T ptr) : mPointer{ptr} { } /* NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic) */ constexpr auto operator++() noexcept -> ptr_wrapper& { ++mPointer; return *this; } constexpr auto operator--() noexcept -> ptr_wrapper& { --mPointer; return *this; } constexpr auto operator++(int) noexcept -> ptr_wrapper { auto temp = *this; ++*this; return temp; } constexpr auto operator--(int) noexcept -> ptr_wrapper { auto temp = *this; --*this; return temp; } constexpr auto operator+=(std::ptrdiff_t n) noexcept -> ptr_wrapper& { mPointer += n; return *this; } constexpr auto operator-=(std::ptrdiff_t n) noexcept -> ptr_wrapper& { mPointer -= n; return *this; } [[nodiscard]] constexpr auto operator*() const noexcept -> value_type& { return *mPointer; } [[nodiscard]] constexpr auto operator->() const noexcept -> value_type* { return mPointer; } [[nodiscard]] constexpr auto operator[](std::size_t idx) const noexcept -> value_type& {return mPointer[idx];} [[nodiscard]] friend constexpr auto operator+(const ptr_wrapper &lhs, std::ptrdiff_t n) noexcept -> ptr_wrapper { return ptr_wrapper{lhs.mPointer + n}; } [[nodiscard]] friend constexpr auto operator+(std::ptrdiff_t n, const ptr_wrapper &rhs) noexcept -> ptr_wrapper { return ptr_wrapper{n + rhs.mPointer}; } [[nodiscard]] friend constexpr auto operator-(const ptr_wrapper &lhs, std::ptrdiff_t n) noexcept -> ptr_wrapper { return ptr_wrapper{lhs.mPointer - n}; } [[nodiscard]] friend constexpr auto operator-(const ptr_wrapper &lhs, const ptr_wrapper &rhs)noexcept->std::ptrdiff_t { return lhs.mPointer - rhs.mPointer; } [[nodiscard]] friend constexpr auto operator==(const ptr_wrapper &lhs, const ptr_wrapper &rhs) noexcept -> bool { return lhs.mPointer == rhs.mPointer; } [[nodiscard]] friend constexpr auto operator!=(const ptr_wrapper &lhs, const ptr_wrapper &rhs) noexcept -> bool { return lhs.mPointer != rhs.mPointer; } [[nodiscard]] friend constexpr auto operator<=(const ptr_wrapper &lhs, const ptr_wrapper &rhs) noexcept -> bool { return lhs.mPointer <= rhs.mPointer; } [[nodiscard]] friend constexpr auto operator>=(const ptr_wrapper &lhs, const ptr_wrapper &rhs) noexcept -> bool { return lhs.mPointer >= rhs.mPointer; } [[nodiscard]] friend constexpr auto operator<(const ptr_wrapper &lhs, const ptr_wrapper &rhs) noexcept -> bool { return lhs.mPointer < rhs.mPointer; } [[nodiscard]] friend constexpr auto operator>(const ptr_wrapper &lhs, const ptr_wrapper &rhs) noexcept -> bool { return lhs.mPointer > rhs.mPointer; } /* NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic) */ }; inline constexpr std::size_t dynamic_extent{static_cast(-1)}; template class span; namespace detail_ { template struct is_span_ : std::false_type { }; template struct is_span_> : std::true_type { }; template inline constexpr bool is_span_v = is_span_>::value; template struct is_std_array_ : std::false_type { }; template struct is_std_array_> : std::true_type { }; template inline constexpr bool is_std_array_v = is_std_array_>::value; template inline constexpr bool has_size_and_data = false; template inline constexpr bool has_size_and_data())),decltype(std::data(std::declval()))>> = true; template inline constexpr bool is_valid_container_type = !is_span_v && !is_std_array_v && !std::is_array::value && has_size_and_data; template inline constexpr bool is_array_compatible = std::is_convertible::value; /* NOLINT(*-avoid-c-arrays) */ template inline constexpr bool is_valid_container = is_valid_container_type && is_array_compatible()))>,T>; } // namespace detail_ #define REQUIRES(...) std::enable_if_t<(__VA_ARGS__),bool> = true /* NOLINTBEGIN(google-explicit-constructor) This largely follows std::span's * constructor behavior, and should be replaced once C++20 is used. */ template class span { public: using element_type = T; using value_type = std::remove_cv_t; using size_type = std::size_t; using difference_type = std::ptrdiff_t; using pointer = T*; using const_pointer = const T*; using reference = T&; using const_reference = const T&; using iterator = ptr_wrapper; using const_iterator = ptr_wrapper; using reverse_iterator = std::reverse_iterator; using const_reverse_iterator = std::reverse_iterator; static constexpr std::size_t extent{E}; template constexpr span() noexcept { } template constexpr explicit span(U iter, size_type size_) : mData{::al::to_address(iter)} { alassert(size_ == extent); } template::value)> constexpr explicit span(U first, V last) : mData{::al::to_address(first)} { alassert(static_cast(last-first) == extent); } template constexpr span(type_identity_t (&arr)[N]) noexcept /* NOLINT(*-avoid-c-arrays) */ : mData{std::data(arr)} { static_assert(N == extent); } template constexpr span(std::array &arr) noexcept : mData{std::data(arr)} { static_assert(N == extent); } template::value)> constexpr span(const std::array &arr) noexcept : mData{std::data(arr)} { static_assert(N == extent); } template)> constexpr explicit span(U&& cont) : span{std::data(cont), std::size(cont)} { } template::value && detail_::is_array_compatible && N == dynamic_extent)> constexpr explicit span(const span &span_) noexcept : mData{std::data(span_)} { alassert(std::size(span_) == extent); } template::value && detail_::is_array_compatible && N == extent)> constexpr span(const span &span_) noexcept : mData{std::data(span_)} { } constexpr span(const span&) noexcept = default; constexpr span& operator=(const span &rhs) noexcept = default; [[nodiscard]] constexpr auto front() const -> reference { return mData[0]; } [[nodiscard]] constexpr auto back() const -> reference { return mData[E-1]; } [[nodiscard]] constexpr auto operator[](size_type idx) const -> reference { return mData[idx]; } [[nodiscard]] constexpr auto data() const noexcept -> pointer { return mData; } [[nodiscard]] constexpr auto size() const noexcept -> size_type { return E; } [[nodiscard]] constexpr auto size_bytes() const noexcept -> size_type { return E * sizeof(value_type); } [[nodiscard]] constexpr auto empty() const noexcept -> bool { return E == 0; } [[nodiscard]] constexpr auto begin() const noexcept -> iterator { return iterator{mData}; } [[nodiscard]] constexpr auto end() const noexcept -> iterator { return iterator{mData+E}; } [[nodiscard]] constexpr auto cbegin() const noexcept -> const_iterator { return const_iterator{mData}; } [[nodiscard]] constexpr auto cend() const noexcept -> const_iterator { return const_iterator{mData+E}; } [[nodiscard]] constexpr auto rbegin() const noexcept -> reverse_iterator { return reverse_iterator{end()}; } [[nodiscard]] constexpr auto rend() const noexcept -> reverse_iterator { return reverse_iterator{begin()}; } [[nodiscard]] constexpr auto crbegin() const noexcept -> const_reverse_iterator { return cend(); } [[nodiscard]] constexpr auto crend() const noexcept -> const_reverse_iterator { return cbegin(); } template [[nodiscard]] constexpr auto first() const noexcept -> span { static_assert(E >= C, "New size exceeds original capacity"); return span{mData, C}; } template [[nodiscard]] constexpr auto last() const noexcept -> span { static_assert(E >= C, "New size exceeds original capacity"); return span{mData+(E-C), C}; } template [[nodiscard]] constexpr auto subspan() const noexcept -> std::enable_if_t> { static_assert(E >= O, "Offset exceeds extent"); static_assert(E-O >= C, "New size exceeds original capacity"); return span{mData+O, C}; } template [[nodiscard]] constexpr auto subspan() const noexcept -> std::enable_if_t> { static_assert(E >= O, "Offset exceeds extent"); return span{mData+O, E-O}; } /* NOTE: Can't declare objects of a specialized template class prior to * defining the specialization. As a result, these methods need to be * defined later. */ [[nodiscard]] constexpr auto first(std::size_t count) const noexcept -> span; [[nodiscard]] constexpr auto last(std::size_t count) const noexcept -> span; [[nodiscard]] constexpr auto subspan(std::size_t offset, std::size_t count=dynamic_extent) const noexcept -> span; private: pointer mData{nullptr}; }; template class span { public: using element_type = T; using value_type = std::remove_cv_t; using size_type = std::size_t; using difference_type = ptrdiff_t; using pointer = T*; using const_pointer = const T*; using reference = T&; using const_reference = const T&; using iterator = ptr_wrapper; using const_iterator = ptr_wrapper; using reverse_iterator = std::reverse_iterator; using const_reverse_iterator = std::reverse_iterator; static constexpr std::size_t extent{dynamic_extent}; constexpr span() noexcept = default; template constexpr span(U iter, size_type count) : mData{::al::to_address(iter)}, mDataLength{count} { } template::value)> constexpr span(U first, V last) : span{::al::to_address(first), static_cast(last-first)} { } template constexpr span(type_identity_t (&arr)[N]) noexcept /* NOLINT(*-avoid-c-arrays) */ : mData{std::data(arr)}, mDataLength{std::size(arr)} { } template constexpr span(std::array &arr) noexcept : mData{std::data(arr)}, mDataLength{std::size(arr)} { } template::value)> constexpr span(const std::array &arr) noexcept : mData{std::data(arr)}, mDataLength{std::size(arr)} { } template)> constexpr span(U&& cont) : span{std::data(cont), std::size(cont)} { } template && (!std::is_same::value || extent != N))> constexpr span(const span &span_) noexcept : span{std::data(span_), std::size(span_)} { } constexpr span(const span&) noexcept = default; constexpr span& operator=(const span &rhs) noexcept = default; [[nodiscard]] constexpr auto front() const -> reference { return mData[0]; } [[nodiscard]] constexpr auto back() const -> reference { return mData[mDataLength-1]; } [[nodiscard]] constexpr auto operator[](size_type idx) const -> reference {return mData[idx];} [[nodiscard]] constexpr auto data() const noexcept -> pointer { return mData; } [[nodiscard]] constexpr auto size() const noexcept -> size_type { return mDataLength; } [[nodiscard]] constexpr auto size_bytes() const noexcept -> size_type { return mDataLength * sizeof(value_type); } [[nodiscard]] constexpr auto empty() const noexcept -> bool { return mDataLength == 0; } [[nodiscard]] constexpr auto begin() const noexcept -> iterator { return iterator{mData}; } [[nodiscard]] constexpr auto end() const noexcept -> iterator { return iterator{mData+mDataLength}; } [[nodiscard]] constexpr auto cbegin() const noexcept -> const_iterator { return const_iterator{mData}; } [[nodiscard]] constexpr auto cend() const noexcept -> const_iterator { return const_iterator{mData+mDataLength}; } [[nodiscard]] constexpr auto rbegin() const noexcept -> reverse_iterator { return reverse_iterator{end()}; } [[nodiscard]] constexpr auto rend() const noexcept -> reverse_iterator { return reverse_iterator{begin()}; } [[nodiscard]] constexpr auto crbegin() const noexcept -> const_reverse_iterator { return cend(); } [[nodiscard]] constexpr auto crend() const noexcept -> const_reverse_iterator { return cbegin(); } template [[nodiscard]] constexpr auto first() const noexcept -> span { assert(C <= mDataLength); return span{mData, C}; } [[nodiscard]] constexpr auto first(std::size_t count) const noexcept -> span { assert(count <= mDataLength); return span{mData, count}; } template [[nodiscard]] constexpr auto last() const noexcept -> span { assert(C <= mDataLength); return span{mData+mDataLength-C, C}; } [[nodiscard]] constexpr auto last(std::size_t count) const noexcept -> span { assert(count <= mDataLength); return span{mData+mDataLength-count, count}; } template [[nodiscard]] constexpr auto subspan() const noexcept -> std::enable_if_t> { assert(O <= mDataLength); assert(C <= mDataLength-O); return span{mData+O, C}; } template [[nodiscard]] constexpr auto subspan() const noexcept -> std::enable_if_t> { assert(O <= mDataLength); return span{mData+O, mDataLength-O}; } [[nodiscard]] constexpr auto subspan(std::size_t offset, std::size_t count=dynamic_extent) const noexcept -> span { assert(offset <= mDataLength); if(count != dynamic_extent) { assert(count <= mDataLength-offset); return span{mData+offset, count}; } return span{mData+offset, mDataLength-offset}; } private: pointer mData{nullptr}; size_type mDataLength{0}; }; template [[nodiscard]] constexpr auto span::first(std::size_t count) const noexcept -> span { assert(count <= size()); return span{mData, count}; } template [[nodiscard]] constexpr auto span::last(std::size_t count) const noexcept -> span { assert(count <= size()); return span{mData+size()-count, count}; } template [[nodiscard]] constexpr auto span::subspan(std::size_t offset, std::size_t count) const noexcept -> span { assert(offset <= size()); if(count != dynamic_extent) { assert(count <= size()-offset); return span{mData+offset, count}; } return span{mData+offset, size()-offset}; } /* NOLINTEND(google-explicit-constructor) */ template span(T, EndOrSize) -> span())>>; template span(T (&)[N]) -> span; /* NOLINT(*-avoid-c-arrays) */ template span(std::array&) -> span; template span(const std::array&) -> span; template)> span(C&&) -> span()))>>; #undef REQUIRES } // namespace al #endif /* AL_SPAN_H */ openal-soft-1.24.2/common/alstring.cpp000066400000000000000000000025431474041540300176540ustar00rootroot00000000000000 #include "config.h" #include "alstring.h" #include #include #include #include namespace al { int case_compare(const std::string_view str0, const std::string_view str1) noexcept { using Traits = std::string_view::traits_type; auto ch0 = str0.cbegin(); auto ch1 = str1.cbegin(); auto ch1end = ch1 + std::min(str0.size(), str1.size()); while(ch1 != ch1end) { const int u0{std::toupper(Traits::to_int_type(*ch0))}; const int u1{std::toupper(Traits::to_int_type(*ch1))}; if(const int diff{u0-u1}) return diff; ++ch0; ++ch1; } if(str0.size() < str1.size()) return -1; if(str0.size() > str1.size()) return 1; return 0; } int case_compare(const std::wstring_view str0, const std::wstring_view str1) noexcept { using Traits = std::wstring_view::traits_type; auto ch0 = str0.cbegin(); auto ch1 = str1.cbegin(); auto ch1end = ch1 + std::min(str0.size(), str1.size()); while(ch1 != ch1end) { const auto u0 = std::towupper(Traits::to_int_type(*ch0)); const auto u1 = std::towupper(Traits::to_int_type(*ch1)); if(const auto diff = static_cast(u0-u1)) return diff; ++ch0; ++ch1; } if(str0.size() < str1.size()) return -1; if(str0.size() > str1.size()) return 1; return 0; } } // namespace al openal-soft-1.24.2/common/alstring.h000066400000000000000000000037221474041540300173210ustar00rootroot00000000000000#ifndef AL_STRING_H #define AL_STRING_H #include #include #include #include #include namespace al { template [[nodiscard]] constexpr auto sizei(const std::basic_string_view str) noexcept -> int { return static_cast(std::min(str.size(), std::numeric_limits::max())); } template [[nodiscard]] constexpr auto sizei(const std::basic_string &str) noexcept -> int { return static_cast(std::min(str.size(), std::numeric_limits::max())); } [[nodiscard]] constexpr bool contains(const std::string_view str0, const std::string_view str1) noexcept { return str0.find(str1) != std::string_view::npos; } [[nodiscard]] constexpr bool starts_with(const std::string_view str0, const std::string_view str1) noexcept { return str0.substr(0, std::min(str0.size(), str1.size())) == str1; } [[nodiscard]] constexpr bool ends_with(const std::string_view str0, const std::string_view str1) noexcept { return str0.substr(str0.size() - std::min(str0.size(), str1.size())) == str1; } [[nodiscard]] int case_compare(const std::string_view str0, const std::string_view str1) noexcept; [[nodiscard]] int case_compare(const std::wstring_view str0, const std::wstring_view str1) noexcept; /* C++20 changes path::u8string() to return a string using a new/distinct * char8_t type for UTF-8 strings. However, support for this with standard * string functions is totally inadequate, and we already hold UTF-8 with plain * char strings. So this function is used to reinterpret a char8_t string as a * char string_view. */ #if defined(__cpp_lib_char8_t) && __cpp_lib_char8_t >= 201907L inline auto u8_as_char(const std::u8string_view str) -> std::string_view #else inline auto u8_as_char(const std::string_view str) -> std::string_view #endif { return std::string_view{reinterpret_cast(str.data()), str.size()}; } } // namespace al #endif /* AL_STRING_H */ openal-soft-1.24.2/common/althrd_setname.cpp000066400000000000000000000036301474041540300210210ustar00rootroot00000000000000 #include "config.h" #include "althrd_setname.h" #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #include void althrd_setname(const char *name [[maybe_unused]]) { #if defined(_MSC_VER) && !defined(_M_ARM) #define MS_VC_EXCEPTION 0x406D1388 #pragma pack(push,8) struct InfoStruct { DWORD dwType; // Must be 0x1000. LPCSTR szName; // Pointer to name (in user addr space). DWORD dwThreadID; // Thread ID (-1=caller thread). DWORD dwFlags; // Reserved for future use, must be zero. }; #pragma pack(pop) InfoStruct info{}; info.dwType = 0x1000; info.szName = name; info.dwThreadID = ~DWORD{0}; info.dwFlags = 0; /* FIXME: How to do this on MinGW? */ __try { RaiseException(MS_VC_EXCEPTION, 0, sizeof(info)/sizeof(ULONG_PTR), (ULONG_PTR*)&info); } __except(EXCEPTION_CONTINUE_EXECUTION) { } #undef MS_VC_EXCEPTION #endif } #else #include #ifdef HAVE_PTHREAD_NP_H #include #endif namespace { using setname_t1 = int(*)(const char*); using setname_t2 = int(*)(pthread_t, const char*); using setname_t3 = void(*)(pthread_t, const char*); using setname_t4 = int(*)(pthread_t, const char*, void*); [[maybe_unused]] void setname_caller(setname_t1 func, const char *name) { func(name); } [[maybe_unused]] void setname_caller(setname_t2 func, const char *name) { func(pthread_self(), name); } [[maybe_unused]] void setname_caller(setname_t3 func, const char *name) { func(pthread_self(), name); } [[maybe_unused]] void setname_caller(setname_t4 func, const char *name) { func(pthread_self(), "%s", const_cast(name)); /* NOLINT(*-const-cast) */ } } // namespace void althrd_setname(const char *name [[maybe_unused]]) { #if defined(HAVE_PTHREAD_SET_NAME_NP) setname_caller(pthread_set_name_np, name); #elif defined(HAVE_PTHREAD_SETNAME_NP) setname_caller(pthread_setname_np, name); #endif } #endif openal-soft-1.24.2/common/althrd_setname.h000066400000000000000000000002161474041540300204630ustar00rootroot00000000000000#ifndef COMMON_ALTHRD_SETNAME_H #define COMMON_ALTHRD_SETNAME_H void althrd_setname(const char *name); #endif /* COMMON_ALTHRD_SETNAME_H */ openal-soft-1.24.2/common/althreads.h000066400000000000000000000072531474041540300174500ustar00rootroot00000000000000#ifndef AL_THREADS_H #define AL_THREADS_H #include #include #include #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #include #elif defined(__STDC_NO_THREADS__) || !__has_include() #include #else #include #endif #include "albit.h" namespace al { template class tss { static_assert(sizeof(T) <= sizeof(void*)); static_assert(std::is_trivially_destructible_v && std::is_trivially_copy_constructible_v); [[nodiscard]] static auto to_ptr(const T &value) noexcept -> void* { if constexpr(std::is_pointer_v) { if constexpr(std::is_const_v>) return const_cast(static_cast(value)); /* NOLINT(*-const-cast) */ else return static_cast(value); } else if constexpr(sizeof(T) == sizeof(void*)) return al::bit_cast(value); else if constexpr(std::is_integral_v) return al::bit_cast(static_cast(value)); } [[nodiscard]] static auto from_ptr(void *ptr) noexcept -> T { if constexpr(std::is_pointer_v) return static_cast(ptr); else if constexpr(sizeof(T) == sizeof(void*)) return al::bit_cast(ptr); else if constexpr(std::is_integral_v) return static_cast(al::bit_cast(ptr)); } #ifdef _WIN32 DWORD mTss{TLS_OUT_OF_INDEXES}; public: tss() : mTss{TlsAlloc()} { if(mTss == TLS_OUT_OF_INDEXES) throw std::runtime_error{"al::tss::tss()"}; } explicit tss(const T &init) : tss{} { if(TlsSetValue(mTss, to_ptr(init)) == FALSE) throw std::runtime_error{"al::tss::tss(T)"}; } ~tss() { TlsFree(mTss); } void set(const T &value) const { if(TlsSetValue(mTss, to_ptr(value)) == FALSE) throw std::runtime_error{"al::tss::set(T)"}; } [[nodiscard]] auto get() const noexcept -> T { return from_ptr(TlsGetValue(mTss)); } #elif defined(__STDC_NO_THREADS__) || !__has_include() pthread_key_t mTss{}; public: tss() { if(int res{pthread_key_create(&mTss, nullptr)}; res != 0) throw std::runtime_error{"al::tss::tss()"}; } explicit tss(const T &init) : tss{} { if(int res{pthread_setspecific(mTss, to_ptr(init))}; res != 0) throw std::runtime_error{"al::tss::tss(T)"}; } ~tss() { pthread_key_delete(mTss); } void set(const T &value) const { if(int res{pthread_setspecific(mTss, to_ptr(value))}; res != 0) throw std::runtime_error{"al::tss::set(T)"}; } [[nodiscard]] auto get() const noexcept -> T { return from_ptr(pthread_getspecific(mTss)); } #else tss_t mTss{}; public: tss() { if(int res{tss_create(&mTss, nullptr)}; res != thrd_success) throw std::runtime_error{"al::tss::tss()"}; } explicit tss(const T &init) : tss{} { if(int res{tss_set(mTss, to_ptr(init))}; res != thrd_success) throw std::runtime_error{"al::tss::tss(T)"}; } ~tss() { tss_delete(mTss); } void set(const T &value) const { if(int res{tss_set(mTss, to_ptr(value))}; res != thrd_success) throw std::runtime_error{"al::tss::set(T)"}; } [[nodiscard]] auto get() const noexcept -> T { return from_ptr(tss_get(mTss)); } #endif /* _WIN32 */ tss(const tss&) = delete; tss(tss&&) = delete; void operator=(const tss&) = delete; void operator=(tss&&) = delete; }; } // namespace al #endif /* AL_THREADS_H */ openal-soft-1.24.2/common/altraits.h000066400000000000000000000004061474041540300173150ustar00rootroot00000000000000#ifndef COMMON_ALTRAITS_H #define COMMON_ALTRAITS_H namespace al { template struct type_identity { using type = T; }; template using type_identity_t = typename type_identity::type; } // namespace al #endif /* COMMON_ALTRAITS_H */ openal-soft-1.24.2/common/atomic.h000066400000000000000000000067131474041540300167550ustar00rootroot00000000000000#ifndef AL_ATOMIC_H #define AL_ATOMIC_H #include #include #include #include "almalloc.h" template auto IncrementRef(std::atomic &ref) noexcept { return ref.fetch_add(1u, std::memory_order_acq_rel)+1u; } template auto DecrementRef(std::atomic &ref) noexcept { return ref.fetch_sub(1u, std::memory_order_acq_rel)-1u; } /* WARNING: A livelock is theoretically possible if another thread keeps * changing the head without giving this a chance to actually swap in the new * one (practically impossible with this little code, but...). */ template inline void AtomicReplaceHead(std::atomic &head, T newhead) { T first_ = head.load(std::memory_order_acquire); do { newhead->next.store(first_, std::memory_order_relaxed); } while(!head.compare_exchange_weak(first_, newhead, std::memory_order_acq_rel, std::memory_order_acquire)); } namespace al { template> class atomic_unique_ptr { std::atomic> mPointer{}; using unique_ptr_t = std::unique_ptr; public: atomic_unique_ptr() = default; atomic_unique_ptr(const atomic_unique_ptr&) = delete; explicit atomic_unique_ptr(std::nullptr_t) noexcept { } explicit atomic_unique_ptr(gsl::owner ptr) noexcept : mPointer{ptr} { } explicit atomic_unique_ptr(unique_ptr_t&& rhs) noexcept : mPointer{rhs.release()} { } ~atomic_unique_ptr() { if(auto ptr = mPointer.exchange(nullptr, std::memory_order_relaxed)) D{}(ptr); } auto operator=(const atomic_unique_ptr&) -> atomic_unique_ptr& = delete; auto operator=(std::nullptr_t) noexcept -> atomic_unique_ptr& { if(auto ptr = mPointer.exchange(nullptr)) D{}(ptr); return *this; } auto operator=(unique_ptr_t&& rhs) noexcept -> atomic_unique_ptr& { if(auto ptr = mPointer.exchange(rhs.release())) D{}(ptr); return *this; } [[nodiscard]] auto load(std::memory_order m=std::memory_order_seq_cst) const noexcept -> T* { return mPointer.load(m); } void store(std::nullptr_t, std::memory_order m=std::memory_order_seq_cst) noexcept { if(auto oldptr = mPointer.exchange(nullptr, m)) D{}(oldptr); } void store(gsl::owner ptr, std::memory_order m=std::memory_order_seq_cst) noexcept { if(auto oldptr = mPointer.exchange(ptr, m)) D{}(oldptr); } void store(unique_ptr_t&& ptr, std::memory_order m=std::memory_order_seq_cst) noexcept { if(auto oldptr = mPointer.exchange(ptr.release(), m)) D{}(oldptr); } [[nodiscard]] auto exchange(std::nullptr_t, std::memory_order m=std::memory_order_seq_cst) noexcept -> unique_ptr_t { return unique_ptr_t{mPointer.exchange(nullptr, m)}; } [[nodiscard]] auto exchange(gsl::owner ptr, std::memory_order m=std::memory_order_seq_cst) noexcept -> unique_ptr_t { return unique_ptr_t{mPointer.exchange(ptr, m)}; } [[nodiscard]] auto exchange(std::unique_ptr&& ptr, std::memory_order m=std::memory_order_seq_cst) noexcept -> unique_ptr_t { return unique_ptr_t{mPointer.exchange(ptr.release(), m)}; } [[nodiscard]] auto is_lock_free() const noexcept -> bool { return mPointer.is_lock_free(); } static constexpr auto is_always_lock_free = std::atomic>::is_always_lock_free; }; } // namespace al #endif /* AL_ATOMIC_H */ openal-soft-1.24.2/common/comptr.h000066400000000000000000000061141474041540300170000ustar00rootroot00000000000000#ifndef COMMON_COMPTR_H #define COMMON_COMPTR_H #ifdef _WIN32 #include #include #define WIN32_LEAN_AND_MEAN #include #include struct ComWrapper { HRESULT mStatus{}; ComWrapper(void *reserved, DWORD coinit) : mStatus{CoInitializeEx(reserved, coinit)} { } explicit ComWrapper(DWORD coinit=COINIT_APARTMENTTHREADED) : mStatus{CoInitializeEx(nullptr, coinit)} { } ComWrapper(ComWrapper&& rhs) { mStatus = std::exchange(rhs.mStatus, E_FAIL); } ComWrapper(const ComWrapper&) = delete; ~ComWrapper() { if(SUCCEEDED(mStatus)) CoUninitialize(); } ComWrapper& operator=(ComWrapper&& rhs) { if(SUCCEEDED(mStatus)) CoUninitialize(); mStatus = std::exchange(rhs.mStatus, E_FAIL); return *this; } ComWrapper& operator=(const ComWrapper&) = delete; [[nodiscard]] HRESULT status() const noexcept { return mStatus; } explicit operator bool() const noexcept { return SUCCEEDED(status()); } void uninit() { if(SUCCEEDED(mStatus)) CoUninitialize(); mStatus = E_FAIL; } }; template /* NOLINTNEXTLINE(clazy-rule-of-three) False positive */ struct ComPtr { using element_type = T; static constexpr bool RefIsNoexcept{noexcept(std::declval().AddRef()) && noexcept(std::declval().Release())}; ComPtr() noexcept = default; ComPtr(const ComPtr &rhs) noexcept(RefIsNoexcept) : mPtr{rhs.mPtr} { if(mPtr) mPtr->AddRef(); } ComPtr(ComPtr&& rhs) noexcept : mPtr{rhs.mPtr} { rhs.mPtr = nullptr; } ComPtr(std::nullptr_t) noexcept { } /* NOLINT(google-explicit-constructor) */ explicit ComPtr(T *ptr) noexcept : mPtr{ptr} { } ~ComPtr() { if(mPtr) mPtr->Release(); } /* NOLINTNEXTLINE(bugprone-unhandled-self-assignment) Yes it is. */ ComPtr& operator=(const ComPtr &rhs) noexcept(RefIsNoexcept) { if constexpr(RefIsNoexcept) { if(rhs.mPtr) rhs.mPtr->AddRef(); if(mPtr) mPtr->Release(); mPtr = rhs.mPtr; return *this; } else { ComPtr tmp{rhs}; if(mPtr) mPtr->Release(); mPtr = tmp.release(); return *this; } } ComPtr& operator=(ComPtr&& rhs) noexcept(RefIsNoexcept) { if(&rhs != this) { if(mPtr) mPtr->Release(); mPtr = std::exchange(rhs.mPtr, nullptr); } return *this; } void reset(T *ptr=nullptr) noexcept(RefIsNoexcept) { if(mPtr) mPtr->Release(); mPtr = ptr; } explicit operator bool() const noexcept { return mPtr != nullptr; } T& operator*() const noexcept { return *mPtr; } T* operator->() const noexcept { return mPtr; } T* get() const noexcept { return mPtr; } T* release() noexcept { return std::exchange(mPtr, nullptr); } void swap(ComPtr &rhs) noexcept { std::swap(mPtr, rhs.mPtr); } void swap(ComPtr&& rhs) noexcept { std::swap(mPtr, rhs.mPtr); } private: T *mPtr{nullptr}; }; #endif /* _WIN32 */ #endif openal-soft-1.24.2/common/dynload.cpp000066400000000000000000000016301474041540300174570ustar00rootroot00000000000000 #include "config.h" #include "dynload.h" #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #include #include "strutils.h" void *LoadLib(const char *name) { std::wstring wname{utf8_to_wstr(name)}; return LoadLibraryW(wname.c_str()); } void CloseLib(void *handle) { FreeLibrary(static_cast(handle)); } void *GetSymbol(void *handle, const char *name) { return reinterpret_cast(GetProcAddress(static_cast(handle), name)); } #elif defined(HAVE_DLFCN_H) #include void *LoadLib(const char *name) { dlerror(); void *handle{dlopen(name, RTLD_NOW)}; const char *err{dlerror()}; if(err) handle = nullptr; return handle; } void CloseLib(void *handle) { dlclose(handle); } void *GetSymbol(void *handle, const char *name) { dlerror(); void *sym{dlsym(handle, name)}; const char *err{dlerror()}; if(err) sym = nullptr; return sym; } #endif openal-soft-1.24.2/common/dynload.h000066400000000000000000000004421474041540300171240ustar00rootroot00000000000000#ifndef AL_DYNLOAD_H #define AL_DYNLOAD_H #if defined(_WIN32) || defined(HAVE_DLFCN_H) #define HAVE_DYNLOAD 1 void *LoadLib(const char *name); void CloseLib(void *handle); void *GetSymbol(void *handle, const char *name); #else #define HAVE_DYNLOAD 0 #endif #endif /* AL_DYNLOAD_H */ openal-soft-1.24.2/common/filesystem.cpp000066400000000000000000000071661474041540300202230ustar00rootroot00000000000000//--------------------------------------------------------------------------------------- // // ghc::filesystem - A C++17-like filesystem implementation for C++11/C++14 // //--------------------------------------------------------------------------------------- // // Copyright (c) 2018, Steffen Schümann // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. // //--------------------------------------------------------------------------------------- // fs_std_impl.hpp - The implementation header for the header/implementation separated usage of // ghc::filesystem that does nothing if std::filesystem is detected. // This file can be used to hide the implementation of ghc::filesystem into a single cpp. // The cpp has to include this before including fs_std_fwd.hpp directly or via a different // header to work. //--------------------------------------------------------------------------------------- #if defined(_MSVC_LANG) && _MSVC_LANG >= 201703L || __cplusplus >= 201703L && defined(__has_include) // ^ Supports MSVC prior to 15.7 without setting /Zc:__cplusplus to fix __cplusplus // _MSVC_LANG works regardless. But without the switch, the compiler always reported 199711L: https://blogs.msdn.microsoft.com/vcblog/2018/04/09/msvc-now-correctly-reports-__cplusplus/ #if __has_include() // Two stage __has_include needed for MSVC 2015 and per https://gcc.gnu.org/onlinedocs/cpp/_005f_005fhas_005finclude.html #define GHC_USE_STD_FS // Old Apple OSs don't support std::filesystem, though the header is available at compile // time. In particular, std::filesystem is unavailable before macOS 10.15, iOS/tvOS 13.0, // and watchOS 6.0. #ifdef __APPLE__ #include // Note: This intentionally uses std::filesystem on any new Apple OS, like visionOS // released after std::filesystem, where std::filesystem is always available. // (All other ___VERSION_MIN_REQUIREDs will be undefined and thus 0.) #if defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && __MAC_OS_X_VERSION_MIN_REQUIRED < 101500 \ || defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && __IPHONE_OS_VERSION_MIN_REQUIRED < 130000 \ || defined(__TV_OS_VERSION_MIN_REQUIRED) && __TV_OS_VERSION_MIN_REQUIRED < 130000 \ || defined(__WATCH_OS_VERSION_MAX_ALLOWED) && __WATCH_OS_VERSION_MAX_ALLOWED < 60000 #undef GHC_USE_STD_FS #endif #endif #endif #endif #ifndef GHC_USE_STD_FS #define GHC_FILESYSTEM_IMPLEMENTATION #include "ghc_filesystem.h" #endif openal-soft-1.24.2/common/filesystem.h000066400000000000000000000102661474041540300176630ustar00rootroot00000000000000//--------------------------------------------------------------------------------------- // // ghc::filesystem - A C++17-like filesystem implementation for C++11/C++14 // //--------------------------------------------------------------------------------------- // // Copyright (c) 2018, Steffen Schümann // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. // //--------------------------------------------------------------------------------------- // fs_std_fwd.hpp - The forwarding header for the header/implementation separated usage of // ghc::filesystem that uses std::filesystem if it detects it. // This file can be include at any place, where fs::filesystem api is needed while // not bleeding implementation details (e.g. system includes) into the global namespace, // as long as one cpp includes fs_std_impl.hpp to deliver the matching implementations. //--------------------------------------------------------------------------------------- #ifndef GHC_FILESYSTEM_STD_FWD_H #define GHC_FILESYSTEM_STD_FWD_H #if defined(_MSVC_LANG) && _MSVC_LANG >= 201703L || __cplusplus >= 201703L && defined(__has_include) // ^ Supports MSVC prior to 15.7 without setting /Zc:__cplusplus to fix __cplusplus // _MSVC_LANG works regardless. But without the switch, the compiler always reported 199711L: https://blogs.msdn.microsoft.com/vcblog/2018/04/09/msvc-now-correctly-reports-__cplusplus/ #if __has_include() // Two stage __has_include needed for MSVC 2015 and per https://gcc.gnu.org/onlinedocs/cpp/_005f_005fhas_005finclude.html #define GHC_USE_STD_FS // Old Apple OSs don't support std::filesystem, though the header is available at compile // time. In particular, std::filesystem is unavailable before macOS 10.15, iOS/tvOS 13.0, // and watchOS 6.0. #ifdef __APPLE__ #include // Note: This intentionally uses std::filesystem on any new Apple OS, like visionOS // released after std::filesystem, where std::filesystem is always available. // (All other ___VERSION_MIN_REQUIREDs will be undefined and thus 0.) #if defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && __MAC_OS_X_VERSION_MIN_REQUIRED < 101500 \ || defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && __IPHONE_OS_VERSION_MIN_REQUIRED < 130000 \ || defined(__TV_OS_VERSION_MIN_REQUIRED) && __TV_OS_VERSION_MIN_REQUIRED < 130000 \ || defined(__WATCH_OS_VERSION_MAX_ALLOWED) && __WATCH_OS_VERSION_MAX_ALLOWED < 60000 #undef GHC_USE_STD_FS #endif #endif #endif #endif #ifdef GHC_USE_STD_FS #include namespace fs { using namespace std::filesystem; using ifstream = std::ifstream; using ofstream = std::ofstream; using fstream = std::fstream; } #else #define GHC_FILESYSTEM_FWD #include "ghc_filesystem.h" namespace fs { using namespace ghc::filesystem; using ifstream = ghc::filesystem::ifstream; using ofstream = ghc::filesystem::ofstream; using fstream = ghc::filesystem::fstream; } #endif #endif // GHC_FILESYSTEM_STD_FWD_H openal-soft-1.24.2/common/flexarray.h000066400000000000000000000142661474041540300175000ustar00rootroot00000000000000#ifndef AL_FLEXARRAY_H #define AL_FLEXARRAY_H #include #include #include #include #include #include #include "almalloc.h" #include "alspan.h" namespace al { /* Storage for flexible array data. This is trivially destructible if type T is * trivially destructible. */ template::value> struct alignas(alignment) FlexArrayStorage : al::span { /* NOLINTBEGIN(bugprone-sizeof-expression) clang-tidy warns about the * sizeof(T) being suspicious when T is a pointer type, which it will be * for flexible arrays of pointers. */ static constexpr size_t Sizeof(size_t count, size_t base=0u) noexcept { return sizeof(FlexArrayStorage) + sizeof(T)*count + base; } /* NOLINTEND(bugprone-sizeof-expression) */ /* NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic) Flexible * arrays store their payloads after the end of the object, which must be * the last in the whole parent chain. */ explicit FlexArrayStorage(size_t size) noexcept(std::is_nothrow_constructible_v) : al::span{::new(static_cast(this+1)) T[size], size} { } /* NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic) */ ~FlexArrayStorage() = default; FlexArrayStorage(const FlexArrayStorage&) = delete; FlexArrayStorage& operator=(const FlexArrayStorage&) = delete; }; template struct alignas(alignment) FlexArrayStorage : al::span { static constexpr size_t Sizeof(size_t count, size_t base=0u) noexcept { return sizeof(FlexArrayStorage) + sizeof(T)*count + base; } /* NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic) */ explicit FlexArrayStorage(size_t size) noexcept(std::is_nothrow_constructible_v) : al::span{::new(static_cast(this+1)) T[size], size} { } /* NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic) */ ~FlexArrayStorage() { std::destroy(this->begin(), this->end()); } FlexArrayStorage(const FlexArrayStorage&) = delete; FlexArrayStorage& operator=(const FlexArrayStorage&) = delete; }; /* A flexible array type. Used either standalone or at the end of a parent * struct, to have a run-time-sized array that's embedded with its size. Should * be used delicately, ensuring there's no additional data after the FlexArray * member. */ template struct FlexArray { using element_type = T; using value_type = std::remove_cv_t; using index_type = size_t; using difference_type = ptrdiff_t; using pointer = T*; using const_pointer = const T*; using reference = T&; using const_reference = const T&; static constexpr std::size_t StorageAlign{std::max(alignof(T), Align)}; using Storage_t_ = FlexArrayStorage), StorageAlign)>; using iterator = typename Storage_t_::iterator; using const_iterator = typename Storage_t_::const_iterator; using reverse_iterator = typename Storage_t_::reverse_iterator; using const_reverse_iterator = typename Storage_t_::const_reverse_iterator; const Storage_t_ mStore; static constexpr index_type Sizeof(index_type count, index_type base=0u) noexcept { return Storage_t_::Sizeof(count, base); } static std::unique_ptr Create(index_type count) { return std::unique_ptr{new(FamCount{count}) FlexArray{count}}; } explicit FlexArray(index_type size) noexcept(std::is_nothrow_constructible_v) : mStore{size} { } ~FlexArray() = default; [[nodiscard]] auto size() const noexcept -> index_type { return mStore.size(); } [[nodiscard]] auto empty() const noexcept -> bool { return mStore.empty(); } [[nodiscard]] auto data() noexcept -> pointer { return mStore.data(); } [[nodiscard]] auto data() const noexcept -> const_pointer { return mStore.data(); } [[nodiscard]] auto operator[](index_type i) noexcept -> reference { return mStore[i]; } [[nodiscard]] auto operator[](index_type i) const noexcept -> const_reference { return mStore[i]; } [[nodiscard]] auto front() noexcept -> reference { return mStore.front(); } [[nodiscard]] auto front() const noexcept -> const_reference { return mStore.front(); } [[nodiscard]] auto back() noexcept -> reference { return mStore.back(); } [[nodiscard]] auto back() const noexcept -> const_reference { return mStore.back(); } [[nodiscard]] auto begin() noexcept -> iterator { return mStore.begin(); } [[nodiscard]] auto begin() const noexcept -> const_iterator { return mStore.cbegin(); } [[nodiscard]] auto cbegin() const noexcept -> const_iterator { return mStore.cbegin(); } [[nodiscard]] auto end() noexcept -> iterator { return mStore.end(); } [[nodiscard]] auto end() const noexcept -> const_iterator { return mStore.cend(); } [[nodiscard]] auto cend() const noexcept -> const_iterator { return mStore.cend(); } [[nodiscard]] auto rbegin() noexcept -> reverse_iterator { return mStore.rbegin(); } [[nodiscard]] auto rbegin() const noexcept -> const_reverse_iterator { return mStore.crbegin(); } [[nodiscard]] auto crbegin() const noexcept -> const_reverse_iterator { return mStore.crbegin(); } [[nodiscard]] auto rend() noexcept -> reverse_iterator { return mStore.rend(); } [[nodiscard]] auto rend() const noexcept -> const_reverse_iterator { return mStore.crend(); } [[nodiscard]] auto crend() const noexcept -> const_reverse_iterator { return mStore.crend(); } gsl::owner operator new(size_t, FamCount count) { return ::operator new[](Sizeof(count), std::align_val_t{alignof(FlexArray)}); } void operator delete(gsl::owner block, FamCount) noexcept { ::operator delete[](block, std::align_val_t{alignof(FlexArray)}); } void operator delete(gsl::owner block) noexcept { ::operator delete[](block, std::align_val_t{alignof(FlexArray)}); } void *operator new(size_t size) = delete; void *operator new[](size_t size) = delete; void operator delete[](void *block) = delete; }; } // namespace al #endif /* AL_FLEXARRAY_H */ openal-soft-1.24.2/common/ghc_filesystem.h000066400000000000000000005666551474041540300205260ustar00rootroot00000000000000//--------------------------------------------------------------------------------------- // // ghc::filesystem - A C++17-like filesystem implementation for C++11/C++14/C++17/C++20 // //--------------------------------------------------------------------------------------- // // Copyright (c) 2018, Steffen Schümann // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. // //--------------------------------------------------------------------------------------- #ifndef GHC_FILESYSTEM_H #define GHC_FILESYSTEM_H // #define BSD manifest constant only in // sys/param.h #ifndef _WIN32 #include #endif #ifndef GHC_OS_DETECTED #if defined(__APPLE__) && defined(__MACH__) #define GHC_OS_APPLE #elif defined(__linux__) #define GHC_OS_LINUX #if defined(__ANDROID__) #define GHC_OS_ANDROID #endif #elif defined(_WIN64) #define GHC_OS_WINDOWS #define GHC_OS_WIN64 #elif defined(_WIN32) #define GHC_OS_WINDOWS #define GHC_OS_WIN32 #elif defined(__CYGWIN__) #define GHC_OS_CYGWIN #elif defined(__sun) && defined(__SVR4) #define GHC_OS_SOLARIS #elif defined(__svr4__) #define GHC_OS_SYS5R4 #elif defined(BSD) #define GHC_OS_BSD #elif defined(__EMSCRIPTEN__) #define GHC_OS_WEB #include #elif defined(__QNX__) #define GHC_OS_QNX #elif defined(__HAIKU__) #define GHC_OS_HAIKU #else #error "Operating system currently not supported!" #endif #define GHC_OS_DETECTED #if (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) #if _MSVC_LANG == 201703L #define GHC_FILESYSTEM_RUNNING_CPP17 #else #define GHC_FILESYSTEM_RUNNING_CPP20 #endif #elif (defined(__cplusplus) && __cplusplus >= 201703L) #if __cplusplus == 201703L #define GHC_FILESYSTEM_RUNNING_CPP17 #else #define GHC_FILESYSTEM_RUNNING_CPP20 #endif #endif #endif #if defined(GHC_FILESYSTEM_IMPLEMENTATION) #define GHC_EXPAND_IMPL #define GHC_INLINE #ifdef GHC_OS_WINDOWS #ifndef GHC_FS_API #define GHC_FS_API #endif #ifndef GHC_FS_API_CLASS #define GHC_FS_API_CLASS #endif #else #ifndef GHC_FS_API #define GHC_FS_API __attribute__((visibility("default"))) #endif #ifndef GHC_FS_API_CLASS #define GHC_FS_API_CLASS __attribute__((visibility("default"))) #endif #endif #elif defined(GHC_FILESYSTEM_FWD) #define GHC_INLINE #ifdef GHC_OS_WINDOWS #ifndef GHC_FS_API #define GHC_FS_API extern #endif #ifndef GHC_FS_API_CLASS #define GHC_FS_API_CLASS #endif #else #ifndef GHC_FS_API #define GHC_FS_API extern #endif #ifndef GHC_FS_API_CLASS #define GHC_FS_API_CLASS #endif #endif #else #define GHC_EXPAND_IMPL #define GHC_INLINE inline #ifndef GHC_FS_API #define GHC_FS_API #endif #ifndef GHC_FS_API_CLASS #define GHC_FS_API_CLASS #endif #endif #ifdef GHC_EXPAND_IMPL #ifdef GHC_OS_WINDOWS #include // additional includes #include #include #include #include #include #else #include #include #include #include #include #include #include #include #ifdef GHC_OS_ANDROID #include #if __ANDROID_API__ < 12 #include #endif #include #define statvfs statfs #else #include #endif #ifdef GHC_OS_CYGWIN #include #endif #if !defined(__ANDROID__) || __ANDROID_API__ >= 26 #include #endif #endif #ifdef GHC_OS_APPLE #include #endif #if defined(__cpp_impl_three_way_comparison) && defined(__has_include) #if __has_include() #define GHC_HAS_THREEWAY_COMP #include #endif #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #else // GHC_EXPAND_IMPL #if defined(__cpp_impl_three_way_comparison) && defined(__has_include) #if __has_include() #define GHC_HAS_THREEWAY_COMP #include #endif #endif #include #include #include #include #include #include #include #ifdef GHC_OS_WINDOWS #include #endif #endif // GHC_EXPAND_IMPL // After standard library includes. // Standard library support for std::string_view. #if defined(__cpp_lib_string_view) #define GHC_HAS_STD_STRING_VIEW #elif defined(_LIBCPP_VERSION) && (_LIBCPP_VERSION >= 4000) && (__cplusplus >= 201402) #define GHC_HAS_STD_STRING_VIEW #elif defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE >= 7) && (__cplusplus >= 201703) #define GHC_HAS_STD_STRING_VIEW #elif defined(_MSC_VER) && (_MSC_VER >= 1910 && _MSVC_LANG >= 201703) #define GHC_HAS_STD_STRING_VIEW #endif // Standard library support for std::experimental::string_view. #if defined(_LIBCPP_VERSION) && (_LIBCPP_VERSION >= 3700 && _LIBCPP_VERSION < 7000) && (__cplusplus >= 201402) #define GHC_HAS_STD_EXPERIMENTAL_STRING_VIEW #elif defined(__GNUC__) && (((__GNUC__ == 4) && (__GNUC_MINOR__ >= 9)) || (__GNUC__ > 4)) && (__cplusplus >= 201402) #define GHC_HAS_STD_EXPERIMENTAL_STRING_VIEW #elif defined(__GLIBCXX__) && defined(_GLIBCXX_USE_DUAL_ABI) && (__cplusplus >= 201402) // macro _GLIBCXX_USE_DUAL_ABI is always defined in libstdc++ from gcc-5 and newer #define GHC_HAS_STD_EXPERIMENTAL_STRING_VIEW #endif #if defined(GHC_HAS_STD_STRING_VIEW) #include #elif defined(GHC_HAS_STD_EXPERIMENTAL_STRING_VIEW) #include #endif #if !defined(GHC_OS_WINDOWS) && !defined(PATH_MAX) #define PATH_MAX 4096 #endif //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Behaviour Switches (see README.md, should match the config in test/filesystem_test.cpp): //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Enforce C++17 API where possible when compiling for C++20, handles the following cases: // * fs::path::u8string() returns std::string instead of std::u8string // #define GHC_FILESYSTEM_ENFORCE_CPP17_API //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // LWG #2682 disables the since then invalid use of the copy option create_symlinks on directories // configure LWG conformance () #define LWG_2682_BEHAVIOUR //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // LWG #2395 makes crate_directory/create_directories not emit an error if there is a regular // file with that name, it is superseded by P1164R1, so only activate if really needed // #define LWG_2935_BEHAVIOUR //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // LWG #2936 enables new element wise (more expensive) path comparison // * if this->root_name().native().compare(p.root_name().native()) != 0 return result // * if this->has_root_directory() and !p.has_root_directory() return -1 // * if !this->has_root_directory() and p.has_root_directory() return -1 // * else result of element wise comparison of path iteration where first comparison is != 0 or 0 // if all comparisons are 0 (on Windows this implementation does case-insensitive root_name() // comparison) #define LWG_2936_BEHAVIOUR //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // LWG #2937 enforces that fs::equivalent emits an error, if !fs::exists(p1)||!exists(p2) #define LWG_2937_BEHAVIOUR //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // UTF8-Everywhere is the original behaviour of ghc::filesystem. But since v1.5 the Windows // version defaults to std::wstring storage backend. Still all std::string will be interpreted // as UTF-8 encoded. With this define you can enforce the old behavior on Windows, using // std::string as backend and for fs::path::native() and char for fs::path::c_str(). This // needs more conversions, so it is (and was before v1.5) slower, bot might help keeping source // homogeneous in a multi-platform project. // #define GHC_WIN_DISABLE_WSTRING_STORAGE_TYPE //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Raise errors/exceptions when invalid unicode codepoints or UTF-8 sequences are found, // instead of replacing them with the unicode replacement character (U+FFFD). // #define GHC_RAISE_UNICODE_ERRORS //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Automatic prefix windows path with "\\?\" if they would break the MAX_PATH length. // instead of replacing them with the unicode replacement character (U+FFFD). #ifndef GHC_WIN_DISABLE_AUTO_PREFIXES #define GHC_WIN_AUTO_PREFIX_LONG_PATH #endif // GHC_WIN_DISABLE_AUTO_PREFIXES //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // ghc::filesystem version in decimal (major * 10000 + minor * 100 + patch) #define GHC_FILESYSTEM_VERSION 10515L #if !defined(GHC_WITH_EXCEPTIONS) && (defined(__EXCEPTIONS) || defined(__cpp_exceptions) || defined(_CPPUNWIND)) #define GHC_WITH_EXCEPTIONS #endif #if !defined(GHC_WITH_EXCEPTIONS) && defined(GHC_RAISE_UNICODE_ERRORS) #error "Can't raise unicode errors with exception support disabled" #endif namespace ghc { namespace filesystem { #if defined(GHC_HAS_CUSTOM_STRING_VIEW) #define GHC_WITH_STRING_VIEW #elif defined(GHC_HAS_STD_STRING_VIEW) #define GHC_WITH_STRING_VIEW using std::basic_string_view; #elif defined(GHC_HAS_STD_EXPERIMENTAL_STRING_VIEW) #define GHC_WITH_STRING_VIEW using std::experimental::basic_string_view; #endif // temporary existing exception type for yet unimplemented parts class GHC_FS_API_CLASS not_implemented_exception : public std::logic_error { public: not_implemented_exception() : std::logic_error("function not implemented yet.") { } }; template class path_helper_base { public: using value_type = char_type; #ifdef GHC_OS_WINDOWS static constexpr value_type preferred_separator = '\\'; #else static constexpr value_type preferred_separator = '/'; #endif }; #if __cplusplus < 201703L template constexpr char_type path_helper_base::preferred_separator; #endif #ifdef GHC_OS_WINDOWS class path; namespace detail { bool has_executable_extension(const path& p); } #endif // [fs.class.path] class path class GHC_FS_API_CLASS path #if defined(GHC_OS_WINDOWS) && !defined(GHC_WIN_DISABLE_WSTRING_STORAGE_TYPE) #define GHC_USE_WCHAR_T #define GHC_NATIVEWP(p) p.c_str() #define GHC_PLATFORM_LITERAL(str) L##str : private path_helper_base { public: using path_helper_base::value_type; #else #define GHC_NATIVEWP(p) p.wstring().c_str() #define GHC_PLATFORM_LITERAL(str) str : private path_helper_base { public: using path_helper_base::value_type; #endif using string_type = std::basic_string; using path_helper_base::preferred_separator; // [fs.enum.path.format] enumeration format /// The path format in which the constructor argument is given. enum format { generic_format, ///< The generic format, internally used by ///< ghc::filesystem::path with slashes native_format, ///< The format native to the current platform this code ///< is build for auto_format, ///< Try to auto-detect the format, fallback to native }; template struct _is_basic_string : std::false_type { }; template struct _is_basic_string> : std::true_type { }; template struct _is_basic_string, std::allocator>> : std::true_type { }; #ifdef GHC_WITH_STRING_VIEW template struct _is_basic_string> : std::true_type { }; template struct _is_basic_string>> : std::true_type { }; #endif template using path_type = typename std::enable_if::value, path>::type; template #if defined(__cpp_lib_char8_t) && !defined(GHC_FILESYSTEM_ENFORCE_CPP17_API) using path_from_string = typename std::enable_if<_is_basic_string::value || std::is_same::type>::value || std::is_same::type>::value || std::is_same::type>::value || std::is_same::type>::value || std::is_same::type>::value || std::is_same::type>::value || std::is_same::type>::value || std::is_same::type>::value || std::is_same::type>::value || std::is_same::type>::value, path>::type; template using path_type_EcharT = typename std::enable_if::value || std::is_same::value || std::is_same::value || std::is_same::value || std::is_same::value, path>::type; #else using path_from_string = typename std::enable_if<_is_basic_string::value || std::is_same::type>::value || std::is_same::type>::value || std::is_same::type>::value || std::is_same::type>::value || std::is_same::type>::value || std::is_same::type>::value || std::is_same::type>::value || std::is_same::type>::value, path>::type; template using path_type_EcharT = typename std::enable_if::value || std::is_same::value || std::is_same::value || std::is_same::value, path>::type; #endif // [fs.path.construct] constructors and destructor path() noexcept; path(const path& p); path(path&& p) noexcept; path(string_type&& source, format fmt = auto_format); template > path(const Source& source, format fmt = auto_format); template path(InputIterator first, InputIterator last, format fmt = auto_format); #ifdef GHC_WITH_EXCEPTIONS template > path(const Source& source, const std::locale& loc, format fmt = auto_format); template path(InputIterator first, InputIterator last, const std::locale& loc, format fmt = auto_format); #endif ~path(); // [fs.path.assign] assignments path& operator=(const path& p); path& operator=(path&& p) noexcept; path& operator=(string_type&& source); path& assign(string_type&& source); template path& operator=(const Source& source); template path& assign(const Source& source); template path& assign(InputIterator first, InputIterator last); // [fs.path.append] appends path& operator/=(const path& p); template path& operator/=(const Source& source); template path& append(const Source& source); template path& append(InputIterator first, InputIterator last); // [fs.path.concat] concatenation path& operator+=(const path& x); path& operator+=(const string_type& x); #ifdef GHC_WITH_STRING_VIEW path& operator+=(basic_string_view x); #endif path& operator+=(const value_type* x); path& operator+=(value_type x); template path_from_string& operator+=(const Source& x); template path_type_EcharT& operator+=(EcharT x); template path& concat(const Source& x); template path& concat(InputIterator first, InputIterator last); // [fs.path.modifiers] modifiers void clear() noexcept; path& make_preferred(); path& remove_filename(); path& replace_filename(const path& replacement); path& replace_extension(const path& replacement = path()); void swap(path& rhs) noexcept; // [fs.path.native.obs] native format observers const string_type& native() const noexcept; const value_type* c_str() const noexcept; operator string_type() const; template , class Allocator = std::allocator> std::basic_string string(const Allocator& a = Allocator()) const; std::string string() const; std::wstring wstring() const; #if defined(__cpp_lib_char8_t) && !defined(GHC_FILESYSTEM_ENFORCE_CPP17_API) std::u8string u8string() const; #else std::string u8string() const; #endif std::u16string u16string() const; std::u32string u32string() const; // [fs.path.generic.obs] generic format observers template , class Allocator = std::allocator> std::basic_string generic_string(const Allocator& a = Allocator()) const; std::string generic_string() const; std::wstring generic_wstring() const; #if defined(__cpp_lib_char8_t) && !defined(GHC_FILESYSTEM_ENFORCE_CPP17_API) std::u8string generic_u8string() const; #else std::string generic_u8string() const; #endif std::u16string generic_u16string() const; std::u32string generic_u32string() const; // [fs.path.compare] compare int compare(const path& p) const noexcept; int compare(const string_type& s) const; #ifdef GHC_WITH_STRING_VIEW int compare(basic_string_view s) const; #endif int compare(const value_type* s) const; // [fs.path.decompose] decomposition path root_name() const; path root_directory() const; path root_path() const; path relative_path() const; path parent_path() const; path filename() const; path stem() const; path extension() const; // [fs.path.query] query bool empty() const noexcept; bool has_root_name() const; bool has_root_directory() const; bool has_root_path() const; bool has_relative_path() const; bool has_parent_path() const; bool has_filename() const; bool has_stem() const; bool has_extension() const; bool is_absolute() const; bool is_relative() const; // [fs.path.gen] generation path lexically_normal() const; path lexically_relative(const path& base) const; path lexically_proximate(const path& base) const; // [fs.path.itr] iterators class iterator; using const_iterator = iterator; iterator begin() const; iterator end() const; private: using impl_value_type = value_type; using impl_string_type = std::basic_string; friend class directory_iterator; void append_name(const value_type* name); static constexpr impl_value_type generic_separator = '/'; template class input_iterator_range { public: typedef InputIterator iterator; typedef InputIterator const_iterator; typedef typename InputIterator::difference_type difference_type; input_iterator_range(const InputIterator& first, const InputIterator& last) : _first(first) , _last(last) { } InputIterator begin() const { return _first; } InputIterator end() const { return _last; } private: InputIterator _first; InputIterator _last; }; friend void swap(path& lhs, path& rhs) noexcept; friend size_t hash_value(const path& p) noexcept; friend path canonical(const path& p, std::error_code& ec); friend bool create_directories(const path& p, std::error_code& ec) noexcept; string_type::size_type root_name_length() const noexcept; void postprocess_path_with_format(format fmt); void check_long_path(); impl_string_type _path; #ifdef GHC_OS_WINDOWS void handle_prefixes(); friend bool detail::has_executable_extension(const path& p); #ifdef GHC_WIN_AUTO_PREFIX_LONG_PATH string_type::size_type _prefixLength{0}; #else // GHC_WIN_AUTO_PREFIX_LONG_PATH static const string_type::size_type _prefixLength{0}; #endif // GHC_WIN_AUTO_PREFIX_LONG_PATH #else static const string_type::size_type _prefixLength{0}; #endif }; // [fs.path.nonmember] path non-member functions GHC_FS_API void swap(path& lhs, path& rhs) noexcept; GHC_FS_API size_t hash_value(const path& p) noexcept; #ifdef GHC_HAS_THREEWAY_COMP GHC_FS_API std::strong_ordering operator<=>(const path& lhs, const path& rhs) noexcept; #endif GHC_FS_API bool operator==(const path& lhs, const path& rhs) noexcept; GHC_FS_API bool operator!=(const path& lhs, const path& rhs) noexcept; GHC_FS_API bool operator<(const path& lhs, const path& rhs) noexcept; GHC_FS_API bool operator<=(const path& lhs, const path& rhs) noexcept; GHC_FS_API bool operator>(const path& lhs, const path& rhs) noexcept; GHC_FS_API bool operator>=(const path& lhs, const path& rhs) noexcept; GHC_FS_API path operator/(const path& lhs, const path& rhs); // [fs.path.io] path inserter and extractor template std::basic_ostream& operator<<(std::basic_ostream& os, const path& p); template std::basic_istream& operator>>(std::basic_istream& is, path& p); // [pfs.path.factory] path factory functions template > #if defined(__cpp_lib_char8_t) && !defined(GHC_FILESYSTEM_ENFORCE_CPP17_API) [[deprecated("use ghc::filesystem::path::path() with std::u8string instead")]] #endif path u8path(const Source& source); template #if defined(__cpp_lib_char8_t) && !defined(GHC_FILESYSTEM_ENFORCE_CPP17_API) [[deprecated("use ghc::filesystem::path::path() with std::u8string instead")]] #endif path u8path(InputIterator first, InputIterator last); // [fs.class.filesystem_error] class filesystem_error class GHC_FS_API_CLASS filesystem_error : public std::system_error { public: filesystem_error(const std::string& what_arg, std::error_code ec); filesystem_error(const std::string& what_arg, const path& p1, std::error_code ec); filesystem_error(const std::string& what_arg, const path& p1, const path& p2, std::error_code ec); const path& path1() const noexcept; const path& path2() const noexcept; const char* what() const noexcept override; private: std::string _what_arg; std::error_code _ec; path _p1, _p2; }; class GHC_FS_API_CLASS path::iterator { public: using value_type = const path; using difference_type = std::ptrdiff_t; using pointer = const path*; using reference = const path&; using iterator_category = std::bidirectional_iterator_tag; iterator(); iterator(const path& p, const impl_string_type::const_iterator& pos); iterator& operator++(); iterator operator++(int); iterator& operator--(); iterator operator--(int); bool operator==(const iterator& other) const; bool operator!=(const iterator& other) const; reference operator*() const; pointer operator->() const; private: friend class path; impl_string_type::const_iterator increment(const impl_string_type::const_iterator& pos) const; impl_string_type::const_iterator decrement(const impl_string_type::const_iterator& pos) const; void updateCurrent(); impl_string_type::const_iterator _first; impl_string_type::const_iterator _last; impl_string_type::const_iterator _prefix; impl_string_type::const_iterator _root; impl_string_type::const_iterator _iter; path _current; }; struct space_info { uintmax_t capacity; uintmax_t free; uintmax_t available; }; // [fs.enum] enumerations // [fs.enum.file_type] enum class file_type { none, not_found, regular, directory, symlink, block, character, fifo, socket, unknown, }; // [fs.enum.perms] enum class perms : uint16_t { none = 0, owner_read = 0400, owner_write = 0200, owner_exec = 0100, owner_all = 0700, group_read = 040, group_write = 020, group_exec = 010, group_all = 070, others_read = 04, others_write = 02, others_exec = 01, others_all = 07, all = 0777, set_uid = 04000, set_gid = 02000, sticky_bit = 01000, mask = 07777, unknown = 0xffff }; // [fs.enum.perm.opts] enum class perm_options : uint16_t { replace = 3, add = 1, remove = 2, nofollow = 4, }; // [fs.enum.copy.opts] enum class copy_options : uint16_t { none = 0, skip_existing = 1, overwrite_existing = 2, update_existing = 4, recursive = 8, copy_symlinks = 0x10, skip_symlinks = 0x20, directories_only = 0x40, create_symlinks = 0x80, #ifndef GHC_OS_WEB create_hard_links = 0x100 #endif }; // [fs.enum.dir.opts] enum class directory_options : uint16_t { none = 0, follow_directory_symlink = 1, skip_permission_denied = 2, }; // [fs.class.file_status] class file_status class GHC_FS_API_CLASS file_status { public: // [fs.file_status.cons] constructors and destructor file_status() noexcept; explicit file_status(file_type ft, perms prms = perms::unknown) noexcept; file_status(const file_status&) noexcept; file_status(file_status&&) noexcept; ~file_status(); // assignments: file_status& operator=(const file_status&) noexcept; file_status& operator=(file_status&&) noexcept; // [fs.file_status.mods] modifiers void type(file_type ft) noexcept; void permissions(perms prms) noexcept; // [fs.file_status.obs] observers file_type type() const noexcept; perms permissions() const noexcept; friend bool operator==(const file_status& lhs, const file_status& rhs) noexcept { return lhs.type() == rhs.type() && lhs.permissions() == rhs.permissions(); } private: file_type _type; perms _perms; }; using file_time_type = std::chrono::time_point; // [fs.class.directory_entry] Class directory_entry class GHC_FS_API_CLASS directory_entry { public: // [fs.dir.entry.cons] constructors and destructor directory_entry() noexcept = default; directory_entry(const directory_entry&) = default; directory_entry(directory_entry&&) noexcept = default; #ifdef GHC_WITH_EXCEPTIONS explicit directory_entry(const path& p); #endif directory_entry(const path& p, std::error_code& ec); ~directory_entry(); // assignments: directory_entry& operator=(const directory_entry&) = default; directory_entry& operator=(directory_entry&&) noexcept = default; // [fs.dir.entry.mods] modifiers #ifdef GHC_WITH_EXCEPTIONS void assign(const path& p); void replace_filename(const path& p); void refresh(); #endif void assign(const path& p, std::error_code& ec); void replace_filename(const path& p, std::error_code& ec); void refresh(std::error_code& ec) noexcept; // [fs.dir.entry.obs] observers const filesystem::path& path() const noexcept; operator const filesystem::path&() const noexcept; #ifdef GHC_WITH_EXCEPTIONS bool exists() const; bool is_block_file() const; bool is_character_file() const; bool is_directory() const; bool is_fifo() const; bool is_other() const; bool is_regular_file() const; bool is_socket() const; bool is_symlink() const; uintmax_t file_size() const; file_time_type last_write_time() const; file_status status() const; file_status symlink_status() const; #endif bool exists(std::error_code& ec) const noexcept; bool is_block_file(std::error_code& ec) const noexcept; bool is_character_file(std::error_code& ec) const noexcept; bool is_directory(std::error_code& ec) const noexcept; bool is_fifo(std::error_code& ec) const noexcept; bool is_other(std::error_code& ec) const noexcept; bool is_regular_file(std::error_code& ec) const noexcept; bool is_socket(std::error_code& ec) const noexcept; bool is_symlink(std::error_code& ec) const noexcept; uintmax_t file_size(std::error_code& ec) const noexcept; file_time_type last_write_time(std::error_code& ec) const noexcept; file_status status(std::error_code& ec) const noexcept; file_status symlink_status(std::error_code& ec) const noexcept; #ifndef GHC_OS_WEB #ifdef GHC_WITH_EXCEPTIONS uintmax_t hard_link_count() const; #endif uintmax_t hard_link_count(std::error_code& ec) const noexcept; #endif #ifdef GHC_HAS_THREEWAY_COMP std::strong_ordering operator<=>(const directory_entry& rhs) const noexcept; #endif bool operator<(const directory_entry& rhs) const noexcept; bool operator==(const directory_entry& rhs) const noexcept; bool operator!=(const directory_entry& rhs) const noexcept; bool operator<=(const directory_entry& rhs) const noexcept; bool operator>(const directory_entry& rhs) const noexcept; bool operator>=(const directory_entry& rhs) const noexcept; private: friend class directory_iterator; #ifdef GHC_WITH_EXCEPTIONS file_type status_file_type() const; #endif file_type status_file_type(std::error_code& ec) const noexcept; filesystem::path _path; file_status _status; file_status _symlink_status; uintmax_t _file_size = static_cast(-1); #ifndef GHC_OS_WINDOWS uintmax_t _hard_link_count = static_cast(-1); #endif time_t _last_write_time = 0; }; // [fs.class.directory.iterator] Class directory_iterator class GHC_FS_API_CLASS directory_iterator { public: class GHC_FS_API_CLASS proxy { public: const directory_entry& operator*() const& noexcept { return _dir_entry; } directory_entry operator*() && noexcept { return std::move(_dir_entry); } private: explicit proxy(const directory_entry& dir_entry) : _dir_entry(dir_entry) { } friend class directory_iterator; friend class recursive_directory_iterator; directory_entry _dir_entry; }; using iterator_category = std::input_iterator_tag; using value_type = directory_entry; using difference_type = std::ptrdiff_t; using pointer = const directory_entry*; using reference = const directory_entry&; // [fs.dir.itr.members] member functions directory_iterator() noexcept; #ifdef GHC_WITH_EXCEPTIONS explicit directory_iterator(const path& p); directory_iterator(const path& p, directory_options options); #endif directory_iterator(const path& p, std::error_code& ec) noexcept; directory_iterator(const path& p, directory_options options, std::error_code& ec) noexcept; directory_iterator(const directory_iterator& rhs); directory_iterator(directory_iterator&& rhs) noexcept; ~directory_iterator(); directory_iterator& operator=(const directory_iterator& rhs); directory_iterator& operator=(directory_iterator&& rhs) noexcept; const directory_entry& operator*() const; const directory_entry* operator->() const; #ifdef GHC_WITH_EXCEPTIONS directory_iterator& operator++(); #endif directory_iterator& increment(std::error_code& ec) noexcept; // other members as required by [input.iterators] #ifdef GHC_WITH_EXCEPTIONS proxy operator++(int) { proxy p{**this}; ++*this; return p; } #endif bool operator==(const directory_iterator& rhs) const; bool operator!=(const directory_iterator& rhs) const; private: friend class recursive_directory_iterator; class impl; std::shared_ptr _impl; }; // [fs.dir.itr.nonmembers] directory_iterator non-member functions GHC_FS_API directory_iterator begin(directory_iterator iter) noexcept; GHC_FS_API directory_iterator end(const directory_iterator&) noexcept; // [fs.class.re.dir.itr] class recursive_directory_iterator class GHC_FS_API_CLASS recursive_directory_iterator { public: using iterator_category = std::input_iterator_tag; using value_type = directory_entry; using difference_type = std::ptrdiff_t; using pointer = const directory_entry*; using reference = const directory_entry&; // [fs.rec.dir.itr.members] constructors and destructor recursive_directory_iterator() noexcept; #ifdef GHC_WITH_EXCEPTIONS explicit recursive_directory_iterator(const path& p); recursive_directory_iterator(const path& p, directory_options options); #endif recursive_directory_iterator(const path& p, directory_options options, std::error_code& ec) noexcept; recursive_directory_iterator(const path& p, std::error_code& ec) noexcept; recursive_directory_iterator(const recursive_directory_iterator& rhs); recursive_directory_iterator(recursive_directory_iterator&& rhs) noexcept; ~recursive_directory_iterator(); // [fs.rec.dir.itr.members] observers directory_options options() const; int depth() const; bool recursion_pending() const; const directory_entry& operator*() const; const directory_entry* operator->() const; // [fs.rec.dir.itr.members] modifiers recursive_directory_iterator& recursive_directory_iterator& operator=(const recursive_directory_iterator& rhs); recursive_directory_iterator& operator=(recursive_directory_iterator&& rhs) noexcept; #ifdef GHC_WITH_EXCEPTIONS recursive_directory_iterator& operator++(); #endif recursive_directory_iterator& increment(std::error_code& ec) noexcept; #ifdef GHC_WITH_EXCEPTIONS void pop(); #endif void pop(std::error_code& ec); void disable_recursion_pending(); // other members as required by [input.iterators] #ifdef GHC_WITH_EXCEPTIONS directory_iterator::proxy operator++(int) { directory_iterator::proxy proxy{**this}; ++*this; return proxy; } #endif bool operator==(const recursive_directory_iterator& rhs) const; bool operator!=(const recursive_directory_iterator& rhs) const; private: struct recursive_directory_iterator_impl { directory_options _options; bool _recursion_pending; std::stack _dir_iter_stack; recursive_directory_iterator_impl(directory_options options, bool recursion_pending) : _options(options) , _recursion_pending(recursion_pending) { } }; std::shared_ptr _impl; }; // [fs.rec.dir.itr.nonmembers] directory_iterator non-member functions GHC_FS_API recursive_directory_iterator begin(recursive_directory_iterator iter) noexcept; GHC_FS_API recursive_directory_iterator end(const recursive_directory_iterator&) noexcept; // [fs.op.funcs] filesystem operations #ifdef GHC_WITH_EXCEPTIONS GHC_FS_API path absolute(const path& p); GHC_FS_API path canonical(const path& p); GHC_FS_API void copy(const path& from, const path& to); GHC_FS_API void copy(const path& from, const path& to, copy_options options); GHC_FS_API bool copy_file(const path& from, const path& to); GHC_FS_API bool copy_file(const path& from, const path& to, copy_options option); GHC_FS_API void copy_symlink(const path& existing_symlink, const path& new_symlink); GHC_FS_API bool create_directories(const path& p); GHC_FS_API bool create_directory(const path& p); GHC_FS_API bool create_directory(const path& p, const path& attributes); GHC_FS_API void create_directory_symlink(const path& to, const path& new_symlink); GHC_FS_API void create_symlink(const path& to, const path& new_symlink); GHC_FS_API path current_path(); GHC_FS_API void current_path(const path& p); GHC_FS_API bool exists(const path& p); GHC_FS_API bool equivalent(const path& p1, const path& p2); GHC_FS_API uintmax_t file_size(const path& p); GHC_FS_API bool is_block_file(const path& p); GHC_FS_API bool is_character_file(const path& p); GHC_FS_API bool is_directory(const path& p); GHC_FS_API bool is_empty(const path& p); GHC_FS_API bool is_fifo(const path& p); GHC_FS_API bool is_other(const path& p); GHC_FS_API bool is_regular_file(const path& p); GHC_FS_API bool is_socket(const path& p); GHC_FS_API bool is_symlink(const path& p); GHC_FS_API file_time_type last_write_time(const path& p); GHC_FS_API void last_write_time(const path& p, file_time_type new_time); GHC_FS_API void permissions(const path& p, perms prms, perm_options opts = perm_options::replace); GHC_FS_API path proximate(const path& p, const path& base = current_path()); GHC_FS_API path read_symlink(const path& p); GHC_FS_API path relative(const path& p, const path& base = current_path()); GHC_FS_API bool remove(const path& p); GHC_FS_API uintmax_t remove_all(const path& p); GHC_FS_API void rename(const path& from, const path& to); GHC_FS_API void resize_file(const path& p, uintmax_t size); GHC_FS_API space_info space(const path& p); GHC_FS_API file_status status(const path& p); GHC_FS_API file_status symlink_status(const path& p); GHC_FS_API path temp_directory_path(); GHC_FS_API path weakly_canonical(const path& p); #endif GHC_FS_API path absolute(const path& p, std::error_code& ec); GHC_FS_API path canonical(const path& p, std::error_code& ec); GHC_FS_API void copy(const path& from, const path& to, std::error_code& ec) noexcept; GHC_FS_API void copy(const path& from, const path& to, copy_options options, std::error_code& ec) noexcept; GHC_FS_API bool copy_file(const path& from, const path& to, std::error_code& ec) noexcept; GHC_FS_API bool copy_file(const path& from, const path& to, copy_options option, std::error_code& ec) noexcept; GHC_FS_API void copy_symlink(const path& existing_symlink, const path& new_symlink, std::error_code& ec) noexcept; GHC_FS_API bool create_directories(const path& p, std::error_code& ec) noexcept; GHC_FS_API bool create_directory(const path& p, std::error_code& ec) noexcept; GHC_FS_API bool create_directory(const path& p, const path& attributes, std::error_code& ec) noexcept; GHC_FS_API void create_directory_symlink(const path& to, const path& new_symlink, std::error_code& ec) noexcept; GHC_FS_API void create_symlink(const path& to, const path& new_symlink, std::error_code& ec) noexcept; GHC_FS_API path current_path(std::error_code& ec); GHC_FS_API void current_path(const path& p, std::error_code& ec) noexcept; GHC_FS_API bool exists(file_status s) noexcept; GHC_FS_API bool exists(const path& p, std::error_code& ec) noexcept; GHC_FS_API bool equivalent(const path& p1, const path& p2, std::error_code& ec) noexcept; GHC_FS_API uintmax_t file_size(const path& p, std::error_code& ec) noexcept; GHC_FS_API bool is_block_file(file_status s) noexcept; GHC_FS_API bool is_block_file(const path& p, std::error_code& ec) noexcept; GHC_FS_API bool is_character_file(file_status s) noexcept; GHC_FS_API bool is_character_file(const path& p, std::error_code& ec) noexcept; GHC_FS_API bool is_directory(file_status s) noexcept; GHC_FS_API bool is_directory(const path& p, std::error_code& ec) noexcept; GHC_FS_API bool is_empty(const path& p, std::error_code& ec) noexcept; GHC_FS_API bool is_fifo(file_status s) noexcept; GHC_FS_API bool is_fifo(const path& p, std::error_code& ec) noexcept; GHC_FS_API bool is_other(file_status s) noexcept; GHC_FS_API bool is_other(const path& p, std::error_code& ec) noexcept; GHC_FS_API bool is_regular_file(file_status s) noexcept; GHC_FS_API bool is_regular_file(const path& p, std::error_code& ec) noexcept; GHC_FS_API bool is_socket(file_status s) noexcept; GHC_FS_API bool is_socket(const path& p, std::error_code& ec) noexcept; GHC_FS_API bool is_symlink(file_status s) noexcept; GHC_FS_API bool is_symlink(const path& p, std::error_code& ec) noexcept; GHC_FS_API file_time_type last_write_time(const path& p, std::error_code& ec) noexcept; GHC_FS_API void last_write_time(const path& p, file_time_type new_time, std::error_code& ec) noexcept; GHC_FS_API void permissions(const path& p, perms prms, std::error_code& ec) noexcept; GHC_FS_API void permissions(const path& p, perms prms, perm_options opts, std::error_code& ec) noexcept; GHC_FS_API path proximate(const path& p, std::error_code& ec); GHC_FS_API path proximate(const path& p, const path& base, std::error_code& ec); GHC_FS_API path read_symlink(const path& p, std::error_code& ec); GHC_FS_API path relative(const path& p, std::error_code& ec); GHC_FS_API path relative(const path& p, const path& base, std::error_code& ec); GHC_FS_API bool remove(const path& p, std::error_code& ec) noexcept; GHC_FS_API uintmax_t remove_all(const path& p, std::error_code& ec) noexcept; GHC_FS_API void rename(const path& from, const path& to, std::error_code& ec) noexcept; GHC_FS_API void resize_file(const path& p, uintmax_t size, std::error_code& ec) noexcept; GHC_FS_API space_info space(const path& p, std::error_code& ec) noexcept; GHC_FS_API file_status status(const path& p, std::error_code& ec) noexcept; GHC_FS_API bool status_known(file_status s) noexcept; GHC_FS_API file_status symlink_status(const path& p, std::error_code& ec) noexcept; GHC_FS_API path temp_directory_path(std::error_code& ec) noexcept; GHC_FS_API path weakly_canonical(const path& p, std::error_code& ec) noexcept; #ifndef GHC_OS_WEB #ifdef GHC_WITH_EXCEPTIONS GHC_FS_API void create_hard_link(const path& to, const path& new_hard_link); GHC_FS_API uintmax_t hard_link_count(const path& p); #endif GHC_FS_API void create_hard_link(const path& to, const path& new_hard_link, std::error_code& ec) noexcept; GHC_FS_API uintmax_t hard_link_count(const path& p, std::error_code& ec) noexcept; #endif #if defined(GHC_OS_WINDOWS) && (!defined(__GLIBCXX__) || (defined(_GLIBCXX_HAVE__WFOPEN) && defined(_GLIBCXX_USE_WCHAR_T))) #define GHC_HAS_FSTREAM_OPEN_WITH_WCHAR #endif // Non-C++17 add-on std::fstream wrappers with path template > class basic_filebuf : public std::basic_filebuf { public: basic_filebuf() {} ~basic_filebuf() override {} basic_filebuf(const basic_filebuf&) = delete; const basic_filebuf& operator=(const basic_filebuf&) = delete; basic_filebuf* open(const path& p, std::ios_base::openmode mode) { #ifdef GHC_HAS_FSTREAM_OPEN_WITH_WCHAR return std::basic_filebuf::open(p.wstring().c_str(), mode) ? this : 0; #else return std::basic_filebuf::open(p.string().c_str(), mode) ? this : 0; #endif } }; template > class basic_ifstream : public std::basic_ifstream { public: basic_ifstream() {} #ifdef GHC_HAS_FSTREAM_OPEN_WITH_WCHAR explicit basic_ifstream(const path& p, std::ios_base::openmode mode = std::ios_base::in) : std::basic_ifstream(p.wstring().c_str(), mode) { } void open(const path& p, std::ios_base::openmode mode = std::ios_base::in) { std::basic_ifstream::open(p.wstring().c_str(), mode); } #else explicit basic_ifstream(const path& p, std::ios_base::openmode mode = std::ios_base::in) : std::basic_ifstream(p.string().c_str(), mode) { } void open(const path& p, std::ios_base::openmode mode = std::ios_base::in) { std::basic_ifstream::open(p.string().c_str(), mode); } #endif basic_ifstream(const basic_ifstream&) = delete; const basic_ifstream& operator=(const basic_ifstream&) = delete; ~basic_ifstream() override {} }; template > class basic_ofstream : public std::basic_ofstream { public: basic_ofstream() {} #ifdef GHC_HAS_FSTREAM_OPEN_WITH_WCHAR explicit basic_ofstream(const path& p, std::ios_base::openmode mode = std::ios_base::out) : std::basic_ofstream(p.wstring().c_str(), mode) { } void open(const path& p, std::ios_base::openmode mode = std::ios_base::out) { std::basic_ofstream::open(p.wstring().c_str(), mode); } #else explicit basic_ofstream(const path& p, std::ios_base::openmode mode = std::ios_base::out) : std::basic_ofstream(p.string().c_str(), mode) { } void open(const path& p, std::ios_base::openmode mode = std::ios_base::out) { std::basic_ofstream::open(p.string().c_str(), mode); } #endif basic_ofstream(const basic_ofstream&) = delete; const basic_ofstream& operator=(const basic_ofstream&) = delete; ~basic_ofstream() override {} }; template > class basic_fstream : public std::basic_fstream { public: basic_fstream() {} #ifdef GHC_HAS_FSTREAM_OPEN_WITH_WCHAR explicit basic_fstream(const path& p, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out) : std::basic_fstream(p.wstring().c_str(), mode) { } void open(const path& p, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out) { std::basic_fstream::open(p.wstring().c_str(), mode); } #else explicit basic_fstream(const path& p, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out) : std::basic_fstream(p.string().c_str(), mode) { } void open(const path& p, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out) { std::basic_fstream::open(p.string().c_str(), mode); } #endif basic_fstream(const basic_fstream&) = delete; const basic_fstream& operator=(const basic_fstream&) = delete; ~basic_fstream() override {} }; typedef basic_filebuf filebuf; typedef basic_filebuf wfilebuf; typedef basic_ifstream ifstream; typedef basic_ifstream wifstream; typedef basic_ofstream ofstream; typedef basic_ofstream wofstream; typedef basic_fstream fstream; typedef basic_fstream wfstream; class GHC_FS_API_CLASS u8arguments { public: u8arguments(int& argc, char**& argv); ~u8arguments() { _refargc = _argc; _refargv = _argv; } bool valid() const { return _isvalid; } private: int _argc; char** _argv; int& _refargc; char**& _refargv; bool _isvalid; #ifdef GHC_OS_WINDOWS std::vector _args; std::vector _argp; #endif }; //------------------------------------------------------------------------------------------------- // Implementation //------------------------------------------------------------------------------------------------- namespace detail { enum utf8_states_t { S_STRT = 0, S_RJCT = 8 }; GHC_FS_API void appendUTF8(std::string& str, uint32_t unicode); GHC_FS_API bool is_surrogate(uint32_t c); GHC_FS_API bool is_high_surrogate(uint32_t c); GHC_FS_API bool is_low_surrogate(uint32_t c); GHC_FS_API unsigned consumeUtf8Fragment(const unsigned state, const uint8_t fragment, uint32_t& codepoint); enum class portable_error { none = 0, exists, not_found, not_supported, not_implemented, invalid_argument, is_a_directory, }; GHC_FS_API std::error_code make_error_code(portable_error err); #ifdef GHC_OS_WINDOWS GHC_FS_API std::error_code make_system_error(uint32_t err = 0); #else GHC_FS_API std::error_code make_system_error(int err = 0); template struct has_d_type : std::false_type{}; template struct has_d_type : std::true_type {}; template GHC_INLINE file_type file_type_from_dirent_impl(const T&, std::false_type) { return file_type::none; } template GHC_INLINE file_type file_type_from_dirent_impl(const T& t, std::true_type) { switch (t.d_type) { #ifdef DT_BLK case DT_BLK: return file_type::block; #endif #ifdef DT_CHR case DT_CHR: return file_type::character; #endif #ifdef DT_DIR case DT_DIR: return file_type::directory; #endif #ifdef DT_FIFO case DT_FIFO: return file_type::fifo; #endif #ifdef DT_LNK case DT_LNK: return file_type::symlink; #endif #ifdef DT_REG case DT_REG: return file_type::regular; #endif #ifdef DT_SOCK case DT_SOCK: return file_type::socket; #endif #ifdef DT_UNKNOWN case DT_UNKNOWN: return file_type::none; #endif default: return file_type::unknown; } } template GHC_INLINE file_type file_type_from_dirent(const T& t) { return file_type_from_dirent_impl(t, has_d_type{}); } #endif } // namespace detail namespace detail { #ifdef GHC_EXPAND_IMPL GHC_INLINE std::error_code make_error_code(portable_error err) { #ifdef GHC_OS_WINDOWS switch (err) { case portable_error::none: return std::error_code(); case portable_error::exists: return std::error_code(ERROR_ALREADY_EXISTS, std::system_category()); case portable_error::not_found: return std::error_code(ERROR_PATH_NOT_FOUND, std::system_category()); case portable_error::not_supported: return std::error_code(ERROR_NOT_SUPPORTED, std::system_category()); case portable_error::not_implemented: return std::error_code(ERROR_CALL_NOT_IMPLEMENTED, std::system_category()); case portable_error::invalid_argument: return std::error_code(ERROR_INVALID_PARAMETER, std::system_category()); case portable_error::is_a_directory: #ifdef ERROR_DIRECTORY_NOT_SUPPORTED return std::error_code(ERROR_DIRECTORY_NOT_SUPPORTED, std::system_category()); #else return std::error_code(ERROR_NOT_SUPPORTED, std::system_category()); #endif } #else switch (err) { case portable_error::none: return std::error_code(); case portable_error::exists: return std::error_code(EEXIST, std::system_category()); case portable_error::not_found: return std::error_code(ENOENT, std::system_category()); case portable_error::not_supported: return std::error_code(ENOTSUP, std::system_category()); case portable_error::not_implemented: return std::error_code(ENOSYS, std::system_category()); case portable_error::invalid_argument: return std::error_code(EINVAL, std::system_category()); case portable_error::is_a_directory: return std::error_code(EISDIR, std::system_category()); } #endif return std::error_code(); } #ifdef GHC_OS_WINDOWS GHC_INLINE std::error_code make_system_error(uint32_t err) { return std::error_code(err ? static_cast(err) : static_cast(::GetLastError()), std::system_category()); } #else GHC_INLINE std::error_code make_system_error(int err) { return std::error_code(err ? err : errno, std::system_category()); } #endif #endif // GHC_EXPAND_IMPL template using EnableBitmask = typename std::enable_if::value || std::is_same::value || std::is_same::value || std::is_same::value, Enum>::type; } // namespace detail template constexpr detail::EnableBitmask operator&(Enum X, Enum Y) { using underlying = typename std::underlying_type::type; return static_cast(static_cast(X) & static_cast(Y)); } template constexpr detail::EnableBitmask operator|(Enum X, Enum Y) { using underlying = typename std::underlying_type::type; return static_cast(static_cast(X) | static_cast(Y)); } template constexpr detail::EnableBitmask operator^(Enum X, Enum Y) { using underlying = typename std::underlying_type::type; return static_cast(static_cast(X) ^ static_cast(Y)); } template constexpr detail::EnableBitmask operator~(Enum X) { using underlying = typename std::underlying_type::type; return static_cast(~static_cast(X)); } template detail::EnableBitmask& operator&=(Enum& X, Enum Y) { X = X & Y; return X; } template detail::EnableBitmask& operator|=(Enum& X, Enum Y) { X = X | Y; return X; } template detail::EnableBitmask& operator^=(Enum& X, Enum Y) { X = X ^ Y; return X; } #ifdef GHC_EXPAND_IMPL namespace detail { GHC_INLINE bool in_range(uint32_t c, uint32_t lo, uint32_t hi) { return (static_cast(c - lo) < (hi - lo + 1)); } GHC_INLINE bool is_surrogate(uint32_t c) { return in_range(c, 0xd800, 0xdfff); } GHC_INLINE bool is_high_surrogate(uint32_t c) { return (c & 0xfffffc00) == 0xd800; } GHC_INLINE bool is_low_surrogate(uint32_t c) { return (c & 0xfffffc00) == 0xdc00; } GHC_INLINE void appendUTF8(std::string& str, uint32_t unicode) { if (unicode <= 0x7f) { str.push_back(static_cast(unicode)); } else if (unicode >= 0x80 && unicode <= 0x7ff) { str.push_back(static_cast((unicode >> 6) + 192)); str.push_back(static_cast((unicode & 0x3f) + 128)); } else if ((unicode >= 0x800 && unicode <= 0xd7ff) || (unicode >= 0xe000 && unicode <= 0xffff)) { str.push_back(static_cast((unicode >> 12) + 224)); str.push_back(static_cast(((unicode & 0xfff) >> 6) + 128)); str.push_back(static_cast((unicode & 0x3f) + 128)); } else if (unicode >= 0x10000 && unicode <= 0x10ffff) { str.push_back(static_cast((unicode >> 18) + 240)); str.push_back(static_cast(((unicode & 0x3ffff) >> 12) + 128)); str.push_back(static_cast(((unicode & 0xfff) >> 6) + 128)); str.push_back(static_cast((unicode & 0x3f) + 128)); } else { #ifdef GHC_RAISE_UNICODE_ERRORS throw filesystem_error("Illegal code point for unicode character.", str, std::make_error_code(std::errc::illegal_byte_sequence)); #else appendUTF8(str, 0xfffd); #endif } } // Thanks to Bjoern Hoehrmann (https://bjoern.hoehrmann.de/utf-8/decoder/dfa/) // and Taylor R Campbell for the ideas to this DFA approach of UTF-8 decoding; // Generating debugging and shrinking my own DFA from scratch was a day of fun! GHC_INLINE unsigned consumeUtf8Fragment(const unsigned state, const uint8_t fragment, uint32_t& codepoint) { static const uint32_t utf8_state_info[] = { // encoded states 0x11111111u, 0x11111111u, 0x77777777u, 0x77777777u, 0x88888888u, 0x88888888u, 0x88888888u, 0x88888888u, 0x22222299u, 0x22222222u, 0x22222222u, 0x22222222u, 0x3333333au, 0x33433333u, 0x9995666bu, 0x99999999u, 0x88888880u, 0x22818108u, 0x88888881u, 0x88888882u, 0x88888884u, 0x88888887u, 0x88888886u, 0x82218108u, 0x82281108u, 0x88888888u, 0x88888883u, 0x88888885u, 0u, 0u, 0u, 0u, }; uint8_t category = fragment < 128 ? 0 : (utf8_state_info[(fragment >> 3) & 0xf] >> ((fragment & 7) << 2)) & 0xf; codepoint = (state ? (codepoint << 6) | (fragment & 0x3fu) : (0xffu >> category) & fragment); return state == S_RJCT ? static_cast(S_RJCT) : static_cast((utf8_state_info[category + 16] >> (state << 2)) & 0xf); } GHC_INLINE bool validUtf8(const std::string& utf8String) { std::string::const_iterator iter = utf8String.begin(); unsigned utf8_state = S_STRT; std::uint32_t codepoint = 0; while (iter < utf8String.end()) { if ((utf8_state = consumeUtf8Fragment(utf8_state, static_cast(*iter++), codepoint)) == S_RJCT) { return false; } } if (utf8_state) { return false; } return true; } } // namespace detail #endif namespace detail { template ::value && (sizeof(typename Utf8String::value_type) == 1) && (sizeof(typename StringType::value_type) == 1)>::type* = nullptr> inline StringType fromUtf8(const Utf8String& utf8String, const typename StringType::allocator_type& alloc = typename StringType::allocator_type()) { return StringType(utf8String.begin(), utf8String.end(), alloc); } template ::value && (sizeof(typename Utf8String::value_type) == 1) && (sizeof(typename StringType::value_type) == 2)>::type* = nullptr> inline StringType fromUtf8(const Utf8String& utf8String, const typename StringType::allocator_type& alloc = typename StringType::allocator_type()) { StringType result(alloc); result.reserve(utf8String.length()); auto iter = utf8String.cbegin(); unsigned utf8_state = S_STRT; std::uint32_t codepoint = 0; while (iter < utf8String.cend()) { if ((utf8_state = consumeUtf8Fragment(utf8_state, static_cast(*iter++), codepoint)) == S_STRT) { if (codepoint <= 0xffff) { result += static_cast(codepoint); } else { codepoint -= 0x10000; result += static_cast((codepoint >> 10) + 0xd800); result += static_cast((codepoint & 0x3ff) + 0xdc00); } codepoint = 0; } else if (utf8_state == S_RJCT) { #ifdef GHC_RAISE_UNICODE_ERRORS throw filesystem_error("Illegal byte sequence for unicode character.", utf8String, std::make_error_code(std::errc::illegal_byte_sequence)); #else result += static_cast(0xfffd); utf8_state = S_STRT; codepoint = 0; #endif } } if (utf8_state) { #ifdef GHC_RAISE_UNICODE_ERRORS throw filesystem_error("Illegal byte sequence for unicode character.", utf8String, std::make_error_code(std::errc::illegal_byte_sequence)); #else result += static_cast(0xfffd); #endif } return result; } template ::value && (sizeof(typename Utf8String::value_type) == 1) && (sizeof(typename StringType::value_type) == 4)>::type* = nullptr> inline StringType fromUtf8(const Utf8String& utf8String, const typename StringType::allocator_type& alloc = typename StringType::allocator_type()) { StringType result(alloc); result.reserve(utf8String.length()); auto iter = utf8String.cbegin(); unsigned utf8_state = S_STRT; std::uint32_t codepoint = 0; while (iter < utf8String.cend()) { if ((utf8_state = consumeUtf8Fragment(utf8_state, static_cast(*iter++), codepoint)) == S_STRT) { result += static_cast(codepoint); codepoint = 0; } else if (utf8_state == S_RJCT) { #ifdef GHC_RAISE_UNICODE_ERRORS throw filesystem_error("Illegal byte sequence for unicode character.", utf8String, std::make_error_code(std::errc::illegal_byte_sequence)); #else result += static_cast(0xfffd); utf8_state = S_STRT; codepoint = 0; #endif } } if (utf8_state) { #ifdef GHC_RAISE_UNICODE_ERRORS throw filesystem_error("Illegal byte sequence for unicode character.", utf8String, std::make_error_code(std::errc::illegal_byte_sequence)); #else result += static_cast(0xfffd); #endif } return result; } template inline StringType fromUtf8(const charT (&utf8String)[N]) { #ifdef GHC_WITH_STRING_VIEW return fromUtf8(basic_string_view(utf8String, N - 1)); #else return fromUtf8(std::basic_string(utf8String, N - 1)); #endif } template ::value && (sizeof(typename strT::value_type) == 1), int>::type size = 1> inline std::string toUtf8(const strT& unicodeString) { return std::string(unicodeString.begin(), unicodeString.end()); } template ::value && (sizeof(typename strT::value_type) == 2), int>::type size = 2> inline std::string toUtf8(const strT& unicodeString) { std::string result; for (auto iter = unicodeString.begin(); iter != unicodeString.end(); ++iter) { char32_t c = *iter; if (is_surrogate(c)) { ++iter; if (iter != unicodeString.end() && is_high_surrogate(c) && is_low_surrogate(*iter)) { appendUTF8(result, (char32_t(c) << 10) + *iter - 0x35fdc00); } else { #ifdef GHC_RAISE_UNICODE_ERRORS throw filesystem_error("Illegal code point for unicode character.", result, std::make_error_code(std::errc::illegal_byte_sequence)); #else appendUTF8(result, 0xfffd); if (iter == unicodeString.end()) { break; } #endif } } else { appendUTF8(result, c); } } return result; } template ::value && (sizeof(typename strT::value_type) == 4), int>::type size = 4> inline std::string toUtf8(const strT& unicodeString) { std::string result; for (auto c : unicodeString) { appendUTF8(result, static_cast(c)); } return result; } template inline std::string toUtf8(const charT* unicodeString) { #ifdef GHC_WITH_STRING_VIEW return toUtf8(basic_string_view>(unicodeString)); #else return toUtf8(std::basic_string>(unicodeString)); #endif } #ifdef GHC_USE_WCHAR_T template ::value && (sizeof(typename WString::value_type) == 2) && (sizeof(typename StringType::value_type) == 1), bool>::type = false> inline StringType fromWChar(const WString& wString, const typename StringType::allocator_type& alloc = typename StringType::allocator_type()) { auto temp = toUtf8(wString); return StringType(temp.begin(), temp.end(), alloc); } template ::value && (sizeof(typename WString::value_type) == 2) && (sizeof(typename StringType::value_type) == 2), bool>::type = false> inline StringType fromWChar(const WString& wString, const typename StringType::allocator_type& alloc = typename StringType::allocator_type()) { return StringType(wString.begin(), wString.end(), alloc); } template ::value && (sizeof(typename WString::value_type) == 2) && (sizeof(typename StringType::value_type) == 4), bool>::type = false> inline StringType fromWChar(const WString& wString, const typename StringType::allocator_type& alloc = typename StringType::allocator_type()) { auto temp = toUtf8(wString); return fromUtf8(temp, alloc); } template ::value && (sizeof(typename strT::value_type) == 1), bool>::type = false> inline std::wstring toWChar(const strT& unicodeString) { return fromUtf8(unicodeString); } template ::value && (sizeof(typename strT::value_type) == 2), bool>::type = false> inline std::wstring toWChar(const strT& unicodeString) { return std::wstring(unicodeString.begin(), unicodeString.end()); } template ::value && (sizeof(typename strT::value_type) == 4), bool>::type = false> inline std::wstring toWChar(const strT& unicodeString) { auto temp = toUtf8(unicodeString); return fromUtf8(temp); } template inline std::wstring toWChar(const charT* unicodeString) { #ifdef GHC_WITH_STRING_VIEW return toWChar(basic_string_view>(unicodeString)); #else return toWChar(std::basic_string>(unicodeString)); #endif } #endif // GHC_USE_WCHAR_T } // namespace detail #ifdef GHC_EXPAND_IMPL namespace detail { template ::value, bool>::type = true> GHC_INLINE bool startsWith(const strT& what, const strT& with) { return with.length() <= what.length() && equal(with.begin(), with.end(), what.begin()); } template ::value, bool>::type = true> GHC_INLINE bool endsWith(const strT& what, const strT& with) { return with.length() <= what.length() && what.compare(what.length() - with.length(), with.size(), with) == 0; } } // namespace detail GHC_INLINE void path::check_long_path() { #if defined(GHC_OS_WINDOWS) && defined(GHC_WIN_AUTO_PREFIX_LONG_PATH) if (is_absolute() && _path.length() >= MAX_PATH - 12 && !detail::startsWith(_path, impl_string_type(GHC_PLATFORM_LITERAL("\\\\?\\")))) { postprocess_path_with_format(native_format); } #endif } GHC_INLINE void path::postprocess_path_with_format(path::format fmt) { #ifdef GHC_RAISE_UNICODE_ERRORS if (!detail::validUtf8(_path)) { path t; t._path = _path; throw filesystem_error("Illegal byte sequence for unicode character.", t, std::make_error_code(std::errc::illegal_byte_sequence)); } #endif switch (fmt) { #ifdef GHC_OS_WINDOWS case path::native_format: case path::auto_format: case path::generic_format: for (auto& c : _path) { if (c == generic_separator) { c = preferred_separator; } } #ifdef GHC_WIN_AUTO_PREFIX_LONG_PATH if (is_absolute() && _path.length() >= MAX_PATH - 12 && !detail::startsWith(_path, impl_string_type(GHC_PLATFORM_LITERAL("\\\\?\\")))) { _path = GHC_PLATFORM_LITERAL("\\\\?\\") + _path; } #endif handle_prefixes(); break; #else case path::auto_format: case path::native_format: case path::generic_format: // nothing to do break; #endif } if (_path.length() > _prefixLength + 2 && _path[_prefixLength] == preferred_separator && _path[_prefixLength + 1] == preferred_separator && _path[_prefixLength + 2] != preferred_separator) { impl_string_type::iterator new_end = std::unique(_path.begin() + static_cast(_prefixLength) + 2, _path.end(), [](path::value_type lhs, path::value_type rhs) { return lhs == rhs && lhs == preferred_separator; }); _path.erase(new_end, _path.end()); } else { impl_string_type::iterator new_end = std::unique(_path.begin() + static_cast(_prefixLength), _path.end(), [](path::value_type lhs, path::value_type rhs) { return lhs == rhs && lhs == preferred_separator; }); _path.erase(new_end, _path.end()); } } #endif // GHC_EXPAND_IMPL template inline path::path(const Source& source, format fmt) #ifdef GHC_USE_WCHAR_T : _path(detail::toWChar(source)) #else : _path(detail::toUtf8(source)) #endif { postprocess_path_with_format(fmt); } template inline path u8path(const Source& source) { return path(source); } template inline path u8path(InputIterator first, InputIterator last) { return path(first, last); } template inline path::path(InputIterator first, InputIterator last, format fmt) : path(std::basic_string::value_type>(first, last), fmt) { // delegated } #ifdef GHC_EXPAND_IMPL namespace detail { GHC_INLINE bool equals_simple_insensitive(const path::value_type* str1, const path::value_type* str2) { #ifdef GHC_OS_WINDOWS #ifdef __GNUC__ while (::tolower((unsigned char)*str1) == ::tolower((unsigned char)*str2++)) { if (*str1++ == 0) return true; } return false; #else // __GNUC__ #ifdef GHC_USE_WCHAR_T return 0 == ::_wcsicmp(str1, str2); #else // GHC_USE_WCHAR_T return 0 == ::_stricmp(str1, str2); #endif // GHC_USE_WCHAR_T #endif // __GNUC__ #else // GHC_OS_WINDOWS return 0 == ::strcasecmp(str1, str2); #endif // GHC_OS_WINDOWS } GHC_INLINE int compare_simple_insensitive(const path::value_type* str1, size_t len1, const path::value_type* str2, size_t len2) { while (len1 > 0 && len2 > 0 && ::tolower(static_cast(*str1)) == ::tolower(static_cast(*str2))) { --len1; --len2; ++str1; ++str2; } if (len1 && len2) { return *str1 < *str2 ? -1 : 1; } if (len1 == 0 && len2 == 0) { return 0; } return len1 == 0 ? -1 : 1; } GHC_INLINE const char* strerror_adapter(char* gnu, char*) { return gnu; } GHC_INLINE const char* strerror_adapter(int posix, char* buffer) { if (posix) { return "Error in strerror_r!"; } return buffer; } template GHC_INLINE std::string systemErrorText(ErrorNumber code = 0) { #if defined(GHC_OS_WINDOWS) LPVOID msgBuf; DWORD dw = code ? static_cast(code) : ::GetLastError(); FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, dw, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR)&msgBuf, 0, NULL); std::string msg = toUtf8(std::wstring((LPWSTR)msgBuf)); LocalFree(msgBuf); return msg; #else char buffer[512]; return strerror_adapter(strerror_r(code ? code : errno, buffer, sizeof(buffer)), buffer); #endif } #ifdef GHC_OS_WINDOWS using CreateSymbolicLinkW_fp = BOOLEAN(WINAPI*)(LPCWSTR, LPCWSTR, DWORD); using CreateHardLinkW_fp = BOOLEAN(WINAPI*)(LPCWSTR, LPCWSTR, LPSECURITY_ATTRIBUTES); GHC_INLINE void create_symlink(const path& target_name, const path& new_symlink, bool to_directory, std::error_code& ec) { std::error_code tec; auto fs = status(target_name, tec); if ((fs.type() == file_type::directory && !to_directory) || (fs.type() == file_type::regular && to_directory)) { ec = detail::make_error_code(detail::portable_error::not_supported); return; } #if defined(__GNUC__) && __GNUC__ >= 8 #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-function-type" #elif defined(_MSC_VER) && !defined(__INTEL_COMPILER) && !defined(__clang__) #pragma warning(push) #pragma warning(disable : 4191) #endif static CreateSymbolicLinkW_fp api_call = reinterpret_cast(GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "CreateSymbolicLinkW")); #if defined(__GNUC__) && __GNUC__ >= 8 #pragma GCC diagnostic pop #elif defined(_MSC_VER) && !defined(__INTEL_COMPILER) && !defined(__clang__) #pragma warning(pop) #endif if (api_call) { if (api_call(GHC_NATIVEWP(new_symlink), GHC_NATIVEWP(target_name), to_directory ? 1 : 0) == 0) { auto result = ::GetLastError(); if (result == ERROR_PRIVILEGE_NOT_HELD && api_call(GHC_NATIVEWP(new_symlink), GHC_NATIVEWP(target_name), to_directory ? 3 : 2) != 0) { return; } ec = detail::make_system_error(result); } } else { ec = detail::make_system_error(ERROR_NOT_SUPPORTED); } } GHC_INLINE void create_hardlink(const path& target_name, const path& new_hardlink, std::error_code& ec) { #if defined(__GNUC__) && __GNUC__ >= 8 #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-function-type" #elif defined(_MSC_VER) && !defined(__INTEL_COMPILER) && !defined(__clang__) #pragma warning(push) #pragma warning(disable : 4191) #endif static CreateHardLinkW_fp api_call = reinterpret_cast(GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "CreateHardLinkW")); #if defined(__GNUC__) && __GNUC__ >= 8 #pragma GCC diagnostic pop #elif defined(_MSC_VER) && !defined(__INTEL_COMPILER) && !defined(__clang__) #pragma warning(pop) #endif if (api_call) { if (api_call(GHC_NATIVEWP(new_hardlink), GHC_NATIVEWP(target_name), NULL) == 0) { ec = detail::make_system_error(); } } else { ec = detail::make_system_error(ERROR_NOT_SUPPORTED); } } GHC_INLINE path getFullPathName(const wchar_t* p, std::error_code& ec) { ULONG size = ::GetFullPathNameW(p, 0, 0, 0); if (size) { std::vector buf(size, 0); ULONG s2 = GetFullPathNameW(p, size, buf.data(), nullptr); if (s2 && s2 < size) { return path(std::wstring(buf.data(), s2)); } } ec = detail::make_system_error(); return path(); } #else GHC_INLINE void create_symlink(const path& target_name, const path& new_symlink, bool, std::error_code& ec) { if (::symlink(target_name.c_str(), new_symlink.c_str()) != 0) { ec = detail::make_system_error(); } } #ifndef GHC_OS_WEB GHC_INLINE void create_hardlink(const path& target_name, const path& new_hardlink, std::error_code& ec) { if (::link(target_name.c_str(), new_hardlink.c_str()) != 0) { ec = detail::make_system_error(); } } #endif #endif template GHC_INLINE file_status file_status_from_st_mode(T mode) { #ifdef GHC_OS_WINDOWS file_type ft = file_type::unknown; if ((mode & _S_IFDIR) == _S_IFDIR) { ft = file_type::directory; } else if ((mode & _S_IFREG) == _S_IFREG) { ft = file_type::regular; } else if ((mode & _S_IFCHR) == _S_IFCHR) { ft = file_type::character; } perms prms = static_cast(mode & 0xfff); return file_status(ft, prms); #else file_type ft = file_type::unknown; if (S_ISDIR(mode)) { ft = file_type::directory; } else if (S_ISREG(mode)) { ft = file_type::regular; } else if (S_ISCHR(mode)) { ft = file_type::character; } else if (S_ISBLK(mode)) { ft = file_type::block; } else if (S_ISFIFO(mode)) { ft = file_type::fifo; } else if (S_ISLNK(mode)) { ft = file_type::symlink; } else if (S_ISSOCK(mode)) { ft = file_type::socket; } perms prms = static_cast(mode & 0xfff); return file_status(ft, prms); #endif } #ifdef GHC_OS_WINDOWS class unique_handle { public: typedef HANDLE element_type; unique_handle() noexcept : _handle(INVALID_HANDLE_VALUE) { } explicit unique_handle(element_type h) noexcept : _handle(h) { } unique_handle(unique_handle&& u) noexcept : _handle(u.release()) { } ~unique_handle() { reset(); } unique_handle& operator=(unique_handle&& u) noexcept { reset(u.release()); return *this; } element_type get() const noexcept { return _handle; } explicit operator bool() const noexcept { return _handle != INVALID_HANDLE_VALUE; } element_type release() noexcept { element_type tmp = _handle; _handle = INVALID_HANDLE_VALUE; return tmp; } void reset(element_type h = INVALID_HANDLE_VALUE) noexcept { element_type tmp = _handle; _handle = h; if (tmp != INVALID_HANDLE_VALUE) { CloseHandle(tmp); } } void swap(unique_handle& u) noexcept { std::swap(_handle, u._handle); } private: element_type _handle; }; #ifndef REPARSE_DATA_BUFFER_HEADER_SIZE typedef struct _REPARSE_DATA_BUFFER { ULONG ReparseTag; USHORT ReparseDataLength; USHORT Reserved; union { struct { USHORT SubstituteNameOffset; USHORT SubstituteNameLength; USHORT PrintNameOffset; USHORT PrintNameLength; ULONG Flags; WCHAR PathBuffer[1]; } SymbolicLinkReparseBuffer; struct { USHORT SubstituteNameOffset; USHORT SubstituteNameLength; USHORT PrintNameOffset; USHORT PrintNameLength; WCHAR PathBuffer[1]; } MountPointReparseBuffer; struct { UCHAR DataBuffer[1]; } GenericReparseBuffer; } DUMMYUNIONNAME; } REPARSE_DATA_BUFFER; #ifndef MAXIMUM_REPARSE_DATA_BUFFER_SIZE #define MAXIMUM_REPARSE_DATA_BUFFER_SIZE (16 * 1024) #endif #endif template struct free_deleter { void operator()(T* p) const { std::free(p); } }; GHC_INLINE std::unique_ptr> getReparseData(const path& p, std::error_code& ec) { unique_handle file(CreateFileW(GHC_NATIVEWP(p), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, 0)); if (!file) { ec = detail::make_system_error(); return nullptr; } std::unique_ptr> reparseData(reinterpret_cast(std::calloc(1, MAXIMUM_REPARSE_DATA_BUFFER_SIZE))); ULONG bufferUsed; if (DeviceIoControl(file.get(), FSCTL_GET_REPARSE_POINT, 0, 0, reparseData.get(), MAXIMUM_REPARSE_DATA_BUFFER_SIZE, &bufferUsed, 0)) { return reparseData; } else { ec = detail::make_system_error(); } return nullptr; } #endif GHC_INLINE path resolveSymlink(const path& p, std::error_code& ec) { #ifdef GHC_OS_WINDOWS path result; auto reparseData = detail::getReparseData(p, ec); if (!ec) { if (reparseData && IsReparseTagMicrosoft(reparseData->ReparseTag)) { switch (reparseData->ReparseTag) { case IO_REPARSE_TAG_SYMLINK: { auto printName = std::wstring(&reparseData->SymbolicLinkReparseBuffer.PathBuffer[reparseData->SymbolicLinkReparseBuffer.PrintNameOffset / sizeof(WCHAR)], reparseData->SymbolicLinkReparseBuffer.PrintNameLength / sizeof(WCHAR)); auto substituteName = std::wstring(&reparseData->SymbolicLinkReparseBuffer.PathBuffer[reparseData->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR)], reparseData->SymbolicLinkReparseBuffer.SubstituteNameLength / sizeof(WCHAR)); if (detail::endsWith(substituteName, printName) && detail::startsWith(substituteName, std::wstring(L"\\??\\"))) { result = printName; } else { result = substituteName; } if (reparseData->SymbolicLinkReparseBuffer.Flags & 0x1 /*SYMLINK_FLAG_RELATIVE*/) { result = p.parent_path() / result; } break; } case IO_REPARSE_TAG_MOUNT_POINT: result = detail::getFullPathName(GHC_NATIVEWP(p), ec); // result = std::wstring(&reparseData->MountPointReparseBuffer.PathBuffer[reparseData->MountPointReparseBuffer.SubstituteNameOffset / sizeof(WCHAR)], reparseData->MountPointReparseBuffer.SubstituteNameLength / sizeof(WCHAR)); break; default: break; } } } return result; #else size_t bufferSize = 256; while (true) { std::vector buffer(bufferSize, static_cast(0)); auto rc = ::readlink(p.c_str(), buffer.data(), buffer.size()); if (rc < 0) { ec = detail::make_system_error(); return path(); } else if (rc < static_cast(bufferSize)) { return path(std::string(buffer.data(), static_cast(rc))); } bufferSize *= 2; } return path(); #endif } #ifdef GHC_OS_WINDOWS GHC_INLINE time_t timeFromFILETIME(const FILETIME& ft) { ULARGE_INTEGER ull; ull.LowPart = ft.dwLowDateTime; ull.HighPart = ft.dwHighDateTime; return static_cast(ull.QuadPart / 10000000ULL - 11644473600ULL); } GHC_INLINE void timeToFILETIME(time_t t, FILETIME& ft) { ULARGE_INTEGER ull; ull.QuadPart = static_cast((t * 10000000LL) + 116444736000000000LL); ft.dwLowDateTime = ull.LowPart; ft.dwHighDateTime = ull.HighPart; } template GHC_INLINE uintmax_t hard_links_from_INFO(const INFO* info) { return static_cast(-1); } template <> GHC_INLINE uintmax_t hard_links_from_INFO(const BY_HANDLE_FILE_INFORMATION* info) { return info->nNumberOfLinks; } template GHC_INLINE bool is_symlink_from_INFO(const path &p, const INFO* info, std::error_code& ec) { if ((info->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) { auto reparseData = detail::getReparseData(p, ec); if (!ec && reparseData && IsReparseTagMicrosoft(reparseData->ReparseTag) && reparseData->ReparseTag == IO_REPARSE_TAG_SYMLINK) { return true; } } return false; } template <> GHC_INLINE bool is_symlink_from_INFO(const path &, const WIN32_FIND_DATAW* info, std::error_code&) { // dwReserved0 is undefined unless dwFileAttributes includes the // FILE_ATTRIBUTE_REPARSE_POINT attribute according to microsoft // documentation. In practice, dwReserved0 is not reset which // causes it to report the incorrect symlink status. // Note that microsoft documentation does not say whether there is // a null value for dwReserved0, so we test for symlink directly // instead of returning the tag which requires returning a null // value for non-reparse-point files. return (info->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) && info->dwReserved0 == IO_REPARSE_TAG_SYMLINK; } template GHC_INLINE file_status status_from_INFO(const path& p, const INFO* info, std::error_code& ec, uintmax_t* sz = nullptr, time_t* lwt = nullptr) { file_type ft = file_type::unknown; if (is_symlink_from_INFO(p, info, ec)) { ft = file_type::symlink; } else if ((info->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { ft = file_type::directory; } else { ft = file_type::regular; } perms prms = perms::owner_read | perms::group_read | perms::others_read; if (!(info->dwFileAttributes & FILE_ATTRIBUTE_READONLY)) { prms = prms | perms::owner_write | perms::group_write | perms::others_write; } if (has_executable_extension(p)) { prms = prms | perms::owner_exec | perms::group_exec | perms::others_exec; } if (sz) { *sz = static_cast(info->nFileSizeHigh) << (sizeof(info->nFileSizeHigh) * 8) | info->nFileSizeLow; } if (lwt) { *lwt = detail::timeFromFILETIME(info->ftLastWriteTime); } return file_status(ft, prms); } #endif GHC_INLINE bool is_not_found_error(std::error_code& ec) { #ifdef GHC_OS_WINDOWS return ec.value() == ERROR_FILE_NOT_FOUND || ec.value() == ERROR_PATH_NOT_FOUND || ec.value() == ERROR_INVALID_NAME; #else return ec.value() == ENOENT || ec.value() == ENOTDIR; #endif } GHC_INLINE file_status symlink_status_ex(const path& p, std::error_code& ec, uintmax_t* sz = nullptr, uintmax_t* nhl = nullptr, time_t* lwt = nullptr) noexcept { #ifdef GHC_OS_WINDOWS file_status fs; WIN32_FILE_ATTRIBUTE_DATA attr; if (!GetFileAttributesExW(GHC_NATIVEWP(p), GetFileExInfoStandard, &attr)) { ec = detail::make_system_error(); } else { ec.clear(); fs = detail::status_from_INFO(p, &attr, ec, sz, lwt); if (nhl) { *nhl = 0; } } if (detail::is_not_found_error(ec)) { return file_status(file_type::not_found); } return ec ? file_status(file_type::none) : fs; #else (void)sz; (void)nhl; (void)lwt; struct ::stat fs; auto result = ::lstat(p.c_str(), &fs); if (result == 0) { ec.clear(); file_status f_s = detail::file_status_from_st_mode(fs.st_mode); return f_s; } ec = detail::make_system_error(); if (detail::is_not_found_error(ec)) { return file_status(file_type::not_found, perms::unknown); } return file_status(file_type::none); #endif } GHC_INLINE file_status status_ex(const path& p, std::error_code& ec, file_status* sls = nullptr, uintmax_t* sz = nullptr, uintmax_t* nhl = nullptr, time_t* lwt = nullptr, int recurse_count = 0) noexcept { ec.clear(); #ifdef GHC_OS_WINDOWS if (recurse_count > 16) { ec = detail::make_system_error(0x2A9 /*ERROR_STOPPED_ON_SYMLINK*/); return file_status(file_type::unknown); } WIN32_FILE_ATTRIBUTE_DATA attr; if (!::GetFileAttributesExW(GHC_NATIVEWP(p), GetFileExInfoStandard, &attr)) { ec = detail::make_system_error(); } else if (attr.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) { auto reparseData = detail::getReparseData(p, ec); if (!ec && reparseData && IsReparseTagMicrosoft(reparseData->ReparseTag) && reparseData->ReparseTag == IO_REPARSE_TAG_SYMLINK) { path target = resolveSymlink(p, ec); file_status result; if (!ec && !target.empty()) { if (sls) { *sls = status_from_INFO(p, &attr, ec); } return detail::status_ex(target, ec, nullptr, sz, nhl, lwt, recurse_count + 1); } return file_status(file_type::unknown); } } if (ec) { if (detail::is_not_found_error(ec)) { return file_status(file_type::not_found); } return file_status(file_type::none); } if (nhl) { *nhl = 0; } return detail::status_from_INFO(p, &attr, ec, sz, lwt); #else (void)recurse_count; struct ::stat st; auto result = ::lstat(p.c_str(), &st); if (result == 0) { ec.clear(); file_status fs = detail::file_status_from_st_mode(st.st_mode); if (sls) { *sls = fs; } if (fs.type() == file_type::symlink) { result = ::stat(p.c_str(), &st); if (result == 0) { fs = detail::file_status_from_st_mode(st.st_mode); } else { ec = detail::make_system_error(); if (detail::is_not_found_error(ec)) { return file_status(file_type::not_found, perms::unknown); } return file_status(file_type::none); } } if (sz) { *sz = static_cast(st.st_size); } if (nhl) { *nhl = st.st_nlink; } if (lwt) { *lwt = st.st_mtime; } return fs; } else { ec = detail::make_system_error(); if (detail::is_not_found_error(ec)) { return file_status(file_type::not_found, perms::unknown); } return file_status(file_type::none); } #endif } } // namespace detail GHC_INLINE u8arguments::u8arguments(int& argc, char**& argv) : _argc(argc) , _argv(argv) , _refargc(argc) , _refargv(argv) , _isvalid(false) { #ifdef GHC_OS_WINDOWS LPWSTR* p; p = ::CommandLineToArgvW(::GetCommandLineW(), &argc); _args.reserve(static_cast(argc)); _argp.reserve(static_cast(argc)); for (size_t i = 0; i < static_cast(argc); ++i) { _args.push_back(detail::toUtf8(std::wstring(p[i]))); _argp.push_back((char*)_args[i].data()); } argv = _argp.data(); ::LocalFree(p); _isvalid = true; #else std::setlocale(LC_ALL, ""); #if defined(__ANDROID__) && __ANDROID_API__ < 26 _isvalid = true; #else if (detail::equals_simple_insensitive(::nl_langinfo(CODESET), "UTF-8")) { _isvalid = true; } #endif #endif } //----------------------------------------------------------------------------- // [fs.path.construct] constructors and destructor GHC_INLINE path::path() noexcept {} GHC_INLINE path::path(const path& p) : _path(p._path) #if defined(GHC_OS_WINDOWS) && defined(GHC_WIN_AUTO_PREFIX_LONG_PATH) , _prefixLength(p._prefixLength) #endif { } GHC_INLINE path::path(path&& p) noexcept : _path(std::move(p._path)) #if defined(GHC_OS_WINDOWS) && defined(GHC_WIN_AUTO_PREFIX_LONG_PATH) , _prefixLength(p._prefixLength) #endif { } GHC_INLINE path::path(string_type&& source, format fmt) : _path(std::move(source)) { postprocess_path_with_format(fmt); } #endif // GHC_EXPAND_IMPL #ifdef GHC_WITH_EXCEPTIONS template inline path::path(const Source& source, const std::locale& loc, format fmt) : path(source, fmt) { std::string locName = loc.name(); if (!(locName.length() >= 5 && (locName.substr(locName.length() - 5) == "UTF-8" || locName.substr(locName.length() - 5) == "utf-8"))) { throw filesystem_error("This implementation only supports UTF-8 locales!", path(_path), detail::make_error_code(detail::portable_error::not_supported)); } } template inline path::path(InputIterator first, InputIterator last, const std::locale& loc, format fmt) : path(std::basic_string::value_type>(first, last), fmt) { std::string locName = loc.name(); if (!(locName.length() >= 5 && (locName.substr(locName.length() - 5) == "UTF-8" || locName.substr(locName.length() - 5) == "utf-8"))) { throw filesystem_error("This implementation only supports UTF-8 locales!", path(_path), detail::make_error_code(detail::portable_error::not_supported)); } } #endif #ifdef GHC_EXPAND_IMPL GHC_INLINE path::~path() {} //----------------------------------------------------------------------------- // [fs.path.assign] assignments GHC_INLINE path& path::operator=(const path& p) { _path = p._path; #if defined(GHC_OS_WINDOWS) && defined(GHC_WIN_AUTO_PREFIX_LONG_PATH) _prefixLength = p._prefixLength; #endif return *this; } GHC_INLINE path& path::operator=(path&& p) noexcept { _path = std::move(p._path); #if defined(GHC_OS_WINDOWS) && defined(GHC_WIN_AUTO_PREFIX_LONG_PATH) _prefixLength = p._prefixLength; #endif return *this; } GHC_INLINE path& path::operator=(path::string_type&& source) { return assign(source); } GHC_INLINE path& path::assign(path::string_type&& source) { _path = std::move(source); postprocess_path_with_format(native_format); return *this; } #endif // GHC_EXPAND_IMPL template inline path& path::operator=(const Source& source) { return assign(source); } template inline path& path::assign(const Source& source) { #ifdef GHC_USE_WCHAR_T _path.assign(detail::toWChar(source)); #else _path.assign(detail::toUtf8(source)); #endif postprocess_path_with_format(native_format); return *this; } template <> inline path& path::assign(const path& source) { _path = source._path; #if defined(GHC_OS_WINDOWS) && defined(GHC_WIN_AUTO_PREFIX_LONG_PATH) _prefixLength = source._prefixLength; #endif return *this; } template inline path& path::assign(InputIterator first, InputIterator last) { _path.assign(first, last); postprocess_path_with_format(native_format); return *this; } #ifdef GHC_EXPAND_IMPL //----------------------------------------------------------------------------- // [fs.path.append] appends GHC_INLINE path& path::operator/=(const path& p) { if (p.empty()) { // was: if ((!has_root_directory() && is_absolute()) || has_filename()) if (!_path.empty() && _path[_path.length() - 1] != preferred_separator && _path[_path.length() - 1] != ':') { _path += preferred_separator; } return *this; } if ((p.is_absolute() && (_path != root_name()._path || p._path != "/")) || (p.has_root_name() && p.root_name() != root_name())) { assign(p); return *this; } if (p.has_root_directory()) { assign(root_name()); } else if ((!has_root_directory() && is_absolute()) || has_filename()) { _path += preferred_separator; } auto iter = p.begin(); bool first = true; if (p.has_root_name()) { ++iter; } while (iter != p.end()) { if (!first && !(!_path.empty() && _path[_path.length() - 1] == preferred_separator)) { _path += preferred_separator; } first = false; _path += (*iter++).native(); } check_long_path(); return *this; } GHC_INLINE void path::append_name(const value_type* name) { if (_path.empty()) { this->operator/=(path(name)); } else { if (_path.back() != path::preferred_separator) { _path.push_back(path::preferred_separator); } _path += name; check_long_path(); } } #endif // GHC_EXPAND_IMPL template inline path& path::operator/=(const Source& source) { return append(source); } template inline path& path::append(const Source& source) { return this->operator/=(path(source)); } template <> inline path& path::append(const path& p) { return this->operator/=(p); } template inline path& path::append(InputIterator first, InputIterator last) { std::basic_string::value_type> part(first, last); return append(part); } #ifdef GHC_EXPAND_IMPL //----------------------------------------------------------------------------- // [fs.path.concat] concatenation GHC_INLINE path& path::operator+=(const path& x) { return concat(x._path); } GHC_INLINE path& path::operator+=(const string_type& x) { return concat(x); } #ifdef GHC_WITH_STRING_VIEW GHC_INLINE path& path::operator+=(basic_string_view x) { return concat(x); } #endif GHC_INLINE path& path::operator+=(const value_type* x) { #ifdef GHC_WITH_STRING_VIEW basic_string_view part(x); #else string_type part(x); #endif return concat(part); } GHC_INLINE path& path::operator+=(value_type x) { #ifdef GHC_OS_WINDOWS if (x == generic_separator) { x = preferred_separator; } #endif if (_path.empty() || _path.back() != preferred_separator) { _path += x; } check_long_path(); return *this; } #endif // GHC_EXPAND_IMPL template inline path::path_from_string& path::operator+=(const Source& x) { return concat(x); } template inline path::path_type_EcharT& path::operator+=(EcharT x) { #ifdef GHC_WITH_STRING_VIEW basic_string_view part(&x, 1); #else std::basic_string part(1, x); #endif concat(part); return *this; } template inline path& path::concat(const Source& x) { path p(x); _path += p._path; postprocess_path_with_format(native_format); return *this; } template inline path& path::concat(InputIterator first, InputIterator last) { _path.append(first, last); postprocess_path_with_format(native_format); return *this; } #ifdef GHC_EXPAND_IMPL //----------------------------------------------------------------------------- // [fs.path.modifiers] modifiers GHC_INLINE void path::clear() noexcept { _path.clear(); #if defined(GHC_OS_WINDOWS) && defined(GHC_WIN_AUTO_PREFIX_LONG_PATH) _prefixLength = 0; #endif } GHC_INLINE path& path::make_preferred() { // as this filesystem implementation only uses generic_format // internally, this must be a no-op return *this; } GHC_INLINE path& path::remove_filename() { if (has_filename()) { _path.erase(_path.size() - filename()._path.size()); } return *this; } GHC_INLINE path& path::replace_filename(const path& replacement) { remove_filename(); return append(replacement); } GHC_INLINE path& path::replace_extension(const path& replacement) { if (has_extension()) { _path.erase(_path.size() - extension()._path.size()); } if (!replacement.empty() && replacement._path[0] != '.') { _path += '.'; } return concat(replacement); } GHC_INLINE void path::swap(path& rhs) noexcept { _path.swap(rhs._path); #if defined(GHC_OS_WINDOWS) && defined(GHC_WIN_AUTO_PREFIX_LONG_PATH) std::swap(_prefixLength, rhs._prefixLength); #endif } //----------------------------------------------------------------------------- // [fs.path.native.obs] native format observers GHC_INLINE const path::string_type& path::native() const noexcept { return _path; } GHC_INLINE const path::value_type* path::c_str() const noexcept { return native().c_str(); } GHC_INLINE path::operator path::string_type() const { return native(); } #endif // GHC_EXPAND_IMPL template inline std::basic_string path::string(const Allocator& a) const { #ifdef GHC_USE_WCHAR_T return detail::fromWChar>(_path, a); #else return detail::fromUtf8>(_path, a); #endif } #ifdef GHC_EXPAND_IMPL GHC_INLINE std::string path::string() const { #ifdef GHC_USE_WCHAR_T return detail::toUtf8(native()); #else return native(); #endif } GHC_INLINE std::wstring path::wstring() const { #ifdef GHC_USE_WCHAR_T return native(); #else return detail::fromUtf8(native()); #endif } #if defined(__cpp_lib_char8_t) && !defined(GHC_FILESYSTEM_ENFORCE_CPP17_API) GHC_INLINE std::u8string path::u8string() const { #ifdef GHC_USE_WCHAR_T return std::u8string(reinterpret_cast(detail::toUtf8(native()).c_str())); #else return std::u8string(reinterpret_cast(c_str())); #endif } #else GHC_INLINE std::string path::u8string() const { #ifdef GHC_USE_WCHAR_T return detail::toUtf8(native()); #else return native(); #endif } #endif GHC_INLINE std::u16string path::u16string() const { // TODO: optimize return detail::fromUtf8(string()); } GHC_INLINE std::u32string path::u32string() const { // TODO: optimize return detail::fromUtf8(string()); } #endif // GHC_EXPAND_IMPL //----------------------------------------------------------------------------- // [fs.path.generic.obs] generic format observers template inline std::basic_string path::generic_string(const Allocator& a) const { #ifdef GHC_OS_WINDOWS #ifdef GHC_USE_WCHAR_T auto result = detail::fromWChar, path::string_type>(_path, a); #else auto result = detail::fromUtf8>(_path, a); #endif for (auto& c : result) { if (c == preferred_separator) { c = generic_separator; } } return result; #else return detail::fromUtf8>(_path, a); #endif } #ifdef GHC_EXPAND_IMPL GHC_INLINE std::string path::generic_string() const { #ifdef GHC_OS_WINDOWS return generic_string(); #else return _path; #endif } GHC_INLINE std::wstring path::generic_wstring() const { #ifdef GHC_OS_WINDOWS return generic_string(); #else return detail::fromUtf8(_path); #endif } // namespace filesystem #if defined(__cpp_lib_char8_t) && !defined(GHC_FILESYSTEM_ENFORCE_CPP17_API) GHC_INLINE std::u8string path::generic_u8string() const { #ifdef GHC_OS_WINDOWS return generic_string(); #else return std::u8string(reinterpret_cast(_path.c_str())); #endif } #else GHC_INLINE std::string path::generic_u8string() const { #ifdef GHC_OS_WINDOWS return generic_string(); #else return _path; #endif } #endif GHC_INLINE std::u16string path::generic_u16string() const { #ifdef GHC_OS_WINDOWS return generic_string(); #else return detail::fromUtf8(_path); #endif } GHC_INLINE std::u32string path::generic_u32string() const { #ifdef GHC_OS_WINDOWS return generic_string(); #else return detail::fromUtf8(_path); #endif } //----------------------------------------------------------------------------- // [fs.path.compare] compare GHC_INLINE int path::compare(const path& p) const noexcept { #ifdef LWG_2936_BEHAVIOUR auto rnl1 = root_name_length(); auto rnl2 = p.root_name_length(); #ifdef GHC_OS_WINDOWS auto rnc = detail::compare_simple_insensitive(_path.c_str(), rnl1, p._path.c_str(), rnl2); #else auto rnc = _path.compare(0, rnl1, p._path, 0, (std::min(rnl1, rnl2))); #endif if (rnc) { return rnc; } bool hrd1 = has_root_directory(), hrd2 = p.has_root_directory(); if (hrd1 != hrd2) { return hrd1 ? 1 : -1; } if (hrd1) { ++rnl1; ++rnl2; } auto iter1 = _path.begin() + static_cast(rnl1); auto iter2 = p._path.begin() + static_cast(rnl2); while (iter1 != _path.end() && iter2 != p._path.end() && *iter1 == *iter2) { ++iter1; ++iter2; } if (iter1 == _path.end()) { return iter2 == p._path.end() ? 0 : -1; } if (iter2 == p._path.end()) { return 1; } if (*iter1 == preferred_separator) { return -1; } if (*iter2 == preferred_separator) { return 1; } return *iter1 < *iter2 ? -1 : 1; #else // LWG_2936_BEHAVIOUR #ifdef GHC_OS_WINDOWS auto rnl1 = root_name_length(); auto rnl2 = p.root_name_length(); auto rnc = detail::compare_simple_insensitive(_path.c_str(), rnl1, p._path.c_str(), rnl2); if (rnc) { return rnc; } return _path.compare(rnl1, std::string::npos, p._path, rnl2, std::string::npos); #else return _path.compare(p._path); #endif #endif } GHC_INLINE int path::compare(const string_type& s) const { return compare(path(s)); } #ifdef GHC_WITH_STRING_VIEW GHC_INLINE int path::compare(basic_string_view s) const { return compare(path(s)); } #endif GHC_INLINE int path::compare(const value_type* s) const { return compare(path(s)); } //----------------------------------------------------------------------------- // [fs.path.decompose] decomposition #ifdef GHC_OS_WINDOWS GHC_INLINE void path::handle_prefixes() { #if defined(GHC_WIN_AUTO_PREFIX_LONG_PATH) _prefixLength = 0; if (_path.length() >= 6 && _path[2] == '?' && std::toupper(static_cast(_path[4])) >= 'A' && std::toupper(static_cast(_path[4])) <= 'Z' && _path[5] == ':') { if (detail::startsWith(_path, impl_string_type(GHC_PLATFORM_LITERAL("\\\\?\\"))) || detail::startsWith(_path, impl_string_type(GHC_PLATFORM_LITERAL("\\??\\")))) { _prefixLength = 4; } } #endif // GHC_WIN_AUTO_PREFIX_LONG_PATH } #endif GHC_INLINE path::string_type::size_type path::root_name_length() const noexcept { #ifdef GHC_OS_WINDOWS if (_path.length() >= _prefixLength + 2 && std::toupper(static_cast(_path[_prefixLength])) >= 'A' && std::toupper(static_cast(_path[_prefixLength])) <= 'Z' && _path[_prefixLength + 1] == ':') { return 2; } #endif if (_path.length() > _prefixLength + 2 && _path[_prefixLength] == preferred_separator && _path[_prefixLength + 1] == preferred_separator && _path[_prefixLength + 2] != preferred_separator && std::isprint(_path[_prefixLength + 2])) { impl_string_type::size_type pos = _path.find(preferred_separator, _prefixLength + 3); if (pos == impl_string_type::npos) { return _path.length(); } else { return pos; } } return 0; } GHC_INLINE path path::root_name() const { return path(_path.substr(_prefixLength, root_name_length()), native_format); } GHC_INLINE path path::root_directory() const { if (has_root_directory()) { static const path _root_dir(std::string(1, preferred_separator), native_format); return _root_dir; } return path(); } GHC_INLINE path path::root_path() const { return path(root_name().string() + root_directory().string(), native_format); } GHC_INLINE path path::relative_path() const { auto rootPathLen = _prefixLength + root_name_length() + (has_root_directory() ? 1 : 0); return path(_path.substr((std::min)(rootPathLen, _path.length())), generic_format); } GHC_INLINE path path::parent_path() const { auto rootPathLen = _prefixLength + root_name_length() + (has_root_directory() ? 1 : 0); if (rootPathLen < _path.length()) { if (empty()) { return path(); } else { auto piter = end(); auto iter = piter.decrement(_path.end()); if (iter > _path.begin() + static_cast(rootPathLen) && *iter != preferred_separator) { --iter; } return path(_path.begin(), iter, native_format); } } else { return *this; } } GHC_INLINE path path::filename() const { return !has_relative_path() ? path() : path(*--end()); } GHC_INLINE path path::stem() const { impl_string_type fn = filename().native(); if (fn != "." && fn != "..") { impl_string_type::size_type pos = fn.rfind('.'); if (pos != impl_string_type::npos && pos > 0) { return path{fn.substr(0, pos), native_format}; } } return path{fn, native_format}; } GHC_INLINE path path::extension() const { if (has_relative_path()) { auto iter = end(); const auto& fn = *--iter; impl_string_type::size_type pos = fn._path.rfind('.'); if (pos != std::string::npos && pos > 0 && fn._path != "..") { return path(fn._path.substr(pos), native_format); } } return path(); } #ifdef GHC_OS_WINDOWS namespace detail { GHC_INLINE bool has_executable_extension(const path& p) { if (p.has_relative_path()) { auto iter = p.end(); const auto& fn = *--iter; auto pos = fn._path.find_last_of('.'); if (pos == std::string::npos || pos == 0 || fn._path.length() - pos != 3) { return false; } const path::value_type* ext = fn._path.c_str() + pos + 1; if (detail::equals_simple_insensitive(ext, GHC_PLATFORM_LITERAL("exe")) || detail::equals_simple_insensitive(ext, GHC_PLATFORM_LITERAL("cmd")) || detail::equals_simple_insensitive(ext, GHC_PLATFORM_LITERAL("bat")) || detail::equals_simple_insensitive(ext, GHC_PLATFORM_LITERAL("com"))) { return true; } } return false; } } // namespace detail #endif //----------------------------------------------------------------------------- // [fs.path.query] query GHC_INLINE bool path::empty() const noexcept { return _path.empty(); } GHC_INLINE bool path::has_root_name() const { return root_name_length() > 0; } GHC_INLINE bool path::has_root_directory() const { auto rootLen = _prefixLength + root_name_length(); return (_path.length() > rootLen && _path[rootLen] == preferred_separator); } GHC_INLINE bool path::has_root_path() const { return has_root_name() || has_root_directory(); } GHC_INLINE bool path::has_relative_path() const { auto rootPathLen = _prefixLength + root_name_length() + (has_root_directory() ? 1 : 0); return rootPathLen < _path.length(); } GHC_INLINE bool path::has_parent_path() const { return !parent_path().empty(); } GHC_INLINE bool path::has_filename() const { return has_relative_path() && !filename().empty(); } GHC_INLINE bool path::has_stem() const { return !stem().empty(); } GHC_INLINE bool path::has_extension() const { return !extension().empty(); } GHC_INLINE bool path::is_absolute() const { #ifdef GHC_OS_WINDOWS return has_root_name() && has_root_directory(); #else return has_root_directory(); #endif } GHC_INLINE bool path::is_relative() const { return !is_absolute(); } //----------------------------------------------------------------------------- // [fs.path.gen] generation GHC_INLINE path path::lexically_normal() const { path dest; bool lastDotDot = false; for (string_type s : *this) { if (s == ".") { dest /= ""; continue; } else if (s == ".." && !dest.empty()) { auto root = root_path(); if (dest == root) { continue; } else if (*(--dest.end()) != "..") { if (dest._path.back() == preferred_separator) { dest._path.pop_back(); } dest.remove_filename(); continue; } } if (!(s.empty() && lastDotDot)) { dest /= s; } lastDotDot = s == ".."; } if (dest.empty()) { dest = "."; } return dest; } GHC_INLINE path path::lexically_relative(const path& base) const { if (root_name() != base.root_name() || is_absolute() != base.is_absolute() || (!has_root_directory() && base.has_root_directory())) { return path(); } const_iterator a = begin(), b = base.begin(); while (a != end() && b != base.end() && *a == *b) { ++a; ++b; } if (a == end() && b == base.end()) { return path("."); } int count = 0; for (const auto& element : input_iterator_range(b, base.end())) { if (element != "." && element != "" && element != "..") { ++count; } else if (element == "..") { --count; } } if (count == 0 && (a == end() || a->empty())) { return path("."); } if (count < 0) { return path(); } path result; for (int i = 0; i < count; ++i) { result /= ".."; } for (const auto& element : input_iterator_range(a, end())) { result /= element; } return result; } GHC_INLINE path path::lexically_proximate(const path& base) const { path result = lexically_relative(base); return result.empty() ? *this : result; } //----------------------------------------------------------------------------- // [fs.path.itr] iterators GHC_INLINE path::iterator::iterator() {} GHC_INLINE path::iterator::iterator(const path& p, const impl_string_type::const_iterator& pos) : _first(p._path.begin()) , _last(p._path.end()) , _prefix(_first + static_cast(p._prefixLength)) , _root(p.has_root_directory() ? _first + static_cast(p._prefixLength + p.root_name_length()) : _last) , _iter(pos) { if (pos != _last) { updateCurrent(); } } GHC_INLINE path::impl_string_type::const_iterator path::iterator::increment(const path::impl_string_type::const_iterator& pos) const { path::impl_string_type::const_iterator i = pos; bool fromStart = i == _first || i == _prefix; if (i != _last) { if (fromStart && i == _first && _prefix > _first) { i = _prefix; } else if (*i++ == preferred_separator) { // we can only sit on a slash if it is a network name or a root if (i != _last && *i == preferred_separator) { if (fromStart && !(i + 1 != _last && *(i + 1) == preferred_separator)) { // leadind double slashes detected, treat this and the // following until a slash as one unit i = std::find(++i, _last, preferred_separator); } else { // skip redundant slashes while (i != _last && *i == preferred_separator) { ++i; } } } } else { #ifdef GHC_OS_WINDOWS if (fromStart && i != _last && *i == ':') { ++i; } else { #else { #endif i = std::find(i, _last, preferred_separator); } } } return i; } GHC_INLINE path::impl_string_type::const_iterator path::iterator::decrement(const path::impl_string_type::const_iterator& pos) const { path::impl_string_type::const_iterator i = pos; if (i != _first) { --i; // if this is now the root slash or the trailing slash, we are done, // else check for network name if (i != _root && (pos != _last || *i != preferred_separator)) { #ifdef GHC_OS_WINDOWS static const impl_string_type seps = GHC_PLATFORM_LITERAL("\\:"); i = std::find_first_of(std::reverse_iterator(i), std::reverse_iterator(_first), seps.begin(), seps.end()).base(); if (i > _first && *i == ':') { i++; } #else i = std::find(std::reverse_iterator(i), std::reverse_iterator(_first), preferred_separator).base(); #endif // Now we have to check if this is a network name if (i - _first == 2 && *_first == preferred_separator && *(_first + 1) == preferred_separator) { i -= 2; } } } return i; } GHC_INLINE void path::iterator::updateCurrent() { if ((_iter == _last) || (_iter != _first && _iter != _last && (*_iter == preferred_separator && _iter != _root) && (_iter + 1 == _last))) { _current.clear(); } else { _current.assign(_iter, increment(_iter)); } } GHC_INLINE path::iterator& path::iterator::operator++() { _iter = increment(_iter); while (_iter != _last && // we didn't reach the end _iter != _root && // this is not a root position *_iter == preferred_separator && // we are on a separator (_iter + 1) != _last // the slash is not the last char ) { ++_iter; } updateCurrent(); return *this; } GHC_INLINE path::iterator path::iterator::operator++(int) { path::iterator i{*this}; ++(*this); return i; } GHC_INLINE path::iterator& path::iterator::operator--() { _iter = decrement(_iter); updateCurrent(); return *this; } GHC_INLINE path::iterator path::iterator::operator--(int) { auto i = *this; --(*this); return i; } GHC_INLINE bool path::iterator::operator==(const path::iterator& other) const { return _iter == other._iter; } GHC_INLINE bool path::iterator::operator!=(const path::iterator& other) const { return _iter != other._iter; } GHC_INLINE path::iterator::reference path::iterator::operator*() const { return _current; } GHC_INLINE path::iterator::pointer path::iterator::operator->() const { return &_current; } GHC_INLINE path::iterator path::begin() const { return iterator(*this, _path.begin()); } GHC_INLINE path::iterator path::end() const { return iterator(*this, _path.end()); } //----------------------------------------------------------------------------- // [fs.path.nonmember] path non-member functions GHC_INLINE void swap(path& lhs, path& rhs) noexcept { swap(lhs._path, rhs._path); } GHC_INLINE size_t hash_value(const path& p) noexcept { return std::hash()(p.generic_string()); } #ifdef GHC_HAS_THREEWAY_COMP GHC_INLINE std::strong_ordering operator<=>(const path& lhs, const path& rhs) noexcept { return lhs.compare(rhs) <=> 0; } #endif GHC_INLINE bool operator==(const path& lhs, const path& rhs) noexcept { return lhs.compare(rhs) == 0; } GHC_INLINE bool operator!=(const path& lhs, const path& rhs) noexcept { return !(lhs == rhs); } GHC_INLINE bool operator<(const path& lhs, const path& rhs) noexcept { return lhs.compare(rhs) < 0; } GHC_INLINE bool operator<=(const path& lhs, const path& rhs) noexcept { return lhs.compare(rhs) <= 0; } GHC_INLINE bool operator>(const path& lhs, const path& rhs) noexcept { return lhs.compare(rhs) > 0; } GHC_INLINE bool operator>=(const path& lhs, const path& rhs) noexcept { return lhs.compare(rhs) >= 0; } GHC_INLINE path operator/(const path& lhs, const path& rhs) { path result(lhs); result /= rhs; return result; } #endif // GHC_EXPAND_IMPL //----------------------------------------------------------------------------- // [fs.path.io] path inserter and extractor template inline std::basic_ostream& operator<<(std::basic_ostream& os, const path& p) { os << "\""; auto ps = p.string(); for (auto c : ps) { if (c == '"' || c == '\\') { os << '\\'; } os << c; } os << "\""; return os; } template inline std::basic_istream& operator>>(std::basic_istream& is, path& p) { std::basic_string tmp; charT c; is >> c; if (c == '"') { auto sf = is.flags(); is >> std::noskipws; while (is) { auto c2 = is.get(); if (is) { if (c2 == '\\') { c2 = is.get(); if (is) { tmp += static_cast(c2); } } else if (c2 == '"') { break; } else { tmp += static_cast(c2); } } } if ((sf & std::ios_base::skipws) == std::ios_base::skipws) { is >> std::skipws; } p = path(tmp); } else { is >> tmp; p = path(static_cast(c) + tmp); } return is; } #ifdef GHC_EXPAND_IMPL //----------------------------------------------------------------------------- // [fs.class.filesystem_error] Class filesystem_error GHC_INLINE filesystem_error::filesystem_error(const std::string& what_arg, std::error_code ec) : std::system_error(ec, what_arg) , _what_arg(what_arg) , _ec(ec) { } GHC_INLINE filesystem_error::filesystem_error(const std::string& what_arg, const path& p1, std::error_code ec) : std::system_error(ec, what_arg) , _what_arg(what_arg) , _ec(ec) , _p1(p1) { if (!_p1.empty()) { _what_arg += ": '" + _p1.string() + "'"; } } GHC_INLINE filesystem_error::filesystem_error(const std::string& what_arg, const path& p1, const path& p2, std::error_code ec) : std::system_error(ec, what_arg) , _what_arg(what_arg) , _ec(ec) , _p1(p1) , _p2(p2) { if (!_p1.empty()) { _what_arg += ": '" + _p1.string() + "'"; } if (!_p2.empty()) { _what_arg += ", '" + _p2.string() + "'"; } } GHC_INLINE const path& filesystem_error::path1() const noexcept { return _p1; } GHC_INLINE const path& filesystem_error::path2() const noexcept { return _p2; } GHC_INLINE const char* filesystem_error::what() const noexcept { return _what_arg.c_str(); } //----------------------------------------------------------------------------- // [fs.op.funcs] filesystem operations #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE path absolute(const path& p) { std::error_code ec; path result = absolute(p, ec); if (ec) { throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); } return result; } #endif GHC_INLINE path absolute(const path& p, std::error_code& ec) { ec.clear(); #ifdef GHC_OS_WINDOWS if (p.empty()) { return absolute(current_path(ec), ec) / ""; } ULONG size = ::GetFullPathNameW(GHC_NATIVEWP(p), 0, 0, 0); if (size) { std::vector buf(size, 0); ULONG s2 = GetFullPathNameW(GHC_NATIVEWP(p), size, buf.data(), nullptr); if (s2 && s2 < size) { path result = path(std::wstring(buf.data(), s2)); if (p.filename() == ".") { result /= "."; } return result; } } ec = detail::make_system_error(); return path(); #else path base = current_path(ec); if (!ec) { if (p.empty()) { return base / p; } if (p.has_root_name()) { if (p.has_root_directory()) { return p; } else { return p.root_name() / base.root_directory() / base.relative_path() / p.relative_path(); } } else { if (p.has_root_directory()) { return base.root_name() / p; } else { return base / p; } } } ec = detail::make_system_error(); return path(); #endif } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE path canonical(const path& p) { std::error_code ec; auto result = canonical(p, ec); if (ec) { throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); } return result; } #endif GHC_INLINE path canonical(const path& p, std::error_code& ec) { if (p.empty()) { ec = detail::make_error_code(detail::portable_error::not_found); return path(); } path work = p.is_absolute() ? p : absolute(p, ec); path result; auto fs = status(work, ec); if (ec) { return path(); } if (fs.type() == file_type::not_found) { ec = detail::make_error_code(detail::portable_error::not_found); return path(); } bool redo; do { auto rootPathLen = work._prefixLength + work.root_name_length() + (work.has_root_directory() ? 1 : 0); redo = false; result.clear(); for (auto pe : work) { if (pe.empty() || pe == ".") { continue; } else if (pe == "..") { result = result.parent_path(); continue; } else if ((result / pe).string().length() <= rootPathLen) { result /= pe; continue; } auto sls = symlink_status(result / pe, ec); if (ec) { return path(); } if (is_symlink(sls)) { redo = true; auto target = read_symlink(result / pe, ec); if (ec) { return path(); } if (target.is_absolute()) { result = target; continue; } else { result /= target; continue; } } else { result /= pe; } } work = result; } while (redo); ec.clear(); return result; } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE void copy(const path& from, const path& to) { copy(from, to, copy_options::none); } GHC_INLINE void copy(const path& from, const path& to, copy_options options) { std::error_code ec; copy(from, to, options, ec); if (ec) { throw filesystem_error(detail::systemErrorText(ec.value()), from, to, ec); } } #endif GHC_INLINE void copy(const path& from, const path& to, std::error_code& ec) noexcept { copy(from, to, copy_options::none, ec); } GHC_INLINE void copy(const path& from, const path& to, copy_options options, std::error_code& ec) noexcept { std::error_code tec; file_status fs_from, fs_to; ec.clear(); if ((options & (copy_options::skip_symlinks | copy_options::copy_symlinks | copy_options::create_symlinks)) != copy_options::none) { fs_from = symlink_status(from, ec); } else { fs_from = status(from, ec); } if (!exists(fs_from)) { if (!ec) { ec = detail::make_error_code(detail::portable_error::not_found); } return; } if ((options & (copy_options::skip_symlinks | copy_options::create_symlinks)) != copy_options::none) { fs_to = symlink_status(to, tec); } else { fs_to = status(to, tec); } if (is_other(fs_from) || is_other(fs_to) || (is_directory(fs_from) && is_regular_file(fs_to)) || (exists(fs_to) && equivalent(from, to, ec))) { ec = detail::make_error_code(detail::portable_error::invalid_argument); } else if (is_symlink(fs_from)) { if ((options & copy_options::skip_symlinks) == copy_options::none) { if (!exists(fs_to) && (options & copy_options::copy_symlinks) != copy_options::none) { copy_symlink(from, to, ec); } else { ec = detail::make_error_code(detail::portable_error::invalid_argument); } } } else if (is_regular_file(fs_from)) { if ((options & copy_options::directories_only) == copy_options::none) { if ((options & copy_options::create_symlinks) != copy_options::none) { create_symlink(from.is_absolute() ? from : canonical(from, ec), to, ec); } #ifndef GHC_OS_WEB else if ((options & copy_options::create_hard_links) != copy_options::none) { create_hard_link(from, to, ec); } #endif else if (is_directory(fs_to)) { copy_file(from, to / from.filename(), options, ec); } else { copy_file(from, to, options, ec); } } } #ifdef LWG_2682_BEHAVIOUR else if (is_directory(fs_from) && (options & copy_options::create_symlinks) != copy_options::none) { ec = detail::make_error_code(detail::portable_error::is_a_directory); } #endif else if (is_directory(fs_from) && (options == copy_options::none || (options & copy_options::recursive) != copy_options::none)) { if (!exists(fs_to)) { create_directory(to, from, ec); if (ec) { return; } } for (auto iter = directory_iterator(from, ec); iter != directory_iterator(); iter.increment(ec)) { if (!ec) { copy(iter->path(), to / iter->path().filename(), options | static_cast(0x8000), ec); } if (ec) { return; } } } return; } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE bool copy_file(const path& from, const path& to) { return copy_file(from, to, copy_options::none); } GHC_INLINE bool copy_file(const path& from, const path& to, copy_options option) { std::error_code ec; auto result = copy_file(from, to, option, ec); if (ec) { throw filesystem_error(detail::systemErrorText(ec.value()), from, to, ec); } return result; } #endif GHC_INLINE bool copy_file(const path& from, const path& to, std::error_code& ec) noexcept { return copy_file(from, to, copy_options::none, ec); } GHC_INLINE bool copy_file(const path& from, const path& to, copy_options options, std::error_code& ec) noexcept { std::error_code tecf, tect; auto sf = status(from, tecf); auto st = status(to, tect); bool overwrite = false; ec.clear(); if (!is_regular_file(sf)) { ec = tecf; return false; } if (exists(st)) { if ((options & copy_options::skip_existing) == copy_options::skip_existing) { return false; } if (!is_regular_file(st) || equivalent(from, to, ec) || (options & (copy_options::overwrite_existing | copy_options::update_existing)) == copy_options::none) { ec = tect ? tect : detail::make_error_code(detail::portable_error::exists); return false; } if ((options & copy_options::update_existing) == copy_options::update_existing) { auto from_time = last_write_time(from, ec); if (ec) { ec = detail::make_system_error(); return false; } auto to_time = last_write_time(to, ec); if (ec) { ec = detail::make_system_error(); return false; } if (from_time <= to_time) { return false; } } overwrite = true; } #ifdef GHC_OS_WINDOWS if (!::CopyFileW(GHC_NATIVEWP(from), GHC_NATIVEWP(to), !overwrite)) { ec = detail::make_system_error(); return false; } return true; #else std::vector buffer(16384, '\0'); int in = -1, out = -1; if ((in = ::open(from.c_str(), O_RDONLY)) < 0) { ec = detail::make_system_error(); return false; } int mode = O_CREAT | O_WRONLY | O_TRUNC; if (!overwrite) { mode |= O_EXCL; } if ((out = ::open(to.c_str(), mode, static_cast(sf.permissions() & perms::all))) < 0) { ec = detail::make_system_error(); ::close(in); return false; } if (st.permissions() != sf.permissions()) { if (::fchmod(out, static_cast(sf.permissions() & perms::all)) != 0) { ec = detail::make_system_error(); ::close(in); ::close(out); return false; } } ssize_t br, bw; while (true) { do { br = ::read(in, buffer.data(), buffer.size()); } while(errno == EINTR && !br); if(!br) { break; } if(br < 0) { ec = detail::make_system_error(); ::close(in); ::close(out); return false; } ssize_t offset = 0; do { if ((bw = ::write(out, buffer.data() + offset, static_cast(br))) > 0) { br -= bw; offset += bw; } else if (bw < 0 && errno != EINTR) { ec = detail::make_system_error(); ::close(in); ::close(out); return false; } } while (br); } ::close(in); ::close(out); return true; #endif } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE void copy_symlink(const path& existing_symlink, const path& new_symlink) { std::error_code ec; copy_symlink(existing_symlink, new_symlink, ec); if (ec) { throw filesystem_error(detail::systemErrorText(ec.value()), existing_symlink, new_symlink, ec); } } #endif GHC_INLINE void copy_symlink(const path& existing_symlink, const path& new_symlink, std::error_code& ec) noexcept { ec.clear(); auto to = read_symlink(existing_symlink, ec); if (!ec) { if (exists(to, ec) && is_directory(to, ec)) { create_directory_symlink(to, new_symlink, ec); } else { create_symlink(to, new_symlink, ec); } } } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE bool create_directories(const path& p) { std::error_code ec; auto result = create_directories(p, ec); if (ec) { throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); } return result; } #endif GHC_INLINE bool create_directories(const path& p, std::error_code& ec) noexcept { path current; ec.clear(); bool didCreate = false; auto rootPathLen = p._prefixLength + p.root_name_length() + (p.has_root_directory() ? 1 : 0); current = p.native().substr(0, rootPathLen); path folders(p._path.substr(rootPathLen)); for (path::string_type part : folders) { current /= part; std::error_code tec; auto fs = status(current, tec); if (tec && fs.type() != file_type::not_found) { ec = tec; return false; } if (!exists(fs)) { create_directory(current, ec); if (ec) { std::error_code tmp_ec; if (is_directory(current, tmp_ec)) { ec.clear(); } else { return false; } } didCreate = true; } #ifndef LWG_2935_BEHAVIOUR else if (!is_directory(fs)) { ec = detail::make_error_code(detail::portable_error::exists); return false; } #endif } return didCreate; } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE bool create_directory(const path& p) { std::error_code ec; auto result = create_directory(p, path(), ec); if (ec) { throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); } return result; } #endif GHC_INLINE bool create_directory(const path& p, std::error_code& ec) noexcept { return create_directory(p, path(), ec); } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE bool create_directory(const path& p, const path& attributes) { std::error_code ec; auto result = create_directory(p, attributes, ec); if (ec) { throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); } return result; } #endif GHC_INLINE bool create_directory(const path& p, const path& attributes, std::error_code& ec) noexcept { std::error_code tec; ec.clear(); auto fs = status(p, tec); #ifdef LWG_2935_BEHAVIOUR if (status_known(fs) && exists(fs)) { return false; } #else if (status_known(fs) && exists(fs) && is_directory(fs)) { return false; } #endif #ifdef GHC_OS_WINDOWS if (!attributes.empty()) { if (!::CreateDirectoryExW(GHC_NATIVEWP(attributes), GHC_NATIVEWP(p), NULL)) { ec = detail::make_system_error(); return false; } } else if (!::CreateDirectoryW(GHC_NATIVEWP(p), NULL)) { ec = detail::make_system_error(); return false; } #else ::mode_t attribs = static_cast(perms::all); if (!attributes.empty()) { struct ::stat fileStat; if (::stat(attributes.c_str(), &fileStat) != 0) { ec = detail::make_system_error(); return false; } attribs = fileStat.st_mode; } if (::mkdir(p.c_str(), attribs) != 0) { ec = detail::make_system_error(); return false; } #endif return true; } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE void create_directory_symlink(const path& to, const path& new_symlink) { std::error_code ec; create_directory_symlink(to, new_symlink, ec); if (ec) { throw filesystem_error(detail::systemErrorText(ec.value()), to, new_symlink, ec); } } #endif GHC_INLINE void create_directory_symlink(const path& to, const path& new_symlink, std::error_code& ec) noexcept { detail::create_symlink(to, new_symlink, true, ec); } #ifndef GHC_OS_WEB #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE void create_hard_link(const path& to, const path& new_hard_link) { std::error_code ec; create_hard_link(to, new_hard_link, ec); if (ec) { throw filesystem_error(detail::systemErrorText(ec.value()), to, new_hard_link, ec); } } #endif GHC_INLINE void create_hard_link(const path& to, const path& new_hard_link, std::error_code& ec) noexcept { detail::create_hardlink(to, new_hard_link, ec); } #endif #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE void create_symlink(const path& to, const path& new_symlink) { std::error_code ec; create_symlink(to, new_symlink, ec); if (ec) { throw filesystem_error(detail::systemErrorText(ec.value()), to, new_symlink, ec); } } #endif GHC_INLINE void create_symlink(const path& to, const path& new_symlink, std::error_code& ec) noexcept { detail::create_symlink(to, new_symlink, false, ec); } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE path current_path() { std::error_code ec; auto result = current_path(ec); if (ec) { throw filesystem_error(detail::systemErrorText(ec.value()), ec); } return result; } #endif GHC_INLINE path current_path(std::error_code& ec) { ec.clear(); #ifdef GHC_OS_WINDOWS DWORD pathlen = ::GetCurrentDirectoryW(0, 0); std::unique_ptr buffer(new wchar_t[size_t(pathlen) + 1]); if (::GetCurrentDirectoryW(pathlen, buffer.get()) == 0) { ec = detail::make_system_error(); return path(); } return path(std::wstring(buffer.get()), path::native_format); #elif defined(__GLIBC__) std::unique_ptr buffer { ::getcwd(NULL, 0), std::free }; if (buffer == nullptr) { ec = detail::make_system_error(); return path(); } return path(buffer.get()); #else size_t pathlen = static_cast(std::max(int(::pathconf(".", _PC_PATH_MAX)), int(PATH_MAX))); std::unique_ptr buffer(new char[pathlen + 1]); if (::getcwd(buffer.get(), pathlen) == nullptr) { ec = detail::make_system_error(); return path(); } return path(buffer.get()); #endif } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE void current_path(const path& p) { std::error_code ec; current_path(p, ec); if (ec) { throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); } } #endif GHC_INLINE void current_path(const path& p, std::error_code& ec) noexcept { ec.clear(); #ifdef GHC_OS_WINDOWS if (!::SetCurrentDirectoryW(GHC_NATIVEWP(p))) { ec = detail::make_system_error(); } #else if (::chdir(p.string().c_str()) == -1) { ec = detail::make_system_error(); } #endif } GHC_INLINE bool exists(file_status s) noexcept { return status_known(s) && s.type() != file_type::not_found; } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE bool exists(const path& p) { return exists(status(p)); } #endif GHC_INLINE bool exists(const path& p, std::error_code& ec) noexcept { file_status s = status(p, ec); if (status_known(s)) { ec.clear(); } return exists(s); } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE bool equivalent(const path& p1, const path& p2) { std::error_code ec; bool result = equivalent(p1, p2, ec); if (ec) { throw filesystem_error(detail::systemErrorText(ec.value()), p1, p2, ec); } return result; } #endif GHC_INLINE bool equivalent(const path& p1, const path& p2, std::error_code& ec) noexcept { ec.clear(); #ifdef GHC_OS_WINDOWS detail::unique_handle file1(::CreateFileW(GHC_NATIVEWP(p1), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0)); auto e1 = ::GetLastError(); detail::unique_handle file2(::CreateFileW(GHC_NATIVEWP(p2), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0)); if (!file1 || !file2) { #ifdef LWG_2937_BEHAVIOUR ec = detail::make_system_error(e1 ? e1 : ::GetLastError()); #else if (file1 == file2) { ec = detail::make_system_error(e1 ? e1 : ::GetLastError()); } #endif return false; } BY_HANDLE_FILE_INFORMATION inf1, inf2; if (!::GetFileInformationByHandle(file1.get(), &inf1)) { ec = detail::make_system_error(); return false; } if (!::GetFileInformationByHandle(file2.get(), &inf2)) { ec = detail::make_system_error(); return false; } return inf1.ftLastWriteTime.dwLowDateTime == inf2.ftLastWriteTime.dwLowDateTime && inf1.ftLastWriteTime.dwHighDateTime == inf2.ftLastWriteTime.dwHighDateTime && inf1.nFileIndexHigh == inf2.nFileIndexHigh && inf1.nFileIndexLow == inf2.nFileIndexLow && inf1.nFileSizeHigh == inf2.nFileSizeHigh && inf1.nFileSizeLow == inf2.nFileSizeLow && inf1.dwVolumeSerialNumber == inf2.dwVolumeSerialNumber; #else struct ::stat s1, s2; auto rc1 = ::stat(p1.c_str(), &s1); auto e1 = errno; auto rc2 = ::stat(p2.c_str(), &s2); if (rc1 || rc2) { #ifdef LWG_2937_BEHAVIOUR ec = detail::make_system_error(e1 ? e1 : errno); #else if (rc1 && rc2) { ec = detail::make_system_error(e1 ? e1 : errno); } #endif return false; } return s1.st_dev == s2.st_dev && s1.st_ino == s2.st_ino && s1.st_size == s2.st_size && s1.st_mtime == s2.st_mtime; #endif } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE uintmax_t file_size(const path& p) { std::error_code ec; auto result = file_size(p, ec); if (ec) { throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); } return result; } #endif GHC_INLINE uintmax_t file_size(const path& p, std::error_code& ec) noexcept { ec.clear(); #ifdef GHC_OS_WINDOWS WIN32_FILE_ATTRIBUTE_DATA attr; if (!GetFileAttributesExW(GHC_NATIVEWP(p), GetFileExInfoStandard, &attr)) { ec = detail::make_system_error(); return static_cast(-1); } return static_cast(attr.nFileSizeHigh) << (sizeof(attr.nFileSizeHigh) * 8) | attr.nFileSizeLow; #else struct ::stat fileStat; if (::stat(p.c_str(), &fileStat) == -1) { ec = detail::make_system_error(); return static_cast(-1); } return static_cast(fileStat.st_size); #endif } #ifndef GHC_OS_WEB #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE uintmax_t hard_link_count(const path& p) { std::error_code ec; auto result = hard_link_count(p, ec); if (ec) { throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); } return result; } #endif GHC_INLINE uintmax_t hard_link_count(const path& p, std::error_code& ec) noexcept { ec.clear(); #ifdef GHC_OS_WINDOWS uintmax_t result = static_cast(-1); detail::unique_handle file(::CreateFileW(GHC_NATIVEWP(p), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0)); BY_HANDLE_FILE_INFORMATION inf; if (!file) { ec = detail::make_system_error(); } else { if (!::GetFileInformationByHandle(file.get(), &inf)) { ec = detail::make_system_error(); } else { result = inf.nNumberOfLinks; } } return result; #else uintmax_t result = 0; file_status fs = detail::status_ex(p, ec, nullptr, nullptr, &result, nullptr); if (fs.type() == file_type::not_found) { ec = detail::make_error_code(detail::portable_error::not_found); } return ec ? static_cast(-1) : result; #endif } #endif GHC_INLINE bool is_block_file(file_status s) noexcept { return s.type() == file_type::block; } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE bool is_block_file(const path& p) { return is_block_file(status(p)); } #endif GHC_INLINE bool is_block_file(const path& p, std::error_code& ec) noexcept { return is_block_file(status(p, ec)); } GHC_INLINE bool is_character_file(file_status s) noexcept { return s.type() == file_type::character; } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE bool is_character_file(const path& p) { return is_character_file(status(p)); } #endif GHC_INLINE bool is_character_file(const path& p, std::error_code& ec) noexcept { return is_character_file(status(p, ec)); } GHC_INLINE bool is_directory(file_status s) noexcept { return s.type() == file_type::directory; } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE bool is_directory(const path& p) { return is_directory(status(p)); } #endif GHC_INLINE bool is_directory(const path& p, std::error_code& ec) noexcept { return is_directory(status(p, ec)); } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE bool is_empty(const path& p) { if (is_directory(p)) { return directory_iterator(p) == directory_iterator(); } else { return file_size(p) == 0; } } #endif GHC_INLINE bool is_empty(const path& p, std::error_code& ec) noexcept { auto fs = status(p, ec); if (ec) { return false; } if (is_directory(fs)) { directory_iterator iter(p, ec); if (ec) { return false; } return iter == directory_iterator(); } else { auto sz = file_size(p, ec); if (ec) { return false; } return sz == 0; } } GHC_INLINE bool is_fifo(file_status s) noexcept { return s.type() == file_type::fifo; } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE bool is_fifo(const path& p) { return is_fifo(status(p)); } #endif GHC_INLINE bool is_fifo(const path& p, std::error_code& ec) noexcept { return is_fifo(status(p, ec)); } GHC_INLINE bool is_other(file_status s) noexcept { return exists(s) && !is_regular_file(s) && !is_directory(s) && !is_symlink(s); } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE bool is_other(const path& p) { return is_other(status(p)); } #endif GHC_INLINE bool is_other(const path& p, std::error_code& ec) noexcept { return is_other(status(p, ec)); } GHC_INLINE bool is_regular_file(file_status s) noexcept { return s.type() == file_type::regular; } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE bool is_regular_file(const path& p) { return is_regular_file(status(p)); } #endif GHC_INLINE bool is_regular_file(const path& p, std::error_code& ec) noexcept { return is_regular_file(status(p, ec)); } GHC_INLINE bool is_socket(file_status s) noexcept { return s.type() == file_type::socket; } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE bool is_socket(const path& p) { return is_socket(status(p)); } #endif GHC_INLINE bool is_socket(const path& p, std::error_code& ec) noexcept { return is_socket(status(p, ec)); } GHC_INLINE bool is_symlink(file_status s) noexcept { return s.type() == file_type::symlink; } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE bool is_symlink(const path& p) { return is_symlink(symlink_status(p)); } #endif GHC_INLINE bool is_symlink(const path& p, std::error_code& ec) noexcept { return is_symlink(symlink_status(p, ec)); } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE file_time_type last_write_time(const path& p) { std::error_code ec; auto result = last_write_time(p, ec); if (ec) { throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); } return result; } #endif GHC_INLINE file_time_type last_write_time(const path& p, std::error_code& ec) noexcept { time_t result = 0; ec.clear(); file_status fs = detail::status_ex(p, ec, nullptr, nullptr, nullptr, &result); return ec ? (file_time_type::min)() : std::chrono::system_clock::from_time_t(result); } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE void last_write_time(const path& p, file_time_type new_time) { std::error_code ec; last_write_time(p, new_time, ec); if (ec) { throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); } } #endif GHC_INLINE void last_write_time(const path& p, file_time_type new_time, std::error_code& ec) noexcept { ec.clear(); auto d = new_time.time_since_epoch(); #ifdef GHC_OS_WINDOWS detail::unique_handle file(::CreateFileW(GHC_NATIVEWP(p), FILE_WRITE_ATTRIBUTES, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL)); FILETIME ft; auto tt = std::chrono::duration_cast(d).count() * 10 + 116444736000000000; ft.dwLowDateTime = static_cast(tt); ft.dwHighDateTime = static_cast(tt >> 32); if (!::SetFileTime(file.get(), 0, 0, &ft)) { ec = detail::make_system_error(); } #elif defined(GHC_OS_APPLE) && \ (defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && __MAC_OS_X_VERSION_MIN_REQUIRED < 101300 \ || defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && __IPHONE_OS_VERSION_MIN_REQUIRED < 110000 \ || defined(__TV_OS_VERSION_MIN_REQUIRED) && __TVOS_VERSION_MIN_REQUIRED < 110000 \ || defined(__WATCH_OS_VERSION_MIN_REQUIRED) && __WATCHOS_VERSION_MIN_REQUIRED < 40000) struct ::stat fs; if (::stat(p.c_str(), &fs) == 0) { struct ::timeval tv[2]; tv[0].tv_sec = fs.st_atimespec.tv_sec; tv[0].tv_usec = static_cast(fs.st_atimespec.tv_nsec / 1000); tv[1].tv_sec = std::chrono::duration_cast(d).count(); tv[1].tv_usec = static_cast(std::chrono::duration_cast(d).count() % 1000000); if (::utimes(p.c_str(), tv) == 0) { return; } } ec = detail::make_system_error(); return; #else #ifndef UTIME_OMIT #define UTIME_OMIT ((1l << 30) - 2l) #endif struct ::timespec times[2]; times[0].tv_sec = 0; times[0].tv_nsec = UTIME_OMIT; times[1].tv_sec = static_cast(std::chrono::duration_cast(d).count()); times[1].tv_nsec = static_cast(std::chrono::duration_cast(d).count() % 1000000000); #if defined(__ANDROID_API__) && __ANDROID_API__ < 12 if (syscall(__NR_utimensat, AT_FDCWD, p.c_str(), times, AT_SYMLINK_NOFOLLOW) != 0) { #else if (::utimensat(static_cast(AT_FDCWD), p.c_str(), times, AT_SYMLINK_NOFOLLOW) != 0) { #endif ec = detail::make_system_error(); } return; #endif } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE void permissions(const path& p, perms prms, perm_options opts) { std::error_code ec; permissions(p, prms, opts, ec); if (ec) { throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); } } #endif GHC_INLINE void permissions(const path& p, perms prms, std::error_code& ec) noexcept { permissions(p, prms, perm_options::replace, ec); } GHC_INLINE void permissions(const path& p, perms prms, perm_options opts, std::error_code& ec) noexcept { if (static_cast(opts & (perm_options::replace | perm_options::add | perm_options::remove)) == 0) { ec = detail::make_error_code(detail::portable_error::invalid_argument); return; } auto fs = symlink_status(p, ec); if ((opts & perm_options::replace) != perm_options::replace) { if ((opts & perm_options::add) == perm_options::add) { prms = fs.permissions() | prms; } else { prms = fs.permissions() & ~prms; } } #ifdef GHC_OS_WINDOWS #ifdef __GNUC__ auto oldAttr = GetFileAttributesW(GHC_NATIVEWP(p)); if (oldAttr != INVALID_FILE_ATTRIBUTES) { DWORD newAttr = ((prms & perms::owner_write) == perms::owner_write) ? oldAttr & ~(static_cast(FILE_ATTRIBUTE_READONLY)) : oldAttr | FILE_ATTRIBUTE_READONLY; if (oldAttr == newAttr || SetFileAttributesW(GHC_NATIVEWP(p), newAttr)) { return; } } ec = detail::make_system_error(); #else int mode = 0; if ((prms & perms::owner_read) == perms::owner_read) { mode |= _S_IREAD; } if ((prms & perms::owner_write) == perms::owner_write) { mode |= _S_IWRITE; } if (::_wchmod(p.wstring().c_str(), mode) != 0) { ec = detail::make_system_error(); } #endif #else if ((opts & perm_options::nofollow) != perm_options::nofollow) { if (::chmod(p.c_str(), static_cast(prms)) != 0) { ec = detail::make_system_error(); } } #endif } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE path proximate(const path& p, std::error_code& ec) { auto cp = current_path(ec); if (!ec) { return proximate(p, cp, ec); } return path(); } #endif #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE path proximate(const path& p, const path& base) { return weakly_canonical(p).lexically_proximate(weakly_canonical(base)); } #endif GHC_INLINE path proximate(const path& p, const path& base, std::error_code& ec) { return weakly_canonical(p, ec).lexically_proximate(weakly_canonical(base, ec)); } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE path read_symlink(const path& p) { std::error_code ec; auto result = read_symlink(p, ec); if (ec) { throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); } return result; } #endif GHC_INLINE path read_symlink(const path& p, std::error_code& ec) { file_status fs = symlink_status(p, ec); if (fs.type() != file_type::symlink) { ec = detail::make_error_code(detail::portable_error::invalid_argument); return path(); } auto result = detail::resolveSymlink(p, ec); return ec ? path() : result; } GHC_INLINE path relative(const path& p, std::error_code& ec) { return relative(p, current_path(ec), ec); } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE path relative(const path& p, const path& base) { return weakly_canonical(p).lexically_relative(weakly_canonical(base)); } #endif GHC_INLINE path relative(const path& p, const path& base, std::error_code& ec) { return weakly_canonical(p, ec).lexically_relative(weakly_canonical(base, ec)); } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE bool remove(const path& p) { std::error_code ec; auto result = remove(p, ec); if (ec) { throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); } return result; } #endif GHC_INLINE bool remove(const path& p, std::error_code& ec) noexcept { ec.clear(); #ifdef GHC_OS_WINDOWS #ifdef GHC_USE_WCHAR_T auto cstr = p.c_str(); #else std::wstring np = detail::fromUtf8(p.u8string()); auto cstr = np.c_str(); #endif DWORD attr = GetFileAttributesW(cstr); if (attr == INVALID_FILE_ATTRIBUTES) { auto error = ::GetLastError(); if (error == ERROR_FILE_NOT_FOUND || error == ERROR_PATH_NOT_FOUND) { return false; } ec = detail::make_system_error(error); } else if (attr & FILE_ATTRIBUTE_READONLY) { auto new_attr = attr & ~static_cast(FILE_ATTRIBUTE_READONLY); if (!SetFileAttributesW(cstr, new_attr)) { auto error = ::GetLastError(); ec = detail::make_system_error(error); } } if (!ec) { if (attr & FILE_ATTRIBUTE_DIRECTORY) { if (!RemoveDirectoryW(cstr)) { ec = detail::make_system_error(); } } else { if (!DeleteFileW(cstr)) { ec = detail::make_system_error(); } } } #else if (::remove(p.c_str()) == -1) { auto error = errno; if (error == ENOENT) { return false; } ec = detail::make_system_error(); } #endif return ec ? false : true; } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE uintmax_t remove_all(const path& p) { std::error_code ec; auto result = remove_all(p, ec); if (ec) { throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); } return result; } #endif GHC_INLINE uintmax_t remove_all(const path& p, std::error_code& ec) noexcept { ec.clear(); uintmax_t count = 0; if (p == "/") { ec = detail::make_error_code(detail::portable_error::not_supported); return static_cast(-1); } std::error_code tec; auto fs = symlink_status(p, tec); if (exists(fs) && is_directory(fs)) { for (auto iter = directory_iterator(p, ec); iter != directory_iterator(); iter.increment(ec)) { if (ec && !detail::is_not_found_error(ec)) { break; } bool is_symlink_result = iter->is_symlink(ec); if (ec) return static_cast(-1); if (!is_symlink_result && iter->is_directory(ec)) { count += remove_all(iter->path(), ec); if (ec) { return static_cast(-1); } } else { if (!ec) { remove(iter->path(), ec); } if (ec) { return static_cast(-1); } ++count; } } } if (!ec) { if (remove(p, ec)) { ++count; } } if (ec) { return static_cast(-1); } return count; } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE void rename(const path& from, const path& to) { std::error_code ec; rename(from, to, ec); if (ec) { throw filesystem_error(detail::systemErrorText(ec.value()), from, to, ec); } } #endif GHC_INLINE void rename(const path& from, const path& to, std::error_code& ec) noexcept { ec.clear(); #ifdef GHC_OS_WINDOWS if (from != to) { if (!MoveFileExW(GHC_NATIVEWP(from), GHC_NATIVEWP(to), (DWORD)MOVEFILE_REPLACE_EXISTING)) { ec = detail::make_system_error(); } } #else if (from != to) { if (::rename(from.c_str(), to.c_str()) != 0) { ec = detail::make_system_error(); } } #endif } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE void resize_file(const path& p, uintmax_t size) { std::error_code ec; resize_file(p, size, ec); if (ec) { throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); } } #endif GHC_INLINE void resize_file(const path& p, uintmax_t size, std::error_code& ec) noexcept { ec.clear(); #ifdef GHC_OS_WINDOWS LARGE_INTEGER lisize; lisize.QuadPart = static_cast(size); if (lisize.QuadPart < 0) { #ifdef ERROR_FILE_TOO_LARGE ec = detail::make_system_error(ERROR_FILE_TOO_LARGE); #else ec = detail::make_system_error(223); #endif return; } detail::unique_handle file(CreateFileW(GHC_NATIVEWP(p), GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL)); if (!file) { ec = detail::make_system_error(); } else if (SetFilePointerEx(file.get(), lisize, NULL, FILE_BEGIN) == 0 || SetEndOfFile(file.get()) == 0) { ec = detail::make_system_error(); } #else if (::truncate(p.c_str(), static_cast(size)) != 0) { ec = detail::make_system_error(); } #endif } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE space_info space(const path& p) { std::error_code ec; auto result = space(p, ec); if (ec) { throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); } return result; } #endif GHC_INLINE space_info space(const path& p, std::error_code& ec) noexcept { ec.clear(); #ifdef GHC_OS_WINDOWS ULARGE_INTEGER freeBytesAvailableToCaller = {{ 0, 0 }}; ULARGE_INTEGER totalNumberOfBytes = {{ 0, 0 }}; ULARGE_INTEGER totalNumberOfFreeBytes = {{ 0, 0 }}; if (!GetDiskFreeSpaceExW(GHC_NATIVEWP(p), &freeBytesAvailableToCaller, &totalNumberOfBytes, &totalNumberOfFreeBytes)) { ec = detail::make_system_error(); return {static_cast(-1), static_cast(-1), static_cast(-1)}; } return {static_cast(totalNumberOfBytes.QuadPart), static_cast(totalNumberOfFreeBytes.QuadPart), static_cast(freeBytesAvailableToCaller.QuadPart)}; #else struct ::statvfs sfs; if (::statvfs(p.c_str(), &sfs) != 0) { ec = detail::make_system_error(); return {static_cast(-1), static_cast(-1), static_cast(-1)}; } return {static_cast(sfs.f_blocks) * static_cast(sfs.f_frsize), static_cast(sfs.f_bfree) * static_cast(sfs.f_frsize), static_cast(sfs.f_bavail) * static_cast(sfs.f_frsize)}; #endif } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE file_status status(const path& p) { std::error_code ec; auto result = status(p, ec); if (result.type() == file_type::none) { throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); } return result; } #endif GHC_INLINE file_status status(const path& p, std::error_code& ec) noexcept { return detail::status_ex(p, ec); } GHC_INLINE bool status_known(file_status s) noexcept { return s.type() != file_type::none; } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE file_status symlink_status(const path& p) { std::error_code ec; auto result = symlink_status(p, ec); if (result.type() == file_type::none) { throw filesystem_error(detail::systemErrorText(ec.value()), ec); } return result; } #endif GHC_INLINE file_status symlink_status(const path& p, std::error_code& ec) noexcept { return detail::symlink_status_ex(p, ec); } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE path temp_directory_path() { std::error_code ec; path result = temp_directory_path(ec); if (ec) { throw filesystem_error(detail::systemErrorText(ec.value()), ec); } return result; } #endif GHC_INLINE path temp_directory_path(std::error_code& ec) noexcept { ec.clear(); #ifdef GHC_OS_WINDOWS wchar_t buffer[512]; auto rc = GetTempPathW(511, buffer); if (!rc || rc > 511) { ec = detail::make_system_error(); return path(); } return path(std::wstring(buffer)); #else static const char* temp_vars[] = {"TMPDIR", "TMP", "TEMP", "TEMPDIR", nullptr}; const char* temp_path = nullptr; for (auto temp_name = temp_vars; *temp_name != nullptr; ++temp_name) { temp_path = std::getenv(*temp_name); if (temp_path) { return path(temp_path); } } return path("/tmp"); #endif } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE path weakly_canonical(const path& p) { std::error_code ec; auto result = weakly_canonical(p, ec); if (ec) { throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); } return result; } #endif GHC_INLINE path weakly_canonical(const path& p, std::error_code& ec) noexcept { path result; ec.clear(); bool scan = true; for (auto pe : p) { if (scan) { std::error_code tec; if (exists(result / pe, tec)) { result /= pe; } else { if (ec) { return path(); } scan = false; if (!result.empty()) { result = canonical(result, ec) / pe; if (ec) { break; } } else { result /= pe; } } } else { result /= pe; } } if (scan) { if (!result.empty()) { result = canonical(result, ec); } } return ec ? path() : result.lexically_normal(); } //----------------------------------------------------------------------------- // [fs.class.file_status] class file_status // [fs.file_status.cons] constructors and destructor GHC_INLINE file_status::file_status() noexcept : file_status(file_type::none) { } GHC_INLINE file_status::file_status(file_type ft, perms prms) noexcept : _type(ft) , _perms(prms) { } GHC_INLINE file_status::file_status(const file_status& other) noexcept : _type(other._type) , _perms(other._perms) { } GHC_INLINE file_status::file_status(file_status&& other) noexcept : _type(other._type) , _perms(other._perms) { } GHC_INLINE file_status::~file_status() {} // assignments: GHC_INLINE file_status& file_status::operator=(const file_status& rhs) noexcept { _type = rhs._type; _perms = rhs._perms; return *this; } GHC_INLINE file_status& file_status::operator=(file_status&& rhs) noexcept { _type = rhs._type; _perms = rhs._perms; return *this; } // [fs.file_status.mods] modifiers GHC_INLINE void file_status::type(file_type ft) noexcept { _type = ft; } GHC_INLINE void file_status::permissions(perms prms) noexcept { _perms = prms; } // [fs.file_status.obs] observers GHC_INLINE file_type file_status::type() const noexcept { return _type; } GHC_INLINE perms file_status::permissions() const noexcept { return _perms; } //----------------------------------------------------------------------------- // [fs.class.directory_entry] class directory_entry // [fs.dir.entry.cons] constructors and destructor // directory_entry::directory_entry() noexcept = default; // directory_entry::directory_entry(const directory_entry&) = default; // directory_entry::directory_entry(directory_entry&&) noexcept = default; #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE directory_entry::directory_entry(const filesystem::path& p) : _path(p) , _file_size(static_cast(-1)) #ifndef GHC_OS_WINDOWS , _hard_link_count(static_cast(-1)) #endif , _last_write_time(0) { refresh(); } #endif GHC_INLINE directory_entry::directory_entry(const filesystem::path& p, std::error_code& ec) : _path(p) , _file_size(static_cast(-1)) #ifndef GHC_OS_WINDOWS , _hard_link_count(static_cast(-1)) #endif , _last_write_time(0) { refresh(ec); } GHC_INLINE directory_entry::~directory_entry() {} // assignments: // directory_entry& directory_entry::operator=(const directory_entry&) = default; // directory_entry& directory_entry::operator=(directory_entry&&) noexcept = default; // [fs.dir.entry.mods] directory_entry modifiers #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE void directory_entry::assign(const filesystem::path& p) { _path = p; refresh(); } #endif GHC_INLINE void directory_entry::assign(const filesystem::path& p, std::error_code& ec) { _path = p; refresh(ec); } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE void directory_entry::replace_filename(const filesystem::path& p) { _path.replace_filename(p); refresh(); } #endif GHC_INLINE void directory_entry::replace_filename(const filesystem::path& p, std::error_code& ec) { _path.replace_filename(p); refresh(ec); } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE void directory_entry::refresh() { std::error_code ec; refresh(ec); if (ec && (_status.type() == file_type::none || _symlink_status.type() != file_type::symlink)) { throw filesystem_error(detail::systemErrorText(ec.value()), _path, ec); } } #endif GHC_INLINE void directory_entry::refresh(std::error_code& ec) noexcept { #ifdef GHC_OS_WINDOWS _status = detail::status_ex(_path, ec, &_symlink_status, &_file_size, nullptr, &_last_write_time); #else _status = detail::status_ex(_path, ec, &_symlink_status, &_file_size, &_hard_link_count, &_last_write_time); #endif } // [fs.dir.entry.obs] directory_entry observers GHC_INLINE const filesystem::path& directory_entry::path() const noexcept { return _path; } GHC_INLINE directory_entry::operator const filesystem::path&() const noexcept { return _path; } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE file_type directory_entry::status_file_type() const { return _status.type() != file_type::none ? _status.type() : filesystem::status(path()).type(); } #endif GHC_INLINE file_type directory_entry::status_file_type(std::error_code& ec) const noexcept { if (_status.type() != file_type::none) { ec.clear(); return _status.type(); } return filesystem::status(path(), ec).type(); } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE bool directory_entry::exists() const { return status_file_type() != file_type::not_found; } #endif GHC_INLINE bool directory_entry::exists(std::error_code& ec) const noexcept { return status_file_type(ec) != file_type::not_found; } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE bool directory_entry::is_block_file() const { return status_file_type() == file_type::block; } #endif GHC_INLINE bool directory_entry::is_block_file(std::error_code& ec) const noexcept { return status_file_type(ec) == file_type::block; } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE bool directory_entry::is_character_file() const { return status_file_type() == file_type::character; } #endif GHC_INLINE bool directory_entry::is_character_file(std::error_code& ec) const noexcept { return status_file_type(ec) == file_type::character; } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE bool directory_entry::is_directory() const { return status_file_type() == file_type::directory; } #endif GHC_INLINE bool directory_entry::is_directory(std::error_code& ec) const noexcept { return status_file_type(ec) == file_type::directory; } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE bool directory_entry::is_fifo() const { return status_file_type() == file_type::fifo; } #endif GHC_INLINE bool directory_entry::is_fifo(std::error_code& ec) const noexcept { return status_file_type(ec) == file_type::fifo; } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE bool directory_entry::is_other() const { auto ft = status_file_type(); return ft != file_type::none && ft != file_type::not_found && ft != file_type::regular && ft != file_type::directory && !is_symlink(); } #endif GHC_INLINE bool directory_entry::is_other(std::error_code& ec) const noexcept { auto ft = status_file_type(ec); bool other = ft != file_type::none && ft != file_type::not_found && ft != file_type::regular && ft != file_type::directory && !is_symlink(ec); return !ec && other; } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE bool directory_entry::is_regular_file() const { return status_file_type() == file_type::regular; } #endif GHC_INLINE bool directory_entry::is_regular_file(std::error_code& ec) const noexcept { return status_file_type(ec) == file_type::regular; } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE bool directory_entry::is_socket() const { return status_file_type() == file_type::socket; } #endif GHC_INLINE bool directory_entry::is_socket(std::error_code& ec) const noexcept { return status_file_type(ec) == file_type::socket; } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE bool directory_entry::is_symlink() const { return _symlink_status.type() != file_type::none ? _symlink_status.type() == file_type::symlink : filesystem::is_symlink(symlink_status()); } #endif GHC_INLINE bool directory_entry::is_symlink(std::error_code& ec) const noexcept { if (_symlink_status.type() != file_type::none) { ec.clear(); return _symlink_status.type() == file_type::symlink; } return filesystem::is_symlink(symlink_status(ec)); } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE uintmax_t directory_entry::file_size() const { if (_file_size != static_cast(-1)) { return _file_size; } return filesystem::file_size(path()); } #endif GHC_INLINE uintmax_t directory_entry::file_size(std::error_code& ec) const noexcept { if (_file_size != static_cast(-1)) { ec.clear(); return _file_size; } return filesystem::file_size(path(), ec); } #ifndef GHC_OS_WEB #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE uintmax_t directory_entry::hard_link_count() const { #ifndef GHC_OS_WINDOWS if (_hard_link_count != static_cast(-1)) { return _hard_link_count; } #endif return filesystem::hard_link_count(path()); } #endif GHC_INLINE uintmax_t directory_entry::hard_link_count(std::error_code& ec) const noexcept { #ifndef GHC_OS_WINDOWS if (_hard_link_count != static_cast(-1)) { ec.clear(); return _hard_link_count; } #endif return filesystem::hard_link_count(path(), ec); } #endif #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE file_time_type directory_entry::last_write_time() const { if (_last_write_time != 0) { return std::chrono::system_clock::from_time_t(_last_write_time); } return filesystem::last_write_time(path()); } #endif GHC_INLINE file_time_type directory_entry::last_write_time(std::error_code& ec) const noexcept { if (_last_write_time != 0) { ec.clear(); return std::chrono::system_clock::from_time_t(_last_write_time); } return filesystem::last_write_time(path(), ec); } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE file_status directory_entry::status() const { if (_status.type() != file_type::none && _status.permissions() != perms::unknown) { return _status; } return filesystem::status(path()); } #endif GHC_INLINE file_status directory_entry::status(std::error_code& ec) const noexcept { if (_status.type() != file_type::none && _status.permissions() != perms::unknown) { ec.clear(); return _status; } return filesystem::status(path(), ec); } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE file_status directory_entry::symlink_status() const { if (_symlink_status.type() != file_type::none && _symlink_status.permissions() != perms::unknown) { return _symlink_status; } return filesystem::symlink_status(path()); } #endif GHC_INLINE file_status directory_entry::symlink_status(std::error_code& ec) const noexcept { if (_symlink_status.type() != file_type::none && _symlink_status.permissions() != perms::unknown) { ec.clear(); return _symlink_status; } return filesystem::symlink_status(path(), ec); } #ifdef GHC_HAS_THREEWAY_COMP GHC_INLINE std::strong_ordering directory_entry::operator<=>(const directory_entry& rhs) const noexcept { return _path <=> rhs._path; } #endif GHC_INLINE bool directory_entry::operator<(const directory_entry& rhs) const noexcept { return _path < rhs._path; } GHC_INLINE bool directory_entry::operator==(const directory_entry& rhs) const noexcept { return _path == rhs._path; } GHC_INLINE bool directory_entry::operator!=(const directory_entry& rhs) const noexcept { return _path != rhs._path; } GHC_INLINE bool directory_entry::operator<=(const directory_entry& rhs) const noexcept { return _path <= rhs._path; } GHC_INLINE bool directory_entry::operator>(const directory_entry& rhs) const noexcept { return _path > rhs._path; } GHC_INLINE bool directory_entry::operator>=(const directory_entry& rhs) const noexcept { return _path >= rhs._path; } //----------------------------------------------------------------------------- // [fs.class.directory_iterator] class directory_iterator #ifdef GHC_OS_WINDOWS class directory_iterator::impl { public: impl(const path& p, directory_options options) : _base(p) , _options(options) , _dirHandle(INVALID_HANDLE_VALUE) { if (!_base.empty()) { ZeroMemory(&_findData, sizeof(WIN32_FIND_DATAW)); if ((_dirHandle = FindFirstFileW(GHC_NATIVEWP((_base / "*")), &_findData)) != INVALID_HANDLE_VALUE) { if (std::wstring(_findData.cFileName) == L"." || std::wstring(_findData.cFileName) == L"..") { increment(_ec); } else { _dir_entry._path = _base / std::wstring(_findData.cFileName); copyToDirEntry(_ec); } } else { auto error = ::GetLastError(); _base = filesystem::path(); if (error != ERROR_ACCESS_DENIED || (options & directory_options::skip_permission_denied) == directory_options::none) { _ec = detail::make_system_error(); } } } } impl(const impl& other) = delete; ~impl() { if (_dirHandle != INVALID_HANDLE_VALUE) { FindClose(_dirHandle); _dirHandle = INVALID_HANDLE_VALUE; } } void increment(std::error_code& ec) { if (_dirHandle != INVALID_HANDLE_VALUE) { do { if (FindNextFileW(_dirHandle, &_findData)) { _dir_entry._path = _base; #ifdef GHC_USE_WCHAR_T _dir_entry._path.append_name(_findData.cFileName); #else #ifdef GHC_RAISE_UNICODE_ERRORS try { _dir_entry._path.append_name(detail::toUtf8(_findData.cFileName).c_str()); } catch (filesystem_error& fe) { ec = fe.code(); return; } #else _dir_entry._path.append_name(detail::toUtf8(_findData.cFileName).c_str()); #endif #endif copyToDirEntry(ec); } else { auto err = ::GetLastError(); if (err != ERROR_NO_MORE_FILES) { _ec = ec = detail::make_system_error(err); } FindClose(_dirHandle); _dirHandle = INVALID_HANDLE_VALUE; _dir_entry._path.clear(); break; } } while (std::wstring(_findData.cFileName) == L"." || std::wstring(_findData.cFileName) == L".."); } else { ec = _ec; } } void copyToDirEntry(std::error_code& ec) { if (_findData.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) { _dir_entry._status = detail::status_ex(_dir_entry._path, ec, &_dir_entry._symlink_status, &_dir_entry._file_size, nullptr, &_dir_entry._last_write_time); } else { _dir_entry._status = detail::status_from_INFO(_dir_entry._path, &_findData, ec, &_dir_entry._file_size, &_dir_entry._last_write_time); _dir_entry._symlink_status = _dir_entry._status; } if (ec) { if (_dir_entry._status.type() != file_type::none && _dir_entry._symlink_status.type() != file_type::none) { ec.clear(); } else { _dir_entry._file_size = static_cast(-1); _dir_entry._last_write_time = 0; } } } path _base; directory_options _options; WIN32_FIND_DATAW _findData; HANDLE _dirHandle; directory_entry _dir_entry; std::error_code _ec; }; #else // POSIX implementation class directory_iterator::impl { public: impl(const path& path, directory_options options) : _base(path) , _options(options) , _dir(nullptr) , _entry(nullptr) { if (!path.empty()) { do { _dir = ::opendir(path.native().c_str()); } while(errno == EINTR && !_dir); if (!_dir) { auto error = errno; _base = filesystem::path(); if ((error != EACCES && error != EPERM) || (options & directory_options::skip_permission_denied) == directory_options::none) { _ec = detail::make_system_error(); } } else { increment(_ec); } } } impl(const impl& other) = delete; ~impl() { if (_dir) { ::closedir(_dir); } } void increment(std::error_code& ec) { if (_dir) { bool skip; do { skip = false; errno = 0; do { _entry = ::readdir(_dir); } while(errno == EINTR && !_entry); if (_entry) { _dir_entry._path = _base; _dir_entry._path.append_name(_entry->d_name); copyToDirEntry(); if (ec && (ec.value() == EACCES || ec.value() == EPERM) && (_options & directory_options::skip_permission_denied) == directory_options::skip_permission_denied) { ec.clear(); skip = true; } } else { ::closedir(_dir); _dir = nullptr; _dir_entry._path.clear(); if (errno && errno != EINTR) { ec = detail::make_system_error(); } break; } } while (skip || std::strcmp(_entry->d_name, ".") == 0 || std::strcmp(_entry->d_name, "..") == 0); } } void copyToDirEntry() { _dir_entry._symlink_status.permissions(perms::unknown); auto ft = detail::file_type_from_dirent(*_entry); _dir_entry._symlink_status.type(ft); if (ft != file_type::symlink) { _dir_entry._status = _dir_entry._symlink_status; } else { _dir_entry._status.type(file_type::none); _dir_entry._status.permissions(perms::unknown); } _dir_entry._file_size = static_cast(-1); _dir_entry._hard_link_count = static_cast(-1); _dir_entry._last_write_time = 0; } path _base; directory_options _options; DIR* _dir; struct ::dirent* _entry; directory_entry _dir_entry; std::error_code _ec; }; #endif // [fs.dir.itr.members] member functions GHC_INLINE directory_iterator::directory_iterator() noexcept : _impl(new impl(path(), directory_options::none)) { } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE directory_iterator::directory_iterator(const path& p) : _impl(new impl(p, directory_options::none)) { if (_impl->_ec) { throw filesystem_error(detail::systemErrorText(_impl->_ec.value()), p, _impl->_ec); } _impl->_ec.clear(); } GHC_INLINE directory_iterator::directory_iterator(const path& p, directory_options options) : _impl(new impl(p, options)) { if (_impl->_ec) { throw filesystem_error(detail::systemErrorText(_impl->_ec.value()), p, _impl->_ec); } } #endif GHC_INLINE directory_iterator::directory_iterator(const path& p, std::error_code& ec) noexcept : _impl(new impl(p, directory_options::none)) { if (_impl->_ec) { ec = _impl->_ec; } } GHC_INLINE directory_iterator::directory_iterator(const path& p, directory_options options, std::error_code& ec) noexcept : _impl(new impl(p, options)) { if (_impl->_ec) { ec = _impl->_ec; } } GHC_INLINE directory_iterator::directory_iterator(const directory_iterator& rhs) : _impl(rhs._impl) { } GHC_INLINE directory_iterator::directory_iterator(directory_iterator&& rhs) noexcept : _impl(std::move(rhs._impl)) { } GHC_INLINE directory_iterator::~directory_iterator() {} GHC_INLINE directory_iterator& directory_iterator::operator=(const directory_iterator& rhs) { _impl = rhs._impl; return *this; } GHC_INLINE directory_iterator& directory_iterator::operator=(directory_iterator&& rhs) noexcept { _impl = std::move(rhs._impl); return *this; } GHC_INLINE const directory_entry& directory_iterator::operator*() const { return _impl->_dir_entry; } GHC_INLINE const directory_entry* directory_iterator::operator->() const { return &_impl->_dir_entry; } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE directory_iterator& directory_iterator::operator++() { std::error_code ec; _impl->increment(ec); if (ec) { throw filesystem_error(detail::systemErrorText(ec.value()), _impl->_dir_entry._path, ec); } return *this; } #endif GHC_INLINE directory_iterator& directory_iterator::increment(std::error_code& ec) noexcept { _impl->increment(ec); return *this; } GHC_INLINE bool directory_iterator::operator==(const directory_iterator& rhs) const { return _impl->_dir_entry._path == rhs._impl->_dir_entry._path; } GHC_INLINE bool directory_iterator::operator!=(const directory_iterator& rhs) const { return _impl->_dir_entry._path != rhs._impl->_dir_entry._path; } // [fs.dir.itr.nonmembers] directory_iterator non-member functions GHC_INLINE directory_iterator begin(directory_iterator iter) noexcept { return iter; } GHC_INLINE directory_iterator end(const directory_iterator&) noexcept { return directory_iterator(); } //----------------------------------------------------------------------------- // [fs.class.rec.dir.itr] class recursive_directory_iterator GHC_INLINE recursive_directory_iterator::recursive_directory_iterator() noexcept : _impl(new recursive_directory_iterator_impl(directory_options::none, true)) { _impl->_dir_iter_stack.push(directory_iterator()); } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE recursive_directory_iterator::recursive_directory_iterator(const path& p) : _impl(new recursive_directory_iterator_impl(directory_options::none, true)) { _impl->_dir_iter_stack.push(directory_iterator(p)); } GHC_INLINE recursive_directory_iterator::recursive_directory_iterator(const path& p, directory_options options) : _impl(new recursive_directory_iterator_impl(options, true)) { _impl->_dir_iter_stack.push(directory_iterator(p, options)); } #endif GHC_INLINE recursive_directory_iterator::recursive_directory_iterator(const path& p, directory_options options, std::error_code& ec) noexcept : _impl(new recursive_directory_iterator_impl(options, true)) { _impl->_dir_iter_stack.push(directory_iterator(p, options, ec)); } GHC_INLINE recursive_directory_iterator::recursive_directory_iterator(const path& p, std::error_code& ec) noexcept : _impl(new recursive_directory_iterator_impl(directory_options::none, true)) { _impl->_dir_iter_stack.push(directory_iterator(p, ec)); } GHC_INLINE recursive_directory_iterator::recursive_directory_iterator(const recursive_directory_iterator& rhs) : _impl(rhs._impl) { } GHC_INLINE recursive_directory_iterator::recursive_directory_iterator(recursive_directory_iterator&& rhs) noexcept : _impl(std::move(rhs._impl)) { } GHC_INLINE recursive_directory_iterator::~recursive_directory_iterator() {} // [fs.rec.dir.itr.members] observers GHC_INLINE directory_options recursive_directory_iterator::options() const { return _impl->_options; } GHC_INLINE int recursive_directory_iterator::depth() const { return static_cast(_impl->_dir_iter_stack.size() - 1); } GHC_INLINE bool recursive_directory_iterator::recursion_pending() const { return _impl->_recursion_pending; } GHC_INLINE const directory_entry& recursive_directory_iterator::operator*() const { return *(_impl->_dir_iter_stack.top()); } GHC_INLINE const directory_entry* recursive_directory_iterator::operator->() const { return &(*(_impl->_dir_iter_stack.top())); } // [fs.rec.dir.itr.members] modifiers recursive_directory_iterator& GHC_INLINE recursive_directory_iterator& recursive_directory_iterator::operator=(const recursive_directory_iterator& rhs) { _impl = rhs._impl; return *this; } GHC_INLINE recursive_directory_iterator& recursive_directory_iterator::operator=(recursive_directory_iterator&& rhs) noexcept { _impl = std::move(rhs._impl); return *this; } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE recursive_directory_iterator& recursive_directory_iterator::operator++() { std::error_code ec; increment(ec); if (ec) { throw filesystem_error(detail::systemErrorText(ec.value()), _impl->_dir_iter_stack.empty() ? path() : _impl->_dir_iter_stack.top()->path(), ec); } return *this; } #endif GHC_INLINE recursive_directory_iterator& recursive_directory_iterator::increment(std::error_code& ec) noexcept { bool isSymLink = (*this)->is_symlink(ec); bool isDir = !ec && (*this)->is_directory(ec); if (isSymLink && detail::is_not_found_error(ec)) { ec.clear(); } if (!ec) { if (recursion_pending() && isDir && (!isSymLink || (options() & directory_options::follow_directory_symlink) != directory_options::none)) { _impl->_dir_iter_stack.push(directory_iterator((*this)->path(), _impl->_options, ec)); } else { _impl->_dir_iter_stack.top().increment(ec); } if (!ec) { while (depth() && _impl->_dir_iter_stack.top() == directory_iterator()) { _impl->_dir_iter_stack.pop(); _impl->_dir_iter_stack.top().increment(ec); } } else if (!_impl->_dir_iter_stack.empty()) { _impl->_dir_iter_stack.pop(); } _impl->_recursion_pending = true; } return *this; } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE void recursive_directory_iterator::pop() { std::error_code ec; pop(ec); if (ec) { throw filesystem_error(detail::systemErrorText(ec.value()), _impl->_dir_iter_stack.empty() ? path() : _impl->_dir_iter_stack.top()->path(), ec); } } #endif GHC_INLINE void recursive_directory_iterator::pop(std::error_code& ec) { if (depth() == 0) { *this = recursive_directory_iterator(); } else { do { _impl->_dir_iter_stack.pop(); _impl->_dir_iter_stack.top().increment(ec); } while (depth() && _impl->_dir_iter_stack.top() == directory_iterator()); } } GHC_INLINE void recursive_directory_iterator::disable_recursion_pending() { _impl->_recursion_pending = false; } // other members as required by [input.iterators] GHC_INLINE bool recursive_directory_iterator::operator==(const recursive_directory_iterator& rhs) const { return _impl->_dir_iter_stack.top() == rhs._impl->_dir_iter_stack.top(); } GHC_INLINE bool recursive_directory_iterator::operator!=(const recursive_directory_iterator& rhs) const { return _impl->_dir_iter_stack.top() != rhs._impl->_dir_iter_stack.top(); } // [fs.rec.dir.itr.nonmembers] directory_iterator non-member functions GHC_INLINE recursive_directory_iterator begin(recursive_directory_iterator iter) noexcept { return iter; } GHC_INLINE recursive_directory_iterator end(const recursive_directory_iterator&) noexcept { return recursive_directory_iterator(); } #endif // GHC_EXPAND_IMPL } // namespace filesystem } // namespace ghc // cleanup some macros #undef GHC_INLINE #undef GHC_EXPAND_IMPL #endif // GHC_FILESYSTEM_H openal-soft-1.24.2/common/intrusive_ptr.h000066400000000000000000000062011474041540300204060ustar00rootroot00000000000000#ifndef INTRUSIVE_PTR_H #define INTRUSIVE_PTR_H #include #include #include #include "atomic.h" #include "opthelpers.h" namespace al { template class intrusive_ref { std::atomic mRef{1u}; protected: ~intrusive_ref() = default; public: unsigned int add_ref() noexcept { return IncrementRef(mRef); } unsigned int dec_ref() noexcept { auto ref = DecrementRef(mRef); if(ref == 0) UNLIKELY delete static_cast(this); return ref; } /** * Release only if doing so would not bring the object to 0 references and * delete it. Returns false if the object could not be released. * * NOTE: The caller is responsible for handling a failed release, as it * means the object has no other references and needs to be be deleted * somehow. */ bool releaseIfNoDelete() noexcept { auto val = mRef.load(std::memory_order_acquire); while(val > 1 && !mRef.compare_exchange_strong(val, val-1, std::memory_order_acq_rel)) { /* val was updated with the current value on failure, so just try * again. */ } return val >= 2; } }; template /* NOLINTNEXTLINE(clazy-rule-of-three) False positive */ class intrusive_ptr { T *mPtr{nullptr}; public: intrusive_ptr() noexcept = default; intrusive_ptr(const intrusive_ptr &rhs) noexcept : mPtr{rhs.mPtr} { if(mPtr) mPtr->add_ref(); } intrusive_ptr(intrusive_ptr&& rhs) noexcept : mPtr{rhs.mPtr} { rhs.mPtr = nullptr; } intrusive_ptr(std::nullptr_t) noexcept { } /* NOLINT(google-explicit-constructor) */ explicit intrusive_ptr(T *ptr) noexcept : mPtr{ptr} { } ~intrusive_ptr() { if(mPtr) mPtr->dec_ref(); } /* NOLINTBEGIN(bugprone-unhandled-self-assignment) * Self-assignment is handled properly here. */ intrusive_ptr& operator=(const intrusive_ptr &rhs) noexcept { static_assert(noexcept(std::declval()->dec_ref()), "dec_ref must be noexcept"); if(rhs.mPtr) rhs.mPtr->add_ref(); if(mPtr) mPtr->dec_ref(); mPtr = rhs.mPtr; return *this; } /* NOLINTEND(bugprone-unhandled-self-assignment) */ intrusive_ptr& operator=(intrusive_ptr&& rhs) noexcept { if(&rhs != this) LIKELY { if(mPtr) mPtr->dec_ref(); mPtr = std::exchange(rhs.mPtr, nullptr); } return *this; } explicit operator bool() const noexcept { return mPtr != nullptr; } [[nodiscard]] auto operator*() const noexcept -> T& { return *mPtr; } [[nodiscard]] auto operator->() const noexcept -> T* { return mPtr; } [[nodiscard]] auto get() const noexcept -> T* { return mPtr; } void reset(T *ptr=nullptr) noexcept { if(mPtr) mPtr->dec_ref(); mPtr = ptr; } T* release() noexcept { return std::exchange(mPtr, nullptr); } void swap(intrusive_ptr &rhs) noexcept { std::swap(mPtr, rhs.mPtr); } void swap(intrusive_ptr&& rhs) noexcept { std::swap(mPtr, rhs.mPtr); } }; } // namespace al #endif /* INTRUSIVE_PTR_H */ openal-soft-1.24.2/common/opthelpers.h000066400000000000000000000064541474041540300176700ustar00rootroot00000000000000#ifndef OPTHELPERS_H #define OPTHELPERS_H #include #include #include #ifdef __has_builtin #define HAS_BUILTIN __has_builtin #else #define HAS_BUILTIN(x) (0) #endif #ifdef __has_cpp_attribute #define HAS_ATTRIBUTE __has_cpp_attribute #else #define HAS_ATTRIBUTE(x) (0) #endif #ifdef __GNUC__ #define force_inline [[gnu::always_inline]] inline #define NOINLINE [[gnu::noinline]] #elif defined(_MSC_VER) #define force_inline __forceinline #define NOINLINE __declspec(noinline) #else #define force_inline inline #define NOINLINE #endif #if defined(__MINGW32__) && defined(__i386__) /* 32-bit MinGW targets have a bug where __STDCPP_DEFAULT_NEW_ALIGNMENT__ * reports 16, despite the default operator new calling standard malloc which * only guarantees 8-byte alignment. As a result, structs that need and specify * 16-byte alignment only get 8-byte alignment. Explicitly specifying 32-byte * alignment forces the over-aligned operator new to be called, giving the * correct (if larger than necessary) alignment. * * Technically this bug affects 32-bit GCC more generally, but typically only * with fairly old glibc versions as newer versions do guarantee the 16-byte * alignment as specified. MinGW is reliant on msvcrt.dll's malloc however, * which can't be updated to give that guarantee. */ #define SIMDALIGN alignas(32) #else #define SIMDALIGN #endif /* Unlike the likely attribute, ASSUME requires the condition to be true or * else it invokes undefined behavior. It's essentially an assert without * actually checking the condition at run-time, allowing for stronger * optimizations than the likely attribute. */ #if HAS_BUILTIN(__builtin_assume) #define ASSUME __builtin_assume #elif defined(_MSC_VER) #define ASSUME __assume #elif __has_attribute(assume) #define ASSUME(x) [[assume(x)]] #elif HAS_BUILTIN(__builtin_unreachable) #define ASSUME(x) do { if(x) break; __builtin_unreachable(); } while(0) #else #define ASSUME(x) (static_cast(0)) #endif /* This shouldn't be needed since unknown attributes are ignored, but older * versions of GCC choke on the attribute syntax in certain situations. */ #if HAS_ATTRIBUTE(likely) #define LIKELY [[likely]] #define UNLIKELY [[unlikely]] #else #define LIKELY #define UNLIKELY #endif #if !defined(_WIN32) && HAS_ATTRIBUTE(gnu::visibility) #define DECL_HIDDEN [[gnu::visibility("hidden")]] #else #define DECL_HIDDEN #endif namespace al { template constexpr std::underlying_type_t to_underlying(T e) noexcept { return static_cast>(e); } [[noreturn]] inline void unreachable() { #if HAS_BUILTIN(__builtin_unreachable) __builtin_unreachable(); #else ASSUME(false); #endif } template force_inline constexpr auto assume_aligned(T *ptr) noexcept { #ifdef __cpp_lib_assume_aligned return std::assume_aligned(ptr); #elif HAS_BUILTIN(__builtin_assume_aligned) return static_cast(__builtin_assume_aligned(ptr, alignment)); #elif defined(_MSC_VER) constexpr std::size_t alignment_mask{(1<(ptr)&alignment_mask) == 0) return ptr; __assume(0); #elif defined(__ICC) __assume_aligned(ptr, alignment); return ptr; #else return ptr; #endif } } // namespace al #endif /* OPTHELPERS_H */ openal-soft-1.24.2/common/pffft.cpp000066400000000000000000002347261474041540300171500ustar00rootroot00000000000000//$ nobt /* Copyright (c) 2013 Julien Pommier ( pommier@modartt.com ) * Copyright (c) 2023 Christopher Robinson * * Based on original fortran 77 code from FFTPACKv4 from NETLIB * (http://www.netlib.org/fftpack), authored by Dr Paul Swarztrauber * of NCAR, in 1985. * * As confirmed by the NCAR fftpack software curators, the following * FFTPACKv5 license applies to FFTPACKv4 sources. My changes are * released under the same terms. * * FFTPACK license: * * http://www.cisl.ucar.edu/css/software/fftpack5/ftpk.html * * Copyright (c) 2004 the University Corporation for Atmospheric * Research ("UCAR"). All rights reserved. Developed by NCAR's * Computational and Information Systems Laboratory, UCAR, * www.cisl.ucar.edu. * * Redistribution and use of the Software in source and binary forms, * with or without modification, is permitted provided that the * following conditions are met: * * - Neither the names of NCAR's Computational and Information Systems * Laboratory, the University Corporation for Atmospheric Research, * nor the names of its sponsors or contributors may be used to * endorse or promote products derived from this Software without * specific prior written permission. * * - Redistributions of source code must retain the above copyright * notices, this list of conditions, and the disclaimer below. * * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions, and the disclaimer below in the * documentation and/or other materials provided with the * distribution. * * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE * SOFTWARE. * * * PFFFT : a Pretty Fast FFT. * * This file is largerly based on the original FFTPACK implementation, modified * in order to take advantage of SIMD instructions of modern CPUs. */ #include "pffft.h" #include #include #include #include #include #include #include #include #include #include #include "albit.h" #include "almalloc.h" #include "alnumbers.h" #include "alnumeric.h" #include "alspan.h" #include "fmt/core.h" #include "fmt/ranges.h" #include "opthelpers.h" using uint = unsigned int; namespace { #if defined(__GNUC__) || defined(_MSC_VER) #define RESTRICT __restrict #else #define RESTRICT #endif /* Vector support macros: the rest of the code is independent of * SSE/Altivec/NEON -- adding support for other platforms with 4-element * vectors should be limited to these macros */ /* Define PFFFT_SIMD_DISABLE if you want to use scalar code instead of SIMD code */ //#define PFFFT_SIMD_DISABLE #ifndef PFFFT_SIMD_DISABLE /* * Altivec support macros */ #if (defined(__ppc__) || defined(__ppc64__) || defined(__powerpc__) || defined(__powerpc64__)) \ && (defined(__VEC__) || defined(__ALTIVEC__)) #include using v4sf = vector float; constexpr uint SimdSize{4}; force_inline v4sf vzero() noexcept { return (vector float)vec_splat_u8(0); } force_inline v4sf vmul(v4sf a, v4sf b) noexcept { return vec_madd(a, b, vzero()); } force_inline v4sf vadd(v4sf a, v4sf b) noexcept { return vec_add(a, b); } force_inline v4sf vmadd(v4sf a, v4sf b, v4sf c) noexcept { return vec_madd(a, b, c); } force_inline v4sf vsub(v4sf a, v4sf b) noexcept { return vec_sub(a, b); } force_inline v4sf ld_ps1(float a) noexcept { return vec_splats(a); } force_inline v4sf vset4(float a, float b, float c, float d) noexcept { /* There a more efficient way to do this? */ alignas(16) std::array vals{{a, b, c, d}}; return vec_ld(0, vals.data()); } force_inline v4sf vinsert0(v4sf v, float a) noexcept { return vec_insert(a, v, 0); } force_inline float vextract0(v4sf v) noexcept { return vec_extract(v, 0); } force_inline void interleave2(v4sf in1, v4sf in2, v4sf &out1, v4sf &out2) noexcept { out1 = vec_mergeh(in1, in2); out2 = vec_mergel(in1, in2); } force_inline void uninterleave2(v4sf in1, v4sf in2, v4sf &out1, v4sf &out2) noexcept { out1 = vec_perm(in1, in2, (vector unsigned char){0,1,2,3,8,9,10,11,16,17,18,19,24,25,26,27}); out2 = vec_perm(in1, in2, (vector unsigned char){4,5,6,7,12,13,14,15,20,21,22,23,28,29,30,31}); } force_inline void vtranspose4(v4sf &x0, v4sf &x1, v4sf &x2, v4sf &x3) noexcept { v4sf y0{vec_mergeh(x0, x2)}; v4sf y1{vec_mergel(x0, x2)}; v4sf y2{vec_mergeh(x1, x3)}; v4sf y3{vec_mergel(x1, x3)}; x0 = vec_mergeh(y0, y2); x1 = vec_mergel(y0, y2); x2 = vec_mergeh(y1, y3); x3 = vec_mergel(y1, y3); } force_inline v4sf vswaphl(v4sf a, v4sf b) noexcept { return vec_perm(a,b, (vector unsigned char){16,17,18,19,20,21,22,23,8,9,10,11,12,13,14,15}); } /* * SSE1 support macros */ #elif defined(__x86_64__) || defined(__SSE__) || defined(_M_X64) || \ (defined(_M_IX86_FP) && _M_IX86_FP >= 1) #include using v4sf = __m128; /* 4 floats by simd vector -- this is pretty much hardcoded in the preprocess/ * finalize functions anyway so you will have to work if you want to enable AVX * with its 256-bit vectors. */ constexpr uint SimdSize{4}; force_inline v4sf vzero() noexcept { return _mm_setzero_ps(); } force_inline v4sf vmul(v4sf a, v4sf b) noexcept { return _mm_mul_ps(a, b); } force_inline v4sf vadd(v4sf a, v4sf b) noexcept { return _mm_add_ps(a, b); } force_inline v4sf vmadd(v4sf a, v4sf b, v4sf c) noexcept { return _mm_add_ps(_mm_mul_ps(a,b), c); } force_inline v4sf vsub(v4sf a, v4sf b) noexcept { return _mm_sub_ps(a, b); } force_inline v4sf ld_ps1(float a) noexcept { return _mm_set1_ps(a); } force_inline v4sf vset4(float a, float b, float c, float d) noexcept { return _mm_setr_ps(a, b, c, d); } force_inline v4sf vinsert0(const v4sf v, const float a) noexcept { return _mm_move_ss(v, _mm_set_ss(a)); } force_inline float vextract0(v4sf v) noexcept { return _mm_cvtss_f32(v); } force_inline void interleave2(const v4sf in1, const v4sf in2, v4sf &out1, v4sf &out2) noexcept { out1 = _mm_unpacklo_ps(in1, in2); out2 = _mm_unpackhi_ps(in1, in2); } force_inline void uninterleave2(v4sf in1, v4sf in2, v4sf &out1, v4sf &out2) noexcept { out1 = _mm_shuffle_ps(in1, in2, _MM_SHUFFLE(2,0,2,0)); out2 = _mm_shuffle_ps(in1, in2, _MM_SHUFFLE(3,1,3,1)); } force_inline void vtranspose4(v4sf &x0, v4sf &x1, v4sf &x2, v4sf &x3) noexcept { _MM_TRANSPOSE4_PS(x0, x1, x2, x3); } force_inline v4sf vswaphl(v4sf a, v4sf b) noexcept { return _mm_shuffle_ps(b, a, _MM_SHUFFLE(3,2,1,0)); } /* * ARM NEON support macros */ #elif defined(__ARM_NEON) || defined(__aarch64__) || defined(__arm64) || defined(_M_ARM64) #include using v4sf = float32x4_t; constexpr uint SimdSize{4}; force_inline v4sf vzero() noexcept { return vdupq_n_f32(0.0f); } force_inline v4sf vmul(v4sf a, v4sf b) noexcept { return vmulq_f32(a, b); } force_inline v4sf vadd(v4sf a, v4sf b) noexcept { return vaddq_f32(a, b); } force_inline v4sf vmadd(v4sf a, v4sf b, v4sf c) noexcept { return vmlaq_f32(c, a, b); } force_inline v4sf vsub(v4sf a, v4sf b) noexcept { return vsubq_f32(a, b); } force_inline v4sf ld_ps1(float a) noexcept { return vdupq_n_f32(a); } force_inline v4sf vset4(float a, float b, float c, float d) noexcept { float32x4_t ret{vmovq_n_f32(a)}; ret = vsetq_lane_f32(b, ret, 1); ret = vsetq_lane_f32(c, ret, 2); ret = vsetq_lane_f32(d, ret, 3); return ret; } force_inline v4sf vinsert0(v4sf v, float a) noexcept { return vsetq_lane_f32(a, v, 0); } force_inline float vextract0(v4sf v) noexcept { return vgetq_lane_f32(v, 0); } force_inline void interleave2(v4sf in1, v4sf in2, v4sf &out1, v4sf &out2) noexcept { float32x4x2_t tmp{vzipq_f32(in1, in2)}; out1 = tmp.val[0]; out2 = tmp.val[1]; } force_inline void uninterleave2(v4sf in1, v4sf in2, v4sf &out1, v4sf &out2) noexcept { float32x4x2_t tmp{vuzpq_f32(in1, in2)}; out1 = tmp.val[0]; out2 = tmp.val[1]; } force_inline void vtranspose4(v4sf &x0, v4sf &x1, v4sf &x2, v4sf &x3) noexcept { /* marginally faster version: * asm("vtrn.32 %q0, %q1;\n" * "vtrn.32 %q2, %q3\n * "vswp %f0, %e2\n * "vswp %f1, %e3" * : "+w"(x0), "+w"(x1), "+w"(x2), "+w"(x3)::); */ float32x4x2_t t0_{vzipq_f32(x0, x2)}; float32x4x2_t t1_{vzipq_f32(x1, x3)}; float32x4x2_t u0_{vzipq_f32(t0_.val[0], t1_.val[0])}; float32x4x2_t u1_{vzipq_f32(t0_.val[1], t1_.val[1])}; x0 = u0_.val[0]; x1 = u0_.val[1]; x2 = u1_.val[0]; x3 = u1_.val[1]; } force_inline v4sf vswaphl(v4sf a, v4sf b) noexcept { return vcombine_f32(vget_low_f32(b), vget_high_f32(a)); } /* * Generic GCC vector macros */ #elif defined(__GNUC__) using v4sf [[gnu::vector_size(16), gnu::aligned(16)]] = float; constexpr uint SimdSize{4}; force_inline constexpr v4sf vzero() noexcept { return v4sf{0.0f, 0.0f, 0.0f, 0.0f}; } force_inline constexpr v4sf vmul(v4sf a, v4sf b) noexcept { return a * b; } force_inline constexpr v4sf vadd(v4sf a, v4sf b) noexcept { return a + b; } force_inline constexpr v4sf vmadd(v4sf a, v4sf b, v4sf c) noexcept { return a*b + c; } force_inline constexpr v4sf vsub(v4sf a, v4sf b) noexcept { return a - b; } force_inline constexpr v4sf ld_ps1(float a) noexcept { return v4sf{a, a, a, a}; } force_inline constexpr v4sf vset4(float a, float b, float c, float d) noexcept { return v4sf{a, b, c, d}; } force_inline constexpr v4sf vinsert0(v4sf v, float a) noexcept { return v4sf{a, v[1], v[2], v[3]}; } force_inline float vextract0(v4sf v) noexcept { return v[0]; } force_inline v4sf unpacklo(v4sf a, v4sf b) noexcept { return v4sf{a[0], b[0], a[1], b[1]}; } force_inline v4sf unpackhi(v4sf a, v4sf b) noexcept { return v4sf{a[2], b[2], a[3], b[3]}; } force_inline void interleave2(v4sf in1, v4sf in2, v4sf &out1, v4sf &out2) noexcept { out1 = unpacklo(in1, in2); out2 = unpackhi(in1, in2); } force_inline void uninterleave2(v4sf in1, v4sf in2, v4sf &out1, v4sf &out2) noexcept { out1 = v4sf{in1[0], in1[2], in2[0], in2[2]}; out2 = v4sf{in1[1], in1[3], in2[1], in2[3]}; } force_inline void vtranspose4(v4sf &x0, v4sf &x1, v4sf &x2, v4sf &x3) noexcept { v4sf tmp0{unpacklo(x0, x1)}; v4sf tmp2{unpacklo(x2, x3)}; v4sf tmp1{unpackhi(x0, x1)}; v4sf tmp3{unpackhi(x2, x3)}; x0 = v4sf{tmp0[0], tmp0[1], tmp2[0], tmp2[1]}; x1 = v4sf{tmp0[2], tmp0[3], tmp2[2], tmp2[3]}; x2 = v4sf{tmp1[0], tmp1[1], tmp3[0], tmp3[1]}; x3 = v4sf{tmp1[2], tmp1[3], tmp3[2], tmp3[3]}; } force_inline v4sf vswaphl(v4sf a, v4sf b) noexcept { return v4sf{b[0], b[1], a[2], a[3]}; } #else #warning "building with simd disabled !\n"; #define PFFFT_SIMD_DISABLE // fallback to scalar code #endif #endif /* PFFFT_SIMD_DISABLE */ // fallback mode for situations where SIMD is not available, use scalar mode instead #ifdef PFFFT_SIMD_DISABLE using v4sf = float; constexpr uint SimdSize{1}; force_inline constexpr v4sf vmul(v4sf a, v4sf b) noexcept { return a * b; } force_inline constexpr v4sf vadd(v4sf a, v4sf b) noexcept { return a + b; } force_inline constexpr v4sf vmadd(v4sf a, v4sf b, v4sf c) noexcept { return a*b + c; } force_inline constexpr v4sf vsub(v4sf a, v4sf b) noexcept { return a - b; } force_inline constexpr v4sf ld_ps1(float a) noexcept { return a; } #else [[maybe_unused]] inline auto valigned(const float *ptr) noexcept -> bool { static constexpr uintptr_t alignmask{SimdSize*sizeof(float) - 1}; return (reinterpret_cast(ptr) & alignmask) == 0; } #endif // shortcuts for complex multiplications force_inline void vcplxmul(v4sf &ar, v4sf &ai, v4sf br, v4sf bi) noexcept { v4sf tmp{vmul(ar, bi)}; ar = vsub(vmul(ar, br), vmul(ai, bi)); ai = vmadd(ai, br, tmp); } force_inline void vcplxmulconj(v4sf &ar, v4sf &ai, v4sf br, v4sf bi) noexcept { v4sf tmp{vmul(ar, bi)}; ar = vmadd(ai, bi, vmul(ar, br)); ai = vsub(vmul(ai, br), tmp); } #if !defined(PFFFT_SIMD_DISABLE) inline void assertv4(const al::span v_f [[maybe_unused]], const float f0 [[maybe_unused]], const float f1 [[maybe_unused]], const float f2 [[maybe_unused]], const float f3 [[maybe_unused]]) { assert(v_f[0] == f0 && v_f[1] == f1 && v_f[2] == f2 && v_f[3] == f3); } template constexpr auto make_float_array(std::integer_sequence) { return std::array{static_cast(N)...}; } /* detect bugs with the vector support macros */ [[maybe_unused]] auto validate_pffft_simd() -> bool { using float4 = std::array; static constexpr auto f = make_float_array(std::make_index_sequence<16>{}); auto a0_v = vset4(f[ 0], f[ 1], f[ 2], f[ 3]); auto a1_v = vset4(f[ 4], f[ 5], f[ 6], f[ 7]); auto a2_v = vset4(f[ 8], f[ 9], f[10], f[11]); auto a3_v = vset4(f[12], f[13], f[14], f[15]); auto t_v = vzero(); auto t_f = al::bit_cast(t_v); fmt::println("VZERO={}", t_f); assertv4(t_f, 0, 0, 0, 0); t_v = vadd(a1_v, a2_v); t_f = al::bit_cast(t_v); fmt::println("VADD(4:7,8:11)={}", t_f); assertv4(t_f, 12, 14, 16, 18); t_v = vmul(a1_v, a2_v); t_f = al::bit_cast(t_v); fmt::println("VMUL(4:7,8:11)={}", t_f); assertv4(t_f, 32, 45, 60, 77); t_v = vmadd(a1_v, a2_v, a0_v); t_f = al::bit_cast(t_v); fmt::println("VMADD(4:7,8:11,0:3)={}", t_f); assertv4(t_f, 32, 46, 62, 80); auto u_v = v4sf{}; interleave2(a1_v, a2_v, t_v, u_v); t_f = al::bit_cast(t_v); auto u_f = al::bit_cast(u_v); fmt::println("INTERLEAVE2(4:7,8:11)={} {}", t_f, u_f); assertv4(t_f, 4, 8, 5, 9); assertv4(u_f, 6, 10, 7, 11); uninterleave2(a1_v, a2_v, t_v, u_v); t_f = al::bit_cast(t_v); u_f = al::bit_cast(u_v); fmt::println("UNINTERLEAVE2(4:7,8:11)={} {}", t_f, u_f); assertv4(t_f, 4, 6, 8, 10); assertv4(u_f, 5, 7, 9, 11); t_v = ld_ps1(f[15]); t_f = al::bit_cast(t_v); fmt::println("LD_PS1(15)={}", t_f); assertv4(t_f, 15, 15, 15, 15); t_v = vswaphl(a1_v, a2_v); t_f = al::bit_cast(t_v); fmt::println("VSWAPHL(4:7,8:11)={}", t_f); assertv4(t_f, 8, 9, 6, 7); vtranspose4(a0_v, a1_v, a2_v, a3_v); auto a0_f = al::bit_cast(a0_v); auto a1_f = al::bit_cast(a1_v); auto a2_f = al::bit_cast(a2_v); auto a3_f = al::bit_cast(a3_v); fmt::println("VTRANSPOSE4(0:3,4:7,8:11,12:15)={} {} {} {}", a0_f, a1_f, a2_f, a3_f); assertv4(a0_f, 0, 4, 8, 12); assertv4(a1_f, 1, 5, 9, 13); assertv4(a2_f, 2, 6, 10, 14); assertv4(a3_f, 3, 7, 11, 15); return true; } #endif //!PFFFT_SIMD_DISABLE /* SSE and co like 16-bytes aligned pointers */ /* with a 64-byte alignment, we are even aligned on L2 cache lines... */ constexpr auto V4sfAlignment = size_t(64); constexpr auto V4sfAlignVal = std::align_val_t(V4sfAlignment); /* NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic) * FIXME: Converting this from raw pointers to spans or something will probably * need significant work to maintain performance, given non-sequential range- * checked accesses and lack of 'restrict' to indicate non-aliased memory. At * least, some tests should be done to check the impact of using range-checked * spans here before blindly switching. */ /* passf2 and passb2 has been merged here, fsign = -1 for passf2, +1 for passb2 */ NOINLINE void passf2_ps(const size_t ido, const size_t l1, const v4sf *cc, v4sf *RESTRICT ch, const float *const wa1, const float fsign) { const size_t l1ido{l1*ido}; if(ido <= 2) { for(size_t k{0};k < l1ido;k += ido, ch += ido, cc += 2*ido) { ch[0] = vadd(cc[0], cc[ido+0]); ch[l1ido] = vsub(cc[0], cc[ido+0]); ch[1] = vadd(cc[1], cc[ido+1]); ch[l1ido + 1] = vsub(cc[1], cc[ido+1]); } } else { for(size_t k{0};k < l1ido;k += ido, ch += ido, cc += 2*ido) { for(size_t i{0};i < ido-1;i += 2) { v4sf tr2{vsub(cc[i+0], cc[i+ido+0])}; v4sf ti2{vsub(cc[i+1], cc[i+ido+1])}; v4sf wr{ld_ps1(wa1[i])}, wi{ld_ps1(wa1[i+1]*fsign)}; ch[i] = vadd(cc[i+0], cc[i+ido+0]); ch[i+1] = vadd(cc[i+1], cc[i+ido+1]); vcplxmul(tr2, ti2, wr, wi); ch[i+l1ido] = tr2; ch[i+l1ido+1] = ti2; } } } } /* passf3 and passb3 has been merged here, fsign = -1 for passf3, +1 for passb3 */ NOINLINE void passf3_ps(const size_t ido, const size_t l1, const v4sf *cc, v4sf *RESTRICT ch, const float *const wa1, const float fsign) { assert(ido > 2); const v4sf taur{ld_ps1(-0.5f)}; const v4sf taui{ld_ps1(0.866025403784439f*fsign)}; const size_t l1ido{l1*ido}; const auto wa2 = wa1 + ido; for(size_t k{0};k < l1ido;k += ido, cc += 3*ido, ch +=ido) { for(size_t i{0};i < ido-1;i += 2) { v4sf tr2{vadd(cc[i+ido], cc[i+2*ido])}; v4sf cr2{vmadd(taur, tr2, cc[i])}; ch[i] = vadd(tr2, cc[i]); v4sf ti2{vadd(cc[i+ido+1], cc[i+2*ido+1])}; v4sf ci2{vmadd(taur, ti2, cc[i+1])}; ch[i+1] = vadd(cc[i+1], ti2); v4sf cr3{vmul(taui, vsub(cc[i+ido], cc[i+2*ido]))}; v4sf ci3{vmul(taui, vsub(cc[i+ido+1], cc[i+2*ido+1]))}; v4sf dr2{vsub(cr2, ci3)}; v4sf dr3{vadd(cr2, ci3)}; v4sf di2{vadd(ci2, cr3)}; v4sf di3{vsub(ci2, cr3)}; float wr1{wa1[i]}, wi1{fsign*wa1[i+1]}, wr2{wa2[i]}, wi2{fsign*wa2[i+1]}; vcplxmul(dr2, di2, ld_ps1(wr1), ld_ps1(wi1)); ch[i+l1ido] = dr2; ch[i+l1ido + 1] = di2; vcplxmul(dr3, di3, ld_ps1(wr2), ld_ps1(wi2)); ch[i+2*l1ido] = dr3; ch[i+2*l1ido+1] = di3; } } } /* passf3 */ NOINLINE void passf4_ps(const size_t ido, const size_t l1, const v4sf *cc, v4sf *RESTRICT ch, const float *const wa1, const float fsign) { /* fsign == -1 for forward transform and +1 for backward transform */ const v4sf vsign{ld_ps1(fsign)}; const size_t l1ido{l1*ido}; if(ido == 2) { for(size_t k{0};k < l1ido;k += ido, ch += ido, cc += 4*ido) { v4sf tr1{vsub(cc[0], cc[2*ido + 0])}; v4sf tr2{vadd(cc[0], cc[2*ido + 0])}; v4sf ti1{vsub(cc[1], cc[2*ido + 1])}; v4sf ti2{vadd(cc[1], cc[2*ido + 1])}; v4sf ti4{vmul(vsub(cc[1*ido + 0], cc[3*ido + 0]), vsign)}; v4sf tr4{vmul(vsub(cc[3*ido + 1], cc[1*ido + 1]), vsign)}; v4sf tr3{vadd(cc[ido + 0], cc[3*ido + 0])}; v4sf ti3{vadd(cc[ido + 1], cc[3*ido + 1])}; ch[0*l1ido + 0] = vadd(tr2, tr3); ch[0*l1ido + 1] = vadd(ti2, ti3); ch[1*l1ido + 0] = vadd(tr1, tr4); ch[1*l1ido + 1] = vadd(ti1, ti4); ch[2*l1ido + 0] = vsub(tr2, tr3); ch[2*l1ido + 1] = vsub(ti2, ti3); ch[3*l1ido + 0] = vsub(tr1, tr4); ch[3*l1ido + 1] = vsub(ti1, ti4); } } else { const auto wa2 = wa1 + ido; const auto wa3 = wa2 + ido; for(size_t k{0};k < l1ido;k += ido, ch+=ido, cc += 4*ido) { for(size_t i{0};i < ido-1;i+=2) { v4sf tr1{vsub(cc[i + 0], cc[i + 2*ido + 0])}; v4sf tr2{vadd(cc[i + 0], cc[i + 2*ido + 0])}; v4sf ti1{vsub(cc[i + 1], cc[i + 2*ido + 1])}; v4sf ti2{vadd(cc[i + 1], cc[i + 2*ido + 1])}; v4sf tr4{vmul(vsub(cc[i + 3*ido + 1], cc[i + 1*ido + 1]), vsign)}; v4sf ti4{vmul(vsub(cc[i + 1*ido + 0], cc[i + 3*ido + 0]), vsign)}; v4sf tr3{vadd(cc[i + ido + 0], cc[i + 3*ido + 0])}; v4sf ti3{vadd(cc[i + ido + 1], cc[i + 3*ido + 1])}; ch[i] = vadd(tr2, tr3); v4sf cr3{vsub(tr2, tr3)}; ch[i + 1] = vadd(ti2, ti3); v4sf ci3{vsub(ti2, ti3)}; v4sf cr2{vadd(tr1, tr4)}; v4sf cr4{vsub(tr1, tr4)}; v4sf ci2{vadd(ti1, ti4)}; v4sf ci4{vsub(ti1, ti4)}; float wr1{wa1[i]}, wi1{fsign*wa1[i+1]}; vcplxmul(cr2, ci2, ld_ps1(wr1), ld_ps1(wi1)); float wr2{wa2[i]}, wi2{fsign*wa2[i+1]}; ch[i + l1ido] = cr2; ch[i + l1ido + 1] = ci2; vcplxmul(cr3, ci3, ld_ps1(wr2), ld_ps1(wi2)); float wr3{wa3[i]}, wi3{fsign*wa3[i+1]}; ch[i + 2*l1ido] = cr3; ch[i + 2*l1ido + 1] = ci3; vcplxmul(cr4, ci4, ld_ps1(wr3), ld_ps1(wi3)); ch[i + 3*l1ido] = cr4; ch[i + 3*l1ido + 1] = ci4; } } } } /* passf4 */ /* * passf5 and passb5 has been merged here, fsign = -1 for passf5, +1 for passb5 */ NOINLINE void passf5_ps(const size_t ido, const size_t l1, const v4sf *cc, v4sf *RESTRICT ch, const float *const wa1, const float fsign) { const v4sf tr11{ld_ps1(0.309016994374947f)}; const v4sf tr12{ld_ps1(-0.809016994374947f)}; const v4sf ti11{ld_ps1(0.951056516295154f*fsign)}; const v4sf ti12{ld_ps1(0.587785252292473f*fsign)}; auto cc_ref = [&cc,ido](size_t a_1, size_t a_2) noexcept -> auto& { return cc[(a_2-1)*ido + a_1 + 1]; }; auto ch_ref = [&ch,ido,l1](size_t a_1, size_t a_3) noexcept -> auto& { return ch[(a_3-1)*l1*ido + a_1 + 1]; }; assert(ido > 2); const auto wa2 = wa1 + ido; const auto wa3 = wa2 + ido; const auto wa4 = wa3 + ido; for(size_t k{0};k < l1;++k, cc += 5*ido, ch += ido) { for(size_t i{0};i < ido-1;i += 2) { v4sf ti5{vsub(cc_ref(i , 2), cc_ref(i , 5))}; v4sf ti2{vadd(cc_ref(i , 2), cc_ref(i , 5))}; v4sf ti4{vsub(cc_ref(i , 3), cc_ref(i , 4))}; v4sf ti3{vadd(cc_ref(i , 3), cc_ref(i , 4))}; v4sf tr5{vsub(cc_ref(i-1, 2), cc_ref(i-1, 5))}; v4sf tr2{vadd(cc_ref(i-1, 2), cc_ref(i-1, 5))}; v4sf tr4{vsub(cc_ref(i-1, 3), cc_ref(i-1, 4))}; v4sf tr3{vadd(cc_ref(i-1, 3), cc_ref(i-1, 4))}; ch_ref(i-1, 1) = vadd(cc_ref(i-1, 1), vadd(tr2, tr3)); ch_ref(i , 1) = vadd(cc_ref(i , 1), vadd(ti2, ti3)); v4sf cr2{vadd(cc_ref(i-1, 1), vmadd(tr11, tr2, vmul(tr12, tr3)))}; v4sf ci2{vadd(cc_ref(i , 1), vmadd(tr11, ti2, vmul(tr12, ti3)))}; v4sf cr3{vadd(cc_ref(i-1, 1), vmadd(tr12, tr2, vmul(tr11, tr3)))}; v4sf ci3{vadd(cc_ref(i , 1), vmadd(tr12, ti2, vmul(tr11, ti3)))}; v4sf cr5{vmadd(ti11, tr5, vmul(ti12, tr4))}; v4sf ci5{vmadd(ti11, ti5, vmul(ti12, ti4))}; v4sf cr4{vsub(vmul(ti12, tr5), vmul(ti11, tr4))}; v4sf ci4{vsub(vmul(ti12, ti5), vmul(ti11, ti4))}; v4sf dr3{vsub(cr3, ci4)}; v4sf dr4{vadd(cr3, ci4)}; v4sf di3{vadd(ci3, cr4)}; v4sf di4{vsub(ci3, cr4)}; v4sf dr5{vadd(cr2, ci5)}; v4sf dr2{vsub(cr2, ci5)}; v4sf di5{vsub(ci2, cr5)}; v4sf di2{vadd(ci2, cr5)}; float wr1{wa1[i]}, wi1{fsign*wa1[i+1]}, wr2{wa2[i]}, wi2{fsign*wa2[i+1]}; float wr3{wa3[i]}, wi3{fsign*wa3[i+1]}, wr4{wa4[i]}, wi4{fsign*wa4[i+1]}; vcplxmul(dr2, di2, ld_ps1(wr1), ld_ps1(wi1)); ch_ref(i - 1, 2) = dr2; ch_ref(i, 2) = di2; vcplxmul(dr3, di3, ld_ps1(wr2), ld_ps1(wi2)); ch_ref(i - 1, 3) = dr3; ch_ref(i, 3) = di3; vcplxmul(dr4, di4, ld_ps1(wr3), ld_ps1(wi3)); ch_ref(i - 1, 4) = dr4; ch_ref(i, 4) = di4; vcplxmul(dr5, di5, ld_ps1(wr4), ld_ps1(wi4)); ch_ref(i - 1, 5) = dr5; ch_ref(i, 5) = di5; } } } NOINLINE void radf2_ps(const size_t ido, const size_t l1, const v4sf *RESTRICT cc, v4sf *RESTRICT ch, const float *const wa1) { const size_t l1ido{l1*ido}; for(size_t k{0};k < l1ido;k += ido) { v4sf a{cc[k]}, b{cc[k + l1ido]}; ch[2*k] = vadd(a, b); ch[2*(k+ido)-1] = vsub(a, b); } if(ido < 2) return; if(ido != 2) { for(size_t k{0};k < l1ido;k += ido) { for(size_t i{2};i < ido;i += 2) { v4sf tr2{cc[i - 1 + k + l1ido]}, ti2{cc[i + k + l1ido]}; v4sf br{cc[i - 1 + k]}, bi{cc[i + k]}; vcplxmulconj(tr2, ti2, ld_ps1(wa1[i - 2]), ld_ps1(wa1[i - 1])); ch[i + 2*k] = vadd(bi, ti2); ch[2*(k+ido) - i] = vsub(ti2, bi); ch[i - 1 + 2*k] = vadd(br, tr2); ch[2*(k+ido) - i -1] = vsub(br, tr2); } } if((ido&1) == 1) return; } const v4sf minus_one{ld_ps1(-1.0f)}; for(size_t k{0};k < l1ido;k += ido) { ch[2*k + ido] = vmul(minus_one, cc[ido-1 + k + l1ido]); ch[2*k + ido-1] = cc[k + ido-1]; } } /* radf2 */ NOINLINE void radb2_ps(const size_t ido, const size_t l1, const v4sf *cc, v4sf *RESTRICT ch, const float *const wa1) { const size_t l1ido{l1*ido}; for(size_t k{0};k < l1ido;k += ido) { v4sf a{cc[2*k]}; v4sf b{cc[2*(k+ido) - 1]}; ch[k] = vadd(a, b); ch[k + l1ido] = vsub(a, b); } if(ido < 2) return; if(ido != 2) { for(size_t k{0};k < l1ido;k += ido) { for(size_t i{2};i < ido;i += 2) { v4sf a{cc[i-1 + 2*k]}; v4sf b{cc[2*(k + ido) - i - 1]}; v4sf c{cc[i+0 + 2*k]}; v4sf d{cc[2*(k + ido) - i + 0]}; ch[i-1 + k] = vadd(a, b); v4sf tr2{vsub(a, b)}; ch[i+0 + k] = vsub(c, d); v4sf ti2{vadd(c, d)}; vcplxmul(tr2, ti2, ld_ps1(wa1[i - 2]), ld_ps1(wa1[i - 1])); ch[i-1 + k + l1ido] = tr2; ch[i+0 + k + l1ido] = ti2; } } if((ido&1) == 1) return; } const v4sf minus_two{ld_ps1(-2.0f)}; for(size_t k{0};k < l1ido;k += ido) { v4sf a{cc[2*k + ido-1]}; v4sf b{cc[2*k + ido]}; ch[k + ido-1] = vadd(a,a); ch[k + ido-1 + l1ido] = vmul(minus_two, b); } } /* radb2 */ void radf3_ps(const size_t ido, const size_t l1, const v4sf *RESTRICT cc, v4sf *RESTRICT ch, const float *const wa1) { const v4sf taur{ld_ps1(-0.5f)}; const v4sf taui{ld_ps1(0.866025403784439f)}; for(size_t k{0};k < l1;++k) { v4sf cr2{vadd(cc[(k + l1)*ido], cc[(k + 2*l1)*ido])}; ch[ (3*k )*ido] = vadd(cc[k*ido], cr2); ch[ (3*k + 2)*ido] = vmul(taui, vsub(cc[(k + l1*2)*ido], cc[(k + l1)*ido])); ch[ido-1 + (3*k + 1)*ido] = vmadd(taur, cr2, cc[k*ido]); } if(ido == 1) return; const auto wa2 = wa1 + ido; for(size_t k{0};k < l1;++k) { for(size_t i{2};i < ido;i += 2) { const size_t ic{ido - i}; v4sf wr1{ld_ps1(wa1[i - 2])}; v4sf wi1{ld_ps1(wa1[i - 1])}; v4sf dr2{cc[i - 1 + (k + l1)*ido]}; v4sf di2{cc[i + (k + l1)*ido]}; vcplxmulconj(dr2, di2, wr1, wi1); v4sf wr2{ld_ps1(wa2[i - 2])}; v4sf wi2{ld_ps1(wa2[i - 1])}; v4sf dr3{cc[i - 1 + (k + l1*2)*ido]}; v4sf di3{cc[i + (k + l1*2)*ido]}; vcplxmulconj(dr3, di3, wr2, wi2); v4sf cr2{vadd(dr2, dr3)}; v4sf ci2{vadd(di2, di3)}; ch[i - 1 + 3*k*ido] = vadd(cc[i - 1 + k*ido], cr2); ch[i + 3*k*ido] = vadd(cc[i + k*ido], ci2); v4sf tr2{vmadd(taur, cr2, cc[i - 1 + k*ido])}; v4sf ti2{vmadd(taur, ci2, cc[i + k*ido])}; v4sf tr3{vmul(taui, vsub(di2, di3))}; v4sf ti3{vmul(taui, vsub(dr3, dr2))}; ch[i - 1 + (3*k + 2)*ido] = vadd(tr2, tr3); ch[ic - 1 + (3*k + 1)*ido] = vsub(tr2, tr3); ch[i + (3*k + 2)*ido] = vadd(ti2, ti3); ch[ic + (3*k + 1)*ido] = vsub(ti3, ti2); } } } /* radf3 */ void radb3_ps(const size_t ido, const size_t l1, const v4sf *RESTRICT cc, v4sf *RESTRICT ch, const float *const wa1) { static constexpr float taur{-0.5f}; static constexpr float taui{0.866025403784439f}; static constexpr float taui_2{taui*2.0f}; const v4sf vtaur{ld_ps1(taur)}; const v4sf vtaui_2{ld_ps1(taui_2)}; for(size_t k{0};k < l1;++k) { v4sf tr2 = cc[ido-1 + (3*k + 1)*ido]; tr2 = vadd(tr2,tr2); v4sf cr2 = vmadd(vtaur, tr2, cc[3*k*ido]); ch[k*ido] = vadd(cc[3*k*ido], tr2); v4sf ci3 = vmul(vtaui_2, cc[(3*k + 2)*ido]); ch[(k + l1)*ido] = vsub(cr2, ci3); ch[(k + 2*l1)*ido] = vadd(cr2, ci3); } if(ido == 1) return; const auto wa2 = wa1 + ido; const v4sf vtaui{ld_ps1(taui)}; for(size_t k{0};k < l1;++k) { for(size_t i{2};i < ido;i += 2) { const size_t ic{ido - i}; v4sf tr2{vadd(cc[i - 1 + (3*k + 2)*ido], cc[ic - 1 + (3*k + 1)*ido])}; v4sf cr2{vmadd(vtaur, tr2, cc[i - 1 + 3*k*ido])}; ch[i - 1 + k*ido] = vadd(cc[i - 1 + 3*k*ido], tr2); v4sf ti2{vsub(cc[i + (3*k + 2)*ido], cc[ic + (3*k + 1)*ido])}; v4sf ci2{vmadd(vtaur, ti2, cc[i + 3*k*ido])}; ch[i + k*ido] = vadd(cc[i + 3*k*ido], ti2); v4sf cr3{vmul(vtaui, vsub(cc[i - 1 + (3*k + 2)*ido], cc[ic - 1 + (3*k + 1)*ido]))}; v4sf ci3{vmul(vtaui, vadd(cc[i + (3*k + 2)*ido], cc[ic + (3*k + 1)*ido]))}; v4sf dr2{vsub(cr2, ci3)}; v4sf dr3{vadd(cr2, ci3)}; v4sf di2{vadd(ci2, cr3)}; v4sf di3{vsub(ci2, cr3)}; vcplxmul(dr2, di2, ld_ps1(wa1[i-2]), ld_ps1(wa1[i-1])); ch[i - 1 + (k + l1)*ido] = dr2; ch[i + (k + l1)*ido] = di2; vcplxmul(dr3, di3, ld_ps1(wa2[i-2]), ld_ps1(wa2[i-1])); ch[i - 1 + (k + 2*l1)*ido] = dr3; ch[i + (k + 2*l1)*ido] = di3; } } } /* radb3 */ NOINLINE void radf4_ps(const size_t ido, const size_t l1, const v4sf *RESTRICT cc, v4sf *RESTRICT ch, const float *const wa1) { const size_t l1ido{l1*ido}; { const v4sf *RESTRICT cc_{cc}, *RESTRICT cc_end{cc + l1ido}; v4sf *RESTRICT ch_{ch}; while(cc != cc_end) { // this loop represents between 25% and 40% of total radf4_ps cost ! v4sf a0{cc[0]}, a1{cc[l1ido]}; v4sf a2{cc[2*l1ido]}, a3{cc[3*l1ido]}; v4sf tr1{vadd(a1, a3)}; v4sf tr2{vadd(a0, a2)}; ch[2*ido-1] = vsub(a0, a2); ch[2*ido ] = vsub(a3, a1); ch[0 ] = vadd(tr1, tr2); ch[4*ido-1] = vsub(tr2, tr1); cc += ido; ch += 4*ido; } cc = cc_; ch = ch_; } if(ido < 2) return; if(ido != 2) { const auto wa2 = wa1 + ido; const auto wa3 = wa2 + ido; for(size_t k{0};k < l1ido;k += ido) { const v4sf *RESTRICT pc{cc + 1 + k}; for(size_t i{2};i < ido;i += 2, pc += 2) { const size_t ic{ido - i}; v4sf cr2{pc[1*l1ido+0]}; v4sf ci2{pc[1*l1ido+1]}; v4sf wr{ld_ps1(wa1[i - 2])}; v4sf wi{ld_ps1(wa1[i - 1])}; vcplxmulconj(cr2,ci2,wr,wi); v4sf cr3{pc[2*l1ido+0]}; v4sf ci3{pc[2*l1ido+1]}; wr = ld_ps1(wa2[i-2]); wi = ld_ps1(wa2[i-1]); vcplxmulconj(cr3, ci3, wr, wi); v4sf cr4{pc[3*l1ido]}; v4sf ci4{pc[3*l1ido+1]}; wr = ld_ps1(wa3[i-2]); wi = ld_ps1(wa3[i-1]); vcplxmulconj(cr4, ci4, wr, wi); /* at this point, on SSE, five of "cr2 cr3 cr4 ci2 ci3 ci4" should be loaded in registers */ v4sf tr1{vadd(cr2,cr4)}; v4sf tr4{vsub(cr4,cr2)}; v4sf tr2{vadd(pc[0],cr3)}; v4sf tr3{vsub(pc[0],cr3)}; ch[i - 1 + 4*k ] = vadd(tr2,tr1); ch[ic - 1 + 4*k + 3*ido] = vsub(tr2,tr1); // at this point tr1 and tr2 can be disposed v4sf ti1{vadd(ci2,ci4)}; v4sf ti4{vsub(ci2,ci4)}; ch[i - 1 + 4*k + 2*ido] = vadd(tr3,ti4); ch[ic - 1 + 4*k + 1*ido] = vsub(tr3,ti4); // dispose tr3, ti4 v4sf ti2{vadd(pc[1],ci3)}; v4sf ti3{vsub(pc[1],ci3)}; ch[i + 4*k ] = vadd(ti1, ti2); ch[ic + 4*k + 3*ido] = vsub(ti1, ti2); ch[i + 4*k + 2*ido] = vadd(tr4, ti3); ch[ic + 4*k + 1*ido] = vsub(tr4, ti3); } } if((ido&1) == 1) return; } const v4sf minus_hsqt2{ld_ps1(al::numbers::sqrt2_v * -0.5f)}; for(size_t k{0};k < l1ido;k += ido) { v4sf a{cc[ido-1 + k + l1ido]}, b{cc[ido-1 + k + 3*l1ido]}; v4sf c{cc[ido-1 + k]}, d{cc[ido-1 + k + 2*l1ido]}; v4sf ti1{vmul(minus_hsqt2, vadd(b, a))}; v4sf tr1{vmul(minus_hsqt2, vsub(b, a))}; ch[ido-1 + 4*k ] = vadd(c, tr1); ch[ido-1 + 4*k + 2*ido] = vsub(c, tr1); ch[ 4*k + 1*ido] = vsub(ti1, d); ch[ 4*k + 3*ido] = vadd(ti1, d); } } /* radf4 */ NOINLINE void radb4_ps(const size_t ido, const size_t l1, const v4sf *RESTRICT cc, v4sf *RESTRICT ch, const float *const wa1) { const v4sf two{ld_ps1(2.0f)}; const size_t l1ido{l1*ido}; { const v4sf *RESTRICT cc_{cc}, *RESTRICT ch_end{ch + l1ido}; v4sf *ch_{ch}; while(ch != ch_end) { v4sf a{cc[0]}, b{cc[4*ido-1]}; v4sf c{cc[2*ido]}, d{cc[2*ido-1]}; v4sf tr3{vmul(two,d)}; v4sf tr2{vadd(a,b)}; v4sf tr1{vsub(a,b)}; v4sf tr4{vmul(two,c)}; ch[0*l1ido] = vadd(tr2, tr3); ch[2*l1ido] = vsub(tr2, tr3); ch[1*l1ido] = vsub(tr1, tr4); ch[3*l1ido] = vadd(tr1, tr4); cc += 4*ido; ch += ido; } cc = cc_; ch = ch_; } if(ido < 2) return; if(ido != 2) { const auto wa2 = wa1 + ido; const auto wa3 = wa2 + ido; for(size_t k{0};k < l1ido;k += ido) { const v4sf *RESTRICT pc{cc - 1 + 4*k}; v4sf *RESTRICT ph{ch + k + 1}; for(size_t i{2};i < ido;i += 2) { v4sf tr1{vsub(pc[ i], pc[4*ido - i])}; v4sf tr2{vadd(pc[ i], pc[4*ido - i])}; v4sf ti4{vsub(pc[2*ido + i], pc[2*ido - i])}; v4sf tr3{vadd(pc[2*ido + i], pc[2*ido - i])}; ph[0] = vadd(tr2, tr3); v4sf cr3{vsub(tr2, tr3)}; v4sf ti3{vsub(pc[2*ido + i + 1], pc[2*ido - i + 1])}; v4sf tr4{vadd(pc[2*ido + i + 1], pc[2*ido - i + 1])}; v4sf cr2{vsub(tr1, tr4)}; v4sf cr4{vadd(tr1, tr4)}; v4sf ti1{vadd(pc[i + 1], pc[4*ido - i + 1])}; v4sf ti2{vsub(pc[i + 1], pc[4*ido - i + 1])}; ph[1] = vadd(ti2, ti3); ph += l1ido; v4sf ci3{vsub(ti2, ti3)}; v4sf ci2{vadd(ti1, ti4)}; v4sf ci4{vsub(ti1, ti4)}; vcplxmul(cr2, ci2, ld_ps1(wa1[i-2]), ld_ps1(wa1[i-1])); ph[0] = cr2; ph[1] = ci2; ph += l1ido; vcplxmul(cr3, ci3, ld_ps1(wa2[i-2]), ld_ps1(wa2[i-1])); ph[0] = cr3; ph[1] = ci3; ph += l1ido; vcplxmul(cr4, ci4, ld_ps1(wa3[i-2]), ld_ps1(wa3[i-1])); ph[0] = cr4; ph[1] = ci4; ph = ph - 3*l1ido + 2; } } if((ido&1) == 1) return; } const v4sf minus_sqrt2{ld_ps1(-1.414213562373095f)}; for(size_t k{0};k < l1ido;k += ido) { const size_t i0{4*k + ido}; v4sf c{cc[i0-1]}, d{cc[i0 + 2*ido-1]}; v4sf a{cc[i0+0]}, b{cc[i0 + 2*ido+0]}; v4sf tr1{vsub(c,d)}; v4sf tr2{vadd(c,d)}; v4sf ti1{vadd(b,a)}; v4sf ti2{vsub(b,a)}; ch[ido-1 + k + 0*l1ido] = vadd(tr2,tr2); ch[ido-1 + k + 1*l1ido] = vmul(minus_sqrt2, vsub(ti1, tr1)); ch[ido-1 + k + 2*l1ido] = vadd(ti2, ti2); ch[ido-1 + k + 3*l1ido] = vmul(minus_sqrt2, vadd(ti1, tr1)); } } /* radb4 */ void radf5_ps(const size_t ido, const size_t l1, const v4sf *RESTRICT cc, v4sf *RESTRICT ch, const float *const wa1) { const v4sf tr11{ld_ps1(0.309016994374947f)}; const v4sf ti11{ld_ps1(0.951056516295154f)}; const v4sf tr12{ld_ps1(-0.809016994374947f)}; const v4sf ti12{ld_ps1(0.587785252292473f)}; auto cc_ref = [&cc,l1,ido](size_t a_1, size_t a_2, size_t a_3) noexcept -> auto& { return cc[(a_3*l1 + a_2)*ido + a_1]; }; auto ch_ref = [&ch,ido](size_t a_1, size_t a_2, size_t a_3) noexcept -> auto& { return ch[(a_3*5 + a_2)*ido + a_1]; }; /* Parameter adjustments */ ch -= 1 + ido * 6; cc -= 1 + ido * (1 + l1); const auto wa2 = wa1 + ido; const auto wa3 = wa2 + ido; const auto wa4 = wa3 + ido; /* Function Body */ for(size_t k{1};k <= l1;++k) { v4sf cr2{vadd(cc_ref(1, k, 5), cc_ref(1, k, 2))}; v4sf ci5{vsub(cc_ref(1, k, 5), cc_ref(1, k, 2))}; v4sf cr3{vadd(cc_ref(1, k, 4), cc_ref(1, k, 3))}; v4sf ci4{vsub(cc_ref(1, k, 4), cc_ref(1, k, 3))}; ch_ref(1, 1, k) = vadd(cc_ref(1, k, 1), vadd(cr2, cr3)); ch_ref(ido, 2, k) = vadd(cc_ref(1, k, 1), vmadd(tr11, cr2, vmul(tr12, cr3))); ch_ref(1, 3, k) = vmadd(ti11, ci5, vmul(ti12, ci4)); ch_ref(ido, 4, k) = vadd(cc_ref(1, k, 1), vmadd(tr12, cr2, vmul(tr11, cr3))); ch_ref(1, 5, k) = vsub(vmul(ti12, ci5), vmul(ti11, ci4)); //fmt::println("pffft: radf5, k={} ch_ref={:f}, ci4={:f}", k, ch_ref(1, 5, k), ci4); } if(ido == 1) return; const size_t idp2{ido + 2}; for(size_t k{1};k <= l1;++k) { for(size_t i{3};i <= ido;i += 2) { const size_t ic{idp2 - i}; v4sf dr2{ld_ps1(wa1[i-3])}; v4sf di2{ld_ps1(wa1[i-2])}; v4sf dr3{ld_ps1(wa2[i-3])}; v4sf di3{ld_ps1(wa2[i-2])}; v4sf dr4{ld_ps1(wa3[i-3])}; v4sf di4{ld_ps1(wa3[i-2])}; v4sf dr5{ld_ps1(wa4[i-3])}; v4sf di5{ld_ps1(wa4[i-2])}; vcplxmulconj(dr2, di2, cc_ref(i-1, k, 2), cc_ref(i, k, 2)); vcplxmulconj(dr3, di3, cc_ref(i-1, k, 3), cc_ref(i, k, 3)); vcplxmulconj(dr4, di4, cc_ref(i-1, k, 4), cc_ref(i, k, 4)); vcplxmulconj(dr5, di5, cc_ref(i-1, k, 5), cc_ref(i, k, 5)); v4sf cr2{vadd(dr2, dr5)}; v4sf ci5{vsub(dr5, dr2)}; v4sf cr5{vsub(di2, di5)}; v4sf ci2{vadd(di2, di5)}; v4sf cr3{vadd(dr3, dr4)}; v4sf ci4{vsub(dr4, dr3)}; v4sf cr4{vsub(di3, di4)}; v4sf ci3{vadd(di3, di4)}; ch_ref(i - 1, 1, k) = vadd(cc_ref(i - 1, k, 1), vadd(cr2, cr3)); ch_ref(i, 1, k) = vsub(cc_ref(i, k, 1), vadd(ci2, ci3)); v4sf tr2{vadd(cc_ref(i - 1, k, 1), vmadd(tr11, cr2, vmul(tr12, cr3)))}; v4sf ti2{vsub(cc_ref(i, k, 1), vmadd(tr11, ci2, vmul(tr12, ci3)))}; v4sf tr3{vadd(cc_ref(i - 1, k, 1), vmadd(tr12, cr2, vmul(tr11, cr3)))}; v4sf ti3{vsub(cc_ref(i, k, 1), vmadd(tr12, ci2, vmul(tr11, ci3)))}; v4sf tr5{vmadd(ti11, cr5, vmul(ti12, cr4))}; v4sf ti5{vmadd(ti11, ci5, vmul(ti12, ci4))}; v4sf tr4{vsub(vmul(ti12, cr5), vmul(ti11, cr4))}; v4sf ti4{vsub(vmul(ti12, ci5), vmul(ti11, ci4))}; ch_ref(i - 1, 3, k) = vsub(tr2, tr5); ch_ref(ic - 1, 2, k) = vadd(tr2, tr5); ch_ref(i , 3, k) = vadd(ti5, ti2); ch_ref(ic , 2, k) = vsub(ti5, ti2); ch_ref(i - 1, 5, k) = vsub(tr3, tr4); ch_ref(ic - 1, 4, k) = vadd(tr3, tr4); ch_ref(i , 5, k) = vadd(ti4, ti3); ch_ref(ic , 4, k) = vsub(ti4, ti3); } } } /* radf5 */ void radb5_ps(const size_t ido, const size_t l1, const v4sf *RESTRICT cc, v4sf *RESTRICT ch, const float *const wa1) { const v4sf tr11{ld_ps1(0.309016994374947f)}; const v4sf ti11{ld_ps1(0.951056516295154f)}; const v4sf tr12{ld_ps1(-0.809016994374947f)}; const v4sf ti12{ld_ps1(0.587785252292473f)}; auto cc_ref = [&cc,ido](size_t a_1, size_t a_2, size_t a_3) noexcept -> auto& { return cc[(a_3*5 + a_2)*ido + a_1]; }; auto ch_ref = [&ch,ido,l1](size_t a_1, size_t a_2, size_t a_3) noexcept -> auto& { return ch[(a_3*l1 + a_2)*ido + a_1]; }; /* Parameter adjustments */ ch -= 1 + ido*(1 + l1); cc -= 1 + ido*6; const auto wa2 = wa1 + ido; const auto wa3 = wa2 + ido; const auto wa4 = wa3 + ido; /* Function Body */ for(size_t k{1};k <= l1;++k) { v4sf ti5{vadd(cc_ref( 1, 3, k), cc_ref(1, 3, k))}; v4sf ti4{vadd(cc_ref( 1, 5, k), cc_ref(1, 5, k))}; v4sf tr2{vadd(cc_ref(ido, 2, k), cc_ref(ido, 2, k))}; v4sf tr3{vadd(cc_ref(ido, 4, k), cc_ref(ido, 4, k))}; ch_ref(1, k, 1) = vadd(cc_ref(1, 1, k), vadd(tr2, tr3)); v4sf cr2{vadd(cc_ref(1, 1, k), vmadd(tr11, tr2, vmul(tr12, tr3)))}; v4sf cr3{vadd(cc_ref(1, 1, k), vmadd(tr12, tr2, vmul(tr11, tr3)))}; v4sf ci5{vmadd(ti11, ti5, vmul(ti12, ti4))}; v4sf ci4{vsub(vmul(ti12, ti5), vmul(ti11, ti4))}; ch_ref(1, k, 2) = vsub(cr2, ci5); ch_ref(1, k, 3) = vsub(cr3, ci4); ch_ref(1, k, 4) = vadd(cr3, ci4); ch_ref(1, k, 5) = vadd(cr2, ci5); } if(ido == 1) return; const size_t idp2{ido + 2}; for(size_t k{1};k <= l1;++k) { for(size_t i{3};i <= ido;i += 2) { const size_t ic{idp2 - i}; v4sf ti5{vadd(cc_ref(i , 3, k), cc_ref(ic , 2, k))}; v4sf ti2{vsub(cc_ref(i , 3, k), cc_ref(ic , 2, k))}; v4sf ti4{vadd(cc_ref(i , 5, k), cc_ref(ic , 4, k))}; v4sf ti3{vsub(cc_ref(i , 5, k), cc_ref(ic , 4, k))}; v4sf tr5{vsub(cc_ref(i-1, 3, k), cc_ref(ic-1, 2, k))}; v4sf tr2{vadd(cc_ref(i-1, 3, k), cc_ref(ic-1, 2, k))}; v4sf tr4{vsub(cc_ref(i-1, 5, k), cc_ref(ic-1, 4, k))}; v4sf tr3{vadd(cc_ref(i-1, 5, k), cc_ref(ic-1, 4, k))}; ch_ref(i - 1, k, 1) = vadd(cc_ref(i-1, 1, k), vadd(tr2, tr3)); ch_ref(i , k, 1) = vadd(cc_ref(i , 1, k), vadd(ti2, ti3)); v4sf cr2{vadd(cc_ref(i-1, 1, k), vmadd(tr11, tr2, vmul(tr12, tr3)))}; v4sf ci2{vadd(cc_ref(i , 1, k), vmadd(tr11, ti2, vmul(tr12, ti3)))}; v4sf cr3{vadd(cc_ref(i-1, 1, k), vmadd(tr12, tr2, vmul(tr11, tr3)))}; v4sf ci3{vadd(cc_ref(i , 1, k), vmadd(tr12, ti2, vmul(tr11, ti3)))}; v4sf cr5{vmadd(ti11, tr5, vmul(ti12, tr4))}; v4sf ci5{vmadd(ti11, ti5, vmul(ti12, ti4))}; v4sf cr4{vsub(vmul(ti12, tr5), vmul(ti11, tr4))}; v4sf ci4{vsub(vmul(ti12, ti5), vmul(ti11, ti4))}; v4sf dr3{vsub(cr3, ci4)}; v4sf dr4{vadd(cr3, ci4)}; v4sf di3{vadd(ci3, cr4)}; v4sf di4{vsub(ci3, cr4)}; v4sf dr5{vadd(cr2, ci5)}; v4sf dr2{vsub(cr2, ci5)}; v4sf di5{vsub(ci2, cr5)}; v4sf di2{vadd(ci2, cr5)}; vcplxmul(dr2, di2, ld_ps1(wa1[i-3]), ld_ps1(wa1[i-2])); vcplxmul(dr3, di3, ld_ps1(wa2[i-3]), ld_ps1(wa2[i-2])); vcplxmul(dr4, di4, ld_ps1(wa3[i-3]), ld_ps1(wa3[i-2])); vcplxmul(dr5, di5, ld_ps1(wa4[i-3]), ld_ps1(wa4[i-2])); ch_ref(i-1, k, 2) = dr2; ch_ref(i, k, 2) = di2; ch_ref(i-1, k, 3) = dr3; ch_ref(i, k, 3) = di3; ch_ref(i-1, k, 4) = dr4; ch_ref(i, k, 4) = di4; ch_ref(i-1, k, 5) = dr5; ch_ref(i, k, 5) = di5; } } } /* radb5 */ NOINLINE v4sf *rfftf1_ps(const size_t n, const v4sf *input_readonly, v4sf *work1, v4sf *work2, const float *wa, const al::span ifac) { assert(work1 != work2); const v4sf *in{input_readonly}; v4sf *out{in == work2 ? work1 : work2}; const size_t nf{ifac[1]}; size_t l2{n}; size_t iw{n-1}; size_t k1{1}; while(true) { const size_t kh{nf - k1}; const size_t ip{ifac[kh + 2]}; const size_t l1{l2 / ip}; const size_t ido{n / l2}; iw -= (ip - 1)*ido; switch(ip) { case 5: radf5_ps(ido, l1, in, out, &wa[iw]); break; case 4: radf4_ps(ido, l1, in, out, &wa[iw]); break; case 3: radf3_ps(ido, l1, in, out, &wa[iw]); break; case 2: radf2_ps(ido, l1, in, out, &wa[iw]); break; default: assert(0); } if(++k1 > nf) return out; l2 = l1; if(out == work2) { out = work1; in = work2; } else { out = work2; in = work1; } } } /* rfftf1 */ NOINLINE v4sf *rfftb1_ps(const size_t n, const v4sf *input_readonly, v4sf *work1, v4sf *work2, const float *wa, const al::span ifac) { assert(work1 != work2); const v4sf *in{input_readonly}; v4sf *out{in == work2 ? work1 : work2}; const size_t nf{ifac[1]}; size_t l1{1}; size_t iw{0}; size_t k1{1}; while(true) { const size_t ip{ifac[k1 + 1]}; const size_t l2{ip*l1}; const size_t ido{n / l2}; switch(ip) { case 5: radb5_ps(ido, l1, in, out, &wa[iw]); break; case 4: radb4_ps(ido, l1, in, out, &wa[iw]); break; case 3: radb3_ps(ido, l1, in, out, &wa[iw]); break; case 2: radb2_ps(ido, l1, in, out, &wa[iw]); break; default: assert(0); } if(++k1 > nf) return out; l1 = l2; iw += (ip - 1)*ido; if(out == work2) { out = work1; in = work2; } else { out = work2; in = work1; } } } v4sf *cfftf1_ps(const size_t n, const v4sf *input_readonly, v4sf *work1, v4sf *work2, const float *wa, const al::span ifac, const float fsign) { assert(work1 != work2); const v4sf *in{input_readonly}; v4sf *out{in == work2 ? work1 : work2}; const size_t nf{ifac[1]}; size_t l1{1}, iw{0}; size_t k1{2}; while(true) { const size_t ip{ifac[k1]}; const size_t l2{ip*l1}; const size_t ido{n / l2}; const size_t idot{ido + ido}; switch(ip) { case 5: passf5_ps(idot, l1, in, out, &wa[iw], fsign); break; case 4: passf4_ps(idot, l1, in, out, &wa[iw], fsign); break; case 3: passf3_ps(idot, l1, in, out, &wa[iw], fsign); break; case 2: passf2_ps(idot, l1, in, out, &wa[iw], fsign); break; default: assert(0); } if(++k1 > nf+1) return out; l1 = l2; iw += (ip - 1)*idot; if(out == work2) { out = work1; in = work2; } else { out = work2; in = work1; } } } uint decompose(const uint n, const al::span ifac, const al::span ntryh) { uint nl{n}, nf{0}; for(const uint ntry : ntryh) { while(nl != 1) { const uint nq{nl / ntry}; const uint nr{nl % ntry}; if(nr != 0) break; ifac[2+nf++] = ntry; nl = nq; if(ntry == 2 && nf != 1) { for(size_t i{2};i <= nf;++i) { size_t ib{nf - i + 2}; ifac[ib + 1] = ifac[ib]; } ifac[2] = 2; } } } ifac[0] = n; ifac[1] = nf; return nf; } void rffti1_ps(const uint n, float *wa, const al::span ifac) { static constexpr std::array ntryh{4u,2u,3u,5u}; const uint nf{decompose(n, ifac, ntryh)}; const double argh{2.0*al::numbers::pi / n}; size_t is{0}; size_t nfm1{nf - 1}; size_t l1{1}; for(size_t k1{0};k1 < nfm1;++k1) { const size_t ip{ifac[k1+2]}; const size_t l2{l1*ip}; const size_t ido{n / l2}; const size_t ipm{ip - 1}; size_t ld{0}; for(size_t j{0};j < ipm;++j) { size_t i{is}; ld += l1; const double argld{static_cast(ld)*argh}; double fi{0.0}; for(size_t ii{2};ii < ido;ii += 2) { fi += 1.0; wa[i++] = static_cast(std::cos(fi*argld)); wa[i++] = static_cast(std::sin(fi*argld)); } is += ido; } l1 = l2; } } /* rffti1 */ void cffti1_ps(const uint n, float *wa, const al::span ifac) { static constexpr std::array ntryh{5u,3u,4u,2u}; const uint nf{decompose(n, ifac, ntryh)}; const double argh{2.0*al::numbers::pi / n}; size_t i{1}; size_t l1{1}; for(size_t k1{0};k1 < nf;++k1) { const size_t ip{ifac[k1+2]}; const size_t l2{l1*ip}; const size_t ido{n / l2}; const size_t idot{ido + ido + 2}; const size_t ipm{ip - 1}; size_t ld{0}; for(size_t j{0};j < ipm;++j) { size_t i1{i}; wa[i-1] = 1; wa[i] = 0; ld += l1; const double argld{static_cast(ld)*argh}; double fi{0.0}; for(size_t ii{3};ii < idot;ii += 2) { fi += 1.0; wa[++i] = static_cast(std::cos(fi*argld)); wa[++i] = static_cast(std::sin(fi*argld)); } if(ip > 5) { wa[i1-1] = wa[i-1]; wa[i1] = wa[i]; } } l1 = l2; } } /* cffti1 */ } // namespace /* NOLINTNEXTLINE(clang-analyzer-optin.performance.Padding) */ struct PFFFT_Setup { uint N{}; uint Ncvec{}; /* nb of complex simd vectors (N/4 if PFFFT_COMPLEX, N/8 if PFFFT_REAL) */ std::array ifac{}; pffft_transform_t transform{}; float *twiddle{}; /* N/4 elements */ al::span e; /* N/4*3 elements */ alignas(V4sfAlignment) std::byte end; }; PFFFTSetupPtr pffft_new_setup(unsigned int N, pffft_transform_t transform) { assert(transform == PFFFT_REAL || transform == PFFFT_COMPLEX); assert(N > 0); /* unfortunately, the fft size must be a multiple of 16 for complex FFTs * and 32 for real FFTs -- a lot of stuff would need to be rewritten to * handle other cases (or maybe just switch to a scalar fft, I don't know..) */ if(transform == PFFFT_REAL) assert((N%(2*SimdSize*SimdSize)) == 0); else assert((N%(SimdSize*SimdSize)) == 0); const uint Ncvec{(transform == PFFFT_REAL ? N/2 : N) / SimdSize}; const size_t storelen{std::max(offsetof(PFFFT_Setup, end) + 2_zu*Ncvec*sizeof(v4sf), sizeof(PFFFT_Setup))}; auto storage = static_cast>(::operator new[](storelen, V4sfAlignVal)); al::span extrastore{&storage[offsetof(PFFFT_Setup, end)], 2_zu*Ncvec*sizeof(v4sf)}; PFFFTSetupPtr s{::new(storage) PFFFT_Setup{}}; s->N = N; s->transform = transform; s->Ncvec = Ncvec; const size_t ecount{2_zu*Ncvec*(SimdSize-1)/SimdSize}; s->e = {std::launder(reinterpret_cast(extrastore.data())), ecount}; s->twiddle = std::launder(reinterpret_cast(&extrastore[ecount*sizeof(v4sf)])); if constexpr(SimdSize > 1) { auto e = std::vector(s->e.size()*SimdSize, 0.0f); for(size_t k{0};k < s->Ncvec;++k) { const size_t i{k / SimdSize}; const size_t j{k % SimdSize}; for(size_t m{0};m < SimdSize-1;++m) { const double A{-2.0*al::numbers::pi*static_cast((m+1)*k) / N}; e[((i*3 + m)*2 + 0)*SimdSize + j] = static_cast(std::cos(A)); e[((i*3 + m)*2 + 1)*SimdSize + j] = static_cast(std::sin(A)); } } std::memcpy(s->e.data(), e.data(), e.size()*sizeof(float)); } if(transform == PFFFT_REAL) rffti1_ps(N/SimdSize, s->twiddle, s->ifac); else cffti1_ps(N/SimdSize, s->twiddle, s->ifac); /* check that N is decomposable with allowed prime factors */ size_t m{1}; for(size_t k{0};k < s->ifac[1];++k) m *= s->ifac[2+k]; if(m != N/SimdSize) s = nullptr; return s; } void pffft_destroy_setup(gsl::owner s) noexcept { std::destroy_at(s); ::operator delete[](gsl::owner{s}, V4sfAlignVal); } #if !defined(PFFFT_SIMD_DISABLE) namespace { /* [0 0 1 2 3 4 5 6 7 8] -> [0 8 7 6 5 4 3 2 1] */ void reversed_copy(const size_t N, const v4sf *in, const int in_stride, v4sf *RESTRICT out) { v4sf g0, g1; interleave2(in[0], in[1], g0, g1); in += in_stride; *--out = vswaphl(g0, g1); // [g0l, g0h], [g1l g1h] -> [g1l, g0h] for(size_t k{1};k < N;++k) { v4sf h0, h1; interleave2(in[0], in[1], h0, h1); in += in_stride; *--out = vswaphl(g1, h0); *--out = vswaphl(h0, h1); g1 = h1; } *--out = vswaphl(g1, g0); } void unreversed_copy(const size_t N, const v4sf *in, v4sf *RESTRICT out, const int out_stride) { v4sf g0{in[0]}, g1{g0}; ++in; for(size_t k{1};k < N;++k) { v4sf h0{*in++}; v4sf h1{*in++}; g1 = vswaphl(g1, h0); h0 = vswaphl(h0, h1); uninterleave2(h0, g1, out[0], out[1]); out += out_stride; g1 = h1; } v4sf h0{*in++}, h1{g0}; g1 = vswaphl(g1, h0); h0 = vswaphl(h0, h1); uninterleave2(h0, g1, out[0], out[1]); } void pffft_cplx_finalize(const size_t Ncvec, const v4sf *in, v4sf *RESTRICT out, const v4sf *e) { assert(in != out); const size_t dk{Ncvec/SimdSize}; // number of 4x4 matrix blocks for(size_t k{0};k < dk;++k) { v4sf r0{in[8*k+0]}, i0{in[8*k+1]}; v4sf r1{in[8*k+2]}, i1{in[8*k+3]}; v4sf r2{in[8*k+4]}, i2{in[8*k+5]}; v4sf r3{in[8*k+6]}, i3{in[8*k+7]}; vtranspose4(r0,r1,r2,r3); vtranspose4(i0,i1,i2,i3); vcplxmul(r1,i1,e[k*6+0],e[k*6+1]); vcplxmul(r2,i2,e[k*6+2],e[k*6+3]); vcplxmul(r3,i3,e[k*6+4],e[k*6+5]); v4sf sr0{vadd(r0,r2)}, dr0{vsub(r0, r2)}; v4sf sr1{vadd(r1,r3)}, dr1{vsub(r1, r3)}; v4sf si0{vadd(i0,i2)}, di0{vsub(i0, i2)}; v4sf si1{vadd(i1,i3)}, di1{vsub(i1, i3)}; /* transformation for each column is: * * [1 1 1 1 0 0 0 0] [r0] * [1 0 -1 0 0 -1 0 1] [r1] * [1 -1 1 -1 0 0 0 0] [r2] * [1 0 -1 0 0 1 0 -1] [r3] * [0 0 0 0 1 1 1 1] * [i0] * [0 1 0 -1 1 0 -1 0] [i1] * [0 0 0 0 1 -1 1 -1] [i2] * [0 -1 0 1 1 0 -1 0] [i3] */ r0 = vadd(sr0, sr1); i0 = vadd(si0, si1); r1 = vadd(dr0, di1); i1 = vsub(di0, dr1); r2 = vsub(sr0, sr1); i2 = vsub(si0, si1); r3 = vsub(dr0, di1); i3 = vadd(di0, dr1); *out++ = r0; *out++ = i0; *out++ = r1; *out++ = i1; *out++ = r2; *out++ = i2; *out++ = r3; *out++ = i3; } } void pffft_cplx_preprocess(const size_t Ncvec, const v4sf *in, v4sf *RESTRICT out, const v4sf *e) { assert(in != out); const size_t dk{Ncvec/SimdSize}; // number of 4x4 matrix blocks for(size_t k{0};k < dk;++k) { v4sf r0{in[8*k+0]}, i0{in[8*k+1]}; v4sf r1{in[8*k+2]}, i1{in[8*k+3]}; v4sf r2{in[8*k+4]}, i2{in[8*k+5]}; v4sf r3{in[8*k+6]}, i3{in[8*k+7]}; v4sf sr0{vadd(r0,r2)}, dr0{vsub(r0, r2)}; v4sf sr1{vadd(r1,r3)}, dr1{vsub(r1, r3)}; v4sf si0{vadd(i0,i2)}, di0{vsub(i0, i2)}; v4sf si1{vadd(i1,i3)}, di1{vsub(i1, i3)}; r0 = vadd(sr0, sr1); i0 = vadd(si0, si1); r1 = vsub(dr0, di1); i1 = vadd(di0, dr1); r2 = vsub(sr0, sr1); i2 = vsub(si0, si1); r3 = vadd(dr0, di1); i3 = vsub(di0, dr1); vcplxmulconj(r1,i1,e[k*6+0],e[k*6+1]); vcplxmulconj(r2,i2,e[k*6+2],e[k*6+3]); vcplxmulconj(r3,i3,e[k*6+4],e[k*6+5]); vtranspose4(r0,r1,r2,r3); vtranspose4(i0,i1,i2,i3); *out++ = r0; *out++ = i0; *out++ = r1; *out++ = i1; *out++ = r2; *out++ = i2; *out++ = r3; *out++ = i3; } } force_inline void pffft_real_finalize_4x4(const v4sf *in0, const v4sf *in1, const v4sf *in, const v4sf *e, v4sf *RESTRICT out) { v4sf r0{*in0}, i0{*in1}; v4sf r1{*in++}; v4sf i1{*in++}; v4sf r2{*in++}; v4sf i2{*in++}; v4sf r3{*in++}; v4sf i3{*in++}; vtranspose4(r0,r1,r2,r3); vtranspose4(i0,i1,i2,i3); /* transformation for each column is: * * [1 1 1 1 0 0 0 0] [r0] * [1 0 -1 0 0 -1 0 1] [r1] * [1 0 -1 0 0 1 0 -1] [r2] * [1 -1 1 -1 0 0 0 0] [r3] * [0 0 0 0 1 1 1 1] * [i0] * [0 -1 0 1 -1 0 1 0] [i1] * [0 -1 0 1 1 0 -1 0] [i2] * [0 0 0 0 -1 1 -1 1] [i3] */ //cerr << "matrix initial, before e , REAL:\n 1: " << r0 << "\n 1: " << r1 << "\n 1: " << r2 << "\n 1: " << r3 << "\n"; //cerr << "matrix initial, before e, IMAG :\n 1: " << i0 << "\n 1: " << i1 << "\n 1: " << i2 << "\n 1: " << i3 << "\n"; vcplxmul(r1,i1,e[0],e[1]); vcplxmul(r2,i2,e[2],e[3]); vcplxmul(r3,i3,e[4],e[5]); //cerr << "matrix initial, real part:\n 1: " << r0 << "\n 1: " << r1 << "\n 1: " << r2 << "\n 1: " << r3 << "\n"; //cerr << "matrix initial, imag part:\n 1: " << i0 << "\n 1: " << i1 << "\n 1: " << i2 << "\n 1: " << i3 << "\n"; v4sf sr0{vadd(r0,r2)}, dr0{vsub(r0,r2)}; v4sf sr1{vadd(r1,r3)}, dr1{vsub(r3,r1)}; v4sf si0{vadd(i0,i2)}, di0{vsub(i0,i2)}; v4sf si1{vadd(i1,i3)}, di1{vsub(i3,i1)}; r0 = vadd(sr0, sr1); r3 = vsub(sr0, sr1); i0 = vadd(si0, si1); i3 = vsub(si1, si0); r1 = vadd(dr0, di1); r2 = vsub(dr0, di1); i1 = vsub(dr1, di0); i2 = vadd(dr1, di0); *out++ = r0; *out++ = i0; *out++ = r1; *out++ = i1; *out++ = r2; *out++ = i2; *out++ = r3; *out++ = i3; } NOINLINE void pffft_real_finalize(const size_t Ncvec, const v4sf *in, v4sf *RESTRICT out, const v4sf *e) { static constexpr float s{al::numbers::sqrt2_v/2.0f}; assert(in != out); const size_t dk{Ncvec/SimdSize}; // number of 4x4 matrix blocks /* fftpack order is f0r f1r f1i f2r f2i ... f(n-1)r f(n-1)i f(n)r */ const v4sf zero{vzero()}; const auto cr = al::bit_cast>(in[0]); const auto ci = al::bit_cast>(in[Ncvec*2-1]); pffft_real_finalize_4x4(&zero, &zero, in+1, e, out); /* [cr0 cr1 cr2 cr3 ci0 ci1 ci2 ci3] * * [Xr(1)] ] [1 1 1 1 0 0 0 0] * [Xr(N/4) ] [0 0 0 0 1 s 0 -s] * [Xr(N/2) ] [1 0 -1 0 0 0 0 0] * [Xr(3N/4)] [0 0 0 0 1 -s 0 s] * [Xi(1) ] [1 -1 1 -1 0 0 0 0] * [Xi(N/4) ] [0 0 0 0 0 -s -1 -s] * [Xi(N/2) ] [0 -1 0 1 0 0 0 0] * [Xi(3N/4)] [0 0 0 0 0 -s 1 -s] */ const float xr0{(cr[0]+cr[2]) + (cr[1]+cr[3])}; out[0] = vinsert0(out[0], xr0); const float xi0{(cr[0]+cr[2]) - (cr[1]+cr[3])}; out[1] = vinsert0(out[1], xi0); const float xr2{(cr[0]-cr[2])}; out[4] = vinsert0(out[4], xr2); const float xi2{(cr[3]-cr[1])}; out[5] = vinsert0(out[5], xi2); const float xr1{ ci[0] + s*(ci[1]-ci[3])}; out[2] = vinsert0(out[2], xr1); const float xi1{-ci[2] - s*(ci[1]+ci[3])}; out[3] = vinsert0(out[3], xi1); const float xr3{ ci[0] - s*(ci[1]-ci[3])}; out[6] = vinsert0(out[6], xr3); const float xi3{ ci[2] - s*(ci[1]+ci[3])}; out[7] = vinsert0(out[7], xi3); for(size_t k{1};k < dk;++k) pffft_real_finalize_4x4(&in[8*k-1], &in[8*k+0], in + 8*k+1, e + k*6, out + k*8); } force_inline void pffft_real_preprocess_4x4(const v4sf *in, const v4sf *e, v4sf *RESTRICT out, const bool first) { v4sf r0{in[0]}, i0{in[1]}, r1{in[2]}, i1{in[3]}; v4sf r2{in[4]}, i2{in[5]}, r3{in[6]}, i3{in[7]}; /* transformation for each column is: * * [1 1 1 1 0 0 0 0] [r0] * [1 0 0 -1 0 -1 -1 0] [r1] * [1 -1 -1 1 0 0 0 0] [r2] * [1 0 0 -1 0 1 1 0] [r3] * [0 0 0 0 1 -1 1 -1] * [i0] * [0 -1 1 0 1 0 0 1] [i1] * [0 0 0 0 1 1 -1 -1] [i2] * [0 1 -1 0 1 0 0 1] [i3] */ v4sf sr0{vadd(r0,r3)}, dr0{vsub(r0,r3)}; v4sf sr1{vadd(r1,r2)}, dr1{vsub(r1,r2)}; v4sf si0{vadd(i0,i3)}, di0{vsub(i0,i3)}; v4sf si1{vadd(i1,i2)}, di1{vsub(i1,i2)}; r0 = vadd(sr0, sr1); r2 = vsub(sr0, sr1); r1 = vsub(dr0, si1); r3 = vadd(dr0, si1); i0 = vsub(di0, di1); i2 = vadd(di0, di1); i1 = vsub(si0, dr1); i3 = vadd(si0, dr1); vcplxmulconj(r1,i1,e[0],e[1]); vcplxmulconj(r2,i2,e[2],e[3]); vcplxmulconj(r3,i3,e[4],e[5]); vtranspose4(r0,r1,r2,r3); vtranspose4(i0,i1,i2,i3); if(!first) { *out++ = r0; *out++ = i0; } *out++ = r1; *out++ = i1; *out++ = r2; *out++ = i2; *out++ = r3; *out++ = i3; } NOINLINE void pffft_real_preprocess(const size_t Ncvec, const v4sf *in, v4sf *RESTRICT out, const v4sf *e) { static constexpr float sqrt2{al::numbers::sqrt2_v}; assert(in != out); const size_t dk{Ncvec/SimdSize}; // number of 4x4 matrix blocks /* fftpack order is f0r f1r f1i f2r f2i ... f(n-1)r f(n-1)i f(n)r */ std::array Xr{}, Xi{}; for(size_t k{0};k < SimdSize;++k) { Xr[k] = vextract0(in[2*k]); Xi[k] = vextract0(in[2*k + 1]); } pffft_real_preprocess_4x4(in, e, out+1, true); // will write only 6 values /* [Xr0 Xr1 Xr2 Xr3 Xi0 Xi1 Xi2 Xi3] * * [cr0] [1 0 2 0 1 0 0 0] * [cr1] [1 0 0 0 -1 0 -2 0] * [cr2] [1 0 -2 0 1 0 0 0] * [cr3] [1 0 0 0 -1 0 2 0] * [ci0] [0 2 0 2 0 0 0 0] * [ci1] [0 s 0 -s 0 -s 0 -s] * [ci2] [0 0 0 0 0 -2 0 2] * [ci3] [0 -s 0 s 0 -s 0 -s] */ for(size_t k{1};k < dk;++k) pffft_real_preprocess_4x4(in+8*k, e + k*6, out-1+k*8, false); const float cr0{(Xr[0]+Xi[0]) + 2*Xr[2]}; const float cr1{(Xr[0]-Xi[0]) - 2*Xi[2]}; const float cr2{(Xr[0]+Xi[0]) - 2*Xr[2]}; const float cr3{(Xr[0]-Xi[0]) + 2*Xi[2]}; out[0] = vset4(cr0, cr1, cr2, cr3); const float ci0{ 2*(Xr[1]+Xr[3])}; const float ci1{ sqrt2*(Xr[1]-Xr[3]) - sqrt2*(Xi[1]+Xi[3])}; const float ci2{ 2*(Xi[3]-Xi[1])}; const float ci3{-sqrt2*(Xr[1]-Xr[3]) - sqrt2*(Xi[1]+Xi[3])}; out[2*Ncvec-1] = vset4(ci0, ci1, ci2, ci3); } void pffft_transform_internal(const PFFFT_Setup *setup, const v4sf *vinput, v4sf *voutput, v4sf *scratch, const pffft_direction_t direction, const bool ordered) { assert(scratch != nullptr); assert(voutput != scratch); const size_t Ncvec{setup->Ncvec}; const bool nf_odd{(setup->ifac[1]&1) != 0}; std::array buff{voutput, scratch}; bool ib{nf_odd != ordered}; if(direction == PFFFT_FORWARD) { /* Swap the initial work buffer for forward FFTs, which helps avoid an * extra copy for output. */ ib = !ib; if(setup->transform == PFFFT_REAL) { ib = (rfftf1_ps(Ncvec*2, vinput, buff[ib], buff[!ib], setup->twiddle, setup->ifac) == buff[1]); pffft_real_finalize(Ncvec, buff[ib], buff[!ib], setup->e.data()); } else { v4sf *tmp{buff[ib]}; for(size_t k=0; k < Ncvec; ++k) uninterleave2(vinput[k*2], vinput[k*2+1], tmp[k*2], tmp[k*2+1]); ib = (cfftf1_ps(Ncvec, buff[ib], buff[!ib], buff[ib], setup->twiddle, setup->ifac, -1.0f) == buff[1]); pffft_cplx_finalize(Ncvec, buff[ib], buff[!ib], setup->e.data()); } if(ordered) pffft_zreorder(setup, reinterpret_cast(buff[!ib]), reinterpret_cast(buff[ib]), PFFFT_FORWARD); else ib = !ib; } else { if(vinput == buff[ib]) ib = !ib; // may happen when finput == foutput if(ordered) { pffft_zreorder(setup, reinterpret_cast(vinput), reinterpret_cast(buff[ib]), PFFFT_BACKWARD); vinput = buff[ib]; ib = !ib; } if(setup->transform == PFFFT_REAL) { pffft_real_preprocess(Ncvec, vinput, buff[ib], setup->e.data()); ib = (rfftb1_ps(Ncvec*2, buff[ib], buff[0], buff[1], setup->twiddle, setup->ifac) == buff[1]); } else { pffft_cplx_preprocess(Ncvec, vinput, buff[ib], setup->e.data()); ib = (cfftf1_ps(Ncvec, buff[ib], buff[0], buff[1], setup->twiddle, setup->ifac, +1.0f) == buff[1]); for(size_t k{0};k < Ncvec;++k) interleave2(buff[ib][k*2], buff[ib][k*2+1], buff[ib][k*2], buff[ib][k*2+1]); } } if(buff[ib] != voutput) { /* extra copy required -- this situation should only happen when finput == foutput */ assert(vinput==voutput); for(size_t k{0};k < Ncvec;++k) { v4sf a{buff[ib][2*k]}, b{buff[ib][2*k+1]}; voutput[2*k] = a; voutput[2*k+1] = b; } } } } // namespace void pffft_zreorder(const PFFFT_Setup *setup, const float *in, float *out, pffft_direction_t direction) { assert(in != out); const size_t N{setup->N}, Ncvec{setup->Ncvec}; const v4sf *vin{reinterpret_cast(in)}; v4sf *RESTRICT vout{reinterpret_cast(out)}; if(setup->transform == PFFFT_REAL) { const size_t dk{N/32}; if(direction == PFFFT_FORWARD) { for(size_t k{0};k < dk;++k) { interleave2(vin[k*8 + 0], vin[k*8 + 1], vout[2*(0*dk + k) + 0], vout[2*(0*dk + k) + 1]); interleave2(vin[k*8 + 4], vin[k*8 + 5], vout[2*(2*dk + k) + 0], vout[2*(2*dk + k) + 1]); } reversed_copy(dk, vin+2, 8, vout + N/SimdSize/2); reversed_copy(dk, vin+6, 8, vout + N/SimdSize); } else { for(size_t k{0};k < dk;++k) { uninterleave2(vin[2*(0*dk + k) + 0], vin[2*(0*dk + k) + 1], vout[k*8 + 0], vout[k*8 + 1]); uninterleave2(vin[2*(2*dk + k) + 0], vin[2*(2*dk + k) + 1], vout[k*8 + 4], vout[k*8 + 5]); } unreversed_copy(dk, vin + N/SimdSize/4, vout + N/SimdSize - 6, -8); unreversed_copy(dk, vin + 3_uz*N/SimdSize/4, vout + N/SimdSize - 2, -8); } } else { if(direction == PFFFT_FORWARD) { for(size_t k{0};k < Ncvec;++k) { size_t kk{(k/4) + (k%4)*(Ncvec/4)}; interleave2(vin[k*2], vin[k*2+1], vout[kk*2], vout[kk*2+1]); } } else { for(size_t k{0};k < Ncvec;++k) { size_t kk{(k/4) + (k%4)*(Ncvec/4)}; uninterleave2(vin[kk*2], vin[kk*2+1], vout[k*2], vout[k*2+1]); } } } } void pffft_zconvolve_scale_accumulate(const PFFFT_Setup *s, const float *a, const float *b, float *ab, float scaling) { const size_t Ncvec{s->Ncvec}; const v4sf *RESTRICT va{reinterpret_cast(a)}; const v4sf *RESTRICT vb{reinterpret_cast(b)}; v4sf *RESTRICT vab{reinterpret_cast(ab)}; #ifdef __arm__ __builtin_prefetch(va); __builtin_prefetch(vb); __builtin_prefetch(vab); __builtin_prefetch(va+2); __builtin_prefetch(vb+2); __builtin_prefetch(vab+2); __builtin_prefetch(va+4); __builtin_prefetch(vb+4); __builtin_prefetch(vab+4); __builtin_prefetch(va+6); __builtin_prefetch(vb+6); __builtin_prefetch(vab+6); #ifndef __clang__ #define ZCONVOLVE_USING_INLINE_NEON_ASM #endif #endif const float ar1{vextract0(va[0])}; const float ai1{vextract0(va[1])}; const float br1{vextract0(vb[0])}; const float bi1{vextract0(vb[1])}; const float abr1{vextract0(vab[0])}; const float abi1{vextract0(vab[1])}; #ifdef ZCONVOLVE_USING_INLINE_ASM /* Inline asm version, unfortunately miscompiled by clang 3.2, at least on * Ubuntu. So this will be restricted to GCC. * * Does it still miscompile with Clang? Is it even needed with today's * optimizers? */ const float *a_{a}, *b_{b}; float *ab_{ab}; size_t N{Ncvec}; asm volatile("mov r8, %2 \n" "vdup.f32 q15, %4 \n" "1: \n" "pld [%0,#64] \n" "pld [%1,#64] \n" "pld [%2,#64] \n" "pld [%0,#96] \n" "pld [%1,#96] \n" "pld [%2,#96] \n" "vld1.f32 {q0,q1}, [%0,:128]! \n" "vld1.f32 {q4,q5}, [%1,:128]! \n" "vld1.f32 {q2,q3}, [%0,:128]! \n" "vld1.f32 {q6,q7}, [%1,:128]! \n" "vld1.f32 {q8,q9}, [r8,:128]! \n" "vmul.f32 q10, q0, q4 \n" "vmul.f32 q11, q0, q5 \n" "vmul.f32 q12, q2, q6 \n" "vmul.f32 q13, q2, q7 \n" "vmls.f32 q10, q1, q5 \n" "vmla.f32 q11, q1, q4 \n" "vld1.f32 {q0,q1}, [r8,:128]! \n" "vmls.f32 q12, q3, q7 \n" "vmla.f32 q13, q3, q6 \n" "vmla.f32 q8, q10, q15 \n" "vmla.f32 q9, q11, q15 \n" "vmla.f32 q0, q12, q15 \n" "vmla.f32 q1, q13, q15 \n" "vst1.f32 {q8,q9},[%2,:128]! \n" "vst1.f32 {q0,q1},[%2,:128]! \n" "subs %3, #2 \n" "bne 1b \n" : "+r"(a_), "+r"(b_), "+r"(ab_), "+r"(N) : "r"(scaling) : "r8", "q0","q1","q2","q3","q4","q5","q6","q7","q8","q9", "q10","q11","q12","q13","q15","memory"); #else /* Default routine, works fine for non-arm cpus with current compilers. */ const v4sf vscal{ld_ps1(scaling)}; for(size_t i{0};i < Ncvec;i += 2) { v4sf ar4{va[2*i+0]}, ai4{va[2*i+1]}; v4sf br4{vb[2*i+0]}, bi4{vb[2*i+1]}; vcplxmul(ar4, ai4, br4, bi4); vab[2*i+0] = vmadd(ar4, vscal, vab[2*i+0]); vab[2*i+1] = vmadd(ai4, vscal, vab[2*i+1]); ar4 = va[2*i+2]; ai4 = va[2*i+3]; br4 = vb[2*i+2]; bi4 = vb[2*i+3]; vcplxmul(ar4, ai4, br4, bi4); vab[2*i+2] = vmadd(ar4, vscal, vab[2*i+2]); vab[2*i+3] = vmadd(ai4, vscal, vab[2*i+3]); } #endif if(s->transform == PFFFT_REAL) { vab[0] = vinsert0(vab[0], abr1 + ar1*br1*scaling); vab[1] = vinsert0(vab[1], abi1 + ai1*bi1*scaling); } } void pffft_zconvolve_accumulate(const PFFFT_Setup *s, const float *a, const float *b, float *ab) { const size_t Ncvec{s->Ncvec}; const v4sf *RESTRICT va{reinterpret_cast(a)}; const v4sf *RESTRICT vb{reinterpret_cast(b)}; v4sf *RESTRICT vab{reinterpret_cast(ab)}; #ifdef __arm__ __builtin_prefetch(va); __builtin_prefetch(vb); __builtin_prefetch(vab); __builtin_prefetch(va+2); __builtin_prefetch(vb+2); __builtin_prefetch(vab+2); __builtin_prefetch(va+4); __builtin_prefetch(vb+4); __builtin_prefetch(vab+4); __builtin_prefetch(va+6); __builtin_prefetch(vb+6); __builtin_prefetch(vab+6); #endif const float ar1{vextract0(va[0])}; const float ai1{vextract0(va[1])}; const float br1{vextract0(vb[0])}; const float bi1{vextract0(vb[1])}; const float abr1{vextract0(vab[0])}; const float abi1{vextract0(vab[1])}; /* No inline assembly for this version. I'm not familiar enough with NEON * assembly, and I don't know that it's needed with today's optimizers. */ for(size_t i{0};i < Ncvec;i += 2) { v4sf ar4{va[2*i+0]}, ai4{va[2*i+1]}; v4sf br4{vb[2*i+0]}, bi4{vb[2*i+1]}; vcplxmul(ar4, ai4, br4, bi4); vab[2*i+0] = vadd(ar4, vab[2*i+0]); vab[2*i+1] = vadd(ai4, vab[2*i+1]); ar4 = va[2*i+2]; ai4 = va[2*i+3]; br4 = vb[2*i+2]; bi4 = vb[2*i+3]; vcplxmul(ar4, ai4, br4, bi4); vab[2*i+2] = vadd(ar4, vab[2*i+2]); vab[2*i+3] = vadd(ai4, vab[2*i+3]); } if(s->transform == PFFFT_REAL) { vab[0] = vinsert0(vab[0], abr1 + ar1*br1); vab[1] = vinsert0(vab[1], abi1 + ai1*bi1); } } void pffft_transform(const PFFFT_Setup *setup, const float *input, float *output, float *work, pffft_direction_t direction) { assert(valigned(input) && valigned(output) && valigned(work)); pffft_transform_internal(setup, reinterpret_cast(al::assume_aligned<16>(input)), reinterpret_cast(al::assume_aligned<16>(output)), reinterpret_cast(al::assume_aligned<16>(work)), direction, false); } void pffft_transform_ordered(const PFFFT_Setup *setup, const float *input, float *output, float *work, pffft_direction_t direction) { assert(valigned(input) && valigned(output) && valigned(work)); pffft_transform_internal(setup, reinterpret_cast(al::assume_aligned<16>(input)), reinterpret_cast(al::assume_aligned<16>(output)), reinterpret_cast(al::assume_aligned<16>(work)), direction, true); } #else // defined(PFFFT_SIMD_DISABLE) // standard routine using scalar floats, without SIMD stuff. namespace { void pffft_transform_internal(const PFFFT_Setup *setup, const float *input, float *output, float *scratch, const pffft_direction_t direction, bool ordered) { const size_t Ncvec{setup->Ncvec}; const bool nf_odd{(setup->ifac[1]&1) != 0}; assert(scratch != nullptr); /* z-domain data for complex transforms is already ordered without SIMD. */ if(setup->transform == PFFFT_COMPLEX) ordered = false; float *buff[2]{output, scratch}; bool ib{nf_odd != ordered}; if(direction == PFFFT_FORWARD) { if(setup->transform == PFFFT_REAL) ib = (rfftf1_ps(Ncvec*2, input, buff[ib], buff[!ib], setup->twiddle, setup->ifac) == buff[1]); else ib = (cfftf1_ps(Ncvec, input, buff[ib], buff[!ib], setup->twiddle, setup->ifac, -1.0f) == buff[1]); if(ordered) { pffft_zreorder(setup, buff[ib], buff[!ib], PFFFT_FORWARD); ib = !ib; } } else { if (input == buff[ib]) ib = !ib; // may happen when finput == foutput if(ordered) { pffft_zreorder(setup, input, buff[ib], PFFFT_BACKWARD); input = buff[ib]; ib = !ib; } if(setup->transform == PFFFT_REAL) ib = (rfftb1_ps(Ncvec*2, input, buff[ib], buff[!ib], setup->twiddle, setup->ifac) == buff[1]); else ib = (cfftf1_ps(Ncvec, input, buff[ib], buff[!ib], setup->twiddle, setup->ifac, +1.0f) == buff[1]); } if(buff[ib] != output) { // extra copy required -- this situation should happens only when finput == foutput assert(input==output); for(size_t k{0};k < Ncvec;++k) { float a{buff[ib][2*k]}, b{buff[ib][2*k+1]}; output[2*k] = a; output[2*k+1] = b; } } } } // namespace void pffft_zreorder(const PFFFT_Setup *setup, const float *in, float *RESTRICT out, pffft_direction_t direction) { const size_t N{setup->N}; if(setup->transform == PFFFT_COMPLEX) { for(size_t k{0};k < 2*N;++k) out[k] = in[k]; return; } else if(direction == PFFFT_FORWARD) { float x_N{in[N-1]}; for(size_t k{N-1};k > 1;--k) out[k] = in[k-1]; out[0] = in[0]; out[1] = x_N; } else { float x_N{in[1]}; for(size_t k{1};k < N-1;++k) out[k] = in[k+1]; out[0] = in[0]; out[N-1] = x_N; } } void pffft_zconvolve_scale_accumulate(const PFFFT_Setup *s, const float *a, const float *b, float *ab, float scaling) { size_t Ncvec{s->Ncvec}; if(s->transform == PFFFT_REAL) { // take care of the fftpack ordering ab[0] += a[0]*b[0]*scaling; ab[2*Ncvec-1] += a[2*Ncvec-1]*b[2*Ncvec-1]*scaling; ++ab; ++a; ++b; --Ncvec; } for(size_t i{0};i < Ncvec;++i) { float ar{a[2*i+0]}, ai{a[2*i+1]}; const float br{b[2*i+0]}, bi{b[2*i+1]}; vcplxmul(ar, ai, br, bi); ab[2*i+0] += ar*scaling; ab[2*i+1] += ai*scaling; } } void pffft_zconvolve_accumulate(const PFFFT_Setup *s, const float *a, const float *b, float *ab) { size_t Ncvec{s->Ncvec}; if(s->transform == PFFFT_REAL) { // take care of the fftpack ordering ab[0] += a[0]*b[0]; ab[2*Ncvec-1] += a[2*Ncvec-1]*b[2*Ncvec-1]; ++ab; ++a; ++b; --Ncvec; } for(size_t i{0};i < Ncvec;++i) { float ar{a[2*i+0]}, ai{a[2*i+1]}; const float br{b[2*i+0]}, bi{b[2*i+1]}; vcplxmul(ar, ai, br, bi); ab[2*i+0] += ar; ab[2*i+1] += ai; } } void pffft_transform(const PFFFT_Setup *setup, const float *input, float *output, float *work, pffft_direction_t direction) { pffft_transform_internal(setup, input, output, work, direction, false); } void pffft_transform_ordered(const PFFFT_Setup *setup, const float *input, float *output, float *work, pffft_direction_t direction) { pffft_transform_internal(setup, input, output, work, direction, true); } #endif /* defined(PFFFT_SIMD_DISABLE) */ /* NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic) */ openal-soft-1.24.2/common/pffft.h000066400000000000000000000207671474041540300166130ustar00rootroot00000000000000/* Copyright (c) 2013 Julien Pommier ( pommier@modartt.com ) Based on original fortran 77 code from FFTPACKv4 from NETLIB, authored by Dr Paul Swarztrauber of NCAR, in 1985. As confirmed by the NCAR fftpack software curators, the following FFTPACKv5 license applies to FFTPACKv4 sources. My changes are released under the same terms. FFTPACK license: http://www.cisl.ucar.edu/css/software/fftpack5/ftpk.html Copyright (c) 2004 the University Corporation for Atmospheric Research ("UCAR"). All rights reserved. Developed by NCAR's Computational and Information Systems Laboratory, UCAR, www.cisl.ucar.edu. Redistribution and use of the Software in source and binary forms, with or without modification, is permitted provided that the following conditions are met: - Neither the names of NCAR's Computational and Information Systems Laboratory, the University Corporation for Atmospheric Research, nor the names of its sponsors or contributors may be used to endorse or promote products derived from this Software without specific prior written permission. - Redistributions of source code must retain the above copyright notices, this list of conditions, and the disclaimer below. - Redistributions in binary form must reproduce the above copyright notice, this list of conditions, and the disclaimer below in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE SOFTWARE. */ /* PFFFT : a Pretty Fast FFT. * * This is basically an adaptation of the single precision fftpack (v4) as * found on netlib taking advantage of SIMD instructions found on CPUs such as * Intel x86 (SSE1), PowerPC (Altivec), and Arm (NEON). * * For architectures where SIMD instructions aren't available, the code falls * back to a scalar version. * * Restrictions: * * - 1D transforms only, with 32-bit single precision. * * - supports only transforms for inputs of length N of the form * N=(2^a)*(3^b)*(5^c), given a >= 5, b >=0, c >= 0 (32, 48, 64, 96, 128, 144, * 160, etc are all acceptable lengths). Performance is best for 128<=N<=8192. * * - all (float*) pointers for the functions below are expected to have a * "SIMD-compatible" alignment, that is 16 bytes. * * You can allocate such buffers with the pffft_aligned_malloc function, and * deallocate them with pffft_aligned_free (or with stuff like posix_memalign, * aligned_alloc, etc). * * Note that for the z-domain data of real transforms, when in the canonical * order (as interleaved complex numbers) both 0-frequency and half-frequency * components, which are real, are assembled in the first entry as * F(0)+i*F(n/2+1). The original fftpack placed F(n/2+1) at the end of the * arrays instead. */ #ifndef PFFFT_H #define PFFFT_H #include #include #include "almalloc.h" /* opaque struct holding internal stuff (precomputed twiddle factors) this * struct can be shared by many threads as it contains only read-only data. */ struct PFFFT_Setup; /* direction of the transform */ enum pffft_direction_t { PFFFT_FORWARD, PFFFT_BACKWARD }; /* type of transform */ enum pffft_transform_t { PFFFT_REAL, PFFFT_COMPLEX }; void pffft_destroy_setup(gsl::owner setup) noexcept; struct PFFFTSetupDeleter { void operator()(gsl::owner setup) const noexcept { pffft_destroy_setup(setup); } }; using PFFFTSetupPtr = std::unique_ptr; /** * Prepare for performing transforms of size N -- the returned PFFFT_Setup * structure is read-only so it can safely be shared by multiple concurrent * threads. */ PFFFTSetupPtr pffft_new_setup(unsigned int N, pffft_transform_t transform); /** * Perform a Fourier transform. The z-domain data is stored in the most * efficient order for transforming back or using for convolution, and as * such, there's no guarantee to the order of the values. If you need to have * its content sorted in the usual way, that is as an array of interleaved * complex numbers, either use pffft_transform_ordered, or call pffft_zreorder * after the forward fft and before the backward fft. * * Transforms are not scaled: PFFFT_BACKWARD(PFFFT_FORWARD(x)) = N*x. Typically * you will want to scale the backward transform by 1/N. * * The 'work' pointer must point to an area of N (2*N for complex fft) floats, * properly aligned. It cannot be NULL. * * The input and output parameters may alias. */ void pffft_transform(const PFFFT_Setup *setup, const float *input, float *output, float *work, pffft_direction_t direction); /** * Similar to pffft_transform, but handles the complex values in the usual form * (interleaved complex numbers). This is similar to calling * pffft_transform(..., PFFFT_FORWARD) followed by * pffft_zreorder(..., PFFFT_FORWARD), or * pffft_zreorder(..., PFFFT_BACKWARD) followed by * pffft_transform(..., PFFFT_BACKWARD), for the given direction. * * The input and output parameters may alias. */ void pffft_transform_ordered(const PFFFT_Setup *setup, const float *input, float *output, float *work, pffft_direction_t direction); /** * Reorder the z-domain data. For PFFFT_FORWARD, it reorders from the internal * representation to the "canonical" order (as interleaved complex numbers). * For PFFFT_BACKWARD, it reorders from the canonical order to the internal * order suitable for pffft_transform(..., PFFFT_BACKWARD) or * pffft_zconvolve_accumulate. * * The input and output parameters should not alias. */ void pffft_zreorder(const PFFFT_Setup *setup, const float *input, float *output, pffft_direction_t direction); /** * Perform a multiplication of the z-domain data in dft_a and dft_b, and scale * and accumulate into dft_ab. The arrays should have been obtained with * pffft_transform(..., PFFFT_FORWARD) or pffft_zreorder(..., PFFFT_BACKWARD) * and should *not* be in the usual order (otherwise just perform the operation * yourself as the dft coeffs are stored as interleaved complex numbers). * * The operation performed is: dft_ab += (dft_a * dft_b)*scaling * * The dft_a, dft_b, and dft_ab parameters may alias. */ void pffft_zconvolve_scale_accumulate(const PFFFT_Setup *setup, const float *dft_a, const float *dft_b, float *dft_ab, float scaling); /** * Perform a multiplication of the z-domain data in dft_a and dft_b, and * accumulate into dft_ab. * * The operation performed is: dft_ab += dft_a * dft_b * * The dft_a, dft_b, and dft_ab parameters may alias. */ void pffft_zconvolve_accumulate(const PFFFT_Setup *setup, const float *dft_a, const float *dft_b, float *dft_ab); struct PFFFTSetup { PFFFTSetupPtr mSetup; PFFFTSetup() = default; PFFFTSetup(const PFFFTSetup&) = delete; PFFFTSetup(PFFFTSetup&& rhs) noexcept = default; explicit PFFFTSetup(std::nullptr_t) noexcept { } explicit PFFFTSetup(unsigned int n, pffft_transform_t transform) : mSetup{pffft_new_setup(n, transform)} { } ~PFFFTSetup() = default; PFFFTSetup& operator=(const PFFFTSetup&) = delete; PFFFTSetup& operator=(PFFFTSetup&& rhs) noexcept = default; [[nodiscard]] explicit operator bool() const noexcept { return mSetup != nullptr; } void transform(const float *input, float *output, float *work, pffft_direction_t direction) const { pffft_transform(mSetup.get(), input, output, work, direction); } void transform_ordered(const float *input, float *output, float *work, pffft_direction_t direction) const { pffft_transform_ordered(mSetup.get(), input, output, work, direction); } void zreorder(const float *input, float *output, pffft_direction_t direction) const { pffft_zreorder(mSetup.get(), input, output, direction); } void zconvolve_scale_accumulate(const float *dft_a, const float *dft_b, float *dft_ab, float scaling) const { pffft_zconvolve_scale_accumulate(mSetup.get(), dft_a, dft_b, dft_ab, scaling); } void zconvolve_accumulate(const float *dft_a, const float *dft_b, float *dft_ab) const { pffft_zconvolve_accumulate(mSetup.get(), dft_a, dft_b, dft_ab); } }; #endif // PFFFT_H openal-soft-1.24.2/common/phase_shifter.h000066400000000000000000000167701474041540300203310ustar00rootroot00000000000000#ifndef PHASE_SHIFTER_H #define PHASE_SHIFTER_H #include "config_simd.h" #if HAVE_SSE_INTRINSICS #include #elif HAVE_NEON #include #endif #include #include #include #include "alnumbers.h" #include "alspan.h" #include "opthelpers.h" /* Implements a wide-band +90 degree phase-shift. Note that this should be * given one sample less of a delay (FilterSize/2 - 1) compared to the direct * signal delay (FilterSize/2) to properly align. */ template struct SIMDALIGN PhaseShifterT { static_assert(FilterSize >= 16, "FilterSize needs to be at least 16"); static_assert((FilterSize&(FilterSize-1)) == 0, "FilterSize needs to be power-of-two"); alignas(16) std::array mCoeffs{}; PhaseShifterT() { /* Every other coefficient is 0, so we only need to calculate and store * the non-0 terms and double-step over the input to apply it. The * calculated coefficients are in reverse to make applying in the time- * domain more efficient. */ for(std::size_t i{0};i < FilterSize/2;++i) { const int k{static_cast(i*2 + 1) - int{FilterSize/2}}; /* Calculate the Blackman window value for this coefficient. */ const double w{2.0*al::numbers::pi * static_cast(i*2 + 1) / double{FilterSize}}; const double window{0.3635819 - 0.4891775*std::cos(w) + 0.1365995*std::cos(2.0*w) - 0.0106411*std::cos(3.0*w)}; const double pk{al::numbers::pi * static_cast(k)}; mCoeffs[i] = static_cast(window * (1.0-std::cos(pk)) / pk); } } void process(const al::span dst, const al::span src) const; private: #if HAVE_NEON static auto unpacklo(float32x4_t a, float32x4_t b) { float32x2x2_t result{vzip_f32(vget_low_f32(a), vget_low_f32(b))}; return vcombine_f32(result.val[0], result.val[1]); } static auto unpackhi(float32x4_t a, float32x4_t b) { float32x2x2_t result{vzip_f32(vget_high_f32(a), vget_high_f32(b))}; return vcombine_f32(result.val[0], result.val[1]); } static auto load4(float32_t a, float32_t b, float32_t c, float32_t d) { float32x4_t ret{vmovq_n_f32(a)}; ret = vsetq_lane_f32(b, ret, 1); ret = vsetq_lane_f32(c, ret, 2); ret = vsetq_lane_f32(d, ret, 3); return ret; } static void vtranspose4(float32x4_t &x0, float32x4_t &x1, float32x4_t &x2, float32x4_t &x3) { float32x4x2_t t0_{vzipq_f32(x0, x2)}; float32x4x2_t t1_{vzipq_f32(x1, x3)}; float32x4x2_t u0_{vzipq_f32(t0_.val[0], t1_.val[0])}; float32x4x2_t u1_{vzipq_f32(t0_.val[1], t1_.val[1])}; x0 = u0_.val[0]; x1 = u0_.val[1]; x2 = u1_.val[0]; x3 = u1_.val[1]; } #endif }; template NOINLINE inline void PhaseShifterT::process(const al::span dst, const al::span src) const { auto in = src.begin(); #if HAVE_SSE_INTRINSICS if(const std::size_t todo{dst.size()>>2}) { auto out = al::span{reinterpret_cast<__m128*>(dst.data()), todo}; std::generate(out.begin(), out.end(), [&in,this] { __m128 r0{_mm_setzero_ps()}; __m128 r1{_mm_setzero_ps()}; __m128 r2{_mm_setzero_ps()}; __m128 r3{_mm_setzero_ps()}; for(std::size_t j{0};j < mCoeffs.size();j+=4) { const __m128 coeffs{_mm_load_ps(&mCoeffs[j])}; const __m128 s0{_mm_loadu_ps(&in[j*2])}; const __m128 s1{_mm_loadu_ps(&in[j*2 + 4])}; const __m128 s2{_mm_movehl_ps(_mm_movelh_ps(s1, s1), s0)}; const __m128 s3{_mm_loadh_pi(_mm_movehl_ps(s1, s1), reinterpret_cast(&in[j*2 + 8]))}; __m128 s{_mm_shuffle_ps(s0, s1, _MM_SHUFFLE(2, 0, 2, 0))}; r0 = _mm_add_ps(r0, _mm_mul_ps(s, coeffs)); s = _mm_shuffle_ps(s0, s1, _MM_SHUFFLE(3, 1, 3, 1)); r1 = _mm_add_ps(r1, _mm_mul_ps(s, coeffs)); s = _mm_shuffle_ps(s2, s3, _MM_SHUFFLE(2, 0, 2, 0)); r2 = _mm_add_ps(r2, _mm_mul_ps(s, coeffs)); s = _mm_shuffle_ps(s2, s3, _MM_SHUFFLE(3, 1, 3, 1)); r3 = _mm_add_ps(r3, _mm_mul_ps(s, coeffs)); } in += 4; _MM_TRANSPOSE4_PS(r0, r1, r2, r3); return _mm_add_ps(_mm_add_ps(r0, r1), _mm_add_ps(r2, r3)); }); } if(const std::size_t todo{dst.size()&3}) { auto out = dst.last(todo); std::generate(out.begin(), out.end(), [&in,this] { __m128 r4{_mm_setzero_ps()}; for(std::size_t j{0};j < mCoeffs.size();j+=4) { const __m128 coeffs{_mm_load_ps(&mCoeffs[j])}; const __m128 s{_mm_setr_ps(in[j*2], in[j*2 + 2], in[j*2 + 4], in[j*2 + 6])}; r4 = _mm_add_ps(r4, _mm_mul_ps(s, coeffs)); } ++in; r4 = _mm_add_ps(r4, _mm_shuffle_ps(r4, r4, _MM_SHUFFLE(0, 1, 2, 3))); r4 = _mm_add_ps(r4, _mm_movehl_ps(r4, r4)); return _mm_cvtss_f32(r4); }); } #elif HAVE_NEON if(const std::size_t todo{dst.size()>>2}) { auto out = al::span{reinterpret_cast(dst.data()), todo}; std::generate(out.begin(), out.end(), [&in,this] { float32x4_t r0{vdupq_n_f32(0.0f)}; float32x4_t r1{vdupq_n_f32(0.0f)}; float32x4_t r2{vdupq_n_f32(0.0f)}; float32x4_t r3{vdupq_n_f32(0.0f)}; for(std::size_t j{0};j < mCoeffs.size();j+=4) { const float32x4_t coeffs{vld1q_f32(&mCoeffs[j])}; const float32x4_t s0{vld1q_f32(&in[j*2])}; const float32x4_t s1{vld1q_f32(&in[j*2 + 4])}; const float32x4_t s2{vcombine_f32(vget_high_f32(s0), vget_low_f32(s1))}; const float32x4_t s3{vcombine_f32(vget_high_f32(s1), vld1_f32(&in[j*2 + 8]))}; const float32x4x2_t values0{vuzpq_f32(s0, s1)}; const float32x4x2_t values1{vuzpq_f32(s2, s3)}; r0 = vmlaq_f32(r0, values0.val[0], coeffs); r1 = vmlaq_f32(r1, values0.val[1], coeffs); r2 = vmlaq_f32(r2, values1.val[0], coeffs); r3 = vmlaq_f32(r3, values1.val[1], coeffs); } in += 4; vtranspose4(r0, r1, r2, r3); return vaddq_f32(vaddq_f32(r0, r1), vaddq_f32(r2, r3)); }); } if(const std::size_t todo{dst.size()&3}) { auto out = dst.last(todo); std::generate(out.begin(), out.end(), [&in,this] { float32x4_t r4{vdupq_n_f32(0.0f)}; for(std::size_t j{0};j < mCoeffs.size();j+=4) { const float32x4_t coeffs{vld1q_f32(&mCoeffs[j])}; const float32x4_t s{load4(in[j*2], in[j*2 + 2], in[j*2 + 4], in[j*2 + 6])}; r4 = vmlaq_f32(r4, s, coeffs); } ++in; r4 = vaddq_f32(r4, vrev64q_f32(r4)); return vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0); }); } #else std::generate(dst.begin(), dst.end(), [&in,this] { float ret{0.0f}; for(std::size_t j{0};j < mCoeffs.size();++j) ret += in[j*2] * mCoeffs[j]; ++in; return ret; }); #endif } #endif /* PHASE_SHIFTER_H */ openal-soft-1.24.2/common/polyphase_resampler.cpp000066400000000000000000000146651474041540300221170ustar00rootroot00000000000000 #include "polyphase_resampler.h" #include #include #include #include #include #include "alnumbers.h" #include "opthelpers.h" using uint = unsigned int; namespace { constexpr double Epsilon{1e-9}; /* The zero-order modified Bessel function of the first kind, used for the * Kaiser window. * * I_0(x) = sum_{k=0}^inf (1 / k!)^2 (x / 2)^(2 k) * = sum_{k=0}^inf ((x / 2)^k / k!)^2 * * This implementation only handles nu = 0, and isn't the most precise (it * starts with the largest value and accumulates successively smaller values, * compounding the rounding and precision error), but it's good enough. */ template constexpr auto cyl_bessel_i(T nu, U x) -> U { if(nu != T{0}) throw std::runtime_error{"cyl_bessel_i: nu != 0"}; /* Start at k=1 since k=0 is trivial. */ const double x2{x/2.0}; double term{1.0}; double sum{1.0}; int k{1}; /* Let the integration converge until the term of the sum is no longer * significant. */ double last_sum{}; do { const double y{x2 / k}; ++k; last_sum = sum; term *= y * y; sum += term; } while(sum != last_sum); return static_cast(sum); } /* This is the normalized cardinal sine (sinc) function. * * sinc(x) = { 1, x = 0 * { sin(pi x) / (pi x), otherwise. */ double Sinc(const double x) { if(std::abs(x) < Epsilon) UNLIKELY return 1.0; return std::sin(al::numbers::pi*x) / (al::numbers::pi*x); } /* Calculate a Kaiser window from the given beta value and a normalized k * [-1, 1]. * * w(k) = { I_0(B sqrt(1 - k^2)) / I_0(B), -1 <= k <= 1 * { 0, elsewhere. * * Where k can be calculated as: * * k = i / l, where -l <= i <= l. * * or: * * k = 2 i / M - 1, where 0 <= i <= M. */ double Kaiser(const double beta, const double k, const double besseli_0_beta) { if(!(k >= -1.0 && k <= 1.0)) return 0.0; return ::cyl_bessel_i(0, beta * std::sqrt(1.0 - k*k)) / besseli_0_beta; } /* Calculates the size (order) of the Kaiser window. Rejection is in dB and * the transition width is normalized frequency (0.5 is nyquist). * * M = { ceil((r - 7.95) / (2.285 2 pi f_t)), r > 21 * { ceil(5.79 / 2 pi f_t), r <= 21. * */ constexpr uint CalcKaiserOrder(const double rejection, const double transition) { const double w_t{2.0 * al::numbers::pi * transition}; if(rejection > 21.0) LIKELY return static_cast(std::ceil((rejection - 7.95) / (2.285 * w_t))); return static_cast(std::ceil(5.79 / w_t)); } // Calculates the beta value of the Kaiser window. Rejection is in dB. constexpr double CalcKaiserBeta(const double rejection) { if(rejection > 50.0) LIKELY return 0.1102 * (rejection - 8.7); if(rejection >= 21.0) return (0.5842 * std::pow(rejection - 21.0, 0.4)) + (0.07886 * (rejection - 21.0)); return 0.0; } /* Calculates a point on the Kaiser-windowed sinc filter for the given half- * width, beta, gain, and cutoff. The point is specified in non-normalized * samples, from 0 to M, where M = (2 l + 1). * * w(k) 2 p f_t sinc(2 f_t x) * * x -- centered sample index (i - l) * k -- normalized and centered window index (x / l) * w(k) -- window function (Kaiser) * p -- gain compensation factor when sampling * f_t -- normalized center frequency (or cutoff; 0.5 is nyquist) */ double SincFilter(const uint l, const double beta, const double besseli_0_beta, const double gain, const double cutoff, const uint i) { const double x{static_cast(i) - l}; return Kaiser(beta, x/l, besseli_0_beta) * 2.0 * gain * cutoff * Sinc(2.0 * cutoff * x); } } // namespace // Calculate the resampling metrics and build the Kaiser-windowed sinc filter // that's used to cut frequencies above the destination nyquist. void PPhaseResampler::init(const uint srcRate, const uint dstRate) { const uint gcd{std::gcd(srcRate, dstRate)}; mP = dstRate / gcd; mQ = srcRate / gcd; /* The cutoff is adjusted by half the transition width, so the transition * ends before the nyquist (0.5). Both are scaled by the downsampling * factor. */ const auto [cutoff, width] = (mP > mQ) ? std::make_tuple(0.475 / mP, 0.05 / mP) : std::make_tuple(0.475 / mQ, 0.05 / mQ); // A rejection of -180 dB is used for the stop band. Round up when // calculating the left offset to avoid increasing the transition width. const uint l{(CalcKaiserOrder(180.0, width)+1) / 2}; const double beta{CalcKaiserBeta(180.0)}; const double besseli_0_beta{::cyl_bessel_i(0, beta)}; mM = l*2 + 1; mL = l; mF.resize(mM); for(uint i{0};i < mM;i++) mF[i] = SincFilter(l, beta, besseli_0_beta, mP, cutoff, i); } // Perform the upsample-filter-downsample resampling operation using a // polyphase filter implementation. void PPhaseResampler::process(const al::span in, const al::span out) { if(out.empty()) UNLIKELY return; // Handle in-place operation. std::vector workspace; al::span work{out}; if(work.data() == in.data()) UNLIKELY { workspace.resize(out.size()); work = workspace; } // Resample the input. const uint p{mP}, q{mQ}, m{mM}, l{mL}; const al::span f{mF}; for(uint i{0};i < out.size();i++) { // Input starts at l to compensate for the filter delay. This will // drop any build-up from the first half of the filter. std::size_t j_f{(l + q*i) % p}; std::size_t j_s{(l + q*i) / p}; // Only take input when 0 <= j_s < in.size(). double r{0.0}; if(j_f < m) LIKELY { std::size_t filt_len{(m-j_f+p-1) / p}; if(j_s+1 > in.size()) LIKELY { std::size_t skip{std::min(j_s+1 - in.size(), filt_len)}; j_f += p*skip; j_s -= skip; filt_len -= skip; } std::size_t todo{std::min(j_s+1, filt_len)}; while(todo) { r += f[j_f] * in[j_s]; j_f += p; --j_s; --todo; } } work[i] = r; } // Clean up after in-place operation. if(work.data() != out.data()) std::copy(work.cbegin(), work.cend(), out.begin()); } openal-soft-1.24.2/common/polyphase_resampler.h000066400000000000000000000032031474041540300215460ustar00rootroot00000000000000#ifndef POLYPHASE_RESAMPLER_H #define POLYPHASE_RESAMPLER_H #include #include "alspan.h" using uint = unsigned int; /* This is a polyphase sinc-filtered resampler. It is built for very high * quality results, rather than real-time performance. * * Upsample Downsample * * p/q = 3/2 p/q = 3/5 * * M-+-+-+-> M-+-+-+-> * -------------------+ ---------------------+ * p s * f f f f|f| | p s * f f f f f | * | 0 * 0 0 0|0|0 | | 0 * 0 0 0 0|0| | * v 0 * 0 0|0|0 0 | v 0 * 0 0 0|0|0 | * s * f|f|f f f | s * f f|f|f f | * 0 * |0|0 0 0 0 | 0 * 0|0|0 0 0 | * --------+=+--------+ 0 * |0|0 0 0 0 | * d . d .|d|. d . d ----------+=+--------+ * d . . . .|d|. . . . * q-> * q-+-+-+-> * * P_f(i,j) = q i mod p + pj * P_s(i,j) = floor(q i / p) - j * d[i=0..N-1] = sum_{j=0}^{floor((M - 1) / p)} { * { f[P_f(i,j)] s[P_s(i,j)], P_f(i,j) < M * { 0, P_f(i,j) >= M. } */ struct PPhaseResampler { void init(const uint srcRate, const uint dstRate); void process(const al::span in, const al::span out); explicit operator bool() const noexcept { return !mF.empty(); } private: uint mP{}, mQ{}, mM{}, mL{}; std::vector mF; }; #endif /* POLYPHASE_RESAMPLER_H */ openal-soft-1.24.2/common/pragmadefs.h000066400000000000000000000010021474041540300175740ustar00rootroot00000000000000#ifndef PRAGMADEFS_H #define PRAGMADEFS_H #if defined(_MSC_VER) #define DIAGNOSTIC_PUSH __pragma(warning(push)) #define DIAGNOSTIC_POP __pragma(warning(pop)) #define std_pragma(...) #define msc_pragma __pragma #else #if defined(__GNUC__) || defined(__clang__) #define DIAGNOSTIC_PUSH _Pragma("GCC diagnostic push") #define DIAGNOSTIC_POP _Pragma("GCC diagnostic pop") #else #define DIAGNOSTIC_PUSH #define DIAGNOSTIC_POP #endif #define std_pragma _Pragma #define msc_pragma(...) #endif #endif /* PRAGMADEFS_H */ openal-soft-1.24.2/common/ringbuffer.cpp000066400000000000000000000146331474041540300201650ustar00rootroot00000000000000/** * OpenAL cross platform audio library * Copyright (C) 1999-2007 by authors. * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * Or go to http://www.gnu.org/copyleft/lgpl.html */ #include "config.h" #include "ringbuffer.h" #include #include #include #include #include #include "alnumeric.h" #include "alspan.h" auto RingBuffer::Create(std::size_t sz, std::size_t elem_sz, bool limit_writes) -> RingBufferPtr { std::size_t power_of_two{0u}; if(sz > 0) { power_of_two = sz - 1; power_of_two |= power_of_two>>1; power_of_two |= power_of_two>>2; power_of_two |= power_of_two>>4; power_of_two |= power_of_two>>8; power_of_two |= power_of_two>>16; if constexpr(sizeof(size_t) > sizeof(uint32_t)) power_of_two |= power_of_two>>32; } ++power_of_two; if(power_of_two < sz || power_of_two > std::numeric_limits::max()>>1 || power_of_two > std::numeric_limits::max()/elem_sz) throw std::overflow_error{"Ring buffer size overflow"}; const std::size_t bufbytes{power_of_two * elem_sz}; RingBufferPtr rb{new(FamCount(bufbytes)) RingBuffer{limit_writes ? sz : power_of_two, power_of_two-1, elem_sz, bufbytes}}; return rb; } void RingBuffer::reset() noexcept { mWriteCount.store(0, std::memory_order_relaxed); mReadCount.store(0, std::memory_order_relaxed); std::fill_n(mBuffer.begin(), (mSizeMask+1)*mElemSize, std::byte{}); } auto RingBuffer::read(void *dest, std::size_t count) noexcept -> std::size_t { const std::size_t w{mWriteCount.load(std::memory_order_acquire)}; const std::size_t r{mReadCount.load(std::memory_order_relaxed)}; const std::size_t readable{w - r}; if(readable == 0) return 0; const std::size_t to_read{std::min(count, readable)}; const std::size_t read_idx{r & mSizeMask}; const std::size_t rdend{read_idx + to_read}; const auto [n1, n2] = (rdend <= mSizeMask+1) ? std::array{to_read, 0_uz} : std::array{mSizeMask+1 - read_idx, rdend&mSizeMask}; auto dstbytes = al::span{static_cast(dest), count*mElemSize}; auto outiter = std::copy_n(mBuffer.begin() + ptrdiff_t(read_idx*mElemSize), n1*mElemSize, dstbytes.begin()); if(n2 > 0) std::copy_n(mBuffer.begin(), n2*mElemSize, outiter); mReadCount.store(r+n1+n2, std::memory_order_release); return to_read; } auto RingBuffer::peek(void *dest, std::size_t count) const noexcept -> std::size_t { const std::size_t w{mWriteCount.load(std::memory_order_acquire)}; const std::size_t r{mReadCount.load(std::memory_order_relaxed)}; const std::size_t readable{w - r}; if(readable == 0) return 0; const std::size_t to_read{std::min(count, readable)}; const std::size_t read_idx{r & mSizeMask}; const std::size_t rdend{read_idx + to_read}; const auto [n1, n2] = (rdend <= mSizeMask+1) ? std::array{to_read, 0_uz} : std::array{mSizeMask+1 - read_idx, rdend&mSizeMask}; auto dstbytes = al::span{static_cast(dest), count*mElemSize}; auto outiter = std::copy_n(mBuffer.begin() + ptrdiff_t(read_idx*mElemSize), n1*mElemSize, dstbytes.begin()); if(n2 > 0) std::copy_n(mBuffer.begin(), n2*mElemSize, outiter); return to_read; } auto RingBuffer::write(const void *src, std::size_t count) noexcept -> std::size_t { const std::size_t w{mWriteCount.load(std::memory_order_relaxed)}; const std::size_t r{mReadCount.load(std::memory_order_acquire)}; const std::size_t writable{mWriteSize - (w - r)}; if(writable == 0) return 0; const std::size_t to_write{std::min(count, writable)}; const std::size_t write_idx{w & mSizeMask}; const std::size_t wrend{write_idx + to_write}; const auto [n1, n2] = (wrend <= mSizeMask+1) ? std::array{to_write, 0_uz} : std::array{mSizeMask+1 - write_idx, wrend&mSizeMask}; auto srcbytes = al::span{static_cast(src), count*mElemSize}; std::copy_n(srcbytes.cbegin(), n1*mElemSize, mBuffer.begin() + ptrdiff_t(write_idx*mElemSize)); if(n2 > 0) std::copy_n(srcbytes.cbegin() + ptrdiff_t(n1*mElemSize), n2*mElemSize, mBuffer.begin()); mWriteCount.store(w+n1+n2, std::memory_order_release); return to_write; } auto RingBuffer::getReadVector() noexcept -> DataPair { const std::size_t w{mWriteCount.load(std::memory_order_acquire)}; const std::size_t r{mReadCount.load(std::memory_order_relaxed)}; const std::size_t readable{w - r}; const std::size_t read_idx{r & mSizeMask}; const std::size_t rdend{read_idx + readable}; if(rdend > mSizeMask+1) { /* Two part vector: the rest of the buffer after the current read ptr, * plus some from the start of the buffer. */ return DataPair{{{mBuffer.data() + read_idx*mElemSize, mSizeMask+1 - read_idx}, {mBuffer.data(), rdend&mSizeMask}}}; } return DataPair{{{mBuffer.data() + read_idx*mElemSize, readable}, {}}}; } auto RingBuffer::getWriteVector() noexcept -> DataPair { const std::size_t w{mWriteCount.load(std::memory_order_relaxed)}; const std::size_t r{mReadCount.load(std::memory_order_acquire)}; const std::size_t writable{mWriteSize - (w - r)}; const std::size_t write_idx{w & mSizeMask}; const std::size_t wrend{write_idx + writable}; if(wrend > mSizeMask+1) { /* Two part vector: the rest of the buffer after the current write ptr, * plus some from the start of the buffer. */ return DataPair{{{mBuffer.data() + write_idx*mElemSize, mSizeMask+1 - write_idx}, {mBuffer.data(), wrend&mSizeMask}}}; } return DataPair{{{mBuffer.data() + write_idx*mElemSize, writable}, {}}}; } openal-soft-1.24.2/common/ringbuffer.h000066400000000000000000000120351474041540300176240ustar00rootroot00000000000000#ifndef RINGBUFFER_H #define RINGBUFFER_H #include #include #include #include #include #include "almalloc.h" #include "flexarray.h" /* NOTE: This lockless ringbuffer implementation is copied from JACK, extended * to include an element size. Consequently, parameters and return values for a * size or count are in 'elements', not bytes. Additionally, it only supports * single-consumer/single-provider operation. */ struct RingBuffer { private: #if defined(__cpp_lib_hardware_interference_size) && !defined(_LIBCPP_VERSION) static constexpr std::size_t sCacheAlignment{std::hardware_destructive_interference_size}; #else /* Assume a 64-byte cache line, the most common/likely value. */ static constexpr std::size_t sCacheAlignment{64}; #endif alignas(sCacheAlignment) std::atomic mWriteCount{0u}; alignas(sCacheAlignment) std::atomic mReadCount{0u}; alignas(sCacheAlignment) const std::size_t mWriteSize; const std::size_t mSizeMask; const std::size_t mElemSize; al::FlexArray mBuffer; public: struct Data { std::byte *buf; std::size_t len; }; using DataPair = std::array; RingBuffer(const std::size_t writesize, const std::size_t mask, const std::size_t elemsize, const std::size_t numbytes) : mWriteSize{writesize}, mSizeMask{mask}, mElemSize{elemsize}, mBuffer{numbytes} { } /** Reset the read and write pointers to zero. This is not thread safe. */ auto reset() noexcept -> void; /** * Return the number of elements available for reading. This is the number * of elements in front of the read pointer and behind the write pointer. */ [[nodiscard]] auto readSpace() const noexcept -> std::size_t { const std::size_t w{mWriteCount.load(std::memory_order_acquire)}; const std::size_t r{mReadCount.load(std::memory_order_acquire)}; /* mWriteCount is never more than mWriteSize greater than mReadCount. */ return w - r; } /** * The copying data reader. Copy at most `count' elements into `dest'. * Returns the actual number of elements copied. */ [[nodiscard]] auto read(void *dest, std::size_t count) noexcept -> std::size_t; /** * The copying data reader w/o read pointer advance. Copy at most `count' * elements into `dest'. Returns the actual number of elements copied. */ [[nodiscard]] auto peek(void *dest, std::size_t count) const noexcept -> std::size_t; /** * The non-copying data reader. Returns two ringbuffer data pointers that * hold the current readable data. If the readable data is in one segment * the second segment has zero length. */ [[nodiscard]] auto getReadVector() noexcept -> DataPair; /** Advance the read pointer `count' places. */ auto readAdvance(std::size_t count) noexcept -> void { const std::size_t w{mWriteCount.load(std::memory_order_acquire)}; const std::size_t r{mReadCount.load(std::memory_order_relaxed)}; [[maybe_unused]] const std::size_t readable{w - r}; assert(readable >= count); mReadCount.store(r+count, std::memory_order_release); } /** * Return the number of elements available for writing. This is the total * number of writable elements excluding what's readable (already written). */ [[nodiscard]] auto writeSpace() const noexcept -> std::size_t { return mWriteSize - readSpace(); } /** * The copying data writer. Copy at most `count' elements from `src'. Returns * the actual number of elements copied. */ [[nodiscard]] auto write(const void *src, std::size_t count) noexcept -> std::size_t; /** * The non-copying data writer. Returns two ringbuffer data pointers that * hold the current writeable data. If the writeable data is in one segment * the second segment has zero length. */ [[nodiscard]] auto getWriteVector() noexcept -> DataPair; /** Advance the write pointer `count' places. */ auto writeAdvance(std::size_t count) noexcept -> void { const std::size_t w{mWriteCount.load(std::memory_order_relaxed)}; const std::size_t r{mReadCount.load(std::memory_order_acquire)}; [[maybe_unused]] const std::size_t writable{mWriteSize - (w - r)}; assert(writable >= count); mWriteCount.store(w+count, std::memory_order_release); } [[nodiscard]] auto getElemSize() const noexcept -> std::size_t { return mElemSize; } /** * Create a new ringbuffer to hold at least `sz' elements of `elem_sz' * bytes. The number of elements is rounded up to a power of two. If * `limit_writes' is true, the writable space will be limited to `sz' * elements regardless of the rounded size. */ [[nodiscard]] static auto Create(std::size_t sz, std::size_t elem_sz, bool limit_writes) -> std::unique_ptr; DEF_FAM_NEWDEL(RingBuffer, mBuffer) }; using RingBufferPtr = std::unique_ptr; #endif /* RINGBUFFER_H */ openal-soft-1.24.2/common/strutils.cpp000066400000000000000000000027461474041540300177270ustar00rootroot00000000000000 #include "config.h" #include "strutils.h" #include #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #include #include "alstring.h" /* NOLINTBEGIN(bugprone-suspicious-stringview-data-usage) */ std::string wstr_to_utf8(std::wstring_view wstr) { std::string ret; const int len{WideCharToMultiByte(CP_UTF8, 0, wstr.data(), al::sizei(wstr), nullptr, 0, nullptr, nullptr)}; if(len > 0) { ret.resize(static_cast(len)); WideCharToMultiByte(CP_UTF8, 0, wstr.data(), al::sizei(wstr), ret.data(), len, nullptr, nullptr); } return ret; } std::wstring utf8_to_wstr(std::string_view str) { std::wstring ret; const int len{MultiByteToWideChar(CP_UTF8, 0, str.data(), al::sizei(str), nullptr, 0)}; if(len > 0) { ret.resize(static_cast(len)); MultiByteToWideChar(CP_UTF8, 0, str.data(), al::sizei(str), ret.data(), len); } return ret; } /* NOLINTEND(bugprone-suspicious-stringview-data-usage) */ #endif namespace al { std::optional getenv(const char *envname) { #ifdef _GAMING_XBOX const char *str{::getenv(envname)}; #else const char *str{std::getenv(envname)}; #endif if(str && *str != '\0') return str; return std::nullopt; } #ifdef _WIN32 std::optional getenv(const WCHAR *envname) { const WCHAR *str{_wgetenv(envname)}; if(str && *str != L'\0') return str; return std::nullopt; } #endif } // namespace al openal-soft-1.24.2/common/strutils.h000066400000000000000000000006771474041540300173750ustar00rootroot00000000000000#ifndef AL_STRUTILS_H #define AL_STRUTILS_H #include #include #ifdef _WIN32 #include #include std::string wstr_to_utf8(std::wstring_view wstr); std::wstring utf8_to_wstr(std::string_view str); #endif namespace al { std::optional getenv(const char *envname); #ifdef _WIN32 std::optional getenv(const wchar_t *envname); #endif } // namespace al #endif /* AL_STRUTILS_H */ openal-soft-1.24.2/common/vecmat.h000066400000000000000000000100411474041540300167450ustar00rootroot00000000000000#ifndef COMMON_VECMAT_H #define COMMON_VECMAT_H #include #include #include #include #include #include "alspan.h" namespace alu { class Vector { alignas(16) std::array mVals{}; public: constexpr Vector() noexcept = default; constexpr Vector(const Vector&) noexcept = default; constexpr Vector(Vector&&) noexcept = default; constexpr explicit Vector(float a, float b, float c, float d) noexcept : mVals{{a,b,c,d}} { } constexpr auto operator=(const Vector&) noexcept -> Vector& = default; constexpr auto operator=(Vector&&) noexcept -> Vector& = default; [[nodiscard]] constexpr auto operator[](std::size_t idx) noexcept -> float& { return mVals[idx]; } [[nodiscard]] constexpr auto operator[](std::size_t idx) const noexcept -> const float& { return mVals[idx]; } constexpr auto operator+=(const Vector &rhs) noexcept -> Vector& { mVals[0] += rhs.mVals[0]; mVals[1] += rhs.mVals[1]; mVals[2] += rhs.mVals[2]; mVals[3] += rhs.mVals[3]; return *this; } [[nodiscard]] constexpr auto operator-(const Vector &rhs) const noexcept -> Vector { return Vector{mVals[0] - rhs.mVals[0], mVals[1] - rhs.mVals[1], mVals[2] - rhs.mVals[2], mVals[3] - rhs.mVals[3]}; } constexpr auto normalize() -> float { const auto length_sqr = float{mVals[0]*mVals[0] + mVals[1]*mVals[1] + mVals[2]*mVals[2]}; if(length_sqr > std::numeric_limits::epsilon()) { const auto length = std::sqrt(length_sqr); auto inv_length = float{1.0f / length}; mVals[0] *= inv_length; mVals[1] *= inv_length; mVals[2] *= inv_length; return length; } mVals[0] = mVals[1] = mVals[2] = 0.0f; return 0.0f; } [[nodiscard]] constexpr auto cross_product(const Vector &rhs) const noexcept -> Vector { return Vector{ mVals[1]*rhs.mVals[2] - mVals[2]*rhs.mVals[1], mVals[2]*rhs.mVals[0] - mVals[0]*rhs.mVals[2], mVals[0]*rhs.mVals[1] - mVals[1]*rhs.mVals[0], 0.0f}; } [[nodiscard]] constexpr auto dot_product(const Vector &rhs) const noexcept -> float { return mVals[0]*rhs.mVals[0] + mVals[1]*rhs.mVals[1] + mVals[2]*rhs.mVals[2]; } }; class Matrix { alignas(16) std::array mVals{}; public: constexpr Matrix() noexcept = default; constexpr Matrix(const Matrix&) noexcept = default; constexpr Matrix(Matrix&&) noexcept = default; constexpr explicit Matrix( float aa, float ab, float ac, float ad, float ba, float bb, float bc, float bd, float ca, float cb, float cc, float cd, float da, float db, float dc, float dd) noexcept : mVals{{aa,ab,ac,ad, ba,bb,bc,bd, ca,cb,cc,cd, da,db,dc,dd}} { } constexpr auto operator=(const Matrix&) noexcept -> Matrix& = default; constexpr auto operator=(Matrix&&) noexcept -> Matrix& = default; [[nodiscard]] constexpr auto operator[](std::size_t idx) noexcept { return al::span{&mVals[idx*4], 4}; } [[nodiscard]] constexpr auto operator[](std::size_t idx) const noexcept { return al::span{&mVals[idx*4], 4}; } [[nodiscard]] static constexpr auto Identity() noexcept -> Matrix { return Matrix{ 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}; } [[nodiscard]] friend constexpr auto operator*(const Matrix &mtx, const Vector &vec) noexcept -> Vector { return Vector{ vec[0]*mtx[0][0] + vec[1]*mtx[1][0] + vec[2]*mtx[2][0] + vec[3]*mtx[3][0], vec[0]*mtx[0][1] + vec[1]*mtx[1][1] + vec[2]*mtx[2][1] + vec[3]*mtx[3][1], vec[0]*mtx[0][2] + vec[1]*mtx[1][2] + vec[2]*mtx[2][2] + vec[3]*mtx[3][2], vec[0]*mtx[0][3] + vec[1]*mtx[1][3] + vec[2]*mtx[2][3] + vec[3]*mtx[3][3]}; } }; } // namespace alu #endif /* COMMON_VECMAT_H */ openal-soft-1.24.2/common/vector.h000066400000000000000000000004261474041540300167760ustar00rootroot00000000000000#ifndef AL_VECTOR_H #define AL_VECTOR_H #include #include #include "almalloc.h" namespace al { template using vector = std::vector>; } // namespace al #endif /* AL_VECTOR_H */ openal-soft-1.24.2/common/win_main_utf8.h000066400000000000000000000061771474041540300202540ustar00rootroot00000000000000#ifndef WIN_MAIN_UTF8_H #define WIN_MAIN_UTF8_H /* For Windows systems this provides a way to get UTF-8 encoded argv strings, * and also overrides fopen to accept UTF-8 filenames. Working with wmain * directly complicates cross-platform compatibility, while normal main() in * Windows uses the current codepage (which has limited availability of * characters). * * For MinGW, you must link with -municode */ #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #include #include #include #ifdef __cplusplus #include #define STATIC_CAST(...) static_cast<__VA_ARGS__> #define REINTERPRET_CAST(...) reinterpret_cast<__VA_ARGS__> #define MAYBE_UNUSED [[maybe_unused]] #else #define STATIC_CAST(...) (__VA_ARGS__) #define REINTERPRET_CAST(...) (__VA_ARGS__) #ifdef __GNUC__ #define MAYBE_UNUSED __attribute__((__unused__)) #else #define MAYBE_UNUSED #endif #endif MAYBE_UNUSED static FILE *my_fopen(const char *fname, const char *mode) { wchar_t *wname=NULL, *wmode=NULL; int namelen, modelen; FILE *file = NULL; errno_t err; namelen = MultiByteToWideChar(CP_UTF8, 0, fname, -1, NULL, 0); modelen = MultiByteToWideChar(CP_UTF8, 0, mode, -1, NULL, 0); if(namelen <= 0 || modelen <= 0) { fprintf(stderr, "Failed to convert UTF-8 fname \"%s\", mode \"%s\"\n", fname, mode); return NULL; } #ifdef __cplusplus auto strbuf = std::make_unique(static_cast(namelen) + static_cast(modelen)); wname = strbuf.get(); #else wname = (wchar_t*)calloc(sizeof(wchar_t), (size_t)namelen + (size_t)modelen); #endif wmode = wname + namelen; MultiByteToWideChar(CP_UTF8, 0, fname, -1, wname, namelen); MultiByteToWideChar(CP_UTF8, 0, mode, -1, wmode, modelen); err = _wfopen_s(&file, wname, wmode); if(err) { errno = err; file = NULL; } #ifndef __cplusplus free(wname); #endif return file; } #define fopen my_fopen /* SDL overrides main and provides UTF-8 args for us. */ #if !defined(SDL_MAIN_NEEDED) && !defined(SDL_MAIN_AVAILABLE) int my_main(int, char**); #define main my_main #ifdef __cplusplus extern "C" #endif int wmain(int argc, wchar_t **wargv) { char **argv; size_t total; int i; total = sizeof(*argv) * STATIC_CAST(size_t)(argc); for(i = 0;i < argc;i++) total += STATIC_CAST(size_t)(WideCharToMultiByte(CP_UTF8, 0, wargv[i], -1, NULL, 0, NULL, NULL)); #ifdef __cplusplus auto argbuf = std::make_unique(total); argv = reinterpret_cast(argbuf.get()); #else argv = (char**)calloc(1, total); #endif argv[0] = REINTERPRET_CAST(char*)(argv + argc); for(i = 0;i < argc-1;i++) { int len = WideCharToMultiByte(CP_UTF8, 0, wargv[i], -1, argv[i], 65535, NULL, NULL); argv[i+1] = argv[i] + len; } WideCharToMultiByte(CP_UTF8, 0, wargv[i], -1, argv[i], 65535, NULL, NULL); #ifdef __cplusplus return main(argc, argv); #else i = main(argc, argv); free(argv); return i; #endif } #endif /* !defined(SDL_MAIN_NEEDED) && !defined(SDL_MAIN_AVAILABLE) */ #endif /* _WIN32 */ #endif /* WIN_MAIN_UTF8_H */ openal-soft-1.24.2/config.h.in000066400000000000000000000025261474041540300160610ustar00rootroot00000000000000/* Define the alignment attribute for externally callable functions. */ #define FORCE_ALIGN @ALSOFT_FORCE_ALIGN@ /* Define if HRTF data is embedded in the library */ #cmakedefine ALSOFT_EMBED_HRTF_DATA /* Define if we have the proc_pidpath function */ #cmakedefine HAVE_PROC_PIDPATH /* Define if we have dlfcn.h */ #cmakedefine HAVE_DLFCN_H /* Define if we have pthread_np.h */ #cmakedefine HAVE_PTHREAD_NP_H /* Define if we have cpuid.h */ #cmakedefine HAVE_CPUID_H /* Define if we have intrin.h */ #cmakedefine HAVE_INTRIN_H /* Define if we have guiddef.h */ #cmakedefine HAVE_GUIDDEF_H /* Define if we have GCC's __get_cpuid() */ #cmakedefine HAVE_GCC_GET_CPUID /* Define if we have the __cpuid() intrinsic */ #cmakedefine HAVE_CPUID_INTRINSIC /* Define if we have pthread_setschedparam() */ #cmakedefine HAVE_PTHREAD_SETSCHEDPARAM /* Define if we have pthread_setname_np() */ #cmakedefine HAVE_PTHREAD_SETNAME_NP /* Define if we have pthread_set_name_np() */ #cmakedefine HAVE_PTHREAD_SET_NAME_NP /* Define the installation data directory */ #cmakedefine ALSOFT_INSTALL_DATADIR "@ALSOFT_INSTALL_DATADIR@" /* Define to 1 if we have DBus/RTKit, else 0 */ #cmakedefine01 HAVE_RTKIT /* Define to 1 if building for winuwp, else 0 */ #cmakedefine01 ALSOFT_UWP /* Define to 1 if building with legacy EAX API support, else 0 */ #cmakedefine01 ALSOFT_EAX openal-soft-1.24.2/config_backends.h.in000066400000000000000000000010571474041540300177110ustar00rootroot00000000000000/* Define to 1 if the given backend is enabled, else 0 */ #cmakedefine01 HAVE_ALSA #cmakedefine01 HAVE_OSS #cmakedefine01 HAVE_PIPEWIRE #cmakedefine01 HAVE_SOLARIS #cmakedefine01 HAVE_SNDIO #cmakedefine01 HAVE_WASAPI #cmakedefine01 HAVE_DSOUND #cmakedefine01 HAVE_WINMM #cmakedefine01 HAVE_PORTAUDIO #cmakedefine01 HAVE_PULSEAUDIO #cmakedefine01 HAVE_JACK #cmakedefine01 HAVE_COREAUDIO #cmakedefine01 HAVE_OPENSL #cmakedefine01 HAVE_OBOE #cmakedefine01 HAVE_OTHERIO #cmakedefine01 HAVE_WAVE #cmakedefine01 HAVE_SDL3 #cmakedefine01 HAVE_SDL2 openal-soft-1.24.2/config_simd.h.in000066400000000000000000000004301474041540300170650ustar00rootroot00000000000000/* Define to 1 if we have SSE CPU extensions, else 0 */ #cmakedefine01 HAVE_SSE #cmakedefine01 HAVE_SSE2 #cmakedefine01 HAVE_SSE3 #cmakedefine01 HAVE_SSE4_1 #cmakedefine01 HAVE_SSE_INTRINSICS /* Define to 1 if we have ARM Neon CPU extensions, else 0 */ #cmakedefine01 HAVE_NEON openal-soft-1.24.2/configs/000077500000000000000000000000001474041540300154615ustar00rootroot00000000000000openal-soft-1.24.2/configs/HRTF+WASAPI Exclusive/000077500000000000000000000000001474041540300211345ustar00rootroot00000000000000openal-soft-1.24.2/configs/HRTF+WASAPI Exclusive/alsoft.ini000066400000000000000000000001701474041540300231230ustar00rootroot00000000000000[General] channels=stereo stereo-mode=headphones stereo-encoding=hrtf period_size=1 [wasapi] exclusive-mode=trueopenal-soft-1.24.2/configs/HRTF/000077500000000000000000000000001474041540300162245ustar00rootroot00000000000000openal-soft-1.24.2/configs/HRTF/alsoft.ini000066400000000000000000000001121474041540300202070ustar00rootroot00000000000000[general] channels=stereo stereo-mode=headphones stereo-encoding=hrtf openal-soft-1.24.2/configs/WASAPI Exclusive/000077500000000000000000000000001474041540300203755ustar00rootroot00000000000000openal-soft-1.24.2/configs/WASAPI Exclusive/alsoft.ini000066400000000000000000000000711474041540300223640ustar00rootroot00000000000000[General] period_size=1 [wasapi] exclusive-mode=trueopenal-soft-1.24.2/core/000077500000000000000000000000001474041540300147615ustar00rootroot00000000000000openal-soft-1.24.2/core/ambdec.cpp000066400000000000000000000230651474041540300167060ustar00rootroot00000000000000 #include "config.h" #include "ambdec.h" #include #include #include #include #include #include #include #include #include #include "albit.h" #include "alspan.h" #include "filesystem.h" #include "fmt/core.h" namespace { std::string read_word(std::istream &f) { std::string ret; f >> ret; return ret; } bool is_at_end(const std::string &buffer, std::size_t endpos) { while(endpos < buffer.length() && std::isspace(buffer[endpos])) ++endpos; return !(endpos < buffer.length() && buffer[endpos] != '#'); } enum class ReaderScope { Global, Speakers, LFMatrix, HFMatrix, }; template auto make_error(size_t linenum, fmt::format_string fmt, Args&& ...args) -> std::optional { std::optional ret; auto &str = ret.emplace(fmt::format("Line {}: ", linenum)); str += fmt::format(std::move(fmt), std::forward(args)...); return ret; } } // namespace AmbDecConf::~AmbDecConf() = default; std::optional AmbDecConf::load(const char *fname) noexcept { fs::ifstream f{fs::u8path(fname)}; if(!f.is_open()) return std::string("Failed to open file \"")+fname+"\""; ReaderScope scope{ReaderScope::Global}; size_t speaker_pos{0}; size_t lfmatrix_pos{0}; size_t hfmatrix_pos{0}; size_t linenum{0}; std::string buffer; while(f.good() && std::getline(f, buffer)) { ++linenum; std::istringstream istr{buffer}; std::string command{read_word(istr)}; if(command.empty() || command[0] == '#') continue; if(command == "/}") { if(scope == ReaderScope::Global) return make_error(linenum, "Unexpected /}} in global scope"); scope = ReaderScope::Global; continue; } if(scope == ReaderScope::Speakers) { if(command == "add_spkr") { if(speaker_pos == Speakers.size()) return make_error(linenum, "Too many speakers specified"); AmbDecConf::SpeakerConf &spkr = Speakers[speaker_pos++]; istr >> spkr.Name; istr >> spkr.Distance; istr >> spkr.Azimuth; istr >> spkr.Elevation; istr >> spkr.Connection; } else return make_error(linenum, "Unexpected speakers command: {}", command); } else if(scope == ReaderScope::LFMatrix || scope == ReaderScope::HFMatrix) { auto &gains = (scope == ReaderScope::LFMatrix) ? LFOrderGain : HFOrderGain; auto matrix = (scope == ReaderScope::LFMatrix) ? LFMatrix : HFMatrix; auto &pos = (scope == ReaderScope::LFMatrix) ? lfmatrix_pos : hfmatrix_pos; if(command == "order_gain") { size_t toread{(ChanMask > Ambi3OrderMask) ? 5u : 4u}; std::size_t curgain{0u}; float value{}; while(toread) { --toread; istr >> value; if(curgain < std::size(gains)) gains[curgain++] = value; } } else if(command == "add_row") { if(pos == Speakers.size()) return make_error(linenum, "Too many matrix rows specified"); unsigned int mask{ChanMask}; AmbDecConf::CoeffArray &mtxrow = matrix[pos++]; mtxrow.fill(0.0f); float value{}; while(mask) { auto idx = static_cast(al::countr_zero(mask)); mask &= ~(1u << idx); istr >> value; if(idx < mtxrow.size()) mtxrow[idx] = value; } } else return make_error(linenum, "Unexpected matrix command: {}", command); } // Global scope commands else if(command == "/description") { while(istr.good() && std::isspace(istr.peek())) istr.ignore(); std::getline(istr, Description); while(!Description.empty() && std::isspace(Description.back())) Description.pop_back(); } else if(command == "/version") { if(Version) return make_error(linenum, "Duplicate version definition"); istr >> Version; if(Version != 3) return make_error(linenum, "Unsupported version: {}", Version); } else if(command == "/dec/chan_mask") { if(ChanMask) return make_error(linenum, "Duplicate chan_mask definition"); istr >> std::hex >> ChanMask >> std::dec; if(!ChanMask || ChanMask > Ambi4OrderMask) return make_error(linenum, "Invalid chan_mask: {:#x}", ChanMask); if(ChanMask > Ambi3OrderMask && CoeffScale == AmbDecScale::FuMa) return make_error(linenum, "FuMa not compatible with over third-order"); } else if(command == "/dec/freq_bands") { if(FreqBands) return make_error(linenum, "Duplicate freq_bands"); istr >> FreqBands; if(FreqBands != 1 && FreqBands != 2) return make_error(linenum, "Invalid freq_bands: {}", FreqBands); } else if(command == "/dec/speakers") { if(!Speakers.empty()) return make_error(linenum, "Duplicate speakers"); size_t numspeakers{}; istr >> numspeakers; if(!numspeakers) return make_error(linenum, "Invalid speakers: {}", numspeakers); Speakers.resize(numspeakers); } else if(command == "/dec/coeff_scale") { if(CoeffScale != AmbDecScale::Unset) return make_error(linenum, "Duplicate coeff_scale"); std::string scale{read_word(istr)}; if(scale == "n3d") CoeffScale = AmbDecScale::N3D; else if(scale == "sn3d") CoeffScale = AmbDecScale::SN3D; else if(scale == "fuma") CoeffScale = AmbDecScale::FuMa; else return make_error(linenum, "Unexpected coeff_scale: {}", scale); if(ChanMask > Ambi3OrderMask && CoeffScale == AmbDecScale::FuMa) return make_error(linenum, "FuMa not compatible with over third-order"); } else if(command == "/opt/xover_freq") { istr >> XOverFreq; } else if(command == "/opt/xover_ratio") { istr >> XOverRatio; } else if(command == "/opt/input_scale" || command == "/opt/nfeff_comp" || command == "/opt/delay_comp" || command == "/opt/level_comp") { /* Unused */ read_word(istr); } else if(command == "/speakers/{") { if(Speakers.empty()) return make_error(linenum, "Speakers defined without a count"); scope = ReaderScope::Speakers; } else if(command == "/lfmatrix/{" || command == "/hfmatrix/{" || command == "/matrix/{") { if(Speakers.empty()) return make_error(linenum, "Matrix defined without a speaker count"); if(!ChanMask) return make_error(linenum, "Matrix defined without a channel mask"); if(Matrix.empty()) { Matrix.resize(Speakers.size() * FreqBands); LFMatrix = al::span{Matrix}.first(Speakers.size()); HFMatrix = al::span{Matrix}.subspan(Speakers.size()*(FreqBands-1)); } if(FreqBands == 1) { if(command != "/matrix/{") return make_error(linenum, "Unexpected \"{}\" for a single-band decoder", command); scope = ReaderScope::HFMatrix; } else { if(command == "/lfmatrix/{") scope = ReaderScope::LFMatrix; else if(command == "/hfmatrix/{") scope = ReaderScope::HFMatrix; else return make_error(linenum, "Unexpected \"{}\" for a dual-band decoder", command); } } else if(command == "/end") { const auto endpos = static_cast(istr.tellg()); if(!is_at_end(buffer, endpos)) return make_error(linenum, "Extra junk on end: {}", std::string_view{buffer}.substr(endpos)); if(speaker_pos < Speakers.size() || hfmatrix_pos < Speakers.size() || (FreqBands == 2 && lfmatrix_pos < Speakers.size())) return make_error(linenum, "Incomplete decoder definition"); if(CoeffScale == AmbDecScale::Unset) return make_error(linenum, "No coefficient scaling defined"); return std::nullopt; } else return make_error(linenum, "Unexpected command: {}", command); istr.clear(); const auto endpos = static_cast(istr.tellg()); if(!is_at_end(buffer, endpos)) return make_error(linenum, "Extra junk on line: {}", std::string_view{buffer}.substr(endpos)); buffer.clear(); } return make_error(linenum, "Unexpected end of file"); } openal-soft-1.24.2/core/ambdec.h000066400000000000000000000023101474041540300163410ustar00rootroot00000000000000#ifndef CORE_AMBDEC_H #define CORE_AMBDEC_H #include #include #include #include #include #include "alspan.h" #include "core/ambidefs.h" /* Helpers to read .ambdec configuration files. */ enum class AmbDecScale { Unset, N3D, SN3D, FuMa, }; struct AmbDecConf { std::string Description; int Version{0}; /* Must be 3 */ unsigned int ChanMask{0u}; unsigned int FreqBands{0u}; /* Must be 1 or 2 */ AmbDecScale CoeffScale{AmbDecScale::Unset}; float XOverFreq{0.0f}; float XOverRatio{0.0f}; struct SpeakerConf { std::string Name; float Distance{0.0f}; float Azimuth{0.0f}; float Elevation{0.0f}; std::string Connection; }; std::vector Speakers; using CoeffArray = std::array; std::vector Matrix; /* Unused when FreqBands == 1 */ std::array LFOrderGain{}; al::span LFMatrix; std::array HFOrderGain{}; al::span HFMatrix; ~AmbDecConf(); std::optional load(const char *fname) noexcept; }; #endif /* CORE_AMBDEC_H */ openal-soft-1.24.2/core/ambidefs.cpp000066400000000000000000000640071474041540300172460ustar00rootroot00000000000000 #include "config.h" #include "ambidefs.h" #include "alnumbers.h" namespace { using AmbiChannelFloatArray = std::array; constexpr auto inv_sqrt3f = static_cast(1.0/al::numbers::sqrt3); /* These HF gains are derived from the same 32-point speaker array. The scale * factor between orders represents the same scale factors for any (regular) * speaker array decoder. e.g. Given a first-order source and second-order * output, applying an HF scale of HFScales[1][0] / HFScales[2][0] to channel 0 * will result in that channel being subsequently decoded for second-order as * if it was a first-order decoder for that same speaker array. */ constexpr std::array HFScales{ std::array{4.000000000e+00f, 2.309401077e+00f, 1.192569588e+00f, 7.189495850e-01f}, std::array{4.000000000e+00f, 2.309401077e+00f, 1.192569588e+00f, 7.189495850e-01f}, std::array{2.981423970e+00f, 2.309401077e+00f, 1.192569588e+00f, 7.189495850e-01f}, std::array{2.359168820e+00f, 2.031565936e+00f, 1.444598386e+00f, 7.189495850e-01f}, /*std::array{1.947005434e+00f, 1.764337084e+00f, 1.424707344e+00f, 9.755104127e-01f, 4.784482742e-01f}, */ }; /* Same as above, but using a 10-point horizontal-only speaker array. Should * only be used when the device is mixing in 2D B-Format for horizontal-only * output. */ constexpr std::array HFScales2D{ std::array{2.236067977e+00f, 1.581138830e+00f, 9.128709292e-01f, 6.050756345e-01f}, std::array{2.236067977e+00f, 1.581138830e+00f, 9.128709292e-01f, 6.050756345e-01f}, std::array{1.825741858e+00f, 1.581138830e+00f, 9.128709292e-01f, 6.050756345e-01f}, std::array{1.581138830e+00f, 1.460781803e+00f, 1.118033989e+00f, 6.050756345e-01f}, /*std::array{1.414213562e+00f, 1.344997024e+00f, 1.144122806e+00f, 8.312538756e-01f, 4.370160244e-01f}, */ }; /* This calculates a first-order "upsampler" matrix. It combines a first-order * decoder matrix with a max-order encoder matrix, creating a matrix that * behaves as if the B-Format input signal is first decoded to a speaker array * at first-order, then those speaker feeds are encoded to a higher-order * signal. While not perfect, this should accurately encode a lower-order * signal into a higher-order signal. */ constexpr std::array FirstOrderDecoder{ std::array{1.250000000e-01f, 1.250000000e-01f, 1.250000000e-01f, 1.250000000e-01f}, std::array{1.250000000e-01f, 1.250000000e-01f, 1.250000000e-01f, -1.250000000e-01f}, std::array{1.250000000e-01f, -1.250000000e-01f, 1.250000000e-01f, 1.250000000e-01f}, std::array{1.250000000e-01f, -1.250000000e-01f, 1.250000000e-01f, -1.250000000e-01f}, std::array{1.250000000e-01f, 1.250000000e-01f, -1.250000000e-01f, 1.250000000e-01f}, std::array{1.250000000e-01f, 1.250000000e-01f, -1.250000000e-01f, -1.250000000e-01f}, std::array{1.250000000e-01f, -1.250000000e-01f, -1.250000000e-01f, 1.250000000e-01f}, std::array{1.250000000e-01f, -1.250000000e-01f, -1.250000000e-01f, -1.250000000e-01f}, }; constexpr std::array FirstOrderEncoder{ CalcAmbiCoeffs( inv_sqrt3f, inv_sqrt3f, inv_sqrt3f), CalcAmbiCoeffs( inv_sqrt3f, inv_sqrt3f, -inv_sqrt3f), CalcAmbiCoeffs(-inv_sqrt3f, inv_sqrt3f, inv_sqrt3f), CalcAmbiCoeffs(-inv_sqrt3f, inv_sqrt3f, -inv_sqrt3f), CalcAmbiCoeffs( inv_sqrt3f, -inv_sqrt3f, inv_sqrt3f), CalcAmbiCoeffs( inv_sqrt3f, -inv_sqrt3f, -inv_sqrt3f), CalcAmbiCoeffs(-inv_sqrt3f, -inv_sqrt3f, inv_sqrt3f), CalcAmbiCoeffs(-inv_sqrt3f, -inv_sqrt3f, -inv_sqrt3f), }; static_assert(FirstOrderDecoder.size() == FirstOrderEncoder.size(), "First-order mismatch"); /* This calculates a 2D first-order "upsampler" matrix. Same as the first-order * matrix, just using a more optimized speaker array for horizontal-only * content. */ constexpr std::array FirstOrder2DDecoder{ std::array{1.666666667e-01f, -9.622504486e-02f, 0.0f, 1.666666667e-01f}, std::array{1.666666667e-01f, -1.924500897e-01f, 0.0f, 0.000000000e+00f}, std::array{1.666666667e-01f, -9.622504486e-02f, 0.0f, -1.666666667e-01f}, std::array{1.666666667e-01f, 9.622504486e-02f, 0.0f, -1.666666667e-01f}, std::array{1.666666667e-01f, 1.924500897e-01f, 0.0f, 0.000000000e+00f}, std::array{1.666666667e-01f, 9.622504486e-02f, 0.0f, 1.666666667e-01f}, }; constexpr std::array FirstOrder2DEncoder{ CalcAmbiCoeffs(-0.50000000000f, 0.0f, 0.86602540379f), CalcAmbiCoeffs(-1.00000000000f, 0.0f, 0.00000000000f), CalcAmbiCoeffs(-0.50000000000f, 0.0f, -0.86602540379f), CalcAmbiCoeffs( 0.50000000000f, 0.0f, -0.86602540379f), CalcAmbiCoeffs( 1.00000000000f, 0.0f, 0.00000000000f), CalcAmbiCoeffs( 0.50000000000f, 0.0f, 0.86602540379f), }; static_assert(FirstOrder2DDecoder.size() == FirstOrder2DEncoder.size(), "First-order 2D mismatch"); /* This calculates a second-order "upsampler" matrix. Same as the first-order * matrix, just using a slightly more dense speaker array suitable for second- * order content. */ constexpr std::array SecondOrderDecoder{ std::array{8.333333333e-02f, 0.000000000e+00f, -7.588274978e-02f, 1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.591525047e-02f, -1.443375673e-01f, 1.167715449e-01f}, std::array{8.333333333e-02f, -1.227808683e-01f, 0.000000000e+00f, 7.588274978e-02f, -1.443375673e-01f, 0.000000000e+00f, -9.316949906e-02f, 0.000000000e+00f, -7.216878365e-02f}, std::array{8.333333333e-02f, -7.588274978e-02f, 1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.443375673e-01f, 1.090847495e-01f, 0.000000000e+00f, -4.460276122e-02f}, std::array{8.333333333e-02f, 0.000000000e+00f, 7.588274978e-02f, 1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.591525047e-02f, 1.443375673e-01f, 1.167715449e-01f}, std::array{8.333333333e-02f, -1.227808683e-01f, 0.000000000e+00f, -7.588274978e-02f, 1.443375673e-01f, 0.000000000e+00f, -9.316949906e-02f, 0.000000000e+00f, -7.216878365e-02f}, std::array{8.333333333e-02f, 7.588274978e-02f, -1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.443375673e-01f, 1.090847495e-01f, 0.000000000e+00f, -4.460276122e-02f}, std::array{8.333333333e-02f, 0.000000000e+00f, -7.588274978e-02f, -1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.591525047e-02f, 1.443375673e-01f, 1.167715449e-01f}, std::array{8.333333333e-02f, 1.227808683e-01f, 0.000000000e+00f, -7.588274978e-02f, -1.443375673e-01f, 0.000000000e+00f, -9.316949906e-02f, 0.000000000e+00f, -7.216878365e-02f}, std::array{8.333333333e-02f, 7.588274978e-02f, 1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, 1.443375673e-01f, 1.090847495e-01f, 0.000000000e+00f, -4.460276122e-02f}, std::array{8.333333333e-02f, 0.000000000e+00f, 7.588274978e-02f, -1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.591525047e-02f, -1.443375673e-01f, 1.167715449e-01f}, std::array{8.333333333e-02f, 1.227808683e-01f, 0.000000000e+00f, 7.588274978e-02f, 1.443375673e-01f, 0.000000000e+00f, -9.316949906e-02f, 0.000000000e+00f, -7.216878365e-02f}, std::array{8.333333333e-02f, -7.588274978e-02f, -1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, 1.443375673e-01f, 1.090847495e-01f, 0.000000000e+00f, -4.460276122e-02f}, }; constexpr std::array SecondOrderEncoder{ CalcAmbiCoeffs( 0.000000000e+00f, -5.257311121e-01f, 8.506508084e-01f), CalcAmbiCoeffs(-8.506508084e-01f, 0.000000000e+00f, 5.257311121e-01f), CalcAmbiCoeffs(-5.257311121e-01f, 8.506508084e-01f, 0.000000000e+00f), CalcAmbiCoeffs( 0.000000000e+00f, 5.257311121e-01f, 8.506508084e-01f), CalcAmbiCoeffs(-8.506508084e-01f, 0.000000000e+00f, -5.257311121e-01f), CalcAmbiCoeffs( 5.257311121e-01f, -8.506508084e-01f, 0.000000000e+00f), CalcAmbiCoeffs( 0.000000000e+00f, -5.257311121e-01f, -8.506508084e-01f), CalcAmbiCoeffs( 8.506508084e-01f, 0.000000000e+00f, -5.257311121e-01f), CalcAmbiCoeffs( 5.257311121e-01f, 8.506508084e-01f, 0.000000000e+00f), CalcAmbiCoeffs( 0.000000000e+00f, 5.257311121e-01f, -8.506508084e-01f), CalcAmbiCoeffs( 8.506508084e-01f, 0.000000000e+00f, 5.257311121e-01f), CalcAmbiCoeffs(-5.257311121e-01f, -8.506508084e-01f, 0.000000000e+00f), }; static_assert(SecondOrderDecoder.size() == SecondOrderEncoder.size(), "Second-order mismatch"); /* This calculates a 2D second-order "upsampler" matrix. Same as the second- * order matrix, just using a more optimized speaker array for horizontal-only * content. */ constexpr std::array SecondOrder2DDecoder{ std::array{1.666666667e-01f, -9.622504486e-02f, 0.0f, 1.666666667e-01f, -1.490711985e-01f, 0.0f, 0.0f, 0.0f, 8.606629658e-02f}, std::array{1.666666667e-01f, -1.924500897e-01f, 0.0f, 0.000000000e+00f, 0.000000000e+00f, 0.0f, 0.0f, 0.0f, -1.721325932e-01f}, std::array{1.666666667e-01f, -9.622504486e-02f, 0.0f, -1.666666667e-01f, 1.490711985e-01f, 0.0f, 0.0f, 0.0f, 8.606629658e-02f}, std::array{1.666666667e-01f, 9.622504486e-02f, 0.0f, -1.666666667e-01f, -1.490711985e-01f, 0.0f, 0.0f, 0.0f, 8.606629658e-02f}, std::array{1.666666667e-01f, 1.924500897e-01f, 0.0f, 0.000000000e+00f, 0.000000000e+00f, 0.0f, 0.0f, 0.0f, -1.721325932e-01f}, std::array{1.666666667e-01f, 9.622504486e-02f, 0.0f, 1.666666667e-01f, 1.490711985e-01f, 0.0f, 0.0f, 0.0f, 8.606629658e-02f}, }; constexpr std::array SecondOrder2DEncoder{ CalcAmbiCoeffs(-0.50000000000f, 0.0f, 0.86602540379f), CalcAmbiCoeffs(-1.00000000000f, 0.0f, 0.00000000000f), CalcAmbiCoeffs(-0.50000000000f, 0.0f, -0.86602540379f), CalcAmbiCoeffs( 0.50000000000f, 0.0f, -0.86602540379f), CalcAmbiCoeffs( 1.00000000000f, 0.0f, 0.00000000000f), CalcAmbiCoeffs( 0.50000000000f, 0.0f, 0.86602540379f), }; static_assert(SecondOrder2DDecoder.size() == SecondOrder2DEncoder.size(), "Second-order 2D mismatch"); /* This calculates a third-order "upsampler" matrix. Same as the first-order * matrix, just using a more dense speaker array suitable for third-order * content. */ constexpr std::array ThirdOrderDecoder{ std::array{5.000000000e-02f, 3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, 6.454972244e-02f, 9.045084972e-02f, 0.000000000e+00f, -1.232790000e-02f, -1.256118221e-01f, 0.000000000e+00f, 1.126112056e-01f, 7.944389175e-02f, 0.000000000e+00f, 2.421151497e-02f, 0.000000000e+00f}, std::array{5.000000000e-02f, -3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -6.454972244e-02f, 9.045084972e-02f, 0.000000000e+00f, -1.232790000e-02f, 1.256118221e-01f, 0.000000000e+00f, -1.126112056e-01f, 7.944389175e-02f, 0.000000000e+00f, 2.421151497e-02f, 0.000000000e+00f}, std::array{5.000000000e-02f, 3.090169944e-02f, -8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -6.454972244e-02f, 9.045084972e-02f, 0.000000000e+00f, -1.232790000e-02f, -1.256118221e-01f, 0.000000000e+00f, 1.126112056e-01f, -7.944389175e-02f, 0.000000000e+00f, -2.421151497e-02f, 0.000000000e+00f}, std::array{5.000000000e-02f, -3.090169944e-02f, -8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, 6.454972244e-02f, 9.045084972e-02f, 0.000000000e+00f, -1.232790000e-02f, 1.256118221e-01f, 0.000000000e+00f, -1.126112056e-01f, -7.944389175e-02f, 0.000000000e+00f, -2.421151497e-02f, 0.000000000e+00f}, std::array{5.000000000e-02f, 8.090169944e-02f, 0.000000000e+00f, 3.090169944e-02f, 6.454972244e-02f, 0.000000000e+00f, -5.590169944e-02f, 0.000000000e+00f, -7.216878365e-02f, -7.763237543e-02f, 0.000000000e+00f, -2.950836627e-02f, 0.000000000e+00f, -1.497759251e-01f, 0.000000000e+00f, -7.763237543e-02f}, std::array{5.000000000e-02f, 8.090169944e-02f, 0.000000000e+00f, -3.090169944e-02f, -6.454972244e-02f, 0.000000000e+00f, -5.590169944e-02f, 0.000000000e+00f, -7.216878365e-02f, -7.763237543e-02f, 0.000000000e+00f, -2.950836627e-02f, 0.000000000e+00f, 1.497759251e-01f, 0.000000000e+00f, 7.763237543e-02f}, std::array{5.000000000e-02f, -8.090169944e-02f, 0.000000000e+00f, 3.090169944e-02f, -6.454972244e-02f, 0.000000000e+00f, -5.590169944e-02f, 0.000000000e+00f, -7.216878365e-02f, 7.763237543e-02f, 0.000000000e+00f, 2.950836627e-02f, 0.000000000e+00f, -1.497759251e-01f, 0.000000000e+00f, -7.763237543e-02f}, std::array{5.000000000e-02f, -8.090169944e-02f, 0.000000000e+00f, -3.090169944e-02f, 6.454972244e-02f, 0.000000000e+00f, -5.590169944e-02f, 0.000000000e+00f, -7.216878365e-02f, 7.763237543e-02f, 0.000000000e+00f, 2.950836627e-02f, 0.000000000e+00f, 1.497759251e-01f, 0.000000000e+00f, 7.763237543e-02f}, std::array{5.000000000e-02f, 0.000000000e+00f, 3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -3.454915028e-02f, 6.454972244e-02f, 8.449668365e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, 3.034486645e-02f, -6.779013272e-02f, 1.659481923e-01f, 4.797944664e-02f}, std::array{5.000000000e-02f, 0.000000000e+00f, 3.090169944e-02f, -8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -3.454915028e-02f, -6.454972244e-02f, 8.449668365e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, 3.034486645e-02f, 6.779013272e-02f, 1.659481923e-01f, -4.797944664e-02f}, std::array{5.000000000e-02f, 0.000000000e+00f, -3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -3.454915028e-02f, -6.454972244e-02f, 8.449668365e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, -3.034486645e-02f, -6.779013272e-02f, -1.659481923e-01f, 4.797944664e-02f}, std::array{5.000000000e-02f, 0.000000000e+00f, -3.090169944e-02f, -8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -3.454915028e-02f, 6.454972244e-02f, 8.449668365e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, -3.034486645e-02f, 6.779013272e-02f, -1.659481923e-01f, -4.797944664e-02f}, std::array{5.000000000e-02f, 5.000000000e-02f, 5.000000000e-02f, 5.000000000e-02f, 6.454972244e-02f, 6.454972244e-02f, 0.000000000e+00f, 6.454972244e-02f, 0.000000000e+00f, 1.016220987e-01f, 6.338656910e-02f, -1.092600649e-02f, -7.364853795e-02f, 1.011266756e-01f, -7.086833869e-02f, -1.482646439e-02f}, std::array{5.000000000e-02f, 5.000000000e-02f, 5.000000000e-02f, -5.000000000e-02f, -6.454972244e-02f, 6.454972244e-02f, 0.000000000e+00f, -6.454972244e-02f, 0.000000000e+00f, 1.016220987e-01f, -6.338656910e-02f, -1.092600649e-02f, -7.364853795e-02f, -1.011266756e-01f, -7.086833869e-02f, 1.482646439e-02f}, std::array{5.000000000e-02f, -5.000000000e-02f, 5.000000000e-02f, 5.000000000e-02f, -6.454972244e-02f, -6.454972244e-02f, 0.000000000e+00f, 6.454972244e-02f, 0.000000000e+00f, -1.016220987e-01f, -6.338656910e-02f, 1.092600649e-02f, -7.364853795e-02f, 1.011266756e-01f, -7.086833869e-02f, -1.482646439e-02f}, std::array{5.000000000e-02f, -5.000000000e-02f, 5.000000000e-02f, -5.000000000e-02f, 6.454972244e-02f, -6.454972244e-02f, 0.000000000e+00f, -6.454972244e-02f, 0.000000000e+00f, -1.016220987e-01f, 6.338656910e-02f, 1.092600649e-02f, -7.364853795e-02f, -1.011266756e-01f, -7.086833869e-02f, 1.482646439e-02f}, std::array{5.000000000e-02f, 5.000000000e-02f, -5.000000000e-02f, 5.000000000e-02f, 6.454972244e-02f, -6.454972244e-02f, 0.000000000e+00f, -6.454972244e-02f, 0.000000000e+00f, 1.016220987e-01f, -6.338656910e-02f, -1.092600649e-02f, 7.364853795e-02f, 1.011266756e-01f, 7.086833869e-02f, -1.482646439e-02f}, std::array{5.000000000e-02f, 5.000000000e-02f, -5.000000000e-02f, -5.000000000e-02f, -6.454972244e-02f, -6.454972244e-02f, 0.000000000e+00f, 6.454972244e-02f, 0.000000000e+00f, 1.016220987e-01f, 6.338656910e-02f, -1.092600649e-02f, 7.364853795e-02f, -1.011266756e-01f, 7.086833869e-02f, 1.482646439e-02f}, std::array{5.000000000e-02f, -5.000000000e-02f, -5.000000000e-02f, 5.000000000e-02f, -6.454972244e-02f, 6.454972244e-02f, 0.000000000e+00f, -6.454972244e-02f, 0.000000000e+00f, -1.016220987e-01f, 6.338656910e-02f, 1.092600649e-02f, 7.364853795e-02f, 1.011266756e-01f, 7.086833869e-02f, -1.482646439e-02f}, std::array{5.000000000e-02f, -5.000000000e-02f, -5.000000000e-02f, -5.000000000e-02f, 6.454972244e-02f, 6.454972244e-02f, 0.000000000e+00f, 6.454972244e-02f, 0.000000000e+00f, -1.016220987e-01f, -6.338656910e-02f, 1.092600649e-02f, 7.364853795e-02f, -1.011266756e-01f, 7.086833869e-02f, 1.482646439e-02f}, }; constexpr std::array ThirdOrderEncoder{ CalcAmbiCoeffs( 0.35682208976f, 0.93417235897f, 0.00000000000f), CalcAmbiCoeffs(-0.35682208976f, 0.93417235897f, 0.00000000000f), CalcAmbiCoeffs( 0.35682208976f, -0.93417235897f, 0.00000000000f), CalcAmbiCoeffs(-0.35682208976f, -0.93417235897f, 0.00000000000f), CalcAmbiCoeffs( 0.93417235897f, 0.00000000000f, 0.35682208976f), CalcAmbiCoeffs( 0.93417235897f, 0.00000000000f, -0.35682208976f), CalcAmbiCoeffs(-0.93417235897f, 0.00000000000f, 0.35682208976f), CalcAmbiCoeffs(-0.93417235897f, 0.00000000000f, -0.35682208976f), CalcAmbiCoeffs( 0.00000000000f, 0.35682208976f, 0.93417235897f), CalcAmbiCoeffs( 0.00000000000f, 0.35682208976f, -0.93417235897f), CalcAmbiCoeffs( 0.00000000000f, -0.35682208976f, 0.93417235897f), CalcAmbiCoeffs( 0.00000000000f, -0.35682208976f, -0.93417235897f), CalcAmbiCoeffs( inv_sqrt3f, inv_sqrt3f, inv_sqrt3f), CalcAmbiCoeffs( inv_sqrt3f, inv_sqrt3f, -inv_sqrt3f), CalcAmbiCoeffs( -inv_sqrt3f, inv_sqrt3f, inv_sqrt3f), CalcAmbiCoeffs( -inv_sqrt3f, inv_sqrt3f, -inv_sqrt3f), CalcAmbiCoeffs( inv_sqrt3f, -inv_sqrt3f, inv_sqrt3f), CalcAmbiCoeffs( inv_sqrt3f, -inv_sqrt3f, -inv_sqrt3f), CalcAmbiCoeffs( -inv_sqrt3f, -inv_sqrt3f, inv_sqrt3f), CalcAmbiCoeffs( -inv_sqrt3f, -inv_sqrt3f, -inv_sqrt3f), }; static_assert(ThirdOrderDecoder.size() == ThirdOrderEncoder.size(), "Third-order mismatch"); /* This calculates a 2D third-order "upsampler" matrix. Same as the third-order * matrix, just using a more optimized speaker array for horizontal-only * content. */ constexpr std::array ThirdOrder2DDecoder{ std::array{1.250000000e-01f, -5.523559567e-02f, 0.0f, 1.333505242e-01f, -9.128709292e-02f, 0.0f, 0.0f, 0.0f, 9.128709292e-02f, -1.104247249e-01f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 4.573941867e-02f}, std::array{1.250000000e-01f, -1.333505242e-01f, 0.0f, 5.523559567e-02f, -9.128709292e-02f, 0.0f, 0.0f, 0.0f, -9.128709292e-02f, 4.573941867e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.104247249e-01f}, std::array{1.250000000e-01f, -1.333505242e-01f, 0.0f, -5.523559567e-02f, 9.128709292e-02f, 0.0f, 0.0f, 0.0f, -9.128709292e-02f, 4.573941867e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.104247249e-01f}, std::array{1.250000000e-01f, -5.523559567e-02f, 0.0f, -1.333505242e-01f, 9.128709292e-02f, 0.0f, 0.0f, 0.0f, 9.128709292e-02f, -1.104247249e-01f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -4.573941867e-02f}, std::array{1.250000000e-01f, 5.523559567e-02f, 0.0f, -1.333505242e-01f, -9.128709292e-02f, 0.0f, 0.0f, 0.0f, 9.128709292e-02f, 1.104247249e-01f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -4.573941867e-02f}, std::array{1.250000000e-01f, 1.333505242e-01f, 0.0f, -5.523559567e-02f, -9.128709292e-02f, 0.0f, 0.0f, 0.0f, -9.128709292e-02f, -4.573941867e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.104247249e-01f}, std::array{1.250000000e-01f, 1.333505242e-01f, 0.0f, 5.523559567e-02f, 9.128709292e-02f, 0.0f, 0.0f, 0.0f, -9.128709292e-02f, -4.573941867e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.104247249e-01f}, std::array{1.250000000e-01f, 5.523559567e-02f, 0.0f, 1.333505242e-01f, 9.128709292e-02f, 0.0f, 0.0f, 0.0f, 9.128709292e-02f, 1.104247249e-01f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 4.573941867e-02f}, }; constexpr std::array ThirdOrder2DEncoder{ CalcAmbiCoeffs(-0.38268343237f, 0.0f, 0.92387953251f), CalcAmbiCoeffs(-0.92387953251f, 0.0f, 0.38268343237f), CalcAmbiCoeffs(-0.92387953251f, 0.0f, -0.38268343237f), CalcAmbiCoeffs(-0.38268343237f, 0.0f, -0.92387953251f), CalcAmbiCoeffs( 0.38268343237f, 0.0f, -0.92387953251f), CalcAmbiCoeffs( 0.92387953251f, 0.0f, -0.38268343237f), CalcAmbiCoeffs( 0.92387953251f, 0.0f, 0.38268343237f), CalcAmbiCoeffs( 0.38268343237f, 0.0f, 0.92387953251f), }; static_assert(ThirdOrder2DDecoder.size() == ThirdOrder2DEncoder.size(), "Third-order 2D mismatch"); /* This calculates a 2D fourth-order "upsampler" matrix. There is no 3D fourth- * order upsampler since fourth-order is the max order we'll be supporting for * the foreseeable future. This is only necessary for mixing horizontal-only * fourth-order content to 3D. */ constexpr std::array FourthOrder2DDecoder{ std::array{1.000000000e-01f, 3.568220898e-02f, 0.0f, 1.098185471e-01f, 6.070619982e-02f, 0.0f, 0.0f, 0.0f, 8.355491589e-02f, 7.735682057e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 5.620301997e-02f, 8.573754253e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 2.785781628e-02f}, std::array{1.000000000e-01f, 9.341723590e-02f, 0.0f, 6.787159473e-02f, 9.822469464e-02f, 0.0f, 0.0f, 0.0f, -3.191513794e-02f, 2.954767620e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -9.093839659e-02f, -5.298871540e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -7.293270986e-02f}, std::array{1.000000000e-01f, 1.154700538e-01f, 0.0f, 0.000000000e+00f, 0.000000000e+00f, 0.0f, 0.0f, 0.0f, -1.032795559e-01f, -9.561828875e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.000000000e+00f, 0.000000000e+00f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 9.014978717e-02f}, std::array{1.000000000e-01f, 9.341723590e-02f, 0.0f, -6.787159473e-02f, -9.822469464e-02f, 0.0f, 0.0f, 0.0f, -3.191513794e-02f, 2.954767620e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 9.093839659e-02f, 5.298871540e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -7.293270986e-02f}, std::array{1.000000000e-01f, 3.568220898e-02f, 0.0f, -1.098185471e-01f, -6.070619982e-02f, 0.0f, 0.0f, 0.0f, 8.355491589e-02f, 7.735682057e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -5.620301997e-02f, -8.573754253e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 2.785781628e-02f}, std::array{1.000000000e-01f, -3.568220898e-02f, 0.0f, -1.098185471e-01f, 6.070619982e-02f, 0.0f, 0.0f, 0.0f, 8.355491589e-02f, -7.735682057e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -5.620301997e-02f, 8.573754253e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 2.785781628e-02f}, std::array{1.000000000e-01f, -9.341723590e-02f, 0.0f, -6.787159473e-02f, 9.822469464e-02f, 0.0f, 0.0f, 0.0f, -3.191513794e-02f, -2.954767620e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 9.093839659e-02f, -5.298871540e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -7.293270986e-02f}, std::array{1.000000000e-01f, -1.154700538e-01f, 0.0f, 0.000000000e+00f, 0.000000000e+00f, 0.0f, 0.0f, 0.0f, -1.032795559e-01f, 9.561828875e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.000000000e+00f, 0.000000000e+00f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 9.014978717e-02f}, std::array{1.000000000e-01f, -9.341723590e-02f, 0.0f, 6.787159473e-02f, -9.822469464e-02f, 0.0f, 0.0f, 0.0f, -3.191513794e-02f, -2.954767620e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -9.093839659e-02f, 5.298871540e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -7.293270986e-02f}, std::array{1.000000000e-01f, -3.568220898e-02f, 0.0f, 1.098185471e-01f, -6.070619982e-02f, 0.0f, 0.0f, 0.0f, 8.355491589e-02f, -7.735682057e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 5.620301997e-02f, -8.573754253e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 2.785781628e-02f}, }; constexpr std::array FourthOrder2DEncoder{ CalcAmbiCoeffs( 3.090169944e-01f, 0.000000000e+00f, 9.510565163e-01f), CalcAmbiCoeffs( 8.090169944e-01f, 0.000000000e+00f, 5.877852523e-01f), CalcAmbiCoeffs( 1.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f), CalcAmbiCoeffs( 8.090169944e-01f, 0.000000000e+00f, -5.877852523e-01f), CalcAmbiCoeffs( 3.090169944e-01f, 0.000000000e+00f, -9.510565163e-01f), CalcAmbiCoeffs(-3.090169944e-01f, 0.000000000e+00f, -9.510565163e-01f), CalcAmbiCoeffs(-8.090169944e-01f, 0.000000000e+00f, -5.877852523e-01f), CalcAmbiCoeffs(-1.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f), CalcAmbiCoeffs(-8.090169944e-01f, 0.000000000e+00f, 5.877852523e-01f), CalcAmbiCoeffs(-3.090169944e-01f, 0.000000000e+00f, 9.510565163e-01f), }; static_assert(FourthOrder2DDecoder.size() == FourthOrder2DEncoder.size(), "Fourth-order 2D mismatch"); template constexpr auto CalcAmbiUpsampler(const std::array,M> &decoder, const std::array &encoder) { std::array res{}; for(size_t i{0};i < decoder[0].size();++i) { for(size_t j{0};j < encoder[0].size();++j) { double sum{0.0}; for(size_t k{0};k < decoder.size();++k) sum += double{decoder[k][i]} * encoder[k][j]; res[i][j] = static_cast(sum); } } return res; } } // namespace const std::array,4> AmbiScale::FirstOrderUp{CalcAmbiUpsampler(FirstOrderDecoder, FirstOrderEncoder)}; const std::array,4> AmbiScale::FirstOrder2DUp{CalcAmbiUpsampler(FirstOrder2DDecoder, FirstOrder2DEncoder)}; const std::array,9> AmbiScale::SecondOrderUp{CalcAmbiUpsampler(SecondOrderDecoder, SecondOrderEncoder)}; const std::array,9> AmbiScale::SecondOrder2DUp{CalcAmbiUpsampler(SecondOrder2DDecoder, SecondOrder2DEncoder)}; const std::array,16> AmbiScale::ThirdOrderUp{CalcAmbiUpsampler(ThirdOrderDecoder, ThirdOrderEncoder)}; const std::array,16> AmbiScale::ThirdOrder2DUp{CalcAmbiUpsampler(ThirdOrder2DDecoder, ThirdOrder2DEncoder)}; const std::array,25> AmbiScale::FourthOrder2DUp{CalcAmbiUpsampler(FourthOrder2DDecoder, FourthOrder2DEncoder)}; std::array AmbiScale::GetHFOrderScales(const uint src_order, const uint dev_order, const bool horizontalOnly) noexcept { std::array res{}; if(!horizontalOnly) { for(size_t i{0};i < MaxAmbiOrder+1;++i) res[i] = HFScales[src_order][i] / HFScales[dev_order][i]; } else { for(size_t i{0};i < MaxAmbiOrder+1;++i) res[i] = HFScales2D[src_order][i] / HFScales2D[dev_order][i]; } return res; } openal-soft-1.24.2/core/ambidefs.h000066400000000000000000000212001474041540300166770ustar00rootroot00000000000000#ifndef CORE_AMBIDEFS_H #define CORE_AMBIDEFS_H #include #include #include #include "alnumbers.h" using uint = unsigned int; /* The maximum number of Ambisonics channels. For a given order (o), the size * needed will be (o+1)**2, thus zero-order has 1, first-order has 4, second- * order has 9, third-order has 16, and fourth-order has 25. */ constexpr auto AmbiChannelsFromOrder(std::size_t order) noexcept -> std::size_t { return (order+1) * (order+1); } inline constexpr auto MaxAmbiOrder = std::uint8_t{3}; inline constexpr auto MaxAmbiChannels = size_t{AmbiChannelsFromOrder(MaxAmbiOrder)}; /* A bitmask of ambisonic channels for 0 to 4th order. This only specifies up * to 4th order, which is the highest order a 32-bit mask value can specify (a * 64-bit mask could handle up to 7th order). */ inline constexpr uint Ambi0OrderMask{0x00000001}; inline constexpr uint Ambi1OrderMask{0x0000000f}; inline constexpr uint Ambi2OrderMask{0x000001ff}; inline constexpr uint Ambi3OrderMask{0x0000ffff}; inline constexpr uint Ambi4OrderMask{0x01ffffff}; /* A bitmask of ambisonic channels with height information. If none of these * channels are used/needed, there's no height (e.g. with most surround sound * speaker setups). This is ACN ordering, with bit 0 being ACN 0, etc. */ inline constexpr uint AmbiPeriphonicMask{0xfe7ce4}; /* The maximum number of ambisonic channels for 2D (non-periphonic) * representation. This is 2 per each order above zero-order, plus 1 for zero- * order. Or simply, o*2 + 1. */ constexpr auto Ambi2DChannelsFromOrder(std::size_t order) noexcept -> std::size_t { return order*2 + 1; } inline constexpr auto MaxAmbi2DChannels = Ambi2DChannelsFromOrder(MaxAmbiOrder); /* NOTE: These are scale factors as applied to Ambisonics content. Decoder * coefficients should be divided by these values to get proper scalings. */ struct AmbiScale { static constexpr auto FromN3D = std::array{ 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f }; static constexpr auto FromSN3D = std::array{ 1.000000000f, /* ACN 0, sqrt(1) */ 1.732050808f, /* ACN 1, sqrt(3) */ 1.732050808f, /* ACN 2, sqrt(3) */ 1.732050808f, /* ACN 3, sqrt(3) */ 2.236067978f, /* ACN 4, sqrt(5) */ 2.236067978f, /* ACN 5, sqrt(5) */ 2.236067978f, /* ACN 6, sqrt(5) */ 2.236067978f, /* ACN 7, sqrt(5) */ 2.236067978f, /* ACN 8, sqrt(5) */ 2.645751311f, /* ACN 9, sqrt(7) */ 2.645751311f, /* ACN 10, sqrt(7) */ 2.645751311f, /* ACN 11, sqrt(7) */ 2.645751311f, /* ACN 12, sqrt(7) */ 2.645751311f, /* ACN 13, sqrt(7) */ 2.645751311f, /* ACN 14, sqrt(7) */ 2.645751311f, /* ACN 15, sqrt(7) */ }; static constexpr auto FromFuMa = std::array{ 1.414213562f, /* ACN 0 (W), sqrt(2) */ 1.732050808f, /* ACN 1 (Y), sqrt(3) */ 1.732050808f, /* ACN 2 (Z), sqrt(3) */ 1.732050808f, /* ACN 3 (X), sqrt(3) */ 1.936491673f, /* ACN 4 (V), sqrt(15)/2 */ 1.936491673f, /* ACN 5 (T), sqrt(15)/2 */ 2.236067978f, /* ACN 6 (R), sqrt(5) */ 1.936491673f, /* ACN 7 (S), sqrt(15)/2 */ 1.936491673f, /* ACN 8 (U), sqrt(15)/2 */ 2.091650066f, /* ACN 9 (Q), sqrt(35/8) */ 1.972026594f, /* ACN 10 (O), sqrt(35)/3 */ 2.231093404f, /* ACN 11 (M), sqrt(224/45) */ 2.645751311f, /* ACN 12 (K), sqrt(7) */ 2.231093404f, /* ACN 13 (L), sqrt(224/45) */ 1.972026594f, /* ACN 14 (N), sqrt(35)/3 */ 2.091650066f, /* ACN 15 (P), sqrt(35/8) */ }; static constexpr auto FromUHJ = std::array{ 1.000000000f, /* ACN 0 (W), sqrt(1) */ 1.224744871f, /* ACN 1 (Y), sqrt(3/2) */ 1.224744871f, /* ACN 2 (Z), sqrt(3/2) */ 1.224744871f, /* ACN 3 (X), sqrt(3/2) */ /* Higher orders not relevant for UHJ. */ 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, }; /* Retrieves per-order HF scaling factors for "upsampling" ambisonic data. */ static std::array GetHFOrderScales(const uint src_order, const uint dev_order, const bool horizontalOnly) noexcept; static const std::array,4> FirstOrderUp; static const std::array,4> FirstOrder2DUp; static const std::array,9> SecondOrderUp; static const std::array,9> SecondOrder2DUp; static const std::array,16> ThirdOrderUp; static const std::array,16> ThirdOrder2DUp; static const std::array,25> FourthOrder2DUp; }; struct AmbiIndex { static constexpr auto FromFuMa = std::array{ 0, /* W */ 3, /* X */ 1, /* Y */ 2, /* Z */ 6, /* R */ 7, /* S */ 5, /* T */ 8, /* U */ 4, /* V */ 12, /* K */ 13, /* L */ 11, /* M */ 14, /* N */ 10, /* O */ 15, /* P */ 9, /* Q */ }; static constexpr auto FromFuMa2D = std::array{ 0, /* W */ 3, /* X */ 1, /* Y */ 8, /* U */ 4, /* V */ 15, /* P */ 9, /* Q */ }; static constexpr auto FromACN = std::array{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; static constexpr auto FromACN2D = std::array{ 0, 1,3, 4,8, 9,15 }; static constexpr auto OrderFromChannel = std::array{ 0, 1,1,1, 2,2,2,2,2, 3,3,3,3,3,3,3, }; static constexpr auto OrderFrom2DChannel = std::array{ 0, 1,1, 2,2, 3,3, }; }; /** * Calculates ambisonic encoder coefficients using the X, Y, and Z direction * components, which must represent a normalized (unit length) vector. * * NOTE: The components use ambisonic coordinates. As a result: * * Ambisonic Y = OpenAL -X * Ambisonic Z = OpenAL Y * Ambisonic X = OpenAL -Z * * The components are ordered such that OpenAL's X, Y, and Z are the first, * second, and third parameters respectively -- simply negate X and Z. */ constexpr auto CalcAmbiCoeffs(const float y, const float z, const float x) { const float xx{x*x}, yy{y*y}, zz{z*z}, xy{x*y}, yz{y*z}, xz{x*z}; return std::array{{ /* Zeroth-order */ 1.0f, /* ACN 0 = 1 */ /* First-order */ al::numbers::sqrt3_v * y, /* ACN 1 = sqrt(3) * Y */ al::numbers::sqrt3_v * z, /* ACN 2 = sqrt(3) * Z */ al::numbers::sqrt3_v * x, /* ACN 3 = sqrt(3) * X */ /* Second-order */ 3.872983346e+00f * xy, /* ACN 4 = sqrt(15) * X * Y */ 3.872983346e+00f * yz, /* ACN 5 = sqrt(15) * Y * Z */ 1.118033989e+00f * (3.0f*zz - 1.0f), /* ACN 6 = sqrt(5)/2 * (3*Z*Z - 1) */ 3.872983346e+00f * xz, /* ACN 7 = sqrt(15) * X * Z */ 1.936491673e+00f * (xx - yy), /* ACN 8 = sqrt(15)/2 * (X*X - Y*Y) */ /* Third-order */ 2.091650066e+00f * (y*(3.0f*xx - yy)), /* ACN 9 = sqrt(35/8) * Y * (3*X*X - Y*Y) */ 1.024695076e+01f * (z*xy), /* ACN 10 = sqrt(105) * Z * X * Y */ 1.620185175e+00f * (y*(5.0f*zz - 1.0f)), /* ACN 11 = sqrt(21/8) * Y * (5*Z*Z - 1) */ 1.322875656e+00f * (z*(5.0f*zz - 3.0f)), /* ACN 12 = sqrt(7)/2 * Z * (5*Z*Z - 3) */ 1.620185175e+00f * (x*(5.0f*zz - 1.0f)), /* ACN 13 = sqrt(21/8) * X * (5*Z*Z - 1) */ 5.123475383e+00f * (z*(xx - yy)), /* ACN 14 = sqrt(105)/2 * Z * (X*X - Y*Y) */ 2.091650066e+00f * (x*(xx - 3.0f*yy)), /* ACN 15 = sqrt(35/8) * X * (X*X - 3*Y*Y) */ /* Fourth-order */ /* ACN 16 = sqrt(35)*3/2 * X * Y * (X*X - Y*Y) */ /* ACN 17 = sqrt(35/2)*3/2 * (3*X*X - Y*Y) * Y * Z */ /* ACN 18 = sqrt(5)*3/2 * X * Y * (7*Z*Z - 1) */ /* ACN 19 = sqrt(5/2)*3/2 * Y * Z * (7*Z*Z - 3) */ /* ACN 20 = 3/8 * (35*Z*Z*Z*Z - 30*Z*Z + 3) */ /* ACN 21 = sqrt(5/2)*3/2 * X * Z * (7*Z*Z - 3) */ /* ACN 22 = sqrt(5)*3/4 * (X*X - Y*Y) * (7*Z*Z - 1) */ /* ACN 23 = sqrt(35/2)*3/2 * (X*X - 3*Y*Y) * X * Z */ /* ACN 24 = sqrt(35)*3/8 * (X*X*X*X - 6*X*X*Y*Y + Y*Y*Y*Y) */ }}; } #endif /* CORE_AMBIDEFS_H */ openal-soft-1.24.2/core/async_event.h000066400000000000000000000021751474041540300174550ustar00rootroot00000000000000#ifndef CORE_EVENT_H #define CORE_EVENT_H #include #include #include #include #include "almalloc.h" struct EffectState; using uint = unsigned int; enum class AsyncEnableBits : std::uint8_t { SourceState, BufferCompleted, Disconnected, Count }; enum class AsyncSrcState : std::uint8_t { Reset, Stop, Play, Pause }; using AsyncKillThread = std::monostate; struct AsyncSourceStateEvent { uint mId; AsyncSrcState mState; }; struct AsyncBufferCompleteEvent { uint mId; uint mCount; }; struct AsyncDisconnectEvent { std::string msg; }; struct AsyncEffectReleaseEvent { EffectState *mEffectState; }; using AsyncEvent = std::variant; template auto &InitAsyncEvent(std::byte *evtbuf, Args&& ...args) { auto *evt = al::construct_at(reinterpret_cast(evtbuf), std::in_place_type, std::forward(args)...); return std::get(*evt); } #endif openal-soft-1.24.2/core/bformatdec.cpp000066400000000000000000000155661474041540300176100ustar00rootroot00000000000000 #include "config.h" #include "bformatdec.h" #include #include #include #include #include #include "alnumbers.h" #include "bufferline.h" #include "filters/splitter.h" #include "flexarray.h" #include "front_stablizer.h" #include "mixer.h" #include "opthelpers.h" namespace { template struct overloaded : Ts... { using Ts::operator()...; }; template overloaded(Ts...) -> overloaded; } // namespace BFormatDec::BFormatDec(const size_t inchans, const al::span coeffs, const al::span coeffslf, const float xover_f0norm, std::unique_ptr stablizer) : mStablizer{std::move(stablizer)} { if(coeffslf.empty()) { auto &decoder = mChannelDec.emplace>(inchans); for(size_t j{0};j < decoder.size();++j) { std::transform(coeffs.cbegin(), coeffs.cend(), decoder[j].mGains.begin(), [j](const ChannelDec &incoeffs) { return incoeffs[j]; }); } } else { auto &decoder = mChannelDec.emplace>(inchans); decoder[0].mXOver.init(xover_f0norm); for(size_t j{1};j < decoder.size();++j) decoder[j].mXOver = decoder[0].mXOver; for(size_t j{0};j < decoder.size();++j) { std::transform(coeffs.cbegin(), coeffs.cend(), decoder[j].mGains[sHFBand].begin(), [j](const ChannelDec &incoeffs) { return incoeffs[j]; }); std::transform(coeffslf.cbegin(), coeffslf.cend(), decoder[j].mGains[sLFBand].begin(), [j](const ChannelDec &incoeffs) { return incoeffs[j]; }); } } } void BFormatDec::process(const al::span OutBuffer, const al::span InSamples, const size_t SamplesToDo) { ASSUME(SamplesToDo > 0); auto decode_dualband = [=](std::vector &decoder) { auto input = InSamples.cbegin(); const auto hfSamples = al::span{mSamples[sHFBand]}.first(SamplesToDo); const auto lfSamples = al::span{mSamples[sLFBand]}.first(SamplesToDo); for(auto &chandec : decoder) { chandec.mXOver.process(al::span{*input++}.first(SamplesToDo), hfSamples, lfSamples); MixSamples(hfSamples, OutBuffer, chandec.mGains[sHFBand], chandec.mGains[sHFBand],0,0); MixSamples(lfSamples, OutBuffer, chandec.mGains[sLFBand], chandec.mGains[sLFBand],0,0); } }; auto decode_singleband = [=](std::vector &decoder) { auto input = InSamples.cbegin(); for(auto &chandec : decoder) { MixSamples(al::span{*input++}.first(SamplesToDo), OutBuffer, chandec.mGains, chandec.mGains, 0, 0); } }; std::visit(overloaded{decode_dualband, decode_singleband}, mChannelDec); } void BFormatDec::processStablize(const al::span OutBuffer, const al::span InSamples, const size_t lidx, const size_t ridx, const size_t cidx, const size_t SamplesToDo) { ASSUME(SamplesToDo > 0); /* Move the existing direct L/R signal out so it doesn't get processed by * the stablizer. */ const auto leftout = al::span{OutBuffer[lidx]}.first(SamplesToDo); const auto rightout = al::span{OutBuffer[ridx]}.first(SamplesToDo); const auto mid = al::span{mStablizer->MidDirect}.first(SamplesToDo); const auto side = al::span{mStablizer->Side}.first(SamplesToDo); std::transform(leftout.cbegin(), leftout.cend(), rightout.cbegin(), mid.begin(),std::plus{}); std::transform(leftout.cbegin(), leftout.cend(), rightout.cbegin(), side.begin(),std::minus{}); std::fill_n(leftout.begin(), leftout.size(), 0.0f); std::fill_n(rightout.begin(), rightout.size(), 0.0f); /* Decode the B-Format input to OutBuffer. */ process(OutBuffer, InSamples, SamplesToDo); /* Include the decoded side signal with the direct side signal. */ for(size_t i{0};i < SamplesToDo;++i) side[i] += leftout[i] - rightout[i]; /* Get the decoded mid signal and band-split it. */ const auto tmpsamples = al::span{mStablizer->Temp}.first(SamplesToDo); std::transform(leftout.cbegin(), leftout.cend(), rightout.cbegin(), tmpsamples.begin(), std::plus{}); mStablizer->MidFilter.process(tmpsamples, mStablizer->MidHF, mStablizer->MidLF); /* Apply an all-pass to all channels to match the band-splitter's phase * shift. This is to keep the phase synchronized between the existing * signal and the split mid signal. */ const size_t NumChannels{OutBuffer.size()}; for(size_t i{0u};i < NumChannels;i++) { /* Skip the left and right channels, which are going to get overwritten, * and substitute the direct mid signal and direct+decoded side signal. */ if(i == lidx) mStablizer->ChannelFilters[i].processAllPass(mid); else if(i == ridx) mStablizer->ChannelFilters[i].processAllPass(side); else mStablizer->ChannelFilters[i].processAllPass({OutBuffer[i].data(), SamplesToDo}); } /* This pans the separate low- and high-frequency signals between being on * the center channel and the left+right channels. The low-frequency signal * is panned 1/3rd toward center and the high-frequency signal is panned * 1/4th toward center. These values can be tweaked. */ const float cos_lf{std::cos(1.0f/3.0f * (al::numbers::pi_v*0.5f))}; const float cos_hf{std::cos(1.0f/4.0f * (al::numbers::pi_v*0.5f))}; const float sin_lf{std::sin(1.0f/3.0f * (al::numbers::pi_v*0.5f))}; const float sin_hf{std::sin(1.0f/4.0f * (al::numbers::pi_v*0.5f))}; const auto centerout = al::span{OutBuffer[cidx]}.first(SamplesToDo); for(size_t i{0};i < SamplesToDo;i++) { /* Add the direct mid signal to the processed mid signal so it can be * properly combined with the direct+decoded side signal. */ const float m{mStablizer->MidLF[i]*cos_lf + mStablizer->MidHF[i]*cos_hf + mid[i]}; const float c{mStablizer->MidLF[i]*sin_lf + mStablizer->MidHF[i]*sin_hf}; const float s{side[i]}; /* The generated center channel signal adds to the existing signal, * while the modified left and right channels replace. */ leftout[i] = (m + s) * 0.5f; rightout[i] = (m - s) * 0.5f; centerout[i] += c * 0.5f; } } std::unique_ptr BFormatDec::Create(const size_t inchans, const al::span coeffs, const al::span coeffslf, const float xover_f0norm, std::unique_ptr stablizer) { return std::make_unique(inchans, coeffs, coeffslf, xover_f0norm, std::move(stablizer)); } openal-soft-1.24.2/core/bformatdec.h000066400000000000000000000040411474041540300172370ustar00rootroot00000000000000#ifndef CORE_BFORMATDEC_H #define CORE_BFORMATDEC_H #include #include #include #include #include #include "alspan.h" #include "ambidefs.h" #include "bufferline.h" #include "devformat.h" #include "filters/splitter.h" #include "front_stablizer.h" #include "opthelpers.h" using ChannelDec = std::array; class SIMDALIGN BFormatDec { static constexpr size_t sHFBand{0}; static constexpr size_t sLFBand{1}; static constexpr size_t sNumBands{2}; struct ChannelDecoderSingle { std::array mGains{}; }; struct ChannelDecoderDual { BandSplitter mXOver; std::array,sNumBands> mGains{}; }; alignas(16) std::array mSamples{}; const std::unique_ptr mStablizer; std::variant,std::vector> mChannelDec; public: BFormatDec(const size_t inchans, const al::span coeffs, const al::span coeffslf, const float xover_f0norm, std::unique_ptr stablizer); [[nodiscard]] auto hasStablizer() const noexcept -> bool { return mStablizer != nullptr; } /* Decodes the ambisonic input to the given output channels. */ void process(const al::span OutBuffer, const al::span InSamples, const size_t SamplesToDo); /* Decodes the ambisonic input to the given output channels with stablization. */ void processStablize(const al::span OutBuffer, const al::span InSamples, const size_t lidx, const size_t ridx, const size_t cidx, const size_t SamplesToDo); static std::unique_ptr Create(const size_t inchans, const al::span coeffs, const al::span coeffslf, const float xover_f0norm, std::unique_ptr stablizer); }; #endif /* CORE_BFORMATDEC_H */ openal-soft-1.24.2/core/bs2b.cpp000066400000000000000000000127421474041540300163230ustar00rootroot00000000000000/*- * Copyright (c) 2005 Boris Mikhaylov * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "config.h" #include #include #include #include #include "alnumbers.h" #include "alspan.h" #include "bs2b.h" namespace { /* Set up all data. */ void init(Bs2b::bs2b *bs2b) { float Fc_lo, Fc_hi; float G_lo, G_hi; switch(bs2b->level) { case Bs2b::LowCLevel: /* Low crossfeed level */ Fc_lo = 360.0f; Fc_hi = 501.0f; G_lo = 0.398107170553497f; G_hi = 0.205671765275719f; break; case Bs2b::MiddleCLevel: /* Middle crossfeed level */ Fc_lo = 500.0f; Fc_hi = 711.0f; G_lo = 0.459726988530872f; G_hi = 0.228208484414988f; break; case Bs2b::HighCLevel: /* High crossfeed level (virtual speakers are closer to itself) */ Fc_lo = 700.0f; Fc_hi = 1021.0f; G_lo = 0.530884444230988f; G_hi = 0.250105790667544f; break; case Bs2b::LowECLevel: /* Low easy crossfeed level */ Fc_lo = 360.0f; Fc_hi = 494.0f; G_lo = 0.316227766016838f; G_hi = 0.168236228897329f; break; case Bs2b::MiddleECLevel: /* Middle easy crossfeed level */ Fc_lo = 500.0f; Fc_hi = 689.0f; G_lo = 0.354813389233575f; G_hi = 0.187169483835901f; break; case Bs2b::HighECLevel: /* High easy crossfeed level */ default: bs2b->level = Bs2b::HighECLevel; Fc_lo = 700.0f; Fc_hi = 975.0f; G_lo = 0.398107170553497f; G_hi = 0.205671765275719f; break; } float g{1.0f / (1.0f - G_hi + G_lo)}; /* $fc = $Fc / $s; * $d = 1 / 2 / pi / $fc; * $x = exp(-1 / $d); */ float x{ std::exp(-al::numbers::pi_v*2.0f*Fc_lo/static_cast(bs2b->srate))}; bs2b->b1_lo = x; bs2b->a0_lo = G_lo * (1.0f - x) * g; x = std::exp(-al::numbers::pi_v*2.0f*Fc_hi/static_cast(bs2b->srate)); bs2b->b1_hi = x; bs2b->a0_hi = (1.0f - G_hi * (1.0f - x)) * g; bs2b->a1_hi = -x * g; } } // namespace /* Exported functions. * See descriptions in "bs2b.h" */ namespace Bs2b { void bs2b::set_params(int level_, int srate_) { if(srate_ < 1) throw std::runtime_error{"BS2B srate < 1"}; level = level_; srate = srate_; init(this); } void bs2b::clear() { history.fill(bs2b::t_last_sample{}); } void bs2b::cross_feed(const al::span Left, const al::span Right) { const auto a0lo = a0_lo; const auto b1lo = b1_lo; const auto a0hi = a0_hi; const auto a1hi = a1_hi; const auto b1hi = b1_hi; auto lsamples = Left.first(std::min(Left.size(), Right.size())); auto rsamples = Right.first(lsamples.size()); auto samples = std::array,128>{}; auto leftio = lsamples.begin(); auto rightio = rsamples.begin(); while(auto rem = std::distance(leftio, lsamples.end())) { const auto todo = std::min(samples.size(), rem); /* Process left input */ auto z_lo = history[0].lo; auto z_hi = history[0].hi; std::transform(leftio, leftio+todo, samples.begin(), [a0hi,a1hi,b1hi,a0lo,b1lo,&z_lo,&z_hi](const float x) noexcept { const auto y0 = a0hi*x + z_hi; z_hi = a1hi*x + b1hi*y0; const auto y1 = a0lo*x + z_lo; z_lo = b1lo*y1; return std::array{y0, y1}; }); history[0].lo = z_lo; history[0].hi = z_hi; /* Process right input */ z_lo = history[1].lo; z_hi = history[1].hi; std::transform(rightio, rightio+todo, samples.cbegin(), samples.begin(), [a0hi,a1hi,b1hi,a0lo,b1lo,&z_lo,&z_hi](const float x, const std::array &out) noexcept { const auto y0 = a0lo*x + z_lo; z_lo = b1lo*y0; const auto y1 = a0hi*x + z_hi; z_hi = a1hi*x + b1hi*y1; return std::array{out[0]+y0, out[1]+y1}; }); history[1].lo = z_lo; history[1].hi = z_hi; leftio = std::transform(samples.cbegin(), samples.cbegin()+todo, leftio, [](const std::array &in) { return in[0]; }); rightio = std::transform(samples.cbegin(), samples.cbegin()+todo, rightio, [](const std::array &in) { return in[1]; }); } } } // namespace Bs2b openal-soft-1.24.2/core/bs2b.h000066400000000000000000000051021474041540300157600ustar00rootroot00000000000000/*- * Copyright (c) 2005 Boris Mikhaylov * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef CORE_BS2B_H #define CORE_BS2B_H #include #include #include "alspan.h" namespace Bs2b { enum { /* Normal crossfeed levels */ LowCLevel = 1, MiddleCLevel = 2, HighCLevel = 3, /* Easy crossfeed levels */ LowECLevel = 4, MiddleECLevel = 5, HighECLevel = 6, DefaultCLevel = HighECLevel }; struct bs2b { int level{}; /* Crossfeed level */ int srate{}; /* Sample rate (Hz) */ /* Lowpass IIR filter coefficients */ float a0_lo{}; float b1_lo{}; /* Highboost IIR filter coefficients */ float a0_hi{}; float a1_hi{}; float b1_hi{}; /* Buffer of filter history * [0] - first channel, [1] - second channel */ struct t_last_sample { float lo{}; float hi{}; }; std::array history{}; /* Clear buffers and set new coefficients with new crossfeed level and * sample rate values. * level - crossfeed level of *Level enum values. * srate - sample rate by Hz. */ void set_params(int level, int srate); /* Return current crossfeed level value */ [[nodiscard]] auto get_level() const noexcept -> int { return level; } /* Return current sample rate value */ [[nodiscard]] auto get_srate() const noexcept -> int { return srate; } /* Clear buffer */ void clear(); void cross_feed(const al::span Left, const al::span Right); }; } // namespace Bs2b #endif /* CORE_BS2B_H */ openal-soft-1.24.2/core/bsinc_defs.h000066400000000000000000000005761474041540300172410ustar00rootroot00000000000000#ifndef CORE_BSINC_DEFS_H #define CORE_BSINC_DEFS_H /* The number of distinct scale and phase intervals within the bsinc filter * tables. */ constexpr unsigned int BSincScaleBits{4}; constexpr unsigned int BSincScaleCount{1 << BSincScaleBits}; constexpr unsigned int BSincPhaseBits{5}; constexpr unsigned int BSincPhaseCount{1 << BSincPhaseBits}; #endif /* CORE_BSINC_DEFS_H */ openal-soft-1.24.2/core/bsinc_tables.cpp000066400000000000000000000241521474041540300201210ustar00rootroot00000000000000 #include "bsinc_tables.h" #include #include #include #include #include #include #include #include #include "alnumbers.h" #include "alnumeric.h" #include "alspan.h" #include "bsinc_defs.h" #include "opthelpers.h" #include "resampler_limits.h" namespace { using uint = unsigned int; /* The zero-order modified Bessel function of the first kind, used for the * Kaiser window. * * I_0(x) = sum_{k=0}^inf (1 / k!)^2 (x / 2)^(2 k) * = sum_{k=0}^inf ((x / 2)^k / k!)^2 * * This implementation only handles nu = 0, and isn't the most precise (it * starts with the largest value and accumulates successively smaller values, * compounding the rounding and precision error), but it's good enough. */ template constexpr auto cyl_bessel_i(T nu, U x) -> U { if(nu != T{0}) throw std::runtime_error{"cyl_bessel_i: nu != 0"}; /* Start at k=1 since k=0 is trivial. */ const double x2{x/2.0}; double term{1.0}; double sum{1.0}; int k{1}; /* Let the integration converge until the term of the sum is no longer * significant. */ double last_sum{}; do { const double y{x2 / k}; ++k; last_sum = sum; term *= y * y; sum += term; } while(sum != last_sum); return static_cast(sum); } /* This is the normalized cardinal sine (sinc) function. * * sinc(x) = { 1, x = 0 * { sin(pi x) / (pi x), otherwise. */ constexpr double Sinc(const double x) { constexpr double epsilon{std::numeric_limits::epsilon()}; if(!(x > epsilon || x < -epsilon)) return 1.0; return std::sin(al::numbers::pi*x) / (al::numbers::pi*x); } /* Calculate a Kaiser window from the given beta value and a normalized k * [-1, 1]. * * w(k) = { I_0(B sqrt(1 - k^2)) / I_0(B), -1 <= k <= 1 * { 0, elsewhere. * * Where k can be calculated as: * * k = i / l, where -l <= i <= l. * * or: * * k = 2 i / M - 1, where 0 <= i <= M. */ constexpr double Kaiser(const double beta, const double k, const double besseli_0_beta) { if(!(k >= -1.0 && k <= 1.0)) return 0.0; return ::cyl_bessel_i(0, beta * std::sqrt(1.0 - k*k)) / besseli_0_beta; } /* Calculates the (normalized frequency) transition width of the Kaiser window. * Rejection is in dB. */ constexpr double CalcKaiserWidth(const double rejection, const uint order) noexcept { if(rejection > 21.19) return (rejection - 7.95) / (2.285 * al::numbers::pi*2.0 * order); /* This enforces a minimum rejection of just above 21.18dB */ return 5.79 / (al::numbers::pi*2.0 * order); } /* Calculates the beta value of the Kaiser window. Rejection is in dB. */ constexpr double CalcKaiserBeta(const double rejection) { if(rejection > 50.0) return 0.1102 * (rejection-8.7); if(rejection >= 21.0) return (0.5842 * std::pow(rejection-21.0, 0.4)) + (0.07886 * (rejection-21.0)); return 0.0; } struct BSincHeader { double width{}; double beta{}; double scaleBase{}; std::array a{}; uint total_size{}; constexpr BSincHeader(uint Rejection, uint Order) noexcept : width{CalcKaiserWidth(Rejection, Order)}, beta{CalcKaiserBeta(Rejection)} , scaleBase{width / 2.0} { uint num_points{Order+1}; for(uint si{0};si < BSincScaleCount;++si) { const double scale{lerpd(scaleBase, 1.0, (si+1) / double{BSincScaleCount})}; const uint a_{std::min(static_cast(num_points / 2.0 / scale), num_points)}; const uint m{2 * a_}; a[si] = a_; total_size += 4 * BSincPhaseCount * ((m+3) & ~3u); } } }; /* 11th and 23rd order filters (12 and 24-point respectively) with a 60dB drop * at nyquist. Each filter will scale up the order when downsampling, to 23rd * and 47th order respectively. */ constexpr BSincHeader bsinc12_hdr{60, 11}; constexpr BSincHeader bsinc24_hdr{60, 23}; template struct SIMDALIGN BSincFilterArray { alignas(16) std::array mTable{}; BSincFilterArray() { static constexpr uint BSincPointsMax{(hdr.a[0]*2u + 3u) & ~3u}; static_assert(BSincPointsMax <= MaxResamplerPadding, "MaxResamplerPadding is too small"); using filter_type = std::array,BSincPhaseCount>; auto filter = std::vector(BSincScaleCount); const double besseli_0_beta{::cyl_bessel_i(0, hdr.beta)}; /* Calculate the Kaiser-windowed Sinc filter coefficients for each * scale and phase index. */ for(uint si{0};si < BSincScaleCount;++si) { const uint m{hdr.a[si] * 2}; const size_t o{(BSincPointsMax-m) / 2}; const double scale{lerpd(hdr.scaleBase, 1.0, (si+1) / double{BSincScaleCount})}; const double cutoff{scale - (hdr.scaleBase * std::max(1.0, scale*2.0))}; const auto a = static_cast(hdr.a[si]); const double l{a - 1.0/BSincPhaseCount}; for(uint pi{0};pi < BSincPhaseCount;++pi) { const double phase{std::floor(l) + (pi/double{BSincPhaseCount})}; for(uint i{0};i < m;++i) { const double x{i - phase}; filter[si][pi][o+i] = Kaiser(hdr.beta, x/l, besseli_0_beta) * cutoff * Sinc(cutoff*x); } } } size_t idx{0}; for(size_t si{0};si < BSincScaleCount;++si) { const size_t m{((hdr.a[si]*2) + 3) & ~3u}; const size_t o{(BSincPointsMax-m) / 2}; /* Write out each phase index's filter and phase delta for this * quality scale. */ for(size_t pi{0};pi < BSincPhaseCount;++pi) { for(size_t i{0};i < m;++i) mTable[idx++] = static_cast(filter[si][pi][o+i]); /* Linear interpolation between phases is simplified by pre- * calculating the delta (b - a) in: x = a + f (b - a) */ if(pi < BSincPhaseCount-1) { for(size_t i{0};i < m;++i) { const double phDelta{filter[si][pi+1][o+i] - filter[si][pi][o+i]}; mTable[idx++] = static_cast(phDelta); } } else { /* The delta target for the last phase index is the first * phase index with the coefficients offset by one. The * first delta targets 0, as it represents a coefficient * for a sample that won't be part of the filter. */ mTable[idx++] = static_cast(0.0 - filter[si][pi][o]); for(size_t i{1};i < m;++i) { const double phDelta{filter[si][0][o+i-1] - filter[si][pi][o+i]}; mTable[idx++] = static_cast(phDelta); } } } /* Now write out each phase index's scale and phase+scale deltas, * to complete the bilinear equation for the combination of phase * and scale. */ if(si < BSincScaleCount-1) { for(size_t pi{0};pi < BSincPhaseCount;++pi) { for(size_t i{0};i < m;++i) { const double scDelta{filter[si+1][pi][o+i] - filter[si][pi][o+i]}; mTable[idx++] = static_cast(scDelta); } if(pi < BSincPhaseCount-1) { for(size_t i{0};i < m;++i) { const double spDelta{(filter[si+1][pi+1][o+i]-filter[si+1][pi][o+i]) - (filter[si][pi+1][o+i]-filter[si][pi][o+i])}; mTable[idx++] = static_cast(spDelta); } } else { mTable[idx++] = static_cast((0.0 - filter[si+1][pi][o]) - (0.0 - filter[si][pi][o])); for(size_t i{1};i < m;++i) { const double spDelta{(filter[si+1][0][o+i-1] - filter[si+1][pi][o+i]) - (filter[si][0][o+i-1] - filter[si][pi][o+i])}; mTable[idx++] = static_cast(spDelta); } } } } else { /* The last scale index doesn't have scale-related deltas. */ for(size_t i{0};i < BSincPhaseCount*m*2;++i) mTable[idx++] = 0.0f; } } assert(idx == hdr.total_size); } [[nodiscard]] constexpr auto getHeader() const noexcept -> const BSincHeader& { return hdr; } [[nodiscard]] constexpr auto getTable() const noexcept { return al::span{mTable}; } }; const BSincFilterArray bsinc12_filter{}; const BSincFilterArray bsinc24_filter{}; template constexpr BSincTable GenerateBSincTable(const T &filter) { BSincTable ret{}; const BSincHeader &hdr = filter.getHeader(); ret.scaleBase = static_cast(hdr.scaleBase); ret.scaleRange = static_cast(1.0 / (1.0 - hdr.scaleBase)); for(size_t i{0};i < BSincScaleCount;++i) ret.m[i] = ((hdr.a[i]*2) + 3) & ~3u; ret.filterOffset[0] = 0; for(size_t i{1};i < BSincScaleCount;++i) ret.filterOffset[i] = ret.filterOffset[i-1] + ret.m[i-1]*4*BSincPhaseCount; ret.Tab = filter.getTable(); return ret; } } // namespace const BSincTable gBSinc12{GenerateBSincTable(bsinc12_filter)}; const BSincTable gBSinc24{GenerateBSincTable(bsinc24_filter)}; openal-soft-1.24.2/core/bsinc_tables.h000066400000000000000000000007211474041540300175620ustar00rootroot00000000000000#ifndef CORE_BSINC_TABLES_H #define CORE_BSINC_TABLES_H #include #include "alspan.h" #include "bsinc_defs.h" #include "opthelpers.h" struct BSincTable { float scaleBase, scaleRange; std::array m; std::array filterOffset; al::span Tab; }; DECL_HIDDEN extern const BSincTable gBSinc12; DECL_HIDDEN extern const BSincTable gBSinc24; #endif /* CORE_BSINC_TABLES_H */ openal-soft-1.24.2/core/buffer_storage.cpp000066400000000000000000000000631474041540300204610ustar00rootroot00000000000000 #include "config.h" #include "buffer_storage.h" openal-soft-1.24.2/core/buffer_storage.h000066400000000000000000000042351474041540300201330ustar00rootroot00000000000000#ifndef CORE_BUFFER_STORAGE_H #define CORE_BUFFER_STORAGE_H #include #include "alspan.h" #include "ambidefs.h" #include "storage_formats.h" using uint = unsigned int; constexpr bool IsBFormat(FmtChannels chans) noexcept { return chans == FmtBFormat2D || chans == FmtBFormat3D; } /* Super Stereo is considered part of the UHJ family here, since it goes * through similar processing as UHJ, both result in a B-Format signal, and * needs the same consideration as BHJ (three channel result with only two * channel input). */ constexpr bool IsUHJ(FmtChannels chans) noexcept { return chans == FmtUHJ2 || chans == FmtUHJ3 || chans == FmtUHJ4 || chans == FmtSuperStereo; } /** Ambisonic formats are either B-Format or UHJ formats. */ constexpr bool IsAmbisonic(FmtChannels chans) noexcept { return IsBFormat(chans) || IsUHJ(chans); } constexpr bool Is2DAmbisonic(FmtChannels chans) noexcept { return chans == FmtBFormat2D || chans == FmtUHJ2 || chans == FmtUHJ3 || chans == FmtSuperStereo; } using CallbackType = int(*)(void*, void*, int) noexcept; struct BufferStorage { CallbackType mCallback{nullptr}; void *mUserData{nullptr}; al::span mData; uint mSampleRate{0u}; FmtChannels mChannels{FmtMono}; FmtType mType{FmtShort}; uint mSampleLen{0u}; uint mBlockAlign{0u}; AmbiLayout mAmbiLayout{AmbiLayout::FuMa}; AmbiScaling mAmbiScaling{AmbiScaling::FuMa}; uint mAmbiOrder{0u}; [[nodiscard]] auto bytesFromFmt() const noexcept -> uint { return BytesFromFmt(mType); } [[nodiscard]] auto channelsFromFmt() const noexcept -> uint { return ChannelsFromFmt(mChannels, mAmbiOrder); } [[nodiscard]] auto frameSizeFromFmt() const noexcept -> uint { return channelsFromFmt() * bytesFromFmt(); } [[nodiscard]] auto blockSizeFromFmt() const noexcept -> uint { if(mType == FmtIMA4) return ((mBlockAlign-1)/2 + 4) * channelsFromFmt(); if(mType == FmtMSADPCM) return ((mBlockAlign-2)/2 + 7) * channelsFromFmt(); return frameSizeFromFmt(); }; [[nodiscard]] auto isBFormat() const noexcept -> bool { return IsBFormat(mChannels); } }; #endif /* CORE_BUFFER_STORAGE_H */ openal-soft-1.24.2/core/bufferline.h000066400000000000000000000007221474041540300172540ustar00rootroot00000000000000#ifndef CORE_BUFFERLINE_H #define CORE_BUFFERLINE_H #include #include "alspan.h" /* Size for temporary storage of buffer data, in floats. Larger values need * more memory and are harder on cache, while smaller values may need more * iterations for mixing. */ inline constexpr size_t BufferLineSize{1024}; using FloatBufferLine = std::array; using FloatBufferSpan = al::span; #endif /* CORE_BUFFERLINE_H */ openal-soft-1.24.2/core/context.cpp000066400000000000000000000147151474041540300171610ustar00rootroot00000000000000 #include "config.h" #include #include #include #include #include #include #include "async_event.h" #include "context.h" #include "device.h" #include "effectslot.h" #include "logging.h" #include "ringbuffer.h" #include "voice.h" #include "voice_change.h" #ifdef __cpp_lib_atomic_is_always_lock_free static_assert(std::atomic::is_always_lock_free, "atomic isn't lock-free"); #endif ContextBase::ContextBase(DeviceBase *device) : mDevice{device} { assert(mEnabledEvts.is_lock_free()); } ContextBase::~ContextBase() { mActiveAuxSlots.store(nullptr, std::memory_order_relaxed); mVoices.store(nullptr, std::memory_order_relaxed); if(mAsyncEvents) { size_t count{0}; for(auto &evt : mAsyncEvents->getReadVector()) { if(evt.len > 0) { std::destroy_n(std::launder(reinterpret_cast(evt.buf)), evt.len); count += evt.len; } } if(count > 0) TRACE("Destructed {} orphaned event{}", count, (count==1)?"":"s"); mAsyncEvents->readAdvance(count); } } void ContextBase::allocVoiceChanges() { static constexpr size_t clustersize{std::tuple_size_v}; VoiceChangeCluster clusterptr{std::make_unique()}; const auto cluster = al::span{*clusterptr}; for(size_t i{1};i < clustersize;++i) cluster[i-1].mNext.store(std::addressof(cluster[i]), std::memory_order_relaxed); cluster[clustersize-1].mNext.store(mVoiceChangeTail, std::memory_order_relaxed); mVoiceChangeClusters.emplace_back(std::move(clusterptr)); mVoiceChangeTail = mVoiceChangeClusters.back()->data(); } void ContextBase::allocVoiceProps() { static constexpr size_t clustersize{std::tuple_size_v}; TRACE("Increasing allocated voice properties to {}", (mVoicePropClusters.size()+1) * clustersize); auto clusterptr = std::make_unique(); auto cluster = al::span{*clusterptr}; for(size_t i{1};i < clustersize;++i) cluster[i-1].next.store(std::addressof(cluster[i]), std::memory_order_relaxed); mVoicePropClusters.emplace_back(std::move(clusterptr)); VoicePropsItem *oldhead{mFreeVoiceProps.load(std::memory_order_acquire)}; do { mVoicePropClusters.back()->back().next.store(oldhead, std::memory_order_relaxed); } while(mFreeVoiceProps.compare_exchange_weak(oldhead, mVoicePropClusters.back()->data(), std::memory_order_acq_rel, std::memory_order_acquire) == false); } void ContextBase::allocVoices(size_t addcount) { static constexpr size_t clustersize{std::tuple_size_v}; /* Convert element count to cluster count. */ addcount = (addcount+(clustersize-1)) / clustersize; if(!addcount) { if(!mVoiceClusters.empty()) return; ++addcount; } if(addcount >= std::numeric_limits::max()/clustersize - mVoiceClusters.size()) throw std::runtime_error{"Allocating too many voices"}; const size_t totalcount{(mVoiceClusters.size()+addcount) * clustersize}; TRACE("Increasing allocated voices to {}", totalcount); while(addcount) { mVoiceClusters.emplace_back(std::make_unique()); --addcount; } auto newarray = VoiceArray::Create(totalcount); auto voice_iter = newarray->begin(); for(VoiceCluster &cluster : mVoiceClusters) voice_iter = std::transform(cluster->begin(), cluster->end(), voice_iter, [](Voice &voice) noexcept -> Voice* { return &voice; }); if(auto oldvoices = mVoices.exchange(std::move(newarray), std::memory_order_acq_rel)) std::ignore = mDevice->waitForMix(); } void ContextBase::allocEffectSlotProps() { static constexpr size_t clustersize{std::tuple_size_v}; TRACE("Increasing allocated effect slot properties to {}", (mEffectSlotPropClusters.size()+1) * clustersize); auto clusterptr = std::make_unique(); auto cluster = al::span{*clusterptr}; for(size_t i{1};i < clustersize;++i) cluster[i-1].next.store(std::addressof(cluster[i]), std::memory_order_relaxed); auto *newcluster = mEffectSlotPropClusters.emplace_back(std::move(clusterptr)).get(); EffectSlotProps *oldhead{mFreeEffectSlotProps.load(std::memory_order_acquire)}; do { newcluster->back().next.store(oldhead, std::memory_order_relaxed); } while(mFreeEffectSlotProps.compare_exchange_weak(oldhead, newcluster->data(), std::memory_order_acq_rel, std::memory_order_acquire) == false); } EffectSlot *ContextBase::getEffectSlot() { for(auto& clusterptr : mEffectSlotClusters) { const auto cluster = al::span{*clusterptr}; auto iter = std::find_if_not(cluster.begin(), cluster.end(), std::mem_fn(&EffectSlot::InUse)); if(iter != cluster.end()) return al::to_address(iter); } auto clusterptr = std::make_unique(); if(1 >= std::numeric_limits::max()/clusterptr->size() - mEffectSlotClusters.size()) throw std::runtime_error{"Allocating too many effect slots"}; const size_t totalcount{(mEffectSlotClusters.size()+1) * clusterptr->size()}; TRACE("Increasing allocated effect slots to {}", totalcount); mEffectSlotClusters.emplace_back(std::move(clusterptr)); return mEffectSlotClusters.back()->data(); } void ContextBase::allocContextProps() { static constexpr size_t clustersize{std::tuple_size_v}; TRACE("Increasing allocated context properties to {}", (mContextPropClusters.size()+1) * clustersize); auto clusterptr = std::make_unique(); auto cluster = al::span{*clusterptr}; for(size_t i{1};i < clustersize;++i) cluster[i-1].next.store(std::addressof(cluster[i]), std::memory_order_relaxed); auto *newcluster = mContextPropClusters.emplace_back(std::move(clusterptr)).get(); ContextProps *oldhead{mFreeContextProps.load(std::memory_order_acquire)}; do { newcluster->back().next.store(oldhead, std::memory_order_relaxed); } while(mFreeContextProps.compare_exchange_weak(oldhead, newcluster->data(), std::memory_order_acq_rel, std::memory_order_acquire) == false); } openal-soft-1.24.2/core/context.h000066400000000000000000000131011474041540300166120ustar00rootroot00000000000000#ifndef CORE_CONTEXT_H #define CORE_CONTEXT_H #include "config.h" #include #include #include #include #include #include #include #include "alsem.h" #include "alspan.h" #include "async_event.h" #include "atomic.h" #include "flexarray.h" #include "opthelpers.h" #include "vecmat.h" struct DeviceBase; struct EffectSlot; struct EffectSlotProps; struct RingBuffer; struct Voice; struct VoiceChange; struct VoicePropsItem; inline constexpr float SpeedOfSoundMetersPerSec{343.3f}; inline constexpr float AirAbsorbGainHF{0.99426f}; /* -0.05dB */ enum class DistanceModel : unsigned char { Disable, Inverse, InverseClamped, Linear, LinearClamped, Exponent, ExponentClamped, Default = InverseClamped }; struct ContextProps { std::array Position; std::array Velocity; std::array OrientAt; std::array OrientUp; float Gain; float MetersPerUnit; float AirAbsorptionGainHF; float DopplerFactor; float DopplerVelocity; float SpeedOfSound; #if ALSOFT_EAX float DistanceFactor; #endif bool SourceDistanceModel; DistanceModel mDistanceModel; std::atomic next{}; }; struct ContextParams { /* Pointer to the most recent property values that are awaiting an update. */ std::atomic ContextUpdate{nullptr}; alu::Vector Position; alu::Matrix Matrix{alu::Matrix::Identity()}; alu::Vector Velocity; float Gain{1.0f}; float MetersPerUnit{1.0f}; float AirAbsorptionGainHF{AirAbsorbGainHF}; float DopplerFactor{1.0f}; float SpeedOfSound{SpeedOfSoundMetersPerSec}; /* in units per sec! */ bool SourceDistanceModel{false}; DistanceModel mDistanceModel{}; }; struct ContextBase { DeviceBase *const mDevice; /* Counter for the pre-mixing updates, in 31.1 fixed point (lowest bit * indicates if updates are currently happening). */ std::atomic mUpdateCount{0u}; std::atomic mHoldUpdates{false}; std::atomic mStopVoicesOnDisconnect{true}; float mGainBoost{1.0f}; /* Linked lists of unused property containers, free to use for future * updates. */ std::atomic mFreeContextProps{nullptr}; std::atomic mFreeVoiceProps{nullptr}; std::atomic mFreeEffectSlotProps{nullptr}; /* The voice change tail is the beginning of the "free" elements, up to and * *excluding* the current. If tail==current, there's no free elements and * new ones need to be allocated. The current voice change is the element * last processed, and any after are pending. */ VoiceChange *mVoiceChangeTail{}; std::atomic mCurrentVoiceChange{}; void allocVoiceChanges(); void allocVoiceProps(); void allocEffectSlotProps(); void allocContextProps(); ContextParams mParams; using VoiceArray = al::FlexArray; al::atomic_unique_ptr mVoices; std::atomic mActiveVoiceCount{}; void allocVoices(size_t addcount); [[nodiscard]] auto getVoicesSpan() const noexcept -> al::span { return {mVoices.load(std::memory_order_relaxed)->data(), mActiveVoiceCount.load(std::memory_order_relaxed)}; } [[nodiscard]] auto getVoicesSpanAcquired() const noexcept -> al::span { return {mVoices.load(std::memory_order_acquire)->data(), mActiveVoiceCount.load(std::memory_order_acquire)}; } using EffectSlotArray = al::FlexArray; /* This array is split in half. The front half is the list of activated * effect slots as set by the app, and the back half is the same list but * sorted to ensure later effect slots are fed by earlier ones. */ al::atomic_unique_ptr mActiveAuxSlots; std::thread mEventThread; al::semaphore mEventSem; std::unique_ptr mAsyncEvents; using AsyncEventBitset = std::bitset; std::atomic mEnabledEvts{0u}; /* Asynchronous voice change actions are processed as a linked list of * VoiceChange objects by the mixer, which is atomically appended to. * However, to avoid allocating each object individually, they're allocated * in clusters that are stored in a vector for easy automatic cleanup. */ using VoiceChangeCluster = std::unique_ptr>; std::vector mVoiceChangeClusters; using VoiceCluster = std::unique_ptr>; std::vector mVoiceClusters; using VoicePropsCluster = std::unique_ptr>; std::vector mVoicePropClusters; EffectSlot *getEffectSlot(); using EffectSlotCluster = std::unique_ptr>; std::vector mEffectSlotClusters; using EffectSlotPropsCluster = std::unique_ptr>; std::vector mEffectSlotPropClusters; /* This could be greater than 2, but there should be no way there can be * more than two context property updates in use simultaneously. */ using ContextPropsCluster = std::unique_ptr>; std::vector mContextPropClusters; explicit ContextBase(DeviceBase *device); ContextBase(const ContextBase&) = delete; ContextBase& operator=(const ContextBase&) = delete; virtual ~ContextBase(); }; #endif /* CORE_CONTEXT_H */ openal-soft-1.24.2/core/converter.cpp000066400000000000000000000416541474041540300175060ustar00rootroot00000000000000 #include "config.h" #include "converter.h" #include #include #include #include #include #include #include #include "albit.h" #include "alnumeric.h" #include "fpu_ctrl.h" namespace { constexpr uint MaxPitch{10}; static_assert((BufferLineSize-1)/MaxPitch > 0, "MaxPitch is too large for BufferLineSize!"); static_assert((INT_MAX>>MixerFracBits)/MaxPitch > BufferLineSize, "MaxPitch and/or BufferLineSize are too large for MixerFracBits!"); template constexpr float LoadSample(DevFmtType_t val) noexcept = delete; template<> constexpr float LoadSample(DevFmtType_t val) noexcept { return float(val) * (1.0f/128.0f); } template<> constexpr float LoadSample(DevFmtType_t val) noexcept { return float(val) * (1.0f/32768.0f); } template<> constexpr float LoadSample(DevFmtType_t val) noexcept { return static_cast(val) * (1.0f/2147483648.0f); } template<> constexpr float LoadSample(DevFmtType_t val) noexcept { return val; } template<> constexpr float LoadSample(DevFmtType_t val) noexcept { return LoadSample(static_cast(val - 128)); } template<> constexpr float LoadSample(DevFmtType_t val) noexcept { return LoadSample(static_cast(val - 32768)); } template<> constexpr float LoadSample(DevFmtType_t val) noexcept { return LoadSample(static_cast(val - 2147483648u)); } template inline void LoadSampleArray(const al::span dst, const void *src, const size_t channel, const size_t srcstep) noexcept { assert(channel < srcstep); const auto srcspan = al::span{static_cast*>(src), dst.size()*srcstep}; auto ssrc = srcspan.cbegin(); std::generate(dst.begin(), dst.end(), [&ssrc,channel,srcstep] { const float ret{LoadSample(ssrc[channel])}; ssrc += ptrdiff_t(srcstep); return ret; }); } void LoadSamples(const al::span dst, const void *src, const size_t channel, const size_t srcstep, const DevFmtType srctype) noexcept { #define HANDLE_FMT(T) \ case T: LoadSampleArray(dst, src, channel, srcstep); break switch(srctype) { HANDLE_FMT(DevFmtByte); HANDLE_FMT(DevFmtUByte); HANDLE_FMT(DevFmtShort); HANDLE_FMT(DevFmtUShort); HANDLE_FMT(DevFmtInt); HANDLE_FMT(DevFmtUInt); HANDLE_FMT(DevFmtFloat); } #undef HANDLE_FMT } template inline DevFmtType_t StoreSample(float) noexcept; template<> inline float StoreSample(float val) noexcept { return val; } template<> inline int32_t StoreSample(float val) noexcept { return fastf2i(std::clamp(val*2147483648.0f, -2147483648.0f, 2147483520.0f)); } template<> inline int16_t StoreSample(float val) noexcept { return static_cast(fastf2i(std::clamp(val*32768.0f, -32768.0f, 32767.0f))); } template<> inline int8_t StoreSample(float val) noexcept { return static_cast(fastf2i(std::clamp(val*128.0f, -128.0f, 127.0f))); } /* Define unsigned output variations. */ template<> inline uint32_t StoreSample(float val) noexcept { return static_cast(StoreSample(val)) + 2147483648u; } template<> inline uint16_t StoreSample(float val) noexcept { return static_cast(StoreSample(val) + 32768); } template<> inline uint8_t StoreSample(float val) noexcept { return static_cast(StoreSample(val) + 128); } template inline void StoreSampleArray(void *dst, const al::span src, const size_t channel, const size_t dststep) noexcept { assert(channel < dststep); const auto dstspan = al::span{static_cast*>(dst), src.size()*dststep}; auto sdst = dstspan.begin(); std::for_each(src.cbegin(), src.cend(), [&sdst,channel,dststep](const float in) { sdst[channel] = StoreSample(in); sdst += ptrdiff_t(dststep); }); } void StoreSamples(void *dst, const al::span src, const size_t channel, const size_t dststep, const DevFmtType dsttype) noexcept { #define HANDLE_FMT(T) \ case T: StoreSampleArray(dst, src, channel, dststep); break switch(dsttype) { HANDLE_FMT(DevFmtByte); HANDLE_FMT(DevFmtUByte); HANDLE_FMT(DevFmtShort); HANDLE_FMT(DevFmtUShort); HANDLE_FMT(DevFmtInt); HANDLE_FMT(DevFmtUInt); HANDLE_FMT(DevFmtFloat); } #undef HANDLE_FMT } template void Mono2Stereo(const al::span dst, const void *src) noexcept { const auto srcspan = al::span{static_cast*>(src), dst.size()>>1}; auto sdst = dst.begin(); std::for_each(srcspan.cbegin(), srcspan.cend(), [&sdst](const auto in) { sdst = std::fill_n(sdst, 2, LoadSample(in)*0.707106781187f); }); } template void Multi2Mono(uint chanmask, const size_t step, const float scale, const al::span dst, const void *src) noexcept { const auto srcspan = al::span{static_cast*>(src), step*dst.size()}; std::fill_n(dst.begin(), dst.size(), 0.0f); for(size_t c{0};chanmask;++c) { if((chanmask&1)) LIKELY { auto ssrc = srcspan.cbegin(); std::for_each(dst.begin(), dst.end(), [&ssrc,step,c](float &sample) { const float s{LoadSample(ssrc[c])}; ssrc += ptrdiff_t(step); sample += s; }); } chanmask >>= 1; } std::for_each(dst.begin(), dst.end(), [scale](float &sample) noexcept { sample *= scale; }); } } // namespace SampleConverterPtr SampleConverter::Create(DevFmtType srcType, DevFmtType dstType, size_t numchans, uint srcRate, uint dstRate, Resampler resampler) { SampleConverterPtr converter; if(numchans < 1 || srcRate < 1 || dstRate < 1) return converter; converter = SampleConverterPtr{new(FamCount(numchans)) SampleConverter{numchans}}; converter->mSrcType = srcType; converter->mDstType = dstType; converter->mSrcTypeSize = BytesFromDevFmt(srcType); converter->mDstTypeSize = BytesFromDevFmt(dstType); converter->mSrcPrepCount = MaxResamplerPadding; converter->mFracOffset = 0; for(auto &chan : converter->mChan) chan.PrevSamples.fill(0.0f); /* Have to set the mixer FPU mode since that's what the resampler code expects. */ FPUCtl mixer_mode{}; const auto step = std::min(std::round(srcRate*double{MixerFracOne}/dstRate), MaxPitch*double{MixerFracOne}); converter->mIncrement = std::max(static_cast(step), 1u); if(converter->mIncrement == MixerFracOne) { converter->mResample = [](const InterpState*, const al::span src, uint, const uint, const al::span dst) { std::copy_n(src.begin()+MaxResamplerEdge, dst.size(), dst.begin()); }; } else converter->mResample = PrepareResampler(resampler, converter->mIncrement, &converter->mState); return converter; } uint SampleConverter::availableOut(uint srcframes) const { if(srcframes < 1) { /* No output samples if there's no input samples. */ return 0; } const uint prepcount{mSrcPrepCount}; if(prepcount < MaxResamplerPadding && MaxResamplerPadding - prepcount >= srcframes) { /* Not enough input samples to generate an output sample. */ return 0; } uint64_t DataSize64{prepcount}; DataSize64 += srcframes; DataSize64 -= MaxResamplerPadding; DataSize64 <<= MixerFracBits; DataSize64 -= mFracOffset; /* If we have a full prep, we can generate at least one sample. */ return static_cast(std::clamp((DataSize64 + mIncrement-1)/mIncrement, 1_u64, uint64_t{std::numeric_limits::max()})); } uint SampleConverter::convert(const void **src, uint *srcframes, void *dst, uint dstframes) { const size_t SrcFrameSize{mChan.size() * mSrcTypeSize}; const size_t DstFrameSize{mChan.size() * mDstTypeSize}; const uint increment{mIncrement}; uint NumSrcSamples{*srcframes}; auto SamplesIn = al::span{static_cast(*src), NumSrcSamples*SrcFrameSize}; auto SamplesOut = al::span{static_cast(dst), dstframes*DstFrameSize}; FPUCtl mixer_mode{}; uint pos{0}; while(pos < dstframes && NumSrcSamples > 0) { const uint prepcount{mSrcPrepCount}; const uint readable{std::min(NumSrcSamples, uint{BufferLineSize} - prepcount)}; if(prepcount < MaxResamplerPadding && MaxResamplerPadding-prepcount >= readable) { /* Not enough input samples to generate an output sample. Store * what we're given for later. */ for(size_t chan{0u};chan < mChan.size();chan++) LoadSamples(al::span{mChan[chan].PrevSamples}.subspan(prepcount, readable), SamplesIn.data(), chan, mChan.size(), mSrcType); mSrcPrepCount = prepcount + readable; NumSrcSamples = 0; break; } const auto SrcData = al::span{mSrcSamples}; const auto DstData = al::span{mDstSamples}; uint DataPosFrac{mFracOffset}; uint64_t DataSize64{prepcount}; DataSize64 += readable; DataSize64 -= MaxResamplerPadding; DataSize64 <<= MixerFracBits; DataSize64 -= DataPosFrac; /* If we have a full prep, we can generate at least one sample. */ auto DstSize = static_cast(std::clamp((DataSize64 + increment-1)/increment, 1_u64, uint64_t{BufferLineSize})); DstSize = std::min(DstSize, dstframes-pos); const uint DataPosEnd{DstSize*increment + DataPosFrac}; const uint SrcDataEnd{DataPosEnd>>MixerFracBits}; assert(prepcount+readable >= SrcDataEnd); const uint nextprep{std::min(prepcount+readable-SrcDataEnd, MaxResamplerPadding)}; for(size_t chan{0u};chan < mChan.size();chan++) { /* Load the previous samples into the source data first, then the * new samples from the input buffer. */ std::copy_n(mChan[chan].PrevSamples.cbegin(), prepcount, SrcData.begin()); LoadSamples(SrcData.subspan(prepcount, readable), SamplesIn.data(), chan, mChan.size(), mSrcType); /* Store as many prep samples for next time as possible, given the * number of output samples being generated. */ auto previter = std::copy_n(SrcData.begin()+ptrdiff_t(SrcDataEnd), nextprep, mChan[chan].PrevSamples.begin()); std::fill(previter, mChan[chan].PrevSamples.end(), 0.0f); /* Now resample, and store the result in the output buffer. */ mResample(&mState, SrcData, DataPosFrac, increment, DstData.first(DstSize)); StoreSamples(SamplesOut.data(), DstData.first(DstSize), chan, mChan.size(), mDstType); } /* Update the number of prep samples still available, as well as the * fractional offset. */ mSrcPrepCount = nextprep; mFracOffset = DataPosEnd & MixerFracMask; /* Update the src and dst pointers in case there's still more to do. */ const uint srcread{std::min(NumSrcSamples, SrcDataEnd + mSrcPrepCount - prepcount)}; SamplesIn = SamplesIn.subspan(SrcFrameSize*srcread); NumSrcSamples -= srcread; SamplesOut = SamplesOut.subspan(DstFrameSize*DstSize); pos += DstSize; } *src = SamplesIn.data(); *srcframes = NumSrcSamples; return pos; } uint SampleConverter::convertPlanar(const void **src, uint *srcframes, void *const*dst, uint dstframes) { const auto srcs = al::span{src, mChan.size()}; const auto dsts = al::span{dst, mChan.size()}; const uint increment{mIncrement}; uint NumSrcSamples{*srcframes}; FPUCtl mixer_mode{}; uint pos{0}; while(pos < dstframes && NumSrcSamples > 0) { const uint prepcount{mSrcPrepCount}; const uint readable{std::min(NumSrcSamples, uint{BufferLineSize} - prepcount)}; if(prepcount < MaxResamplerPadding && MaxResamplerPadding-prepcount >= readable) { /* Not enough input samples to generate an output sample. Store * what we're given for later. */ for(size_t chan{0u};chan < mChan.size();chan++) { auto samples = al::span{static_cast(srcs[chan]), NumSrcSamples*size_t{mSrcTypeSize}}; LoadSamples(al::span{mChan[chan].PrevSamples}.subspan(prepcount, readable), samples.data(), 0, 1, mSrcType); srcs[chan] = samples.subspan(size_t{mSrcTypeSize}*readable).data(); } mSrcPrepCount = prepcount + readable; NumSrcSamples = 0; break; } const auto SrcData = al::span{mSrcSamples}; const auto DstData = al::span{mDstSamples}; uint DataPosFrac{mFracOffset}; uint64_t DataSize64{prepcount}; DataSize64 += readable; DataSize64 -= MaxResamplerPadding; DataSize64 <<= MixerFracBits; DataSize64 -= DataPosFrac; /* If we have a full prep, we can generate at least one sample. */ auto DstSize = static_cast(std::clamp((DataSize64 + increment-1)/increment, 1_u64, uint64_t{BufferLineSize})); DstSize = std::min(DstSize, dstframes-pos); const uint DataPosEnd{DstSize*increment + DataPosFrac}; const uint SrcDataEnd{DataPosEnd>>MixerFracBits}; assert(prepcount+readable >= SrcDataEnd); const uint nextprep{std::min(prepcount+readable-SrcDataEnd, MaxResamplerPadding)}; for(size_t chan{0u};chan < mChan.size();chan++) { /* Load the previous samples into the source data first, then the * new samples from the input buffer. */ auto srciter = std::copy_n(mChan[chan].PrevSamples.cbegin(),prepcount,SrcData.begin()); LoadSamples({srciter, readable}, srcs[chan], 0, 1, mSrcType); /* Store as many prep samples for next time as possible, given the * number of output samples being generated. */ auto previter = std::copy_n(SrcData.begin()+ptrdiff_t(SrcDataEnd), nextprep, mChan[chan].PrevSamples.begin()); std::fill(previter, mChan[chan].PrevSamples.end(), 0.0f); /* Now resample, and store the result in the output buffer. */ mResample(&mState, SrcData, DataPosFrac, increment, DstData.first(DstSize)); auto DstSamples = al::span{static_cast(dsts[chan]), size_t{mDstTypeSize}*dstframes}.subspan(pos*size_t{mDstTypeSize}); StoreSamples(DstSamples.data(), DstData.first(DstSize), 0, 1, mDstType); } /* Update the number of prep samples still available, as well as the * fractional offset. */ mSrcPrepCount = nextprep; mFracOffset = DataPosEnd & MixerFracMask; /* Update the src and dst pointers in case there's still more to do. */ const uint srcread{std::min(NumSrcSamples, SrcDataEnd + mSrcPrepCount - prepcount)}; std::for_each(srcs.begin(), srcs.end(), [this,NumSrcSamples,srcread](const void *&srcref) { auto srcspan = al::span{static_cast(srcref), size_t{mSrcTypeSize}*NumSrcSamples}; srcref = srcspan.subspan(size_t{mSrcTypeSize}*srcread).data(); }); NumSrcSamples -= srcread; pos += DstSize; } *srcframes = NumSrcSamples; return pos; } void ChannelConverter::convert(const void *src, float *dst, uint frames) const { if(mDstChans == DevFmtMono) { const float scale{std::sqrt(1.0f / static_cast(al::popcount(mChanMask)))}; switch(mSrcType) { #define HANDLE_FMT(T) case T: Multi2Mono(mChanMask, mSrcStep, scale, {dst, frames}, src); break HANDLE_FMT(DevFmtByte); HANDLE_FMT(DevFmtUByte); HANDLE_FMT(DevFmtShort); HANDLE_FMT(DevFmtUShort); HANDLE_FMT(DevFmtInt); HANDLE_FMT(DevFmtUInt); HANDLE_FMT(DevFmtFloat); #undef HANDLE_FMT } } else if(mChanMask == 0x1 && mDstChans == DevFmtStereo) { switch(mSrcType) { #define HANDLE_FMT(T) case T: Mono2Stereo({dst, frames*2_uz}, src); break HANDLE_FMT(DevFmtByte); HANDLE_FMT(DevFmtUByte); HANDLE_FMT(DevFmtShort); HANDLE_FMT(DevFmtUShort); HANDLE_FMT(DevFmtInt); HANDLE_FMT(DevFmtUInt); HANDLE_FMT(DevFmtFloat); #undef HANDLE_FMT } } } openal-soft-1.24.2/core/converter.h000066400000000000000000000037301474041540300171440ustar00rootroot00000000000000#ifndef CORE_CONVERTER_H #define CORE_CONVERTER_H #include #include #include #include "almalloc.h" #include "devformat.h" #include "flexarray.h" #include "mixer/defs.h" #include "resampler_limits.h" using uint = unsigned int; struct SampleConverter { DevFmtType mSrcType{}; DevFmtType mDstType{}; uint mSrcTypeSize{}; uint mDstTypeSize{}; uint mSrcPrepCount{}; uint mFracOffset{}; uint mIncrement{}; InterpState mState; ResamplerFunc mResample{}; alignas(16) FloatBufferLine mSrcSamples{}; alignas(16) FloatBufferLine mDstSamples{}; struct ChanSamples { alignas(16) std::array PrevSamples; }; al::FlexArray mChan; explicit SampleConverter(size_t numchans) : mChan{numchans} { } [[nodiscard]] auto convert(const void **src, uint *srcframes, void *dst, uint dstframes) -> uint; [[nodiscard]] auto convertPlanar(const void **src, uint *srcframes, void *const*dst, uint dstframes) -> uint; [[nodiscard]] auto availableOut(uint srcframes) const -> uint; using SampleOffset = std::chrono::duration>; [[nodiscard]] auto currentInputDelay() const noexcept -> SampleOffset { const int64_t prep{int64_t{mSrcPrepCount} - MaxResamplerEdge}; return SampleOffset{(prep< Create(DevFmtType srcType, DevFmtType dstType, size_t numchans, uint srcRate, uint dstRate, Resampler resampler); DEF_FAM_NEWDEL(SampleConverter, mChan) }; using SampleConverterPtr = std::unique_ptr; struct ChannelConverter { DevFmtType mSrcType{}; uint mSrcStep{}; uint mChanMask{}; DevFmtChannels mDstChans{}; [[nodiscard]] auto is_active() const noexcept -> bool { return mChanMask != 0; } void convert(const void *src, float *dst, uint frames) const; }; #endif /* CORE_CONVERTER_H */ openal-soft-1.24.2/core/cpu_caps.cpp000066400000000000000000000103651474041540300172670ustar00rootroot00000000000000 #include "config.h" #include "config_simd.h" #include "cpu_caps.h" #if defined(_WIN32) && (defined(_M_ARM) || defined(_M_ARM64)) #define WIN32_LEAN_AND_MEAN #include #ifndef PF_ARM_NEON_INSTRUCTIONS_AVAILABLE #define PF_ARM_NEON_INSTRUCTIONS_AVAILABLE 19 #endif #endif #if defined(HAVE_CPUID_H) #include #elif defined(HAVE_INTRIN_H) #include #endif #include #include #include #include namespace { #if defined(HAVE_GCC_GET_CPUID) \ && (defined(__i386__) || defined(__x86_64__) || defined(_M_IX86) || defined(_M_X64)) using reg_type = unsigned int; inline std::array get_cpuid(unsigned int f) { std::array ret{}; __get_cpuid(f, ret.data(), &ret[1], &ret[2], &ret[3]); return ret; } #define CAN_GET_CPUID #elif defined(HAVE_CPUID_INTRINSIC) \ && (defined(__i386__) || defined(__x86_64__) || defined(_M_IX86) || defined(_M_X64)) using reg_type = int; inline std::array get_cpuid(unsigned int f) { std::array ret{}; (__cpuid)(ret.data(), f); return ret; } #define CAN_GET_CPUID #endif } // namespace std::optional GetCPUInfo() { CPUInfo ret; #ifdef CAN_GET_CPUID auto cpuregs = get_cpuid(0); if(cpuregs[0] == 0) return std::nullopt; const reg_type maxfunc{cpuregs[0]}; cpuregs = get_cpuid(0x80000000); const reg_type maxextfunc{cpuregs[0]}; ret.mVendor.append(reinterpret_cast(&cpuregs[1]), 4); ret.mVendor.append(reinterpret_cast(&cpuregs[3]), 4); ret.mVendor.append(reinterpret_cast(&cpuregs[2]), 4); auto iter_end = std::remove(ret.mVendor.begin(), ret.mVendor.end(), '\0'); iter_end = std::unique(ret.mVendor.begin(), iter_end, [](auto&& c0, auto&& c1) { return std::isspace(c0) && std::isspace(c1); }); ret.mVendor.erase(iter_end, ret.mVendor.end()); if(!ret.mVendor.empty() && std::isspace(ret.mVendor.back())) ret.mVendor.pop_back(); if(!ret.mVendor.empty() && std::isspace(ret.mVendor.front())) ret.mVendor.erase(ret.mVendor.begin()); if(maxextfunc >= 0x80000004) { cpuregs = get_cpuid(0x80000002); ret.mName.append(reinterpret_cast(cpuregs.data()), 16); cpuregs = get_cpuid(0x80000003); ret.mName.append(reinterpret_cast(cpuregs.data()), 16); cpuregs = get_cpuid(0x80000004); ret.mName.append(reinterpret_cast(cpuregs.data()), 16); iter_end = std::remove(ret.mName.begin(), ret.mName.end(), '\0'); iter_end = std::unique(ret.mName.begin(), iter_end, [](auto&& c0, auto&& c1) { return std::isspace(c0) && std::isspace(c1); }); ret.mName.erase(iter_end, ret.mName.end()); if(!ret.mName.empty() && std::isspace(ret.mName.back())) ret.mName.pop_back(); if(!ret.mName.empty() && std::isspace(ret.mName.front())) ret.mName.erase(ret.mName.begin()); } if(maxfunc >= 1) { cpuregs = get_cpuid(1); if((cpuregs[3]&(1<<25))) ret.mCaps |= CPU_CAP_SSE; if((ret.mCaps&CPU_CAP_SSE) && (cpuregs[3]&(1<<26))) ret.mCaps |= CPU_CAP_SSE2; if((ret.mCaps&CPU_CAP_SSE2) && (cpuregs[2]&(1<<0))) ret.mCaps |= CPU_CAP_SSE3; if((ret.mCaps&CPU_CAP_SSE3) && (cpuregs[2]&(1<<19))) ret.mCaps |= CPU_CAP_SSE4_1; } #else /* Assume support for whatever's supported if we can't check for it */ #if HAVE_SSE4_1 #warning "Assuming SSE 4.1 run-time support!" ret.mCaps |= CPU_CAP_SSE | CPU_CAP_SSE2 | CPU_CAP_SSE3 | CPU_CAP_SSE4_1; #elif HAVE_SSE3 #warning "Assuming SSE 3 run-time support!" ret.mCaps |= CPU_CAP_SSE | CPU_CAP_SSE2 | CPU_CAP_SSE3; #elif HAVE_SSE2 #warning "Assuming SSE 2 run-time support!" ret.mCaps |= CPU_CAP_SSE | CPU_CAP_SSE2; #elif HAVE_SSE #warning "Assuming SSE run-time support!" ret.mCaps |= CPU_CAP_SSE; #endif #endif /* CAN_GET_CPUID */ #if HAVE_NEON #ifdef __ARM_NEON ret.mCaps |= CPU_CAP_NEON; #elif defined(_WIN32) && (defined(_M_ARM) || defined(_M_ARM64)) if(IsProcessorFeaturePresent(PF_ARM_NEON_INSTRUCTIONS_AVAILABLE)) ret.mCaps |= CPU_CAP_NEON; #else #warning "Assuming NEON run-time support!" ret.mCaps |= CPU_CAP_NEON; #endif #endif return ret; } openal-soft-1.24.2/core/cpu_caps.h000066400000000000000000000006401474041540300167270ustar00rootroot00000000000000#ifndef CORE_CPU_CAPS_H #define CORE_CPU_CAPS_H #include #include inline int CPUCapFlags{0}; enum { CPU_CAP_SSE = 1<<0, CPU_CAP_SSE2 = 1<<1, CPU_CAP_SSE3 = 1<<2, CPU_CAP_SSE4_1 = 1<<3, CPU_CAP_NEON = 1<<4, }; struct CPUInfo { std::string mVendor; std::string mName; int mCaps{0}; }; std::optional GetCPUInfo(); #endif /* CORE_CPU_CAPS_H */ openal-soft-1.24.2/core/cubic_defs.h000066400000000000000000000006241474041540300172220ustar00rootroot00000000000000#ifndef CORE_CUBIC_DEFS_H #define CORE_CUBIC_DEFS_H #include /* The number of distinct phase intervals within the cubic filter tables. */ constexpr unsigned int CubicPhaseBits{5}; constexpr unsigned int CubicPhaseCount{1 << CubicPhaseBits}; struct CubicCoefficients { alignas(16) std::array mCoeffs; alignas(16) std::array mDeltas; }; #endif /* CORE_CUBIC_DEFS_H */ openal-soft-1.24.2/core/cubic_tables.cpp000066400000000000000000000121451474041540300201070ustar00rootroot00000000000000 #include "cubic_tables.h" #include #include #include #include "alnumbers.h" #include "alnumeric.h" #include "cubic_defs.h" /* These gaussian filter tables are inspired by the gaussian-like filter found * in the SNES. This is based on the public domain code developed by Near, with * the help of Ryphecha and nocash, from the nesdev.org forums. * * * * Additional changes were made here, the most obvious being that it has full * floating-point precision instead of 11-bit fixed-point, but also an offset * adjustment for the coefficients to better preserve phase. */ namespace { [[nodiscard]] auto GetCoeff(double idx) noexcept -> double { const double k{0.5 + idx}; if(k > 512.0) return 0.0; const double s{ std::sin(al::numbers::pi*1.280/1024.0 * k)}; const double t{(std::cos(al::numbers::pi*2.000/1023.0 * k) - 1.0) * 0.50}; const double u{(std::cos(al::numbers::pi*4.000/1023.0 * k) - 1.0) * 0.08}; return s * (t + u + 1.0) / k; } } // namespace GaussianTable::GaussianTable() { static constexpr double IndexScale{512.0 / double{CubicPhaseCount*2}}; /* Fill in the main coefficients. */ for(std::size_t pi{0};pi < CubicPhaseCount;++pi) { const double coeff0{GetCoeff(static_cast(CubicPhaseCount + pi)*IndexScale)}; const double coeff1{GetCoeff(static_cast(pi)*IndexScale)}; const double coeff2{GetCoeff(static_cast(CubicPhaseCount - pi)*IndexScale)}; const double coeff3{GetCoeff(static_cast(CubicPhaseCount*2_uz-pi)*IndexScale)}; const double scale{1.0 / (coeff0 + coeff1 + coeff2 + coeff3)}; mTable[pi].mCoeffs[0] = static_cast(coeff0 * scale); mTable[pi].mCoeffs[1] = static_cast(coeff1 * scale); mTable[pi].mCoeffs[2] = static_cast(coeff2 * scale); mTable[pi].mCoeffs[3] = static_cast(coeff3 * scale); } /* Fill in the coefficient deltas. */ for(std::size_t pi{0};pi < CubicPhaseCount-1;++pi) { mTable[pi].mDeltas[0] = mTable[pi+1].mCoeffs[0] - mTable[pi].mCoeffs[0]; mTable[pi].mDeltas[1] = mTable[pi+1].mCoeffs[1] - mTable[pi].mCoeffs[1]; mTable[pi].mDeltas[2] = mTable[pi+1].mCoeffs[2] - mTable[pi].mCoeffs[2]; mTable[pi].mDeltas[3] = mTable[pi+1].mCoeffs[3] - mTable[pi].mCoeffs[3]; } const std::size_t pi{CubicPhaseCount - 1}; mTable[pi].mDeltas[0] = 0.0f - mTable[pi].mCoeffs[0]; mTable[pi].mDeltas[1] = mTable[0].mCoeffs[0] - mTable[pi].mCoeffs[1]; mTable[pi].mDeltas[2] = mTable[0].mCoeffs[1] - mTable[pi].mCoeffs[2]; mTable[pi].mDeltas[3] = mTable[0].mCoeffs[2] - mTable[pi].mCoeffs[3]; } SplineTable::SplineTable() { /* This filter table is based on a Catmull-Rom spline. It retains more of * the original high-frequency content, at the cost of increased harmonics. */ for(std::size_t pi{0};pi < CubicPhaseCount;++pi) { const double mu{static_cast(pi) / double{CubicPhaseCount}}; const double mu2{mu*mu}, mu3{mu2*mu}; mTable[pi].mCoeffs[0] = static_cast(-0.5*mu3 + mu2 + -0.5*mu); mTable[pi].mCoeffs[1] = static_cast( 1.5*mu3 + -2.5*mu2 + 1.0); mTable[pi].mCoeffs[2] = static_cast(-1.5*mu3 + 2.0*mu2 + 0.5*mu); mTable[pi].mCoeffs[3] = static_cast( 0.5*mu3 + -0.5*mu2); } for(std::size_t pi{0};pi < CubicPhaseCount-1;++pi) { mTable[pi].mDeltas[0] = mTable[pi+1].mCoeffs[0] - mTable[pi].mCoeffs[0]; mTable[pi].mDeltas[1] = mTable[pi+1].mCoeffs[1] - mTable[pi].mCoeffs[1]; mTable[pi].mDeltas[2] = mTable[pi+1].mCoeffs[2] - mTable[pi].mCoeffs[2]; mTable[pi].mDeltas[3] = mTable[pi+1].mCoeffs[3] - mTable[pi].mCoeffs[3]; } const std::size_t pi{CubicPhaseCount - 1}; mTable[pi].mDeltas[0] = 0.0f - mTable[pi].mCoeffs[0]; mTable[pi].mDeltas[1] = mTable[0].mCoeffs[0] - mTable[pi].mCoeffs[1]; mTable[pi].mDeltas[2] = mTable[0].mCoeffs[1] - mTable[pi].mCoeffs[2]; mTable[pi].mDeltas[3] = mTable[0].mCoeffs[2] - mTable[pi].mCoeffs[3]; } CubicFilter::CubicFilter() { static constexpr double IndexScale{512.0 / double{sTableSteps*2}}; /* Only half the coefficients need to be iterated here, since Coeff2 and * Coeff3 are just Coeff1 and Coeff0 in reverse respectively. */ for(size_t i{0};i < sTableSteps/2 + 1;++i) { const double coeff0{GetCoeff(static_cast(sTableSteps + i)*IndexScale)}; const double coeff1{GetCoeff(static_cast(i)*IndexScale)}; const double coeff2{GetCoeff(static_cast(sTableSteps - i)*IndexScale)}; const double coeff3{GetCoeff(static_cast(sTableSteps*2_uz - i)*IndexScale)}; const double scale{1.0 / (coeff0 + coeff1 + coeff2 + coeff3)}; mFilter[sTableSteps + i] = static_cast(coeff0 * scale); mFilter[i] = static_cast(coeff1 * scale); mFilter[sTableSteps - i] = static_cast(coeff2 * scale); mFilter[sTableSteps*2 - i] = static_cast(coeff3 * scale); } } openal-soft-1.24.2/core/cubic_tables.h000066400000000000000000000023411474041540300175510ustar00rootroot00000000000000#ifndef CORE_CUBIC_TABLES_H #define CORE_CUBIC_TABLES_H #include #include #include "cubic_defs.h" #include "opthelpers.h" struct SIMDALIGN CubicTable { std::array mTable{}; }; struct GaussianTable : CubicTable { GaussianTable(); }; inline const GaussianTable gGaussianFilter; struct SplineTable : CubicTable { SplineTable(); }; inline const SplineTable gSplineFilter; struct CubicFilter { static constexpr std::size_t sTableBits{8}; static constexpr std::size_t sTableSteps{1 << sTableBits}; static constexpr std::size_t sTableMask{sTableSteps - 1}; std::array mFilter{}; CubicFilter(); [[nodiscard]] constexpr auto getCoeff0(std::size_t i) const noexcept -> float { return mFilter[sTableSteps+i]; } [[nodiscard]] constexpr auto getCoeff1(std::size_t i) const noexcept -> float { return mFilter[i]; } [[nodiscard]] constexpr auto getCoeff2(std::size_t i) const noexcept -> float { return mFilter[sTableSteps-i]; } [[nodiscard]] constexpr auto getCoeff3(std::size_t i) const noexcept -> float { return mFilter[sTableSteps*2-i]; } }; inline const CubicFilter gCubicTable; #endif /* CORE_CUBIC_TABLES_H */ openal-soft-1.24.2/core/dbus_wrap.cpp000066400000000000000000000017731474041540300174630ustar00rootroot00000000000000 #include "config.h" #include "dbus_wrap.h" #if HAVE_DYNLOAD #include #include #include "logging.h" void PrepareDBus() { const char *libname{"libdbus-1.so.3"}; dbus_handle = LoadLib(libname); if(!dbus_handle) { WARN("Failed to load {}", libname); return; } auto load_func = [](auto &f, const char *name) -> void { f = reinterpret_cast>(GetSymbol(dbus_handle, name)); }; #define LOAD_FUNC(x) do { \ load_func(p##x, #x); \ if(!p##x) \ { \ WARN("Failed to load function {}", #x); \ CloseLib(dbus_handle); \ dbus_handle = nullptr; \ return; \ } \ } while(0); DBUS_FUNCTIONS(LOAD_FUNC) #undef LOAD_FUNC } #endif openal-soft-1.24.2/core/dbus_wrap.h000066400000000000000000000051121474041540300171170ustar00rootroot00000000000000#ifndef CORE_DBUS_WRAP_H #define CORE_DBUS_WRAP_H #include #include #include "dynload.h" #if HAVE_DYNLOAD #include #define DBUS_FUNCTIONS(MAGIC) \ MAGIC(dbus_error_init) \ MAGIC(dbus_error_free) \ MAGIC(dbus_bus_get) \ MAGIC(dbus_connection_set_exit_on_disconnect) \ MAGIC(dbus_connection_unref) \ MAGIC(dbus_connection_send_with_reply_and_block) \ MAGIC(dbus_message_unref) \ MAGIC(dbus_message_new_method_call) \ MAGIC(dbus_message_append_args) \ MAGIC(dbus_message_iter_init) \ MAGIC(dbus_message_iter_next) \ MAGIC(dbus_message_iter_recurse) \ MAGIC(dbus_message_iter_get_arg_type) \ MAGIC(dbus_message_iter_get_basic) \ MAGIC(dbus_set_error_from_message) inline void *dbus_handle{}; #define DECL_FUNC(x) inline decltype(x) *p##x{}; DBUS_FUNCTIONS(DECL_FUNC) #undef DECL_FUNC #ifndef IN_IDE_PARSER #define dbus_error_init (*pdbus_error_init) #define dbus_error_free (*pdbus_error_free) #define dbus_bus_get (*pdbus_bus_get) #define dbus_connection_set_exit_on_disconnect (*pdbus_connection_set_exit_on_disconnect) #define dbus_connection_unref (*pdbus_connection_unref) #define dbus_connection_send_with_reply_and_block (*pdbus_connection_send_with_reply_and_block) #define dbus_message_unref (*pdbus_message_unref) #define dbus_message_new_method_call (*pdbus_message_new_method_call) #define dbus_message_append_args (*pdbus_message_append_args) #define dbus_message_iter_init (*pdbus_message_iter_init) #define dbus_message_iter_next (*pdbus_message_iter_next) #define dbus_message_iter_recurse (*pdbus_message_iter_recurse) #define dbus_message_iter_get_arg_type (*pdbus_message_iter_get_arg_type) #define dbus_message_iter_get_basic (*pdbus_message_iter_get_basic) #define dbus_set_error_from_message (*pdbus_set_error_from_message) #endif void PrepareDBus(); inline auto HasDBus() { static std::once_flag init_dbus{}; std::call_once(init_dbus, []{ PrepareDBus(); }); return dbus_handle; } #else constexpr bool HasDBus() noexcept { return true; } #endif namespace dbus { struct Error { Error() { dbus_error_init(&mError); } Error(const Error&) = delete; Error(Error&&) = delete; ~Error() { dbus_error_free(&mError); } void operator=(const Error&) = delete; void operator=(Error&&) = delete; DBusError* operator->() { return &mError; } DBusError &get() { return mError; } private: DBusError mError{}; }; struct ConnectionDeleter { void operator()(DBusConnection *c) { dbus_connection_unref(c); } }; using ConnectionPtr = std::unique_ptr; } // namespace dbus #endif /* CORE_DBUS_WRAP_H */ openal-soft-1.24.2/core/devformat.cpp000066400000000000000000000037571474041540300174700ustar00rootroot00000000000000 #include "config.h" #include "devformat.h" #include namespace { using namespace std::string_view_literals; } // namespace uint BytesFromDevFmt(DevFmtType type) noexcept { switch(type) { case DevFmtByte: return sizeof(int8_t); case DevFmtUByte: return sizeof(uint8_t); case DevFmtShort: return sizeof(int16_t); case DevFmtUShort: return sizeof(uint16_t); case DevFmtInt: return sizeof(int32_t); case DevFmtUInt: return sizeof(uint32_t); case DevFmtFloat: return sizeof(float); } return 0; } uint ChannelsFromDevFmt(DevFmtChannels chans, uint ambiorder) noexcept { switch(chans) { case DevFmtMono: return 1; case DevFmtStereo: return 2; case DevFmtQuad: return 4; case DevFmtX51: return 6; case DevFmtX61: return 7; case DevFmtX71: return 8; case DevFmtX714: return 12; case DevFmtX7144: return 16; case DevFmtX3D71: return 8; case DevFmtAmbi3D: return (ambiorder+1) * (ambiorder+1); } return 0; } auto DevFmtTypeString(DevFmtType type) noexcept -> std::string_view { switch(type) { case DevFmtByte: return "Int8"sv; case DevFmtUByte: return "UInt8"sv; case DevFmtShort: return "Int16"sv; case DevFmtUShort: return "UInt16"sv; case DevFmtInt: return "Int32"sv; case DevFmtUInt: return "UInt32"sv; case DevFmtFloat: return "Float32"sv; } return "(unknown type)"sv; } auto DevFmtChannelsString(DevFmtChannels chans) noexcept -> std::string_view { switch(chans) { case DevFmtMono: return "Mono"sv; case DevFmtStereo: return "Stereo"sv; case DevFmtQuad: return "Quadraphonic"sv; case DevFmtX51: return "5.1 Surround"sv; case DevFmtX61: return "6.1 Surround"sv; case DevFmtX71: return "7.1 Surround"sv; case DevFmtX714: return "7.1.4 Surround"sv; case DevFmtX7144: return "7.1.4.4 Surround"sv; case DevFmtX3D71: return "3D7.1 Surround"sv; case DevFmtAmbi3D: return "Ambisonic 3D"sv; } return "(unknown channels)"sv; } openal-soft-1.24.2/core/devformat.h000066400000000000000000000050321474041540300171210ustar00rootroot00000000000000#ifndef CORE_DEVFORMAT_H #define CORE_DEVFORMAT_H #include #include #include using uint = unsigned int; enum Channel : unsigned char { FrontLeft = 0, FrontRight, FrontCenter, LFE, BackLeft, BackRight, BackCenter, SideLeft, SideRight, TopCenter, TopFrontLeft, TopFrontCenter, TopFrontRight, TopBackLeft, TopBackCenter, TopBackRight, BottomFrontLeft, BottomFrontRight, BottomBackLeft, BottomBackRight, Aux0, Aux1, Aux2, Aux3, Aux4, Aux5, Aux6, Aux7, Aux8, Aux9, Aux10, Aux11, Aux12, Aux13, Aux14, Aux15, MaxChannels }; /* Device formats */ enum DevFmtType : unsigned char { DevFmtByte, DevFmtUByte, DevFmtShort, DevFmtUShort, DevFmtInt, DevFmtUInt, DevFmtFloat, DevFmtTypeDefault = DevFmtFloat }; enum DevFmtChannels : unsigned char { DevFmtMono, DevFmtStereo, DevFmtQuad, DevFmtX51, DevFmtX61, DevFmtX71, DevFmtX714, DevFmtX7144, DevFmtX3D71, DevFmtAmbi3D, DevFmtChannelsDefault = DevFmtStereo }; inline constexpr std::size_t MaxOutputChannels{16}; /* DevFmtType traits, providing the type, etc given a DevFmtType. */ template struct DevFmtTypeTraits { }; template<> struct DevFmtTypeTraits { using Type = int8_t; }; template<> struct DevFmtTypeTraits { using Type = uint8_t; }; template<> struct DevFmtTypeTraits { using Type = int16_t; }; template<> struct DevFmtTypeTraits { using Type = uint16_t; }; template<> struct DevFmtTypeTraits { using Type = int32_t; }; template<> struct DevFmtTypeTraits { using Type = uint32_t; }; template<> struct DevFmtTypeTraits { using Type = float; }; template using DevFmtType_t = typename DevFmtTypeTraits::Type; uint BytesFromDevFmt(DevFmtType type) noexcept; uint ChannelsFromDevFmt(DevFmtChannels chans, uint ambiorder) noexcept; inline uint FrameSizeFromDevFmt(DevFmtChannels chans, DevFmtType type, uint ambiorder) noexcept { return ChannelsFromDevFmt(chans, ambiorder) * BytesFromDevFmt(type); } auto DevFmtTypeString(DevFmtType type) noexcept -> std::string_view; auto DevFmtChannelsString(DevFmtChannels chans) noexcept -> std::string_view; enum class DevAmbiLayout : bool { FuMa, ACN, Default = ACN }; enum class DevAmbiScaling : unsigned char { FuMa, SN3D, N3D, Default = SN3D }; #endif /* CORE_DEVFORMAT_H */ openal-soft-1.24.2/core/device.cpp000066400000000000000000000004621474041540300167260ustar00rootroot00000000000000 #include "config.h" #include "bformatdec.h" #include "bs2b.h" #include "device.h" #include "front_stablizer.h" #include "hrtf.h" #include "mastering.h" DeviceBase::DeviceBase(DeviceType type) : Type{type}, mContexts{al::FlexArray::Create(0)} { } DeviceBase::~DeviceBase() = default; openal-soft-1.24.2/core/device.h000066400000000000000000000276461474041540300164100ustar00rootroot00000000000000#ifndef CORE_DEVICE_H #define CORE_DEVICE_H #include #include #include #include #include #include #include #include #include "almalloc.h" #include "alspan.h" #include "ambidefs.h" #include "atomic.h" #include "bufferline.h" #include "devformat.h" #include "filters/nfc.h" #include "flexarray.h" #include "fmt/core.h" #include "intrusive_ptr.h" #include "mixer/hrtfdefs.h" #include "opthelpers.h" #include "resampler_limits.h" #include "uhjfilter.h" #include "vector.h" class BFormatDec; namespace Bs2b { struct bs2b; } // namespace Bs2b class Compressor; struct ContextBase; struct DirectHrtfState; struct HrtfStore; using uint = unsigned int; inline constexpr std::size_t MinOutputRate{8000}; inline constexpr std::size_t MaxOutputRate{192000}; inline constexpr std::size_t DefaultOutputRate{48000}; inline constexpr std::size_t DefaultUpdateSize{960}; /* 20ms */ inline constexpr std::size_t DefaultNumUpdates{3}; enum class DeviceType : std::uint8_t { Playback, Capture, Loopback }; enum class RenderMode : std::uint8_t { Normal, Pairwise, Hrtf }; enum class StereoEncoding : std::uint8_t { Basic, Uhj, Hrtf, Default = Basic }; struct InputRemixMap { struct TargetMix { Channel channel; float mix; }; Channel channel; al::span targets; }; struct DistanceComp { /* Maximum delay in samples for speaker distance compensation. */ static constexpr uint MaxDelay{1024}; struct ChanData { al::span Buffer; /* Valid size is [0...MaxDelay). */ float Gain{1.0f}; }; std::array mChannels; al::FlexArray mSamples; explicit DistanceComp(std::size_t count) : mSamples{count} { } static std::unique_ptr Create(std::size_t numsamples) { return std::unique_ptr{new(FamCount(numsamples)) DistanceComp{numsamples}}; } DEF_FAM_NEWDEL(DistanceComp, mSamples) }; constexpr auto InvalidChannelIndex = static_cast(~0u); struct BFChannelConfig { float Scale; uint Index; }; struct MixParams { /* Coefficient channel mapping for mixing to the buffer. */ std::array AmbiMap{}; al::span Buffer; /** * Helper to set an identity/pass-through panning for ambisonic mixing. The * source is expected to be a 3D ACN/N3D ambisonic buffer, and for each * channel [0...count), the given functor is called with the source channel * index, destination channel index, and the gain for that channel. If the * destination channel is InvalidChannelIndex, the given source channel is * not used for output. */ template void setAmbiMixParams(const MixParams &inmix, const float gainbase, F func) const { const std::size_t numIn{inmix.Buffer.size()}; const std::size_t numOut{Buffer.size()}; for(std::size_t i{0};i < numIn;++i) { std::uint8_t idx{InvalidChannelIndex}; float gain{0.0f}; for(std::size_t j{0};j < numOut;++j) { if(AmbiMap[j].Index == inmix.AmbiMap[i].Index) { idx = static_cast(j); gain = AmbiMap[j].Scale * gainbase; break; } } func(i, idx, gain); } } }; struct RealMixParams { al::span RemixMap; std::array ChannelIndex{}; al::span Buffer; }; using AmbiRotateMatrix = std::array,MaxAmbiChannels>; enum { // Frequency was requested by the app or config file FrequencyRequest, // Channel configuration was requested by the app or config file ChannelsRequest, // Sample type was requested by the config file SampleTypeRequest, // Specifies if the DSP is paused at user request DevicePaused, // Specifies if the output plays directly on/in ears (headphones, headset, // ear buds, etc). DirectEar, /* Specifies if output is using speaker virtualization (e.g. Windows * Spatial Audio). */ Virtualization, DeviceFlagsCount }; enum class DeviceState : std::uint8_t { Unprepared, Configured, Playing }; /* NOLINTNEXTLINE(clang-analyzer-optin.performance.Padding) */ struct SIMDALIGN DeviceBase { std::atomic Connected{true}; const DeviceType Type{}; std::string mDeviceName; uint mSampleRate{}; uint mUpdateSize{}; uint mBufferSize{}; DevFmtChannels FmtChans{}; DevFmtType FmtType{}; uint mAmbiOrder{0}; float mXOverFreq{400.0f}; /* If the main device mix is horizontal/2D only. */ bool m2DMixing{false}; /* For DevFmtAmbi* output only, specifies the channel order and * normalization. */ DevAmbiLayout mAmbiLayout{DevAmbiLayout::Default}; DevAmbiScaling mAmbiScale{DevAmbiScaling::Default}; // Device flags std::bitset Flags; DeviceState mDeviceState{DeviceState::Unprepared}; uint NumAuxSends{}; /* Rendering mode. */ RenderMode mRenderMode{RenderMode::Normal}; /* The average speaker distance as determined by the ambdec configuration, * HRTF data set, or the NFC-HOA reference delay. Only used for NFC. */ float AvgSpeakerDist{0.0f}; /* The default NFC filter. Not used directly, but is pre-initialized with * the control distance from AvgSpeakerDist. */ NfcFilter mNFCtrlFilter{}; using seconds32 = std::chrono::duration; using nanoseconds32 = std::chrono::duration; std::atomic mSamplesDone{0u}; /* Split the clock to avoid a 64-bit atomic for certain 32-bit targets. */ std::atomic mClockBaseSec{seconds32{}}; std::atomic mClockBaseNSec{nanoseconds32{}}; std::chrono::nanoseconds FixedLatency{0}; AmbiRotateMatrix mAmbiRotateMatrix{}; AmbiRotateMatrix mAmbiRotateMatrix2{}; /* Temp storage used for mixer processing. */ static constexpr std::size_t MixerLineSize{BufferLineSize + DecoderBase::sMaxPadding}; static constexpr std::size_t MixerChannelsMax{16}; alignas(16) std::array mSampleData{}; alignas(16) std::array mResampleData{}; alignas(16) std::array FilteredData{}; alignas(16) std::array ExtraSampleData{}; /* Persistent storage for HRTF mixing. */ alignas(16) std::array HrtfAccumData{}; /* Mixing buffer used by the Dry mix and Real output. */ al::vector MixBuffer; /* The "dry" path corresponds to the main output. */ MixParams Dry; std::array NumChannelsPerOrder{}; /* "Real" output, which will be written to the device buffer. May alias the * dry buffer. */ RealMixParams RealOut; /* HRTF state and info */ std::unique_ptr mHrtfState; al::intrusive_ptr mHrtf; uint mIrSize{0}; /* Ambisonic-to-UHJ encoder */ std::unique_ptr mUhjEncoder; /* Ambisonic decoder for speakers */ std::unique_ptr AmbiDecoder; /* Stereo-to-binaural filter */ std::unique_ptr Bs2b; using PostProc = void(DeviceBase::*)(const size_t SamplesToDo); PostProc PostProcess{nullptr}; std::unique_ptr Limiter; /* Delay buffers used to compensate for speaker distances. */ std::unique_ptr ChannelDelays; /* Dithering control. */ float DitherDepth{0.0f}; uint DitherSeed{0u}; /* Running count of the mixer invocations, in 31.1 fixed point. This * actually increments *twice* when mixing, first at the start and then at * the end, so the bottom bit indicates if the device is currently mixing * and the upper bits indicates how many mixes have been done. */ std::atomic mMixCount{0u}; // Contexts created on this device al::atomic_unique_ptr> mContexts; [[nodiscard]] auto bytesFromFmt() const noexcept -> uint { return BytesFromDevFmt(FmtType); } [[nodiscard]] auto channelsFromFmt() const noexcept -> uint { return ChannelsFromDevFmt(FmtChans, mAmbiOrder); } [[nodiscard]] auto frameSizeFromFmt() const noexcept -> uint { return bytesFromFmt() * channelsFromFmt(); } struct MixLock { DeviceBase *const self; const uint mEndVal; MixLock(DeviceBase *device, const uint endval) noexcept : self{device}, mEndVal{endval} { } MixLock(const MixLock&) = delete; void operator=(const MixLock&) = delete; /* Update the mix count when the lock goes out of scope to "release" it * (lsb should be 0). */ ~MixLock() { self->mMixCount.store(mEndVal, std::memory_order_release); } }; auto getWriteMixLock() noexcept -> MixLock { /* Increment the mix count at the start of mixing and writing clock * info (lsb should be 1). */ const auto oldCount = mMixCount.fetch_add(1u, std::memory_order_acq_rel); return MixLock{this, oldCount+2}; } /** Waits for the mixer to not be mixing or updating the clock. */ [[nodiscard]] auto waitForMix() const noexcept -> uint { uint refcount{mMixCount.load(std::memory_order_acquire)}; while((refcount&1)) refcount = mMixCount.load(std::memory_order_acquire); return refcount; } /** * Helper to get the current clock time from the device's ClockBase, and * SamplesDone converted from the sample rate. Should only be called while * watching the MixCount. */ [[nodiscard]] auto getClockTime() const noexcept -> std::chrono::nanoseconds { using std::chrono::seconds; using std::chrono::nanoseconds; auto ns = nanoseconds{seconds{mSamplesDone.load(std::memory_order_relaxed)}} / mSampleRate; return nanoseconds{mClockBaseNSec.load(std::memory_order_relaxed)} + mClockBaseSec.load(std::memory_order_relaxed) + ns; } void ProcessHrtf(const std::size_t SamplesToDo); void ProcessAmbiDec(const std::size_t SamplesToDo); void ProcessAmbiDecStablized(const std::size_t SamplesToDo); void ProcessUhj(const std::size_t SamplesToDo); void ProcessBs2b(const std::size_t SamplesToDo); void postProcess(const std::size_t SamplesToDo) { if(PostProcess) LIKELY (this->*PostProcess)(SamplesToDo); } void renderSamples(const al::span outBuffers, const uint numSamples); void renderSamples(void *outBuffer, const uint numSamples, const std::size_t frameStep); /* Caller must lock the device state, and the mixer must not be running. */ void doDisconnect(std::string msg); template void handleDisconnect(fmt::format_string fmt, Args&& ...args) { doDisconnect(fmt::format(std::move(fmt), std::forward(args)...)); } /** * Returns the index for the given channel name (e.g. FrontCenter), or * InvalidChannelIndex if it doesn't exist. */ [[nodiscard]] auto channelIdxByName(Channel chan) const noexcept -> std::uint8_t { return RealOut.ChannelIndex[chan]; } private: uint renderSamples(const uint numSamples); protected: explicit DeviceBase(DeviceType type); ~DeviceBase(); public: DeviceBase(const DeviceBase&) = delete; DeviceBase& operator=(const DeviceBase&) = delete; }; /* Must be less than 15 characters (16 including terminating null) for * compatibility with pthread_setname_np limitations. */ [[nodiscard]] constexpr auto GetMixerThreadName() noexcept -> const char* { return "alsoft-mixer"; } [[nodiscard]] constexpr auto GetRecordThreadName() noexcept -> const char* { return "alsoft-record"; } #endif /* CORE_DEVICE_H */ openal-soft-1.24.2/core/effects/000077500000000000000000000000001474041540300164005ustar00rootroot00000000000000openal-soft-1.24.2/core/effects/base.h000066400000000000000000000110011474041540300174540ustar00rootroot00000000000000#ifndef CORE_EFFECTS_BASE_H #define CORE_EFFECTS_BASE_H #include #include #include #include "alspan.h" #include "core/bufferline.h" #include "intrusive_ptr.h" #include "opthelpers.h" struct BufferStorage; struct ContextBase; struct DeviceBase; struct EffectSlot; struct MixParams; struct RealMixParams; /** Target gain for the reverb decay feedback reaching the decay time. */ inline constexpr float ReverbDecayGain{0.001f}; /* -60 dB */ inline constexpr float ReverbMaxReflectionsDelay{0.3f}; inline constexpr float ReverbMaxLateReverbDelay{0.1f}; enum class ChorusWaveform { Sinusoid, Triangle }; inline constexpr float ChorusMaxDelay{0.016f}; inline constexpr float FlangerMaxDelay{0.004f}; inline constexpr float EchoMaxDelay{0.207f}; inline constexpr float EchoMaxLRDelay{0.404f}; enum class FShifterDirection { Down, Up, Off }; enum class ModulatorWaveform { Sinusoid, Sawtooth, Square }; enum class VMorpherPhenome { A, E, I, O, U, AA, AE, AH, AO, EH, ER, IH, IY, UH, UW, B, D, F, G, J, K, L, M, N, P, R, S, T, V, Z }; enum class VMorpherWaveform { Sinusoid, Triangle, Sawtooth }; struct ReverbProps { float Density; float Diffusion; float Gain; float GainHF; float GainLF; float DecayTime; float DecayHFRatio; float DecayLFRatio; float ReflectionsGain; float ReflectionsDelay; std::array ReflectionsPan; float LateReverbGain; float LateReverbDelay; std::array LateReverbPan; float EchoTime; float EchoDepth; float ModulationTime; float ModulationDepth; float AirAbsorptionGainHF; float HFReference; float LFReference; float RoomRolloffFactor; bool DecayHFLimit; }; struct AutowahProps { float AttackTime; float ReleaseTime; float Resonance; float PeakGain; }; struct ChorusProps { ChorusWaveform Waveform; int Phase; float Rate; float Depth; float Feedback; float Delay; }; struct CompressorProps { bool OnOff; }; struct DistortionProps { float Edge; float Gain; float LowpassCutoff; float EQCenter; float EQBandwidth; }; struct EchoProps { float Delay; float LRDelay; float Damping; float Feedback; float Spread; }; struct EqualizerProps { float LowCutoff; float LowGain; float Mid1Center; float Mid1Gain; float Mid1Width; float Mid2Center; float Mid2Gain; float Mid2Width; float HighCutoff; float HighGain; }; struct FshifterProps { float Frequency; FShifterDirection LeftDirection; FShifterDirection RightDirection; }; struct ModulatorProps { float Frequency; float HighPassCutoff; ModulatorWaveform Waveform; }; struct PshifterProps { int CoarseTune; int FineTune; }; struct VmorpherProps { float Rate; VMorpherPhenome PhonemeA; VMorpherPhenome PhonemeB; int PhonemeACoarseTuning; int PhonemeBCoarseTuning; VMorpherWaveform Waveform; }; struct DedicatedProps { enum TargetType : bool { Dialog, Lfe }; TargetType Target; float Gain; }; struct ConvolutionProps { std::array OrientAt; std::array OrientUp; }; using EffectProps = std::variant; struct EffectTarget { MixParams *Main; RealMixParams *RealOut; }; struct SIMDALIGN EffectState : public al::intrusive_ref { al::span mOutTarget; virtual ~EffectState() = default; virtual void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) = 0; virtual void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props, const EffectTarget target) = 0; virtual void process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) = 0; }; struct EffectStateFactory { EffectStateFactory() = default; EffectStateFactory(const EffectStateFactory&) = delete; EffectStateFactory(EffectStateFactory&&) = delete; virtual ~EffectStateFactory() = default; void operator=(const EffectStateFactory&) = delete; void operator=(EffectStateFactory&&) = delete; virtual al::intrusive_ptr create() = 0; }; #endif /* CORE_EFFECTS_BASE_H */ openal-soft-1.24.2/core/effectslot.cpp000066400000000000000000000004301474041540300176200ustar00rootroot00000000000000 #include "config.h" #include "effectslot.h" #include #include "almalloc.h" #include "context.h" std::unique_ptr EffectSlot::CreatePtrArray(size_t count) { return std::unique_ptr{new(FamCount{count}) EffectSlotArray(count)}; } openal-soft-1.24.2/core/effectslot.h000066400000000000000000000034671474041540300173020ustar00rootroot00000000000000#ifndef CORE_EFFECTSLOT_H #define CORE_EFFECTSLOT_H #include #include #include "device.h" #include "effects/base.h" #include "flexarray.h" #include "intrusive_ptr.h" struct EffectSlot; struct WetBuffer; using EffectSlotArray = al::FlexArray; enum class EffectSlotType : unsigned char { None, Reverb, Chorus, Autowah, Compressor, Convolution, Dedicated, Distortion, Echo, Equalizer, Flanger, FrequencyShifter, PitchShifter, RingModulator, VocalMorpher, }; struct EffectSlotProps { float Gain; bool AuxSendAuto; EffectSlot *Target; EffectSlotType Type; EffectProps Props; al::intrusive_ptr State; std::atomic next{}; }; struct EffectSlot { bool InUse{false}; std::atomic Update{nullptr}; /* Wet buffer configuration is ACN channel order with N3D scaling. * Consequently, effects that only want to work with mono input can use * channel 0 by itself. Effects that want multichannel can process the * ambisonics signal and make a B-Format source pan. */ MixParams Wet; float Gain{1.0f}; bool AuxSendAuto{true}; EffectSlot *Target{nullptr}; EffectSlotType EffectType{EffectSlotType::None}; EffectProps mEffectProps; al::intrusive_ptr mEffectState; float RoomRolloff{0.0f}; /* Added to the source's room rolloff, not multiplied. */ float DecayTime{0.0f}; float DecayLFRatio{0.0f}; float DecayHFRatio{0.0f}; bool DecayHFLimit{false}; float AirAbsorptionGainHF{1.0f}; /* Mixing buffer used by the Wet mix. */ al::vector mWetBuffer; static std::unique_ptr CreatePtrArray(size_t count); }; #endif /* CORE_EFFECTSLOT_H */ openal-soft-1.24.2/core/except.cpp000066400000000000000000000001741474041540300167570ustar00rootroot00000000000000 #include "config.h" #include "except.h" namespace al { base_exception::~base_exception() = default; } // namespace al openal-soft-1.24.2/core/except.h000066400000000000000000000014761474041540300164320ustar00rootroot00000000000000#ifndef CORE_EXCEPT_H #define CORE_EXCEPT_H #include #include #include namespace al { class base_exception : public std::exception { std::string mMessage; public: base_exception() = default; template,bool> = true> explicit base_exception(T&& msg) : mMessage{std::forward(msg)} { } base_exception(const base_exception&) = default; base_exception(base_exception&&) = default; ~base_exception() override; auto operator=(const base_exception&) -> base_exception& = default; auto operator=(base_exception&&) -> base_exception& = default; [[nodiscard]] auto what() const noexcept -> const char* override { return mMessage.c_str(); } }; } // namespace al #endif /* CORE_EXCEPT_H */ openal-soft-1.24.2/core/filters/000077500000000000000000000000001474041540300164315ustar00rootroot00000000000000openal-soft-1.24.2/core/filters/biquad.cpp000066400000000000000000000130061474041540300204020ustar00rootroot00000000000000 #include "config.h" #include "biquad.h" #include #include #include #include #include "alnumbers.h" #include "opthelpers.h" template void BiquadFilterR::setParams(BiquadType type, Real f0norm, Real gain, Real rcpQ) { /* HACK: Limit gain to -100dB. This shouldn't ever happen, all callers * already clamp to minimum of 0.001, or have a limited range of values * that don't go below 0.126. But it seems to with some callers. This needs * to be investigated. */ gain = std::max(gain, Real(0.00001)); const Real w0{al::numbers::pi_v*2.0f * f0norm}; const Real sin_w0{std::sin(w0)}; const Real cos_w0{std::cos(w0)}; const Real alpha{sin_w0/2.0f * rcpQ}; Real sqrtgain_alpha_2; std::array a{{1.0f, 0.0f, 0.0f}}; std::array b{{1.0f, 0.0f, 0.0f}}; /* Calculate filter coefficients depending on filter type */ switch(type) { case BiquadType::HighShelf: sqrtgain_alpha_2 = 2.0f * std::sqrt(gain) * alpha; b[0] = gain*((gain+1.0f) + (gain-1.0f)*cos_w0 + sqrtgain_alpha_2); b[1] = -2.0f*gain*((gain-1.0f) + (gain+1.0f)*cos_w0 ); b[2] = gain*((gain+1.0f) + (gain-1.0f)*cos_w0 - sqrtgain_alpha_2); a[0] = (gain+1.0f) - (gain-1.0f)*cos_w0 + sqrtgain_alpha_2; a[1] = 2.0f* ((gain-1.0f) - (gain+1.0f)*cos_w0 ); a[2] = (gain+1.0f) - (gain-1.0f)*cos_w0 - sqrtgain_alpha_2; break; case BiquadType::LowShelf: sqrtgain_alpha_2 = 2.0f * std::sqrt(gain) * alpha; b[0] = gain*((gain+1.0f) - (gain-1.0f)*cos_w0 + sqrtgain_alpha_2); b[1] = 2.0f*gain*((gain-1.0f) - (gain+1.0f)*cos_w0 ); b[2] = gain*((gain+1.0f) - (gain-1.0f)*cos_w0 - sqrtgain_alpha_2); a[0] = (gain+1.0f) + (gain-1.0f)*cos_w0 + sqrtgain_alpha_2; a[1] = -2.0f* ((gain-1.0f) + (gain+1.0f)*cos_w0 ); a[2] = (gain+1.0f) + (gain-1.0f)*cos_w0 - sqrtgain_alpha_2; break; case BiquadType::Peaking: b[0] = 1.0f + alpha * gain; b[1] = -2.0f * cos_w0; b[2] = 1.0f - alpha * gain; a[0] = 1.0f + alpha / gain; a[1] = -2.0f * cos_w0; a[2] = 1.0f - alpha / gain; break; case BiquadType::LowPass: b[0] = (1.0f - cos_w0) / 2.0f; b[1] = 1.0f - cos_w0; b[2] = (1.0f - cos_w0) / 2.0f; a[0] = 1.0f + alpha; a[1] = -2.0f * cos_w0; a[2] = 1.0f - alpha; break; case BiquadType::HighPass: b[0] = (1.0f + cos_w0) / 2.0f; b[1] = -(1.0f + cos_w0); b[2] = (1.0f + cos_w0) / 2.0f; a[0] = 1.0f + alpha; a[1] = -2.0f * cos_w0; a[2] = 1.0f - alpha; break; case BiquadType::BandPass: b[0] = alpha; b[1] = 0.0f; b[2] = -alpha; a[0] = 1.0f + alpha; a[1] = -2.0f * cos_w0; a[2] = 1.0f - alpha; break; } mA1 = a[1] / a[0]; mA2 = a[2] / a[0]; mB0 = b[0] / a[0]; mB1 = b[1] / a[0]; mB2 = b[2] / a[0]; } template void BiquadFilterR::process(const al::span src, const al::span dst) { const Real b0{mB0}; const Real b1{mB1}; const Real b2{mB2}; const Real a1{mA1}; const Real a2{mA2}; Real z1{mZ1}; Real z2{mZ2}; /* Processing loop is Transposed Direct Form II. This requires less storage * compared to Direct Form I (only two delay components, instead of a four- * sample history; the last two inputs and outputs), and works better for * floating-point which favors summing similarly-sized values while being * less bothered by overflow. * * See: http://www.earlevel.com/main/2003/02/28/biquads/ */ auto proc_sample = [b0,b1,b2,a1,a2,&z1,&z2](Real input) noexcept -> Real { const Real output{input*b0 + z1}; z1 = input*b1 - output*a1 + z2; z2 = input*b2 - output*a2; return output; }; std::transform(src.cbegin(), src.cend(), dst.begin(), proc_sample); mZ1 = z1; mZ2 = z2; } template void BiquadFilterR::dualProcess(BiquadFilterR &other, const al::span src, const al::span dst) { const Real b00{mB0}; const Real b01{mB1}; const Real b02{mB2}; const Real a01{mA1}; const Real a02{mA2}; const Real b10{other.mB0}; const Real b11{other.mB1}; const Real b12{other.mB2}; const Real a11{other.mA1}; const Real a12{other.mA2}; Real z01{mZ1}; Real z02{mZ2}; Real z11{other.mZ1}; Real z12{other.mZ2}; auto proc_sample = [b00,b01,b02,a01,a02,b10,b11,b12,a11,a12,&z01,&z02,&z11,&z12](Real input) noexcept -> Real { const Real tmpout{input*b00 + z01}; z01 = input*b01 - tmpout*a01 + z02; z02 = input*b02 - tmpout*a02; input = tmpout; const Real output{input*b10 + z11}; z11 = input*b11 - output*a11 + z12; z12 = input*b12 - output*a12; return output; }; std::transform(src.cbegin(), src.cend(), dst.begin(), proc_sample); mZ1 = z01; mZ2 = z02; other.mZ1 = z11; other.mZ2 = z12; } template class BiquadFilterR; template class BiquadFilterR; openal-soft-1.24.2/core/filters/biquad.h000066400000000000000000000123011474041540300200440ustar00rootroot00000000000000#ifndef CORE_FILTERS_BIQUAD_H #define CORE_FILTERS_BIQUAD_H #include #include #include #include #include "alnumbers.h" #include "alspan.h" /* Filters implementation is based on the "Cookbook formulae for audio * EQ biquad filter coefficients" by Robert Bristow-Johnson * http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt */ /* Implementation note: For the shelf and peaking filters, the specified gain * is for the centerpoint of the transition band. This better fits EFX filter * behavior, which expects the shelf's reference frequency to reach the given * gain. To set the gain for the shelf or peak itself, use the square root of * the desired linear gain (or halve the dB gain). */ enum class BiquadType { /** EFX-style low-pass filter, specifying a gain and reference frequency. */ HighShelf, /** EFX-style high-pass filter, specifying a gain and reference frequency. */ LowShelf, /** Peaking filter, specifying a gain and reference frequency. */ Peaking, /** Low-pass cut-off filter, specifying a cut-off frequency. */ LowPass, /** High-pass cut-off filter, specifying a cut-off frequency. */ HighPass, /** Band-pass filter, specifying a center frequency. */ BandPass, }; template class BiquadFilterR { /* Last two delayed components for direct form II. */ Real mZ1{0}, mZ2{0}; /* Transfer function coefficients "b" (numerator) */ Real mB0{1}, mB1{0}, mB2{0}; /* Transfer function coefficients "a" (denominator; a0 is pre-applied). */ Real mA1{0}, mA2{0}; void setParams(BiquadType type, Real f0norm, Real gain, Real rcpQ); /** * Calculates the rcpQ (i.e. 1/Q) coefficient for shelving filters, using * the reference gain and shelf slope parameter. * \param gain 0 < gain * \param slope 0 < slope <= 1 */ static Real rcpQFromSlope(Real gain, Real slope) { return std::sqrt((gain + Real{1}/gain)*(Real{1}/slope - Real{1}) + Real{2}); } /** * Calculates the rcpQ (i.e. 1/Q) coefficient for filters, using the * normalized reference frequency and bandwidth. * \param f0norm 0 < f0norm < 0.5. * \param bandwidth 0 < bandwidth */ static Real rcpQFromBandwidth(Real f0norm, Real bandwidth) { const Real w0{al::numbers::pi_v*Real{2} * f0norm}; return 2.0f*std::sinh(std::log(Real{2})/Real{2}*bandwidth*w0/std::sin(w0)); } public: void clear() noexcept { mZ1 = mZ2 = Real{0}; } /** * Sets the filter state for the specified filter type and its parameters. * * \param type The type of filter to apply. * \param f0norm The normalized reference frequency (ref / sample_rate). * This is the center point for the Shelf, Peaking, and BandPass filter * types, or the cutoff frequency for the LowPass and HighPass filter * types. * \param gain The gain for the reference frequency response. Only used by * the Shelf and Peaking filter types. * \param slope Slope steepness of the transition band. */ void setParamsFromSlope(BiquadType type, Real f0norm, Real gain, Real slope) { gain = std::max(gain, 0.001f); /* Limit -60dB */ setParams(type, f0norm, gain, rcpQFromSlope(gain, slope)); } /** * Sets the filter state for the specified filter type and its parameters. * * \param type The type of filter to apply. * \param f0norm The normalized reference frequency (ref / sample_rate). * This is the center point for the Shelf, Peaking, and BandPass filter * types, or the cutoff frequency for the LowPass and HighPass filter * types. * \param gain The gain for the reference frequency response. Only used by * the Shelf and Peaking filter types. * \param bandwidth Normalized bandwidth of the transition band. */ void setParamsFromBandwidth(BiquadType type, Real f0norm, Real gain, Real bandwidth) { setParams(type, f0norm, gain, rcpQFromBandwidth(f0norm, bandwidth)); } void copyParamsFrom(const BiquadFilterR &other) { mB0 = other.mB0; mB1 = other.mB1; mB2 = other.mB2; mA1 = other.mA1; mA2 = other.mA2; } void process(const al::span src, const al::span dst); /** Processes this filter and the other at the same time. */ void dualProcess(BiquadFilterR &other, const al::span src, const al::span dst); /* Rather hacky. It's just here to support "manual" processing. */ [[nodiscard]] auto getComponents() const noexcept -> std::array { return {{mZ1,mZ2}}; } void setComponents(Real z1, Real z2) noexcept { mZ1 = z1; mZ2 = z2; } [[nodiscard]] auto processOne(const Real in, Real &z1, Real &z2) const noexcept -> Real { const Real out{in*mB0 + z1}; z1 = in*mB1 - out*mA1 + z2; z2 = in*mB2 - out*mA2; return out; } }; template struct DualBiquadR { BiquadFilterR &f0, &f1; void process(const al::span src, const al::span dst) { f0.dualProcess(f1, src, dst); } }; using BiquadFilter = BiquadFilterR; using DualBiquad = DualBiquadR; #endif /* CORE_FILTERS_BIQUAD_H */ openal-soft-1.24.2/core/filters/nfc.cpp000066400000000000000000000241011474041540300177010ustar00rootroot00000000000000 #include "config.h" #include "nfc.h" #include /* Near-field control filters are the basis for handling the near-field effect. * The near-field effect is a bass-boost present in the directional components * of a recorded signal, created as a result of the wavefront curvature (itself * a function of sound distance). Proper reproduction dictates this be * compensated for using a bass-cut given the playback speaker distance, to * avoid excessive bass in the playback. * * For real-time rendered audio, emulating the near-field effect based on the * sound source's distance, and subsequently compensating for it at output * based on the speaker distances, can create a more realistic perception of * sound distance beyond a simple 1/r attenuation. * * These filters do just that. Each one applies a low-shelf filter, created as * the combination of a bass-boost for a given sound source distance (near- * field emulation) along with a bass-cut for a given control/speaker distance * (near-field compensation). * * Note that it is necessary to apply a cut along with the boost, since the * boost alone is unstable in higher-order ambisonics as it causes an infinite * DC gain (even first-order ambisonics requires there to be no DC offset for * the boost to work). Consequently, ambisonics requires a control parameter to * be used to avoid an unstable boost-only filter. NFC-HOA defines this control * as a reference delay, calculated with: * * reference_delay = control_distance / speed_of_sound * * This means w0 (for input) or w1 (for output) should be set to: * * wN = 1 / (reference_delay * sample_rate) * * when dealing with NFC-HOA content. For FOA input content, which does not * specify a reference_delay variable, w0 should be set to 0 to apply only * near-field compensation for output. It's important that w1 be a finite, * positive, non-0 value or else the bass-boost will become unstable again. * Also, w0 should not be too large compared to w1, to avoid excessively loud * low frequencies. */ namespace { constexpr std::array B{ std::array{ 0.0f, 0.0f, 0.0f, 0.0f}, std::array{ 1.0f, 0.0f, 0.0f, 0.0f}, std::array{ 3.0f, 3.0f, 0.0f, 0.0f}, std::array{3.6778f, 6.4595f, 2.3222f, 0.0f}, std::array{4.2076f, 11.4877f, 5.7924f, 9.1401f} }; NfcFilter1 NfcFilterCreate1(const float w0, const float w1) noexcept { auto nfc = NfcFilter1{}; /* Calculate bass-cut coefficients. */ auto r = 0.5f * w1; auto b_00 = B[1][0] * r; auto g_0 = 1.0f + b_00; nfc.base_gain = 1.0f / g_0; nfc.a1 = 2.0f * b_00 / g_0; /* Calculate bass-boost coefficients. */ r = 0.5f * w0; b_00 = B[1][0] * r; g_0 = 1.0f + b_00; nfc.gain = nfc.base_gain * g_0; nfc.b1 = 2.0f * b_00 / g_0; return nfc; } void NfcFilterAdjust1(NfcFilter1 *nfc, const float w0) noexcept { const auto r = 0.5f * w0; const auto b_00 = B[1][0] * r; const auto g_0 = 1.0f + b_00; nfc->gain = nfc->base_gain * g_0; nfc->b1 = 2.0f * b_00 / g_0; } NfcFilter2 NfcFilterCreate2(const float w0, const float w1) noexcept { auto nfc = NfcFilter2{}; /* Calculate bass-cut coefficients. */ auto r = 0.5f * w1; auto b_10 = B[2][0] * r; auto b_11 = B[2][1] * (r*r); auto g_1 = 1.0f + b_10 + b_11; nfc.base_gain = 1.0f / g_1; nfc.a1 = (2.0f*b_10 + 4.0f*b_11) / g_1; nfc.a2 = 4.0f * b_11 / g_1; /* Calculate bass-boost coefficients. */ r = 0.5f * w0; b_10 = B[2][0] * r; b_11 = B[2][1] * r * r; g_1 = 1.0f + b_10 + b_11; nfc.gain = nfc.base_gain * g_1; nfc.b1 = (2.0f*b_10 + 4.0f*b_11) / g_1; nfc.b2 = 4.0f * b_11 / g_1; return nfc; } void NfcFilterAdjust2(NfcFilter2 *nfc, const float w0) noexcept { const auto r = 0.5f * w0; const auto b_10 = B[2][0] * r; const auto b_11 = B[2][1] * (r*r); const auto g_1 = 1.0f + b_10 + b_11; nfc->gain = nfc->base_gain * g_1; nfc->b1 = (2.0f*b_10 + 4.0f*b_11) / g_1; nfc->b2 = 4.0f * b_11 / g_1; } NfcFilter3 NfcFilterCreate3(const float w0, const float w1) noexcept { auto nfc = NfcFilter3{}; /* Calculate bass-cut coefficients. */ auto r = 0.5f * w1; auto b_10 = B[3][0] * r; auto b_11 = B[3][1] * (r*r); auto b_00 = B[3][2] * r; auto g_1 = 1.0f + b_10 + b_11; auto g_0 = 1.0f + b_00; nfc.base_gain = 1.0f / (g_1 * g_0); nfc.a1 = (2.0f*b_10 + 4.0f*b_11) / g_1; nfc.a2 = 4.0f * b_11 / g_1; nfc.a3 = 2.0f * b_00 / g_0; /* Calculate bass-boost coefficients. */ r = 0.5f * w0; b_10 = B[3][0] * r; b_11 = B[3][1] * (r*r); b_00 = B[3][2] * r; g_1 = 1.0f + b_10 + b_11; g_0 = 1.0f + b_00; nfc.gain = nfc.base_gain * (g_1 * g_0); nfc.b1 = (2.0f*b_10 + 4.0f*b_11) / g_1; nfc.b2 = 4.0f * b_11 / g_1; nfc.b3 = 2.0f * b_00 / g_0; return nfc; } void NfcFilterAdjust3(NfcFilter3 *nfc, const float w0) noexcept { const auto r = 0.5f * w0; const auto b_10 = B[3][0] * r; const auto b_11 = B[3][1] * (r*r); const auto b_00 = B[3][2] * r; const auto g_1 = 1.0f + b_10 + b_11; const auto g_0 = 1.0f + b_00; nfc->gain = nfc->base_gain * (g_1 * g_0); nfc->b1 = (2.0f*b_10 + 4.0f*b_11) / g_1; nfc->b2 = 4.0f * b_11 / g_1; nfc->b3 = 2.0f * b_00 / g_0; } NfcFilter4 NfcFilterCreate4(const float w0, const float w1) noexcept { auto nfc = NfcFilter4{}; /* Calculate bass-cut coefficients. */ auto r = 0.5f * w1; auto b_10 = B[4][0] * r; auto b_11 = B[4][1] * (r*r); auto b_00 = B[4][2] * r; auto b_01 = B[4][3] * (r*r); auto g_1 = 1.0f + b_10 + b_11; auto g_0 = 1.0f + b_00 + b_01; nfc.base_gain = 1.0f / (g_1 * g_0); nfc.a1 = (2.0f*b_10 + 4.0f*b_11) / g_1; nfc.a2 = 4.0f * b_11 / g_1; nfc.a3 = (2.0f*b_00 + 4.0f*b_01) / g_0; nfc.a4 = 4.0f * b_01 / g_0; /* Calculate bass-boost coefficients. */ r = 0.5f * w0; b_10 = B[4][0] * r; b_11 = B[4][1] * (r*r); b_00 = B[4][2] * r; b_01 = B[4][3] * (r*r); g_1 = 1.0f + b_10 + b_11; g_0 = 1.0f + b_00 + b_01; nfc.gain = nfc.base_gain * (g_1 * g_0); nfc.b1 = (2.0f*b_10 + 4.0f*b_11) / g_1; nfc.b2 = 4.0f * b_11 / g_1; nfc.b3 = (2.0f*b_00 + 4.0f*b_01) / g_0; nfc.b4 = 4.0f * b_01 / g_0; return nfc; } void NfcFilterAdjust4(NfcFilter4 *nfc, const float w0) noexcept { const auto r = 0.5f * w0; const auto b_10 = B[4][0] * r; const auto b_11 = B[4][1] * (r*r); const auto b_00 = B[4][2] * r; const auto b_01 = B[4][3] * (r*r); const auto g_1 = 1.0f + b_10 + b_11; const auto g_0 = 1.0f + b_00 + b_01; nfc->gain = nfc->base_gain * (g_1 * g_0); nfc->b1 = (2.0f*b_10 + 4.0f*b_11) / g_1; nfc->b2 = 4.0f * b_11 / g_1; nfc->b3 = (2.0f*b_00 + 4.0f*b_01) / g_0; nfc->b4 = 4.0f * b_01 / g_0; } } // namespace void NfcFilter::init(const float w1) noexcept { first = NfcFilterCreate1(0.0f, w1); second = NfcFilterCreate2(0.0f, w1); third = NfcFilterCreate3(0.0f, w1); fourth = NfcFilterCreate4(0.0f, w1); } void NfcFilter::adjust(const float w0) noexcept { NfcFilterAdjust1(&first, w0); NfcFilterAdjust2(&second, w0); NfcFilterAdjust3(&third, w0); NfcFilterAdjust4(&fourth, w0); } void NfcFilter::process1(const al::span src, const al::span dst) { const float gain{first.gain}; const float b1{first.b1}; const float a1{first.a1}; float z1{first.z[0]}; auto proc_sample = [gain,b1,a1,&z1](const float in) noexcept -> float { const float y{in*gain - a1*z1}; const float out{y + b1*z1}; z1 += y; return out; }; std::transform(src.cbegin(), src.cend(), dst.begin(), proc_sample); first.z[0] = z1; } void NfcFilter::process2(const al::span src, const al::span dst) { const float gain{second.gain}; const float b1{second.b1}; const float b2{second.b2}; const float a1{second.a1}; const float a2{second.a2}; float z1{second.z[0]}; float z2{second.z[1]}; auto proc_sample = [gain,b1,b2,a1,a2,&z1,&z2](const float in) noexcept -> float { const float y{in*gain - a1*z1 - a2*z2}; const float out{y + b1*z1 + b2*z2}; z2 += z1; z1 += y; return out; }; std::transform(src.cbegin(), src.cend(), dst.begin(), proc_sample); second.z[0] = z1; second.z[1] = z2; } void NfcFilter::process3(const al::span src, const al::span dst) { const float gain{third.gain}; const float b1{third.b1}; const float b2{third.b2}; const float b3{third.b3}; const float a1{third.a1}; const float a2{third.a2}; const float a3{third.a3}; float z1{third.z[0]}; float z2{third.z[1]}; float z3{third.z[2]}; auto proc_sample = [gain,b1,b2,b3,a1,a2,a3,&z1,&z2,&z3](const float in) noexcept -> float { float y{in*gain - a1*z1 - a2*z2}; float out{y + b1*z1 + b2*z2}; z2 += z1; z1 += y; y = out - a3*z3; out = y + b3*z3; z3 += y; return out; }; std::transform(src.cbegin(), src.cend(), dst.begin(), proc_sample); third.z[0] = z1; third.z[1] = z2; third.z[2] = z3; } void NfcFilter::process4(const al::span src, const al::span dst) { const float gain{fourth.gain}; const float b1{fourth.b1}; const float b2{fourth.b2}; const float b3{fourth.b3}; const float b4{fourth.b4}; const float a1{fourth.a1}; const float a2{fourth.a2}; const float a3{fourth.a3}; const float a4{fourth.a4}; float z1{fourth.z[0]}; float z2{fourth.z[1]}; float z3{fourth.z[2]}; float z4{fourth.z[3]}; auto proc_sample = [gain,b1,b2,b3,b4,a1,a2,a3,a4,&z1,&z2,&z3,&z4](const float in) noexcept -> float { float y{in*gain - a1*z1 - a2*z2}; float out{y + b1*z1 + b2*z2}; z2 += z1; z1 += y; y = out - a3*z3 - a4*z4; out = y + b3*z3 + b4*z4; z4 += z3; z3 += y; return out; }; std::transform(src.cbegin(), src.cend(), dst.begin(), proc_sample); fourth.z[0] = z1; fourth.z[1] = z2; fourth.z[2] = z3; fourth.z[3] = z4; } openal-soft-1.24.2/core/filters/nfc.h000066400000000000000000000036521474041540300173560ustar00rootroot00000000000000#ifndef CORE_FILTERS_NFC_H #define CORE_FILTERS_NFC_H #include #include #include "alspan.h" struct NfcFilter1 { float base_gain{1.0f}, gain{1.0f}; float b1{}, a1{}; std::array z{}; }; struct NfcFilter2 { float base_gain{1.0f}, gain{1.0f}; float b1{}, b2{}, a1{}, a2{}; std::array z{}; }; struct NfcFilter3 { float base_gain{1.0f}, gain{1.0f}; float b1{}, b2{}, b3{}, a1{}, a2{}, a3{}; std::array z{}; }; struct NfcFilter4 { float base_gain{1.0f}, gain{1.0f}; float b1{}, b2{}, b3{}, b4{}, a1{}, a2{}, a3{}, a4{}; std::array z{}; }; class NfcFilter { NfcFilter1 first; NfcFilter2 second; NfcFilter3 third; NfcFilter4 fourth; public: /* NOTE: * w0 = speed_of_sound / (source_distance * sample_rate); * w1 = speed_of_sound / (control_distance * sample_rate); * * Generally speaking, the control distance should be approximately the * average speaker distance, or based on the reference delay if outputting * NFC-HOA. It must not be negative, 0, or infinite. The source distance * should not be too small relative to the control distance. */ void init(const float w1) noexcept; void adjust(const float w0) noexcept; /* Near-field control filter for first-order ambisonic channels (1-3). */ void process1(const al::span src, const al::span dst); /* Near-field control filter for second-order ambisonic channels (4-8). */ void process2(const al::span src, const al::span dst); /* Near-field control filter for third-order ambisonic channels (9-15). */ void process3(const al::span src, const al::span dst); /* Near-field control filter for fourth-order ambisonic channels (16-24). */ void process4(const al::span src, const al::span dst); }; #endif /* CORE_FILTERS_NFC_H */ openal-soft-1.24.2/core/filters/splitter.cpp000066400000000000000000000121521474041540300210040ustar00rootroot00000000000000 #include "config.h" #include "splitter.h" #include #include #include #include #include "alnumbers.h" #include "opthelpers.h" template void BandSplitterR::init(Real f0norm) { const Real w{f0norm * (al::numbers::pi_v*2)}; const Real cw{std::cos(w)}; if(cw > std::numeric_limits::epsilon()) mCoeff = (std::sin(w) - 1.0f) / cw; else mCoeff = cw * -0.5f; mLpZ1 = 0.0f; mLpZ2 = 0.0f; mApZ1 = 0.0f; } template void BandSplitterR::process(const al::span input, const al::span hpout, const al::span lpout) { const Real ap_coeff{mCoeff}; const Real lp_coeff{mCoeff*0.5f + 0.5f}; Real lp_z1{mLpZ1}; Real lp_z2{mLpZ2}; Real ap_z1{mApZ1}; assert(lpout.size() <= input.size()); auto lpiter = lpout.begin(); auto proc_sample = [ap_coeff,lp_coeff,&lp_z1,&lp_z2,&ap_z1,&lpiter](const Real in) noexcept -> Real { /* Low-pass sample processing. */ Real d{(in - lp_z1) * lp_coeff}; Real lp_y{lp_z1 + d}; lp_z1 = lp_y + d; d = (lp_y - lp_z2) * lp_coeff; lp_y = lp_z2 + d; lp_z2 = lp_y + d; *(lpiter++) = lp_y; /* All-pass sample processing. */ Real ap_y{in*ap_coeff + ap_z1}; ap_z1 = in - ap_y*ap_coeff; /* High-pass generated from removing low-passed output. */ return ap_y - lp_y; }; std::transform(input.cbegin(), input.cend(), hpout.begin(), proc_sample); mLpZ1 = lp_z1; mLpZ2 = lp_z2; mApZ1 = ap_z1; } template void BandSplitterR::processHfScale(const al::span input, const al::span output, const Real hfscale) { const Real ap_coeff{mCoeff}; const Real lp_coeff{mCoeff*0.5f + 0.5f}; Real lp_z1{mLpZ1}; Real lp_z2{mLpZ2}; Real ap_z1{mApZ1}; auto proc_sample = [hfscale,ap_coeff,lp_coeff,&lp_z1,&lp_z2,&ap_z1](const Real in) noexcept -> Real { /* Low-pass sample processing. */ Real d{(in - lp_z1) * lp_coeff}; Real lp_y{lp_z1 + d}; lp_z1 = lp_y + d; d = (lp_y - lp_z2) * lp_coeff; lp_y = lp_z2 + d; lp_z2 = lp_y + d; /* All-pass sample processing. */ Real ap_y{in*ap_coeff + ap_z1}; ap_z1 = in - ap_y*ap_coeff; /* High-pass generated by removing the low-passed signal, which is then * scaled and added back to the low-passed signal. */ return (ap_y-lp_y)*hfscale + lp_y; }; std::transform(input.cbegin(), input.cend(), output.begin(), proc_sample); mLpZ1 = lp_z1; mLpZ2 = lp_z2; mApZ1 = ap_z1; } template void BandSplitterR::processHfScale(const al::span samples, const Real hfscale) { const Real ap_coeff{mCoeff}; const Real lp_coeff{mCoeff*0.5f + 0.5f}; Real lp_z1{mLpZ1}; Real lp_z2{mLpZ2}; Real ap_z1{mApZ1}; auto proc_sample = [hfscale,ap_coeff,lp_coeff,&lp_z1,&lp_z2,&ap_z1](const Real in) noexcept -> Real { /* Low-pass sample processing. */ Real d{(in - lp_z1) * lp_coeff}; Real lp_y{lp_z1 + d}; lp_z1 = lp_y + d; d = (lp_y - lp_z2) * lp_coeff; lp_y = lp_z2 + d; lp_z2 = lp_y + d; /* All-pass sample processing. */ Real ap_y{in*ap_coeff + ap_z1}; ap_z1 = in - ap_y*ap_coeff; /* High-pass generated by removing the low-passed signal, which is then * scaled and added back to the low-passed signal. */ return (ap_y-lp_y)*hfscale + lp_y; }; std::transform(samples.begin(), samples.end(), samples.begin(), proc_sample); mLpZ1 = lp_z1; mLpZ2 = lp_z2; mApZ1 = ap_z1; } template void BandSplitterR::processScale(const al::span samples, const Real hfscale, const Real lfscale) { const Real ap_coeff{mCoeff}; const Real lp_coeff{mCoeff*0.5f + 0.5f}; Real lp_z1{mLpZ1}; Real lp_z2{mLpZ2}; Real ap_z1{mApZ1}; auto proc_sample = [hfscale,lfscale,ap_coeff,lp_coeff,&lp_z1,&lp_z2,&ap_z1](const Real in) noexcept -> Real { Real d{(in - lp_z1) * lp_coeff}; Real lp_y{lp_z1 + d}; lp_z1 = lp_y + d; d = (lp_y - lp_z2) * lp_coeff; lp_y = lp_z2 + d; lp_z2 = lp_y + d; Real ap_y{in*ap_coeff + ap_z1}; ap_z1 = in - ap_y*ap_coeff; /* Apply separate factors to the high and low frequencies. */ return (ap_y-lp_y)*hfscale + lp_y*lfscale; }; std::transform(samples.begin(), samples.end(), samples.begin(), proc_sample); mLpZ1 = lp_z1; mLpZ2 = lp_z2; mApZ1 = ap_z1; } template void BandSplitterR::processAllPass(const al::span samples) { const Real coeff{mCoeff}; Real z1{mApZ1}; auto proc_sample = [coeff,&z1](const Real in) noexcept -> Real { const Real out{in*coeff + z1}; z1 = in - out*coeff; return out; }; std::transform(samples.cbegin(), samples.cend(), samples.begin(), proc_sample); mApZ1 = z1; } template class BandSplitterR; template class BandSplitterR; openal-soft-1.24.2/core/filters/splitter.h000066400000000000000000000024311474041540300204500ustar00rootroot00000000000000#ifndef CORE_FILTERS_SPLITTER_H #define CORE_FILTERS_SPLITTER_H #include #include "alspan.h" /* Band splitter. Splits a signal into two phase-matching frequency bands. */ template class BandSplitterR { Real mCoeff{0.0f}; Real mLpZ1{0.0f}; Real mLpZ2{0.0f}; Real mApZ1{0.0f}; public: BandSplitterR() = default; BandSplitterR(const BandSplitterR&) = default; explicit BandSplitterR(Real f0norm) { init(f0norm); } BandSplitterR& operator=(const BandSplitterR&) = default; void init(Real f0norm); void clear() noexcept { mLpZ1 = mLpZ2 = mApZ1 = 0.0f; } void process(const al::span input, const al::span hpout, const al::span lpout); void processHfScale(const al::span input, const al::span output, const Real hfscale); void processHfScale(const al::span samples, const Real hfscale); void processScale(const al::span samples, const Real hfscale, const Real lfscale); /** * The all-pass portion of the band splitter. Applies the same phase shift * without splitting or scaling the signal. */ void processAllPass(const al::span samples); }; using BandSplitter = BandSplitterR; #endif /* CORE_FILTERS_SPLITTER_H */ openal-soft-1.24.2/core/fmt_traits.h000066400000000000000000000126001474041540300173050ustar00rootroot00000000000000#ifndef CORE_FMT_TRAITS_H #define CORE_FMT_TRAITS_H #include #include #include "storage_formats.h" namespace al { inline constexpr auto muLawDecompressionTable = std::array{{ -32124,-31100,-30076,-29052,-28028,-27004,-25980,-24956, -23932,-22908,-21884,-20860,-19836,-18812,-17788,-16764, -15996,-15484,-14972,-14460,-13948,-13436,-12924,-12412, -11900,-11388,-10876,-10364, -9852, -9340, -8828, -8316, -7932, -7676, -7420, -7164, -6908, -6652, -6396, -6140, -5884, -5628, -5372, -5116, -4860, -4604, -4348, -4092, -3900, -3772, -3644, -3516, -3388, -3260, -3132, -3004, -2876, -2748, -2620, -2492, -2364, -2236, -2108, -1980, -1884, -1820, -1756, -1692, -1628, -1564, -1500, -1436, -1372, -1308, -1244, -1180, -1116, -1052, -988, -924, -876, -844, -812, -780, -748, -716, -684, -652, -620, -588, -556, -524, -492, -460, -428, -396, -372, -356, -340, -324, -308, -292, -276, -260, -244, -228, -212, -196, -180, -164, -148, -132, -120, -112, -104, -96, -88, -80, -72, -64, -56, -48, -40, -32, -24, -16, -8, 0, 32124, 31100, 30076, 29052, 28028, 27004, 25980, 24956, 23932, 22908, 21884, 20860, 19836, 18812, 17788, 16764, 15996, 15484, 14972, 14460, 13948, 13436, 12924, 12412, 11900, 11388, 10876, 10364, 9852, 9340, 8828, 8316, 7932, 7676, 7420, 7164, 6908, 6652, 6396, 6140, 5884, 5628, 5372, 5116, 4860, 4604, 4348, 4092, 3900, 3772, 3644, 3516, 3388, 3260, 3132, 3004, 2876, 2748, 2620, 2492, 2364, 2236, 2108, 1980, 1884, 1820, 1756, 1692, 1628, 1564, 1500, 1436, 1372, 1308, 1244, 1180, 1116, 1052, 988, 924, 876, 844, 812, 780, 748, 716, 684, 652, 620, 588, 556, 524, 492, 460, 428, 396, 372, 356, 340, 324, 308, 292, 276, 260, 244, 228, 212, 196, 180, 164, 148, 132, 120, 112, 104, 96, 88, 80, 72, 64, 56, 48, 40, 32, 24, 16, 8, 0 }}; inline constexpr auto aLawDecompressionTable = std::array{{ -5504, -5248, -6016, -5760, -4480, -4224, -4992, -4736, -7552, -7296, -8064, -7808, -6528, -6272, -7040, -6784, -2752, -2624, -3008, -2880, -2240, -2112, -2496, -2368, -3776, -3648, -4032, -3904, -3264, -3136, -3520, -3392, -22016,-20992,-24064,-23040,-17920,-16896,-19968,-18944, -30208,-29184,-32256,-31232,-26112,-25088,-28160,-27136, -11008,-10496,-12032,-11520, -8960, -8448, -9984, -9472, -15104,-14592,-16128,-15616,-13056,-12544,-14080,-13568, -344, -328, -376, -360, -280, -264, -312, -296, -472, -456, -504, -488, -408, -392, -440, -424, -88, -72, -120, -104, -24, -8, -56, -40, -216, -200, -248, -232, -152, -136, -184, -168, -1376, -1312, -1504, -1440, -1120, -1056, -1248, -1184, -1888, -1824, -2016, -1952, -1632, -1568, -1760, -1696, -688, -656, -752, -720, -560, -528, -624, -592, -944, -912, -1008, -976, -816, -784, -880, -848, 5504, 5248, 6016, 5760, 4480, 4224, 4992, 4736, 7552, 7296, 8064, 7808, 6528, 6272, 7040, 6784, 2752, 2624, 3008, 2880, 2240, 2112, 2496, 2368, 3776, 3648, 4032, 3904, 3264, 3136, 3520, 3392, 22016, 20992, 24064, 23040, 17920, 16896, 19968, 18944, 30208, 29184, 32256, 31232, 26112, 25088, 28160, 27136, 11008, 10496, 12032, 11520, 8960, 8448, 9984, 9472, 15104, 14592, 16128, 15616, 13056, 12544, 14080, 13568, 344, 328, 376, 360, 280, 264, 312, 296, 472, 456, 504, 488, 408, 392, 440, 424, 88, 72, 120, 104, 24, 8, 56, 40, 216, 200, 248, 232, 152, 136, 184, 168, 1376, 1312, 1504, 1440, 1120, 1056, 1248, 1184, 1888, 1824, 2016, 1952, 1632, 1568, 1760, 1696, 688, 656, 752, 720, 560, 528, 624, 592, 944, 912, 1008, 976, 816, 784, 880, 848 }}; template struct FmtTypeTraits { }; template<> struct FmtTypeTraits { using Type = std::uint8_t; constexpr float operator()(const Type val) const noexcept { return float(val)*(1.0f/128.0f) - 1.0f; } }; template<> struct FmtTypeTraits { using Type = std::int16_t; constexpr float operator()(const Type val) const noexcept { return float(val) * (1.0f/32768.0f); } }; template<> struct FmtTypeTraits { using Type = std::int32_t; constexpr float operator()(const Type val) const noexcept { return static_cast(val)*(1.0f/2147483648.0f); } }; template<> struct FmtTypeTraits { using Type = float; constexpr float operator()(const Type val) const noexcept { return val; } }; template<> struct FmtTypeTraits { using Type = double; constexpr float operator()(const Type val) const noexcept { return static_cast(val); } }; template<> struct FmtTypeTraits { using Type = std::uint8_t; constexpr float operator()(const Type val) const noexcept { return float(muLawDecompressionTable[val]) * (1.0f/32768.0f); } }; template<> struct FmtTypeTraits { using Type = std::uint8_t; constexpr float operator()(const Type val) const noexcept { return float(aLawDecompressionTable[val]) * (1.0f/32768.0f); } }; } // namespace al #endif /* CORE_FMT_TRAITS_H */ openal-soft-1.24.2/core/fpu_ctrl.cpp000066400000000000000000000035461474041540300173130ustar00rootroot00000000000000 #include "config.h" #include "config_simd.h" #include "fpu_ctrl.h" #ifdef HAVE_INTRIN_H #include #endif #if HAVE_SSE_INTRINSICS #include #elif HAVE_SSE #include #endif #if HAVE_SSE && !defined(_MM_DENORMALS_ZERO_MASK) /* Some headers seem to be missing these? */ #define _MM_DENORMALS_ZERO_MASK 0x0040u #define _MM_DENORMALS_ZERO_ON 0x0040u #endif #if !HAVE_SSE_INTRINSICS && HAVE_SSE #include "cpu_caps.h" #endif namespace { #if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)) [[gnu::target("sse")]] #endif [[maybe_unused]] void disable_denormals(unsigned int *state [[maybe_unused]]) { #if HAVE_SSE_INTRINSICS *state = _mm_getcsr(); unsigned int sseState{*state}; sseState &= ~(_MM_FLUSH_ZERO_MASK | _MM_DENORMALS_ZERO_MASK); sseState |= _MM_FLUSH_ZERO_ON | _MM_DENORMALS_ZERO_ON; _mm_setcsr(sseState); #elif HAVE_SSE *state = _mm_getcsr(); unsigned int sseState{*state}; sseState &= ~_MM_FLUSH_ZERO_MASK; sseState |= _MM_FLUSH_ZERO_ON; if((CPUCapFlags&CPU_CAP_SSE2)) { sseState &= ~_MM_DENORMALS_ZERO_MASK; sseState |= _MM_DENORMALS_ZERO_ON; } _mm_setcsr(sseState); #endif } #if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)) [[gnu::target("sse")]] #endif [[maybe_unused]] void reset_fpu(unsigned int state [[maybe_unused]]) { #if HAVE_SSE_INTRINSICS || HAVE_SSE _mm_setcsr(state); #endif } } // namespace unsigned int FPUCtl::Set() noexcept { unsigned int state{}; #if HAVE_SSE_INTRINSICS disable_denormals(&state); #elif HAVE_SSE if((CPUCapFlags&CPU_CAP_SSE)) disable_denormals(&state); #endif return state; } void FPUCtl::Reset(unsigned int state [[maybe_unused]]) noexcept { #if HAVE_SSE_INTRINSICS reset_fpu(state); #elif HAVE_SSE if((CPUCapFlags&CPU_CAP_SSE)) reset_fpu(state); #endif } openal-soft-1.24.2/core/fpu_ctrl.h000066400000000000000000000012351474041540300167510ustar00rootroot00000000000000#ifndef CORE_FPU_CTRL_H #define CORE_FPU_CTRL_H class FPUCtl { unsigned int sse_state{}; bool in_mode{}; static unsigned int Set() noexcept; static void Reset(unsigned int state) noexcept; public: FPUCtl() noexcept : sse_state{Set()}, in_mode{true} { } ~FPUCtl() { if(in_mode) Reset(sse_state); } FPUCtl(const FPUCtl&) = delete; FPUCtl& operator=(const FPUCtl&) = delete; void enter() noexcept { if(!in_mode) sse_state = Set(); in_mode = true; } void leave() noexcept { if(in_mode) Reset(sse_state); in_mode = false; } }; #endif /* CORE_FPU_CTRL_H */ openal-soft-1.24.2/core/front_stablizer.h000066400000000000000000000016021474041540300203400ustar00rootroot00000000000000#ifndef CORE_FRONT_STABLIZER_H #define CORE_FRONT_STABLIZER_H #include #include #include "almalloc.h" #include "bufferline.h" #include "filters/splitter.h" #include "flexarray.h" struct FrontStablizer { explicit FrontStablizer(size_t numchans) : ChannelFilters{numchans} { } alignas(16) std::array MidDirect{}; alignas(16) std::array Side{}; alignas(16) std::array Temp{}; BandSplitter MidFilter; alignas(16) FloatBufferLine MidLF{}; alignas(16) FloatBufferLine MidHF{}; al::FlexArray ChannelFilters; static std::unique_ptr Create(size_t numchans) { return std::unique_ptr{new(FamCount(numchans)) FrontStablizer{numchans}}; } DEF_FAM_NEWDEL(FrontStablizer, ChannelFilters) }; #endif /* CORE_FRONT_STABLIZER_H */ openal-soft-1.24.2/core/helpers.cpp000066400000000000000000000344551474041540300171420ustar00rootroot00000000000000 #include "config.h" #include "helpers.h" #if defined(_WIN32) #define WIN32_LEAN_AND_MEAN #include #endif #include #include #include #include #include #include #include #include #include #include "almalloc.h" #include "alnumeric.h" #include "alspan.h" #include "alstring.h" #include "filesystem.h" #include "logging.h" #include "strutils.h" namespace { using namespace std::string_view_literals; std::mutex gSearchLock; void DirectorySearch(const fs::path &path, const std::string_view ext, std::vector *const results) { const auto base = results->size(); try { auto fpath = path.lexically_normal(); if(!fs::exists(fpath)) return; TRACE("Searching {} for *{}", al::u8_as_char(fpath.u8string()), ext); for(auto&& dirent : fs::directory_iterator{fpath}) { auto&& entrypath = dirent.path(); if(!entrypath.has_extension()) continue; if(fs::status(entrypath).type() != fs::file_type::regular) continue; const auto u8ext = entrypath.extension().u8string(); if(al::case_compare(al::u8_as_char(u8ext), ext) == 0) results->emplace_back(al::u8_as_char(entrypath.u8string())); } } catch(std::exception& e) { ERR("Exception enumerating files: {}", e.what()); } const auto newlist = al::span{*results}.subspan(base); std::sort(newlist.begin(), newlist.end()); for(const auto &name : newlist) TRACE(" got {}", name); } } // namespace #ifdef _WIN32 #include #include const PathNamePair &GetProcBinary() { auto get_procbin = [] { #if !ALSOFT_UWP DWORD pathlen{256}; auto fullpath = std::wstring(pathlen, L'\0'); DWORD len{GetModuleFileNameW(nullptr, fullpath.data(), pathlen)}; while(len == fullpath.size()) { pathlen <<= 1; if(pathlen == 0) { /* pathlen overflow (more than 4 billion characters??) */ len = 0; break; } fullpath.resize(pathlen); len = GetModuleFileNameW(nullptr, fullpath.data(), pathlen); } if(len == 0) { ERR("Failed to get process name: error {}", GetLastError()); return PathNamePair{}; } fullpath.resize(len); #else const WCHAR *exePath{__wargv[0]}; if(!exePath) { ERR("Failed to get process name: __wargv[0] == nullptr"); return PathNamePair{}; } std::wstring fullpath{exePath}; #endif std::replace(fullpath.begin(), fullpath.end(), L'/', L'\\'); PathNamePair res{}; if(auto seppos = fullpath.rfind(L'\\'); seppos < fullpath.size()) { res.path = wstr_to_utf8(std::wstring_view{fullpath}.substr(0, seppos)); res.fname = wstr_to_utf8(std::wstring_view{fullpath}.substr(seppos+1)); } else res.fname = wstr_to_utf8(fullpath); TRACE("Got binary: {}, {}", res.path, res.fname); return res; }; static const PathNamePair procbin{get_procbin()}; return procbin; } namespace { #if !ALSOFT_UWP && !defined(_GAMING_XBOX) struct CoTaskMemDeleter { void operator()(void *mem) const { CoTaskMemFree(mem); } }; #endif } // namespace auto SearchDataFiles(const std::string_view ext) -> std::vector { auto srchlock = std::lock_guard{gSearchLock}; /* Search the app-local directory. */ auto results = std::vector{}; if(auto localpath = al::getenv(L"ALSOFT_LOCAL_PATH")) DirectorySearch(*localpath, ext, &results); else if(auto curpath = fs::current_path(); !curpath.empty()) DirectorySearch(curpath, ext, &results); return results; } auto SearchDataFiles(const std::string_view ext, const std::string_view subdir) -> std::vector { std::lock_guard srchlock{gSearchLock}; /* If the path is absolute, use it directly. */ std::vector results; auto path = fs::u8path(subdir); if(path.is_absolute()) { DirectorySearch(path, ext, &results); return results; } #if !ALSOFT_UWP && !defined(_GAMING_XBOX) /* Search the local and global data dirs. */ for(const auto &folderid : std::array{FOLDERID_RoamingAppData, FOLDERID_ProgramData}) { std::unique_ptr buffer; const HRESULT hr{SHGetKnownFolderPath(folderid, KF_FLAG_DONT_UNEXPAND, nullptr, al::out_ptr(buffer))}; if(FAILED(hr) || !buffer || !*buffer) continue; DirectorySearch(fs::path{buffer.get()}/path, ext, &results); } #endif return results; } void SetRTPriority() { #if !ALSOFT_UWP if(RTPrioLevel > 0) { if(!SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL)) ERR("Failed to set priority level for thread"); } #endif } #else #include #include #include #ifdef __FreeBSD__ #include #endif #ifdef __HAIKU__ #include #endif #ifdef HAVE_PROC_PIDPATH #include #endif #if defined(HAVE_PTHREAD_SETSCHEDPARAM) && !defined(__OpenBSD__) #include #include #endif #if HAVE_RTKIT #include #include "dbus_wrap.h" #include "rtkit.h" #ifndef RLIMIT_RTTIME #define RLIMIT_RTTIME 15 #endif #endif const PathNamePair &GetProcBinary() { auto get_procbin = [] { std::string pathname; #ifdef __FreeBSD__ size_t pathlen{}; std::array mib{{CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1}}; if(sysctl(mib.data(), mib.size(), nullptr, &pathlen, nullptr, 0) == -1) WARN("Failed to sysctl kern.proc.pathname: {}", std::generic_category().message(errno)); else { auto procpath = std::vector(pathlen+1, '\0'); sysctl(mib.data(), mib.size(), procpath.data(), &pathlen, nullptr, 0); pathname = procpath.data(); } #endif #ifdef HAVE_PROC_PIDPATH if(pathname.empty()) { std::array procpath{}; const pid_t pid{getpid()}; if(proc_pidpath(pid, procpath.data(), procpath.size()) < 1) ERR("proc_pidpath({}, ...) failed: {}", pid, std::generic_category().message(errno)); else pathname = procpath.data(); } #endif #ifdef __HAIKU__ if(pathname.empty()) { std::array procpath{}; if(find_path(B_APP_IMAGE_SYMBOL, B_FIND_PATH_IMAGE_PATH, NULL, procpath.data(), procpath.size()) == B_OK) pathname = procpath.data(); } #endif #ifndef __SWITCH__ if(pathname.empty()) { const std::array SelfLinkNames{ "/proc/self/exe"sv, "/proc/self/file"sv, "/proc/curproc/exe"sv, "/proc/curproc/file"sv, }; for(const std::string_view name : SelfLinkNames) { try { if(!fs::exists(name)) continue; if(auto path = fs::read_symlink(name); !path.empty()) { pathname = al::u8_as_char(path.u8string()); break; } } catch(std::exception& e) { WARN("Exception getting symlink {}: {}", name, e.what()); } } } #endif PathNamePair res{}; if(auto seppos = pathname.rfind('/'); seppos < pathname.size()) { res.path = std::string_view{pathname}.substr(0, seppos); res.fname = std::string_view{pathname}.substr(seppos+1); } else res.fname = pathname; TRACE("Got binary: \"{}\", \"{}\"", res.path, res.fname); return res; }; static const PathNamePair procbin{get_procbin()}; return procbin; } auto SearchDataFiles(const std::string_view ext) -> std::vector { auto srchlock = std::lock_guard{gSearchLock}; /* Search the app-local directory. */ auto results = std::vector{}; if(auto localpath = al::getenv("ALSOFT_LOCAL_PATH")) DirectorySearch(*localpath, ext, &results); else if(auto curpath = fs::current_path(); !curpath.empty()) DirectorySearch(curpath, ext, &results); return results; } auto SearchDataFiles(const std::string_view ext, const std::string_view subdir) -> std::vector { std::lock_guard srchlock{gSearchLock}; std::vector results; auto path = fs::u8path(subdir); if(path.is_absolute()) { DirectorySearch(path, ext, &results); return results; } /* Search local data dir */ if(auto datapath = al::getenv("XDG_DATA_HOME")) DirectorySearch(fs::path{*datapath}/path, ext, &results); else if(auto homepath = al::getenv("HOME")) DirectorySearch(fs::path{*homepath}/".local/share"/path, ext, &results); /* Search global data dirs */ std::string datadirs{al::getenv("XDG_DATA_DIRS").value_or("/usr/local/share/:/usr/share/")}; size_t curpos{0u}; while(curpos < datadirs.size()) { size_t nextpos{datadirs.find(':', curpos)}; std::string_view pathname{(nextpos != std::string::npos) ? std::string_view{datadirs}.substr(curpos, nextpos++ - curpos) : std::string_view{datadirs}.substr(curpos)}; curpos = nextpos; if(!pathname.empty()) DirectorySearch(fs::path{pathname}/path, ext, &results); } #ifdef ALSOFT_INSTALL_DATADIR /* Search the installation data directory */ if(auto instpath = fs::path{ALSOFT_INSTALL_DATADIR}; !instpath.empty()) DirectorySearch(instpath/path, ext, &results); #endif return results; } namespace { bool SetRTPriorityPthread(int prio [[maybe_unused]]) { int err{ENOTSUP}; #if defined(HAVE_PTHREAD_SETSCHEDPARAM) && !defined(__OpenBSD__) /* Get the min and max priority for SCHED_RR. Limit the max priority to * half, for now, to ensure the thread can't take the highest priority and * go rogue. */ int rtmin{sched_get_priority_min(SCHED_RR)}; int rtmax{sched_get_priority_max(SCHED_RR)}; rtmax = (rtmax-rtmin)/2 + rtmin; struct sched_param param{}; param.sched_priority = std::clamp(prio, rtmin, rtmax); #ifdef SCHED_RESET_ON_FORK err = pthread_setschedparam(pthread_self(), SCHED_RR|SCHED_RESET_ON_FORK, ¶m); if(err == EINVAL) #endif err = pthread_setschedparam(pthread_self(), SCHED_RR, ¶m); if(err == 0) return true; #endif WARN("pthread_setschedparam failed: {} ({})", std::generic_category().message(err), err); return false; } bool SetRTPriorityRTKit(int prio [[maybe_unused]]) { #if HAVE_RTKIT if(!HasDBus()) { WARN("D-Bus not available"); return false; } dbus::Error error; dbus::ConnectionPtr conn{dbus_bus_get(DBUS_BUS_SYSTEM, &error.get())}; if(!conn) { WARN("D-Bus connection failed with {}: {}", error->name, error->message); return false; } /* Don't stupidly exit if the connection dies while doing this. */ dbus_connection_set_exit_on_disconnect(conn.get(), false); int nicemin{}; int err{rtkit_get_min_nice_level(conn.get(), &nicemin)}; if(err == -ENOENT) { err = std::abs(err); ERR("Could not query RTKit: {} ({})", std::generic_category().message(err), err); return false; } int rtmax{rtkit_get_max_realtime_priority(conn.get())}; TRACE("Maximum real-time priority: {}, minimum niceness: {}", rtmax, nicemin); auto limit_rttime = [](DBusConnection *c) -> int { using ulonglong = unsigned long long; long long maxrttime{rtkit_get_rttime_usec_max(c)}; if(maxrttime <= 0) return static_cast(std::abs(maxrttime)); const ulonglong umaxtime{static_cast(maxrttime)}; struct rlimit rlim{}; if(getrlimit(RLIMIT_RTTIME, &rlim) != 0) return errno; TRACE("RTTime max: {} (hard: {}, soft: {})", umaxtime, rlim.rlim_max, rlim.rlim_cur); if(rlim.rlim_max > umaxtime) { rlim.rlim_max = static_cast(std::min(umaxtime, std::numeric_limits::max())); rlim.rlim_cur = std::min(rlim.rlim_cur, rlim.rlim_max); if(setrlimit(RLIMIT_RTTIME, &rlim) != 0) return errno; } return 0; }; if(rtmax > 0) { if(AllowRTTimeLimit) { err = limit_rttime(conn.get()); if(err != 0) WARN("Failed to set RLIMIT_RTTIME for RTKit: {} ({})", std::generic_category().message(err), err); } /* Limit the maximum real-time priority to half. */ rtmax = (rtmax+1)/2; prio = std::clamp(prio, 1, rtmax); TRACE("Making real-time with priority {} (max: {})", prio, rtmax); err = rtkit_make_realtime(conn.get(), 0, prio); if(err == 0) return true; err = std::abs(err); WARN("Failed to set real-time priority: {} ({})", std::generic_category().message(err), err); } /* Don't try to set the niceness for non-Linux systems. Standard POSIX has * niceness as a per-process attribute, while the intent here is for the * audio processing thread only to get a priority boost. Currently only * Linux is known to have per-thread niceness. */ #ifdef __linux__ if(nicemin < 0) { TRACE("Making high priority with niceness {}", nicemin); err = rtkit_make_high_priority(conn.get(), 0, nicemin); if(err == 0) return true; err = std::abs(err); WARN("Failed to set high priority: {} ({})", std::generic_category().message(err), err); } #endif /* __linux__ */ #else WARN("D-Bus not supported"); #endif return false; } } // namespace void SetRTPriority() { if(RTPrioLevel <= 0) return; if(SetRTPriorityPthread(RTPrioLevel)) return; if(SetRTPriorityRTKit(RTPrioLevel)) return; } #endif openal-soft-1.24.2/core/helpers.h000066400000000000000000000011321474041540300165710ustar00rootroot00000000000000#ifndef CORE_HELPERS_H #define CORE_HELPERS_H #include #include #include struct PathNamePair { std::string path, fname; }; const PathNamePair &GetProcBinary(); /* Mixing thread priority level */ inline int RTPrioLevel{1}; /* Allow reducing the process's RTTime limit for RTKit. */ inline bool AllowRTTimeLimit{true}; void SetRTPriority(); auto SearchDataFiles(const std::string_view ext) -> std::vector; auto SearchDataFiles(const std::string_view ext, const std::string_view subdir) -> std::vector; #endif /* CORE_HELPERS_H */ openal-soft-1.24.2/core/hrtf.cpp000066400000000000000000001457521474041540300164460ustar00rootroot00000000000000 #include "config.h" #include "hrtf.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "albit.h" #include "almalloc.h" #include "alnumbers.h" #include "alnumeric.h" #include "alspan.h" #include "alstring.h" #include "ambidefs.h" #include "filesystem.h" #include "filters/splitter.h" #include "fmt/core.h" #include "helpers.h" #include "logging.h" #include "mixer/hrtfdefs.h" #include "opthelpers.h" #include "polyphase_resampler.h" namespace { using namespace std::string_view_literals; struct HrtfEntry { std::string mDispName; std::string mFilename; template HrtfEntry(T&& dispname, U&& fname) : mDispName{std::forward(dispname)}, mFilename{std::forward(fname)} { } /* GCC warns when it tries to inline this. */ ~HrtfEntry(); }; HrtfEntry::~HrtfEntry() = default; struct LoadedHrtf { std::string mFilename; uint mSampleRate{}; std::unique_ptr mEntry; template LoadedHrtf(T&& name, uint srate, U&& entry) : mFilename{std::forward(name)}, mSampleRate{srate}, mEntry{std::forward(entry)} { } LoadedHrtf(LoadedHrtf&&) = default; /* GCC warns when it tries to inline this. */ ~LoadedHrtf(); LoadedHrtf& operator=(LoadedHrtf&&) = default; }; LoadedHrtf::~LoadedHrtf() = default; /* Data set limits must be the same as or more flexible than those defined in * the makemhr utility. */ constexpr uint MinFdCount{1}; constexpr uint MaxFdCount{16}; constexpr uint MinFdDistance{50}; constexpr uint MaxFdDistance{2500}; constexpr uint MinEvCount{5}; constexpr uint MaxEvCount{181}; constexpr uint MinAzCount{1}; constexpr uint MaxAzCount{255}; constexpr uint MaxHrirDelay{HrtfHistoryLength - 1}; constexpr uint HrirDelayFracBits{2}; constexpr uint HrirDelayFracOne{1 << HrirDelayFracBits}; constexpr uint HrirDelayFracHalf{HrirDelayFracOne >> 1}; /* The sample rate is stored as a 24-bit integer, so 16MHz is the largest * supported. */ constexpr uint MaxSampleRate{0xff'ff'ff}; static_assert(MaxHrirDelay*HrirDelayFracOne < 256, "MAX_HRIR_DELAY or DELAY_FRAC too large"); constexpr auto HeaderMarkerSize = 8_uz; [[nodiscard]] constexpr auto GetMarker00Name() noexcept { return "MinPHR00"sv; } [[nodiscard]] constexpr auto GetMarker01Name() noexcept { return "MinPHR01"sv; } [[nodiscard]] constexpr auto GetMarker02Name() noexcept { return "MinPHR02"sv; } [[nodiscard]] constexpr auto GetMarker03Name() noexcept { return "MinPHR03"sv; } static_assert(GetMarker00Name().size() == HeaderMarkerSize); static_assert(GetMarker01Name().size() == HeaderMarkerSize); static_assert(GetMarker02Name().size() == HeaderMarkerSize); static_assert(GetMarker03Name().size() == HeaderMarkerSize); /* First value for pass-through coefficients (remaining are 0), used for omni- * directional sounds. */ constexpr auto PassthruCoeff = static_cast(1.0/al::numbers::sqrt2); std::mutex LoadedHrtfLock; std::vector LoadedHrtfs; std::mutex EnumeratedHrtfLock; std::vector EnumeratedHrtfs; /* NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic) * To access a memory buffer through the std::istream interface, a custom * std::streambuf implementation is needed that has to do pointer manipulation * for seeking. With C++23, we may be able to use std::spanstream instead. */ class databuf final : public std::streambuf { int_type underflow() override { return traits_type::eof(); } pos_type seekoff(off_type offset, std::ios_base::seekdir whence, std::ios_base::openmode mode) override { if((mode&std::ios_base::out) || !(mode&std::ios_base::in)) return traits_type::eof(); switch(whence) { case std::ios_base::beg: if(offset < 0 || offset > egptr()-eback()) return traits_type::eof(); setg(eback(), eback()+offset, egptr()); break; case std::ios_base::cur: if((offset >= 0 && offset > egptr()-gptr()) || (offset < 0 && -offset > gptr()-eback())) return traits_type::eof(); setg(eback(), gptr()+offset, egptr()); break; case std::ios_base::end: if(offset > 0 || -offset > egptr()-eback()) return traits_type::eof(); setg(eback(), egptr()+offset, egptr()); break; default: return traits_type::eof(); } return gptr() - eback(); } pos_type seekpos(pos_type pos, std::ios_base::openmode mode) override { // Simplified version of seekoff if((mode&std::ios_base::out) || !(mode&std::ios_base::in)) return traits_type::eof(); if(pos < 0 || pos > egptr()-eback()) return traits_type::eof(); setg(eback(), eback()+static_cast(pos), egptr()); return pos; } public: explicit databuf(const al::span data) noexcept { setg(data.data(), data.data(), al::to_address(data.end())); } }; /* NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic) */ class idstream final : public std::istream { databuf mStreamBuf; public: explicit idstream(const al::span data) : std::istream{nullptr}, mStreamBuf{data} { init(&mStreamBuf); } }; struct IdxBlend { uint idx; float blend; }; /* Calculate the elevation index given the polar elevation in radians. This * will return an index between 0 and (evcount - 1). */ IdxBlend CalcEvIndex(uint evcount, float ev) { ev = (al::numbers::inv_pi_v*ev + 0.5f) * static_cast(evcount-1); const auto idx = float2uint(ev); return IdxBlend{std::min(idx, evcount-1u), ev-static_cast(idx)}; } /* Calculate the azimuth index given the polar azimuth in radians. This will * return an index between 0 and (azcount - 1). */ IdxBlend CalcAzIndex(uint azcount, float az) { az = (al::numbers::inv_pi_v*0.5f*az + 1.0f) * static_cast(azcount); const auto idx = float2uint(az); return IdxBlend{idx%azcount, az-static_cast(idx)}; } } // namespace /* Calculates static HRIR coefficients and delays for the given polar elevation * and azimuth in radians. The coefficients are normalized. */ void HrtfStore::getCoeffs(float elevation, float azimuth, float distance, float spread, const HrirSpan coeffs, const al::span delays) const { const float dirfact{1.0f - (al::numbers::inv_pi_v/2.0f * spread)}; size_t ebase{0}; auto match_field = [&ebase,distance](const Field &field) noexcept -> bool { if(distance >= field.distance) return true; ebase += field.evCount; return false; }; auto field = std::find_if(mFields.begin(), mFields.end()-1, match_field); /* Calculate the elevation indices. */ const auto elev0 = CalcEvIndex(field->evCount, elevation); const size_t elev1_idx{std::min(elev0.idx+1u, field->evCount-1u)}; const size_t ir0offset{mElev[ebase + elev0.idx].irOffset}; const size_t ir1offset{mElev[ebase + elev1_idx].irOffset}; /* Calculate azimuth indices. */ const auto az0 = CalcAzIndex(mElev[ebase + elev0.idx].azCount, azimuth); const auto az1 = CalcAzIndex(mElev[ebase + elev1_idx].azCount, azimuth); /* Calculate the HRIR indices to blend. */ const std::array idx{{ ir0offset + az0.idx, ir0offset + ((az0.idx+1) % mElev[ebase + elev0.idx].azCount), ir1offset + az1.idx, ir1offset + ((az1.idx+1) % mElev[ebase + elev1_idx].azCount) }}; /* Calculate bilinear blending weights, attenuated according to the * directional panning factor. */ const std::array blend{{ (1.0f-elev0.blend) * (1.0f-az0.blend) * dirfact, (1.0f-elev0.blend) * ( az0.blend) * dirfact, ( elev0.blend) * (1.0f-az1.blend) * dirfact, ( elev0.blend) * ( az1.blend) * dirfact }}; /* Calculate the blended HRIR delays. */ float d{float(mDelays[idx[0]][0])*blend[0] + float(mDelays[idx[1]][0])*blend[1] + float(mDelays[idx[2]][0])*blend[2] + float(mDelays[idx[3]][0])*blend[3]}; delays[0] = fastf2u(d * float{1.0f/HrirDelayFracOne}); d = float(mDelays[idx[0]][1])*blend[0] + float(mDelays[idx[1]][1])*blend[1] + float(mDelays[idx[2]][1])*blend[2] + float(mDelays[idx[3]][1])*blend[3]; delays[1] = fastf2u(d * float{1.0f/HrirDelayFracOne}); /* Calculate the blended HRIR coefficients. */ auto coeffout = coeffs.begin(); coeffout[0][0] = PassthruCoeff * (1.0f-dirfact); coeffout[0][1] = PassthruCoeff * (1.0f-dirfact); std::fill_n(coeffout+1, size_t{HrirLength-1}, std::array{0.0f, 0.0f}); for(size_t c{0};c < 4;c++) { const float mult{blend[c]}; auto blend_coeffs = [mult](const float2 &src, const float2 &coeff) noexcept -> float2 { return float2{{src[0]*mult + coeff[0], src[1]*mult + coeff[1]}}; }; std::transform(mCoeffs[idx[c]].cbegin(), mCoeffs[idx[c]].cend(), coeffout, coeffout, blend_coeffs); } } std::unique_ptr DirectHrtfState::Create(size_t num_chans) { return std::unique_ptr{new(FamCount(num_chans)) DirectHrtfState{num_chans}}; } void DirectHrtfState::build(const HrtfStore *Hrtf, const uint irSize, const bool perHrirMin, const al::span AmbiPoints, const al::span> AmbiMatrix, const float XOverFreq, const al::span AmbiOrderHFGain) { using double2 = std::array; struct ImpulseResponse { const ConstHrirSpan hrir; uint ldelay, rdelay; }; const double xover_norm{double{XOverFreq} / Hrtf->mSampleRate}; mChannels[0].mSplitter.init(static_cast(xover_norm)); mChannels[0].mHfScale = AmbiOrderHFGain[0]; for(size_t i{1};i < mChannels.size();++i) { const size_t order{AmbiIndex::OrderFromChannel[i]}; mChannels[i].mSplitter = mChannels[0].mSplitter; mChannels[i].mHfScale = AmbiOrderHFGain[order]; } uint min_delay{HrtfHistoryLength*HrirDelayFracOne}, max_delay{0}; std::vector impres; impres.reserve(AmbiPoints.size()); auto calc_res = [Hrtf,&max_delay,&min_delay](const AngularPoint &pt) -> ImpulseResponse { auto &field = Hrtf->mFields[0]; const auto elev0 = CalcEvIndex(field.evCount, pt.Elev.value); const size_t elev1_idx{std::min(elev0.idx+1u, field.evCount-1u)}; const size_t ir0offset{Hrtf->mElev[elev0.idx].irOffset}; const size_t ir1offset{Hrtf->mElev[elev1_idx].irOffset}; const auto az0 = CalcAzIndex(Hrtf->mElev[elev0.idx].azCount, pt.Azim.value); const auto az1 = CalcAzIndex(Hrtf->mElev[elev1_idx].azCount, pt.Azim.value); const std::array idx{ ir0offset + az0.idx, ir0offset + ((az0.idx+1) % Hrtf->mElev[elev0.idx].azCount), ir1offset + az1.idx, ir1offset + ((az1.idx+1) % Hrtf->mElev[elev1_idx].azCount) }; /* The largest blend factor serves as the closest HRIR. */ const size_t irOffset{idx[(elev0.blend >= 0.5f)*2 + (az1.blend >= 0.5f)]}; ImpulseResponse res{Hrtf->mCoeffs[irOffset], Hrtf->mDelays[irOffset][0], Hrtf->mDelays[irOffset][1]}; min_delay = std::min(min_delay, std::min(res.ldelay, res.rdelay)); max_delay = std::max(max_delay, std::max(res.ldelay, res.rdelay)); return res; }; std::transform(AmbiPoints.begin(), AmbiPoints.end(), std::back_inserter(impres), calc_res); auto hrir_delay_round = [](const uint d) noexcept -> uint { return (d+HrirDelayFracHalf) >> HrirDelayFracBits; }; TRACE("Min delay: {:.2f}, max delay: {:.2f}, FIR length: {}", min_delay/double{HrirDelayFracOne}, max_delay/double{HrirDelayFracOne}, irSize); auto tmpres = std::vector>(mChannels.size()); max_delay = 0; auto matrixline = AmbiMatrix.cbegin(); for(auto &impulse : impres) { const ConstHrirSpan hrir{impulse.hrir}; const uint base_delay{perHrirMin ? std::min(impulse.ldelay, impulse.rdelay) : min_delay}; const uint ldelay{hrir_delay_round(impulse.ldelay - base_delay)}; const uint rdelay{hrir_delay_round(impulse.rdelay - base_delay)}; max_delay = std::max(max_delay, std::max(impulse.ldelay, impulse.rdelay) - base_delay); auto gains = matrixline->cbegin(); ++matrixline; for(auto &result : tmpres) { const double mult{*(gains++)}; const size_t numirs{HrirLength - std::max(ldelay, rdelay)}; size_t lidx{ldelay}, ridx{rdelay}; for(size_t j{0};j < numirs;++j) { result[lidx++][0] += hrir[j][0] * mult; result[ridx++][1] += hrir[j][1] * mult; } } } impres.clear(); auto output = mChannels.begin(); for(auto &result : tmpres) { auto cast_array2 = [](const double2 &in) noexcept -> float2 { return float2{{static_cast(in[0]), static_cast(in[1])}}; }; std::transform(result.cbegin(), result.cend(), output->mCoeffs.begin(), cast_array2); ++output; } tmpres.clear(); const uint max_length{std::min(hrir_delay_round(max_delay) + irSize, HrirLength)}; TRACE("New max delay: {:.2f}, FIR length: {}", max_delay/double{HrirDelayFracOne}, max_length); mIrSize = max_length; } namespace { std::unique_ptr CreateHrtfStore(uint rate, uint8_t irSize, const al::span fields, const al::span elevs, const HrirArray *coeffs, const ubyte2 *delays) { static_assert(alignof(HrtfStore::Field) <= alignof(HrtfStore)); static_assert(alignof(HrtfStore::Elevation) <= alignof(HrtfStore)); static_assert(16 <= alignof(HrtfStore)); if(rate > MaxSampleRate) throw std::runtime_error{"Sample rate is too large (max: "+std::to_string(MaxSampleRate)+"hz)"}; const size_t irCount{size_t{elevs.back().azCount} + elevs.back().irOffset}; size_t total{sizeof(HrtfStore)}; total = RoundUp(total, alignof(HrtfStore::Field)); /* Align for field infos */ total += sizeof(std::declval().mFields[0])*fields.size(); total = RoundUp(total, alignof(HrtfStore::Elevation)); /* Align for elevation infos */ total += sizeof(std::declval().mElev[0])*elevs.size(); total = RoundUp(total, 16); /* Align for coefficients using SIMD */ total += sizeof(std::declval().mCoeffs[0])*irCount; total += sizeof(std::declval().mDelays[0])*irCount; static constexpr auto AlignVal = std::align_val_t{alignof(HrtfStore)}; std::unique_ptr Hrtf{::new(::operator new[](total, AlignVal)) HrtfStore{}}; Hrtf->mRef.store(1u, std::memory_order_relaxed); Hrtf->mSampleRate = rate & 0xff'ff'ff; Hrtf->mIrSize = irSize; /* Set up pointers to storage following the main HRTF struct. */ auto storage = al::span{reinterpret_cast(Hrtf.get()), total}; auto base = storage.begin(); ptrdiff_t offset{sizeof(HrtfStore)}; offset = RoundUp(offset, alignof(HrtfStore::Field)); /* Align for field infos */ auto field_ = al::span{reinterpret_cast(al::to_address(base + offset)), fields.size()}; offset += ptrdiff_t(sizeof(field_[0])*fields.size()); offset = RoundUp(offset, alignof(HrtfStore::Elevation)); /* Align for elevation infos */ auto elev_ = al::span{reinterpret_cast(al::to_address(base + offset)), elevs.size()}; offset += ptrdiff_t(sizeof(elev_[0])*elevs.size()); offset = RoundUp(offset, 16); /* Align for coefficients using SIMD */ auto coeffs_ = al::span{reinterpret_cast(al::to_address(base + offset)), irCount}; offset += ptrdiff_t(sizeof(coeffs_[0])*irCount); auto delays_ = al::span{reinterpret_cast(al::to_address(base + offset)), irCount}; offset += ptrdiff_t(sizeof(delays_[0])*irCount); if(size_t(offset) != total) throw std::runtime_error{"HrtfStore allocation size mismatch"}; /* Copy input data to storage. */ std::uninitialized_copy(fields.cbegin(), fields.cend(), field_.begin()); std::uninitialized_copy(elevs.cbegin(), elevs.cend(), elev_.begin()); std::uninitialized_copy_n(coeffs, irCount, coeffs_.begin()); std::uninitialized_copy_n(delays, irCount, delays_.begin()); /* Finally, assign the storage pointers. */ Hrtf->mFields = field_; Hrtf->mElev = elev_; Hrtf->mCoeffs = coeffs_; Hrtf->mDelays = delays_; return Hrtf; } void MirrorLeftHrirs(const al::span elevs, al::span coeffs, al::span delays) { for(const auto &elev : elevs) { const ushort evoffset{elev.irOffset}; const ushort azcount{elev.azCount}; for(size_t j{0};j < azcount;j++) { const size_t lidx{evoffset + j}; const size_t ridx{evoffset + ((azcount-j) % azcount)}; const size_t irSize{coeffs[ridx].size()}; for(size_t k{0};k < irSize;k++) coeffs[ridx][k][1] = coeffs[lidx][k][0]; delays[ridx][1] = delays[lidx][0]; } } } template constexpr std::enable_if_t::value && num_bits < sizeof(T)*8, T> fixsign(T value) noexcept { constexpr auto signbit = static_cast(1u << (num_bits-1)); return static_cast((value^signbit) - signbit); } template constexpr std::enable_if_t::value || num_bits == sizeof(T)*8, T> fixsign(T value) noexcept { return value; } template inline std::enable_if_t readle(std::istream &data) { static_assert((num_bits&7) == 0, "num_bits must be a multiple of 8"); static_assert(num_bits <= sizeof(T)*8, "num_bits is too large for the type"); alignas(T) std::array ret{}; if(!data.read(ret.data(), num_bits/8)) return static_cast(EOF); return fixsign(al::bit_cast(ret)); } template inline std::enable_if_t readle(std::istream &data) { static_assert((num_bits&7) == 0, "num_bits must be a multiple of 8"); static_assert(num_bits <= sizeof(T)*8, "num_bits is too large for the type"); alignas(T) std::array ret{}; if(!data.read(ret.data(), num_bits/8)) return static_cast(EOF); std::reverse(ret.begin(), ret.end()); return fixsign(al::bit_cast(ret)); } template<> inline uint8_t readle(std::istream &data) { return static_cast(data.get()); } std::unique_ptr LoadHrtf00(std::istream &data) { uint rate{readle(data)}; ushort irCount{readle(data)}; ushort irSize{readle(data)}; ubyte evCount{readle(data)}; if(!data || data.eof()) throw std::runtime_error{"Premature end of file"}; if(irSize < MinIrLength || irSize > HrirLength) { ERR("Unsupported HRIR size, irSize={} ({} to {})", irSize, MinIrLength, HrirLength); return nullptr; } if(evCount < MinEvCount || evCount > MaxEvCount) { ERR("Unsupported elevation count: evCount={} ({} to {})", evCount, MinEvCount, MaxEvCount); return nullptr; } auto elevs = std::vector(evCount); for(auto &elev : elevs) elev.irOffset = readle(data); if(!data || data.eof()) throw std::runtime_error{"Premature end of file"}; for(size_t i{1};i < evCount;i++) { if(elevs[i].irOffset <= elevs[i-1].irOffset) { ERR("Invalid evOffset: evOffset[{}]={} (last={})", i, elevs[i].irOffset, elevs[i-1].irOffset); return nullptr; } } if(irCount <= elevs.back().irOffset) { ERR("Invalid evOffset: evOffset[{}]={} (irCount={})", elevs.size()-1, elevs.back().irOffset, irCount); return nullptr; } for(size_t i{1};i < evCount;i++) { elevs[i-1].azCount = static_cast(elevs[i].irOffset - elevs[i-1].irOffset); if(elevs[i-1].azCount < MinAzCount || elevs[i-1].azCount > MaxAzCount) { ERR("Unsupported azimuth count: azCount[{}]={} ({} to {})", i-1, elevs[i-1].azCount, MinAzCount, MaxAzCount); return nullptr; } } elevs.back().azCount = static_cast(irCount - elevs.back().irOffset); if(elevs.back().azCount < MinAzCount || elevs.back().azCount > MaxAzCount) { ERR("Unsupported azimuth count: azCount[{}]={} ({} to {})", elevs.size()-1, elevs.back().azCount, MinAzCount, MaxAzCount); return nullptr; } auto coeffs = std::vector(irCount, HrirArray{}); auto delays = std::vector(irCount); for(auto &hrir : coeffs) { for(auto &val : al::span{hrir}.first(irSize)) val[0] = float(readle(data)) / 32768.0f; } for(auto &val : delays) val[0] = readle(data); if(!data || data.eof()) throw std::runtime_error{"Premature end of file"}; for(size_t i{0};i < irCount;i++) { if(delays[i][0] > MaxHrirDelay) { ERR("Invalid delays[{}]: {} ({})", i, delays[i][0], MaxHrirDelay); return nullptr; } delays[i][0] <<= HrirDelayFracBits; } /* Mirror the left ear responses to the right ear. */ MirrorLeftHrirs(elevs, coeffs, delays); const std::array field{HrtfStore::Field{0.0f, evCount}}; return CreateHrtfStore(rate, static_cast(irSize), field, elevs, coeffs.data(), delays.data()); } std::unique_ptr LoadHrtf01(std::istream &data) { uint rate{readle(data)}; uint8_t irSize{readle(data)}; ubyte evCount{readle(data)}; if(!data || data.eof()) throw std::runtime_error{"Premature end of file"}; if(irSize < MinIrLength || irSize > HrirLength) { ERR("Unsupported HRIR size, irSize={} ({} to {})", irSize, MinIrLength, HrirLength); return nullptr; } if(evCount < MinEvCount || evCount > MaxEvCount) { ERR("Unsupported elevation count: evCount={} ({} to {})", evCount, MinEvCount, MaxEvCount); return nullptr; } auto elevs = std::vector(evCount); for(auto &elev : elevs) elev.azCount = readle(data); if(!data || data.eof()) throw std::runtime_error{"Premature end of file"}; for(size_t i{0};i < evCount;++i) { if(elevs[i].azCount < MinAzCount || elevs[i].azCount > MaxAzCount) { ERR("Unsupported azimuth count: azCount[{}]={} ({} to {})", i, elevs[i].azCount, MinAzCount, MaxAzCount); return nullptr; } } elevs[0].irOffset = 0; for(size_t i{1};i < evCount;i++) elevs[i].irOffset = static_cast(elevs[i-1].irOffset + elevs[i-1].azCount); const ushort irCount{static_cast(elevs.back().irOffset + elevs.back().azCount)}; auto coeffs = std::vector(irCount, HrirArray{}); auto delays = std::vector(irCount); for(auto &hrir : coeffs) { for(auto &val : al::span{hrir}.first(irSize)) val[0] = float(readle(data)) / 32768.0f; } for(auto &val : delays) val[0] = readle(data); if(!data || data.eof()) throw std::runtime_error{"Premature end of file"}; for(size_t i{0};i < irCount;i++) { if(delays[i][0] > MaxHrirDelay) { ERR("Invalid delays[{}]: {} ({})", i, delays[i][0], MaxHrirDelay); return nullptr; } delays[i][0] <<= HrirDelayFracBits; } /* Mirror the left ear responses to the right ear. */ MirrorLeftHrirs(elevs, coeffs, delays); const std::array field{HrtfStore::Field{0.0f, evCount}}; return CreateHrtfStore(rate, irSize, field, elevs, coeffs.data(), delays.data()); } std::unique_ptr LoadHrtf02(std::istream &data) { static constexpr ubyte SampleType_S16{0}; static constexpr ubyte SampleType_S24{1}; static constexpr ubyte ChanType_LeftOnly{0}; static constexpr ubyte ChanType_LeftRight{1}; uint rate{readle(data)}; ubyte sampleType{readle(data)}; ubyte channelType{readle(data)}; uint8_t irSize{readle(data)}; ubyte fdCount{readle(data)}; if(!data || data.eof()) throw std::runtime_error{"Premature end of file"}; if(sampleType > SampleType_S24) { ERR("Unsupported sample type: {}", sampleType); return nullptr; } if(channelType > ChanType_LeftRight) { ERR("Unsupported channel type: {}", channelType); return nullptr; } if(irSize < MinIrLength || irSize > HrirLength) { ERR("Unsupported HRIR size, irSize={} ({} to {})", irSize, MinIrLength, HrirLength); return nullptr; } if(fdCount < 1 || fdCount > MaxFdCount) { ERR("Unsupported number of field-depths: fdCount={} ({} to {})", fdCount, MinFdCount, MaxFdCount); return nullptr; } auto fields = std::vector(fdCount); auto elevs = std::vector{}; for(size_t f{0};f < fdCount;f++) { const ushort distance{readle(data)}; const ubyte evCount{readle(data)}; if(!data || data.eof()) throw std::runtime_error{"Premature end of file"}; if(distance < MinFdDistance || distance > MaxFdDistance) { ERR("Unsupported field distance[{}]={} ({} to {} millimeters)", f, distance, MinFdDistance, MaxFdDistance); return nullptr; } if(evCount < MinEvCount || evCount > MaxEvCount) { ERR("Unsupported elevation count: evCount[{}]={} ({} to {})", f, evCount, MinEvCount, MaxEvCount); return nullptr; } fields[f].distance = float(distance) / 1000.0f; fields[f].evCount = evCount; if(f > 0 && fields[f].distance <= fields[f-1].distance) { ERR("Field distance[{}] is not after previous ({:f} > {:f})", f, fields[f].distance, fields[f-1].distance); return nullptr; } const size_t ebase{elevs.size()}; elevs.resize(ebase + evCount); for(auto &elev : al::span{elevs}.subspan(ebase, evCount)) elev.azCount = readle(data); if(!data || data.eof()) throw std::runtime_error{"Premature end of file"}; for(size_t e{0};e < evCount;e++) { if(elevs[ebase+e].azCount < MinAzCount || elevs[ebase+e].azCount > MaxAzCount) { ERR("Unsupported azimuth count: azCount[{}][{}]={} ({} to {})", f, e, elevs[ebase+e].azCount, MinAzCount, MaxAzCount); return nullptr; } } } elevs[0].irOffset = 0; std::partial_sum(elevs.cbegin(), elevs.cend(), elevs.begin(), [](const HrtfStore::Elevation &last, const HrtfStore::Elevation &cur) -> HrtfStore::Elevation { return HrtfStore::Elevation{cur.azCount, static_cast(last.azCount + last.irOffset)}; }); const auto irTotal = static_cast(elevs.back().azCount + elevs.back().irOffset); auto coeffs = std::vector(irTotal, HrirArray{}); auto delays = std::vector(irTotal); if(channelType == ChanType_LeftOnly) { if(sampleType == SampleType_S16) { for(auto &hrir : coeffs) { for(auto &val : al::span{hrir}.first(irSize)) val[0] = float(readle(data)) / 32768.0f; } } else if(sampleType == SampleType_S24) { for(auto &hrir : coeffs) { for(auto &val : al::span{hrir}.first(irSize)) val[0] = static_cast(readle(data)) / 8388608.0f; } } for(auto &val : delays) val[0] = readle(data); if(!data || data.eof()) throw std::runtime_error{"Premature end of file"}; for(size_t i{0};i < irTotal;++i) { if(delays[i][0] > MaxHrirDelay) { ERR("Invalid delays[{}][0]: {} ({})", i, delays[i][0], MaxHrirDelay); return nullptr; } delays[i][0] <<= HrirDelayFracBits; } /* Mirror the left ear responses to the right ear. */ MirrorLeftHrirs(elevs, coeffs, delays); } else if(channelType == ChanType_LeftRight) { if(sampleType == SampleType_S16) { for(auto &hrir : coeffs) { for(auto &val : al::span{hrir}.first(irSize)) { val[0] = float(readle(data)) / 32768.0f; val[1] = float(readle(data)) / 32768.0f; } } } else if(sampleType == SampleType_S24) { for(auto &hrir : coeffs) { for(auto &val : al::span{hrir}.first(irSize)) { val[0] = static_cast(readle(data)) / 8388608.0f; val[1] = static_cast(readle(data)) / 8388608.0f; } } } for(auto &val : delays) { val[0] = readle(data); val[1] = readle(data); } if(!data || data.eof()) throw std::runtime_error{"Premature end of file"}; for(size_t i{0};i < irTotal;++i) { if(delays[i][0] > MaxHrirDelay) { ERR("Invalid delays[{}][0]: {} ({})", i, delays[i][0], MaxHrirDelay); return nullptr; } if(delays[i][1] > MaxHrirDelay) { ERR("Invalid delays[{}][1]: {} ({})", i, delays[i][1], MaxHrirDelay); return nullptr; } delays[i][0] <<= HrirDelayFracBits; delays[i][1] <<= HrirDelayFracBits; } } if(fdCount > 1) { auto fields_ = std::vector(fields.size()); auto elevs_ = std::vector(elevs.size()); auto coeffs_ = std::vector(coeffs.size()); auto delays_ = std::vector(delays.size()); /* Simple reverse for the per-field elements. */ std::reverse_copy(fields.cbegin(), fields.cend(), fields_.begin()); /* Each field has a group of elevations, which each have an azimuth * count. Reverse the order of the groups, keeping the relative order * of per-group azimuth counts. */ auto elevs_end = elevs_.end(); auto copy_azs = [&elevs,&elevs_end](const ptrdiff_t ebase, const HrtfStore::Field &field) -> ptrdiff_t { auto elevs_src = elevs.begin()+ebase; elevs_end = std::copy_backward(elevs_src, elevs_src+field.evCount, elevs_end); return ebase + field.evCount; }; std::ignore = std::accumulate(fields.cbegin(), fields.cend(), ptrdiff_t{0}, copy_azs); assert(elevs_.begin() == elevs_end); /* Reestablish the IR offset for each elevation index, given the new * ordering of elevations. */ elevs_[0].irOffset = 0; std::partial_sum(elevs_.cbegin(), elevs_.cend(), elevs_.begin(), [](const HrtfStore::Elevation &last, const HrtfStore::Elevation &cur) -> HrtfStore::Elevation { return HrtfStore::Elevation{cur.azCount, static_cast(last.azCount + last.irOffset)}; }); /* Reverse the order of each field's group of IRs. */ auto coeffs_end = coeffs_.end(); auto delays_end = delays_.end(); auto copy_irs = [&elevs,&coeffs,&delays,&coeffs_end,&delays_end]( const ptrdiff_t ebase, const HrtfStore::Field &field) -> ptrdiff_t { auto accum_az = [](const ptrdiff_t count, const HrtfStore::Elevation &elev) noexcept -> ptrdiff_t { return count + elev.azCount; }; const auto elev_mid = elevs.cbegin() + ebase; const auto abase = std::accumulate(elevs.cbegin(), elev_mid, ptrdiff_t{0}, accum_az); const auto num_azs = std::accumulate(elev_mid, elev_mid + field.evCount, ptrdiff_t{0}, accum_az); coeffs_end = std::copy_backward(coeffs.cbegin() + abase, coeffs.cbegin() + (abase+num_azs), coeffs_end); delays_end = std::copy_backward(delays.cbegin() + abase, delays.cbegin() + (abase+num_azs), delays_end); return ebase + field.evCount; }; std::ignore = std::accumulate(fields.cbegin(), fields.cend(), ptrdiff_t{0}, copy_irs); assert(coeffs_.begin() == coeffs_end); assert(delays_.begin() == delays_end); fields = std::move(fields_); elevs = std::move(elevs_); coeffs = std::move(coeffs_); delays = std::move(delays_); } return CreateHrtfStore(rate, irSize, fields, elevs, coeffs.data(), delays.data()); } std::unique_ptr LoadHrtf03(std::istream &data) { static constexpr ubyte ChanType_LeftOnly{0}; static constexpr ubyte ChanType_LeftRight{1}; uint rate{readle(data)}; ubyte channelType{readle(data)}; uint8_t irSize{readle(data)}; ubyte fdCount{readle(data)}; if(!data || data.eof()) throw std::runtime_error{"Premature end of file"}; if(channelType > ChanType_LeftRight) { ERR("Unsupported channel type: {}", channelType); return nullptr; } if(irSize < MinIrLength || irSize > HrirLength) { ERR("Unsupported HRIR size, irSize={} ({} to {})", irSize, MinIrLength, HrirLength); return nullptr; } if(fdCount < 1 || fdCount > MaxFdCount) { ERR("Unsupported number of field-depths: fdCount={} ({} to {})", fdCount, MinFdCount, MaxFdCount); return nullptr; } auto fields = std::vector(fdCount); auto elevs = std::vector{}; for(size_t f{0};f < fdCount;f++) { const ushort distance{readle(data)}; const ubyte evCount{readle(data)}; if(!data || data.eof()) throw std::runtime_error{"Premature end of file"}; if(distance < MinFdDistance || distance > MaxFdDistance) { ERR("Unsupported field distance[{}]={} ({} to {} millimeters)", f, distance, MinFdDistance, MaxFdDistance); return nullptr; } if(evCount < MinEvCount || evCount > MaxEvCount) { ERR("Unsupported elevation count: evCount[{}]={} ({} to {})", f, evCount, MinEvCount, MaxEvCount); return nullptr; } fields[f].distance = float(distance) / 1000.0f; fields[f].evCount = evCount; if(f > 0 && fields[f].distance > fields[f-1].distance) { ERR("Field distance[{}] is not before previous ({:f} <= {:f})", f, fields[f].distance, fields[f-1].distance); return nullptr; } const size_t ebase{elevs.size()}; elevs.resize(ebase + evCount); for(auto &elev : al::span{elevs}.subspan(ebase, evCount)) elev.azCount = readle(data); if(!data || data.eof()) throw std::runtime_error{"Premature end of file"}; for(size_t e{0};e < evCount;e++) { if(elevs[ebase+e].azCount < MinAzCount || elevs[ebase+e].azCount > MaxAzCount) { ERR("Unsupported azimuth count: azCount[{}][{}]={} ({} to {})", f, e, elevs[ebase+e].azCount, MinAzCount, MaxAzCount); return nullptr; } } } elevs[0].irOffset = 0; std::partial_sum(elevs.cbegin(), elevs.cend(), elevs.begin(), [](const HrtfStore::Elevation &last, const HrtfStore::Elevation &cur) -> HrtfStore::Elevation { return HrtfStore::Elevation{cur.azCount, static_cast(last.azCount + last.irOffset)}; }); const auto irTotal = static_cast(elevs.back().azCount + elevs.back().irOffset); auto coeffs = std::vector(irTotal, HrirArray{}); auto delays = std::vector(irTotal); if(channelType == ChanType_LeftOnly) { for(auto &hrir : coeffs) { for(auto &val : al::span{hrir}.first(irSize)) val[0] = static_cast(readle(data)) / 8388608.0f; } for(auto &val : delays) val[0] = readle(data); if(!data || data.eof()) throw std::runtime_error{"Premature end of file"}; for(size_t i{0};i < irTotal;++i) { if(delays[i][0] > MaxHrirDelay<(readle(data)) / 8388608.0f; val[1] = static_cast(readle(data)) / 8388608.0f; } } for(auto &val : delays) { val[0] = readle(data); val[1] = readle(data); } if(!data || data.eof()) throw std::runtime_error{"Premature end of file"}; for(size_t i{0};i < irTotal;++i) { if(delays[i][0] > MaxHrirDelay< MaxHrirDelay< bool { return name == entry.mDispName; }; auto &enum_names = EnumeratedHrtfs; return std::find_if(enum_names.cbegin(), enum_names.cend(), match_name) != enum_names.cend(); } void AddFileEntry(const std::string_view filename) { /* Check if this file has already been enumerated. */ auto enum_iter = std::find_if(EnumeratedHrtfs.cbegin(), EnumeratedHrtfs.cend(), [filename](const HrtfEntry &entry) -> bool { return entry.mFilename == filename; }); if(enum_iter != EnumeratedHrtfs.cend()) { TRACE("Skipping duplicate file entry {}", filename); return; } /* TODO: Get a human-readable name from the HRTF data (possibly coming in a * format update). */ const auto namepos = std::max(filename.rfind('/')+1, filename.rfind('\\')+1); const auto extpos = filename.substr(namepos).rfind('.'); const auto basename = (extpos == std::string::npos) ? filename.substr(namepos) : filename.substr(namepos, extpos); auto count = 1; auto newname = std::string{basename}; while(checkName(newname)) newname = fmt::format("{} #{}", basename, ++count); const auto &entry = EnumeratedHrtfs.emplace_back(newname, filename); TRACE("Adding file entry \"{}\"", entry.mFilename); } /* Unfortunate that we have to duplicate AddFileEntry to take a memory buffer * for input instead of opening the given filename. */ void AddBuiltInEntry(const std::string_view dispname, uint residx) { auto filename = fmt::format("!{}_{}", residx, dispname); auto enum_iter = std::find_if(EnumeratedHrtfs.cbegin(), EnumeratedHrtfs.cend(), [&filename](const HrtfEntry &entry) -> bool { return entry.mFilename == filename; }); if(enum_iter != EnumeratedHrtfs.cend()) { TRACE("Skipping duplicate file entry {}", filename); return; } /* TODO: Get a human-readable name from the HRTF data (possibly coming in a * format update). */ auto count = 1; auto newname = std::string{dispname}; while(checkName(newname)) newname = fmt::format("{} #{}", dispname, ++count); const auto &entry = EnumeratedHrtfs.emplace_back(std::move(newname), std::move(filename)); TRACE("Adding built-in entry \"{}\"", entry.mFilename); } #define IDR_DEFAULT_HRTF_MHR 1 #ifndef ALSOFT_EMBED_HRTF_DATA al::span GetResource(int /*name*/) { return {}; } #else /* NOLINTNEXTLINE(*-avoid-c-arrays) */ constexpr unsigned char hrtf_default[]{ #include "default_hrtf.txt" }; al::span GetResource(int name) { if(name == IDR_DEFAULT_HRTF_MHR) return {reinterpret_cast(hrtf_default), sizeof(hrtf_default)}; return {}; } #endif } // namespace std::vector EnumerateHrtf(std::optional pathopt) { std::lock_guard enumlock{EnumeratedHrtfLock}; EnumeratedHrtfs.clear(); for(const auto &fname : SearchDataFiles(".mhr"sv)) AddFileEntry(fname); bool usedefaults{true}; if(pathopt) { std::string_view pathlist{*pathopt}; while(!pathlist.empty()) { while(!pathlist.empty() && (std::isspace(pathlist.front()) || pathlist.front() == ',')) pathlist.remove_prefix(1); if(pathlist.empty()) break; auto endpos = std::min(pathlist.find(','), pathlist.size()); auto entry = pathlist.substr(0, endpos); if(endpos < pathlist.size()) pathlist.remove_prefix(++endpos); else { pathlist.remove_prefix(endpos); usedefaults = false; } while(!entry.empty() && std::isspace(entry.back())) entry.remove_suffix(1); if(!entry.empty()) { for(const auto &fname : SearchDataFiles(".mhr"sv, entry)) AddFileEntry(fname); } } } if(usedefaults) { for(const auto &fname : SearchDataFiles(".mhr"sv, "openal/hrtf"sv)) AddFileEntry(fname); if(!GetResource(IDR_DEFAULT_HRTF_MHR).empty()) AddBuiltInEntry("Built-In HRTF", IDR_DEFAULT_HRTF_MHR); } std::vector list; list.reserve(EnumeratedHrtfs.size()); for(auto &entry : EnumeratedHrtfs) list.emplace_back(entry.mDispName); return list; } HrtfStorePtr GetLoadedHrtf(const std::string_view name, const uint devrate) try { if(devrate > MaxSampleRate) { WARN("Device sample rate too large for HRTF ({}hz > {}hz)", devrate, MaxSampleRate); return nullptr; } std::lock_guard enumlock{EnumeratedHrtfLock}; auto entry_iter = std::find_if(EnumeratedHrtfs.cbegin(), EnumeratedHrtfs.cend(), [name](const HrtfEntry &entry) -> bool { return entry.mDispName == name; }); if(entry_iter == EnumeratedHrtfs.cend()) return nullptr; const std::string &fname = entry_iter->mFilename; std::lock_guard loadlock{LoadedHrtfLock}; auto hrtf_lt_fname = [devrate](LoadedHrtf &hrtf, const std::string_view filename) -> bool { return hrtf.mSampleRate < devrate || (hrtf.mSampleRate == devrate && hrtf.mFilename < filename); }; auto handle = std::lower_bound(LoadedHrtfs.begin(), LoadedHrtfs.end(), fname, hrtf_lt_fname); if(handle != LoadedHrtfs.end() && handle->mSampleRate == devrate && handle->mFilename == fname) { if(HrtfStore *hrtf{handle->mEntry.get()}) { assert(hrtf->mSampleRate == devrate); hrtf->add_ref(); return HrtfStorePtr{hrtf}; } } std::unique_ptr stream; int residx{}; char ch{}; /* NOLINTNEXTLINE(cert-err34-c,cppcoreguidelines-pro-type-vararg) */ if(sscanf(fname.c_str(), "!%d%c", &residx, &ch) == 2 && ch == '_') { TRACE("Loading {}...", fname); al::span res{GetResource(residx)}; if(res.empty()) { ERR("Could not get resource {}, {}", residx, name); return nullptr; } /* NOLINTNEXTLINE(*-const-cast) */ stream = std::make_unique(al::span{const_cast(res.data()), res.size()}); } else { TRACE("Loading {}...", fname); auto fstr = std::make_unique(fs::u8path(fname), std::ios::binary); if(!fstr->is_open()) { ERR("Could not open {}", fname); return nullptr; } stream = std::move(fstr); } auto hrtf = std::unique_ptr{}; auto magic = std::array{}; stream->read(magic.data(), magic.size()); if(stream->gcount() < std::streamsize{magic.size()}) ERR("{} data is too short ({} bytes)", name, stream->gcount()); else if(GetMarker03Name() == std::string_view{magic.data(), magic.size()}) { TRACE("Detected data set format v3"); hrtf = LoadHrtf03(*stream); } else if(GetMarker02Name() == std::string_view{magic.data(), magic.size()}) { TRACE("Detected data set format v2"); hrtf = LoadHrtf02(*stream); } else if(GetMarker01Name() == std::string_view{magic.data(), magic.size()}) { TRACE("Detected data set format v1"); hrtf = LoadHrtf01(*stream); } else if(GetMarker00Name() == std::string_view{magic.data(), magic.size()}) { TRACE("Detected data set format v0"); hrtf = LoadHrtf00(*stream); } else ERR("Invalid header in {}: \"{}\"", name, std::string_view{magic.data(), magic.size()}); stream.reset(); if(!hrtf) return nullptr; if(hrtf->mSampleRate != devrate) { TRACE("Resampling HRTF {} ({}hz -> {}hz)", name, uint{hrtf->mSampleRate}, devrate); /* Calculate the last elevation's index and get the total IR count. */ const size_t lastEv{std::accumulate(hrtf->mFields.begin(), hrtf->mFields.end(), 0_uz, [](const size_t curval, const HrtfStore::Field &field) noexcept -> size_t { return curval + field.evCount; } ) - 1}; const size_t irCount{size_t{hrtf->mElev[lastEv].irOffset} + hrtf->mElev[lastEv].azCount}; /* Resample all the IRs. */ std::array,2> inout{}; PPhaseResampler rs; rs.init(hrtf->mSampleRate, devrate); for(size_t i{0};i < irCount;++i) { /* NOLINTNEXTLINE(*-const-cast) */ auto coeffs = al::span{const_cast(hrtf->mCoeffs[i])}; for(size_t j{0};j < 2;++j) { std::transform(coeffs.cbegin(), coeffs.cend(), inout[0].begin(), [j](const float2 &in) noexcept -> double { return in[j]; }); rs.process(inout[0], inout[1]); for(size_t k{0};k < HrirLength;++k) coeffs[k][j] = static_cast(inout[1][k]); } } rs = {}; /* Scale the delays for the new sample rate. */ float max_delay{0.0f}; auto new_delays = std::vector(irCount); const float rate_scale{static_cast(devrate)/static_cast(hrtf->mSampleRate)}; for(size_t i{0};i < irCount;++i) { for(size_t j{0};j < 2;++j) { const float new_delay{std::round(float(hrtf->mDelays[i][j]) * rate_scale) / float{HrirDelayFracOne}}; max_delay = std::max(max_delay, new_delay); new_delays[i][j] = new_delay; } } /* If the new delays exceed the max, scale it down to fit (essentially * shrinking the head radius; not ideal but better than a per-delay * clamp). */ float delay_scale{HrirDelayFracOne}; if(max_delay > MaxHrirDelay) { WARN("Resampled delay exceeds max ({:.2f} > {})", max_delay, MaxHrirDelay); delay_scale *= float{MaxHrirDelay} / max_delay; } for(size_t i{0};i < irCount;++i) { /* NOLINTNEXTLINE(*-const-cast) */ auto delays = al::span{const_cast(hrtf->mDelays[i])}; std::transform(new_delays[i].cbegin(), new_delays[i].cend(), delays.begin(), [delay_scale](const float delay) { return static_cast(float2int(delay*delay_scale + 0.5f)); }); } /* Scale the IR size for the new sample rate and update the stored * sample rate. */ const float newIrSize{std::round(static_cast(hrtf->mIrSize) * rate_scale)}; hrtf->mIrSize = static_cast(std::min(float{HrirLength}, newIrSize)); hrtf->mSampleRate = devrate & 0xff'ff'ff; } handle = LoadedHrtfs.emplace(handle, fname, devrate, std::move(hrtf)); TRACE("Loaded HRTF {} for sample rate {}hz, {}-sample filter", name, uint{handle->mEntry->mSampleRate}, uint{handle->mEntry->mIrSize}); return HrtfStorePtr{handle->mEntry.get()}; } catch(std::exception& e) { ERR("Failed to load {}: {}", name, e.what()); return nullptr; } void HrtfStore::add_ref() { auto ref = IncrementRef(mRef); TRACE("HrtfStore {} increasing refcount to {}", decltype(std::declval()){this}, ref); } void HrtfStore::dec_ref() { auto ref = DecrementRef(mRef); TRACE("HrtfStore {} decreasing refcount to {}", decltype(std::declval()){this}, ref); if(ref == 0) { std::lock_guard loadlock{LoadedHrtfLock}; /* Go through and remove all unused HRTFs. */ auto remove_unused = [](LoadedHrtf &hrtf) -> bool { HrtfStore *entry{hrtf.mEntry.get()}; if(entry && entry->mRef.load() == 0) { TRACE("Unloading unused HRTF {}", hrtf.mFilename); hrtf.mEntry = nullptr; return true; } return false; }; auto iter = std::remove_if(LoadedHrtfs.begin(), LoadedHrtfs.end(), remove_unused); LoadedHrtfs.erase(iter, LoadedHrtfs.end()); } } openal-soft-1.24.2/core/hrtf.h000066400000000000000000000054461474041540300161060ustar00rootroot00000000000000#ifndef CORE_HRTF_H #define CORE_HRTF_H #include #include #include #include #include #include #include #include "almalloc.h" #include "alspan.h" #include "ambidefs.h" #include "bufferline.h" #include "flexarray.h" #include "intrusive_ptr.h" #include "mixer/hrtfdefs.h" struct alignas(16) HrtfStore { std::atomic mRef{}; uint mSampleRate : 24; uint mIrSize : 8; struct Field { float distance; ubyte evCount; }; /* NOTE: Fields are stored *backwards*. field[0] is the farthest field, and * field[fdCount-1] is the nearest. */ al::span mFields; struct Elevation { ushort azCount; ushort irOffset; }; al::span mElev; al::span mCoeffs; al::span mDelays; void getCoeffs(float elevation, float azimuth, float distance, float spread, const HrirSpan coeffs, const al::span delays) const; void add_ref(); void dec_ref(); void *operator new(size_t) = delete; void *operator new[](size_t) = delete; void operator delete[](void*) noexcept = delete; void operator delete(gsl::owner block, void*) noexcept { ::operator delete[](block, std::align_val_t{alignof(HrtfStore)}); } void operator delete(gsl::owner block) noexcept { ::operator delete[](block, std::align_val_t{alignof(HrtfStore)}); } }; using HrtfStorePtr = al::intrusive_ptr; struct EvRadians { float value; }; struct AzRadians { float value; }; struct AngularPoint { EvRadians Elev; AzRadians Azim; }; struct DirectHrtfState { std::array mTemp{}; /* HRTF filter state for dry buffer content */ uint mIrSize{0}; al::FlexArray mChannels; explicit DirectHrtfState(size_t numchans) : mChannels{numchans} { } /** * Produces HRTF filter coefficients for decoding B-Format, given a set of * virtual speaker positions, a matching decoding matrix, and per-order * high-frequency gains for the decoder. The calculated impulse responses * are ordered and scaled according to the matrix input. */ void build(const HrtfStore *Hrtf, const uint irSize, const bool perHrirMin, const al::span AmbiPoints, const al::span> AmbiMatrix, const float XOverFreq, const al::span AmbiOrderHFGain); static std::unique_ptr Create(size_t num_chans); DEF_FAM_NEWDEL(DirectHrtfState, mChannels) }; std::vector EnumerateHrtf(std::optional pathopt); HrtfStorePtr GetLoadedHrtf(const std::string_view name, const uint devrate); #endif /* CORE_HRTF_H */ openal-soft-1.24.2/core/logging.cpp000066400000000000000000000065611474041540300171230ustar00rootroot00000000000000 #include "config.h" #include "logging.h" #include #include #include #include #include #include #include #include #include #include "alstring.h" #include "strutils.h" #if defined(_WIN32) #define WIN32_LEAN_AND_MEAN #include #elif defined(__ANDROID__) #include #endif FILE *gLogFile{stderr}; #ifdef _DEBUG LogLevel gLogLevel{LogLevel::Warning}; #else LogLevel gLogLevel{LogLevel::Error}; #endif namespace { using namespace std::string_view_literals; enum class LogState : uint8_t { FirstRun, Ready, Disable }; std::mutex LogCallbackMutex; LogState gLogState{LogState::FirstRun}; LogCallbackFunc gLogCallback{}; void *gLogCallbackPtr{}; constexpr auto GetLevelCode(LogLevel level) noexcept -> std::optional { switch(level) { case LogLevel::Disable: break; case LogLevel::Error: return 'E'; case LogLevel::Warning: return 'W'; case LogLevel::Trace: return 'I'; } return std::nullopt; } } // namespace void al_set_log_callback(LogCallbackFunc callback, void *userptr) { auto cblock = std::lock_guard{LogCallbackMutex}; gLogCallback = callback; gLogCallbackPtr = callback ? userptr : nullptr; if(gLogState == LogState::FirstRun) { auto extlogopt = al::getenv("ALSOFT_DISABLE_LOG_CALLBACK"); if(!extlogopt || *extlogopt != "1") gLogState = LogState::Ready; else gLogState = LogState::Disable; } } void al_print_impl(LogLevel level, const fmt::string_view fmt, fmt::format_args args) { const auto msg = fmt::vformat(fmt, std::move(args)); auto prefix = "[ALSOFT] (--) "sv; switch(level) { case LogLevel::Disable: break; case LogLevel::Error: prefix = "[ALSOFT] (EE) "sv; break; case LogLevel::Warning: prefix = "[ALSOFT] (WW) "sv; break; case LogLevel::Trace: prefix = "[ALSOFT] (II) "sv; break; } if(gLogLevel >= level) { auto logfile = gLogFile; fmt::println(logfile, "{}{}", prefix, msg); fflush(logfile); } #if defined(_WIN32) && !defined(NDEBUG) /* OutputDebugStringW has no 'level' property to distinguish between * informational, warning, or error debug messages. So only print them for * non-Release builds. */ OutputDebugStringW(utf8_to_wstr(fmt::format("{}{}\n", prefix, msg)).c_str()); #elif defined(__ANDROID__) auto android_severity = [](LogLevel l) noexcept { switch(l) { case LogLevel::Trace: return ANDROID_LOG_DEBUG; case LogLevel::Warning: return ANDROID_LOG_WARN; case LogLevel::Error: return ANDROID_LOG_ERROR; /* Should not happen. */ case LogLevel::Disable: break; } return ANDROID_LOG_ERROR; }; /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) */ __android_log_print(android_severity(level), "openal", "%.*s%s", al::sizei(prefix), prefix.data(), msg.c_str()); #endif auto cblock = std::lock_guard{LogCallbackMutex}; if(gLogState != LogState::Disable) { if(auto logcode = GetLevelCode(level)) { if(gLogCallback) gLogCallback(gLogCallbackPtr, *logcode, msg.data(), al::sizei(msg)); else if(gLogState == LogState::FirstRun) gLogState = LogState::Disable; } } } openal-soft-1.24.2/core/logging.h000066400000000000000000000016411474041540300165620ustar00rootroot00000000000000#ifndef CORE_LOGGING_H #define CORE_LOGGING_H #include #include "fmt/core.h" #include "opthelpers.h" enum class LogLevel { Disable, Error, Warning, Trace }; DECL_HIDDEN extern LogLevel gLogLevel; DECL_HIDDEN extern FILE *gLogFile; using LogCallbackFunc = void(*)(void *userptr, char level, const char *message, int length) noexcept; void al_set_log_callback(LogCallbackFunc callback, void *userptr); void al_print_impl(LogLevel level, const fmt::string_view fmt, fmt::format_args args); template void al_print(LogLevel level, fmt::format_string fmt, Args&& ...args) noexcept try { al_print_impl(level, fmt, fmt::make_format_args(args...)); } catch(...) { } #define TRACE(...) al_print(LogLevel::Trace, __VA_ARGS__) #define WARN(...) al_print(LogLevel::Warning, __VA_ARGS__) #define ERR(...) al_print(LogLevel::Error, __VA_ARGS__) #endif /* CORE_LOGGING_H */ openal-soft-1.24.2/core/mastering.cpp000066400000000000000000000350721474041540300174650ustar00rootroot00000000000000 #include "config.h" #include "mastering.h" #include #include #include #include #include #include #include #include "alnumeric.h" #include "alspan.h" #include "opthelpers.h" /* These structures assume BufferLineSize is a power of 2. */ static_assert((BufferLineSize & (BufferLineSize-1)) == 0, "BufferLineSize is not a power of 2"); struct SIMDALIGN SlidingHold { alignas(16) FloatBufferLine mValues; std::array mExpiries; uint mLowerIndex; uint mUpperIndex; uint mLength; }; namespace { template constexpr auto assume_aligned_span(const al::span s) noexcept -> al::span { return al::span{al::assume_aligned(s.data()), s.size()}; } /* This sliding hold follows the input level with an instant attack and a * fixed duration hold before an instant release to the next highest level. * It is a sliding window maximum (descending maxima) implementation based on * Richard Harter's ascending minima algorithm available at: * * http://www.richardhartersworld.com/cri/2001/slidingmin.html */ float UpdateSlidingHold(SlidingHold *Hold, const uint i, const float in) { static constexpr uint mask{BufferLineSize - 1}; const uint length{Hold->mLength}; const al::span values{Hold->mValues}; const al::span expiries{Hold->mExpiries}; uint lowerIndex{Hold->mLowerIndex}; uint upperIndex{Hold->mUpperIndex}; if(i >= expiries[upperIndex]) upperIndex = (upperIndex + 1) & mask; if(in >= values[upperIndex]) { values[upperIndex] = in; expiries[upperIndex] = i + length; lowerIndex = upperIndex; } else { auto findLowerIndex = [&lowerIndex,in,values]() noexcept -> bool { do { if(!(in >= values[lowerIndex])) return true; } while(lowerIndex--); return false; }; while(!findLowerIndex()) lowerIndex = mask; lowerIndex = (lowerIndex + 1) & mask; values[lowerIndex] = in; expiries[lowerIndex] = i + length; } Hold->mLowerIndex = lowerIndex; Hold->mUpperIndex = upperIndex; return values[upperIndex]; } void ShiftSlidingHold(SlidingHold *Hold, const uint n) { auto exp_upper = Hold->mExpiries.begin() + Hold->mUpperIndex; if(Hold->mLowerIndex < Hold->mUpperIndex) { std::transform(exp_upper, Hold->mExpiries.end(), exp_upper, [n](const uint e) noexcept { return e - n; }); exp_upper = Hold->mExpiries.begin(); } const auto exp_lower = Hold->mExpiries.begin() + Hold->mLowerIndex; std::transform(exp_upper, exp_lower+1, exp_upper, [n](const uint e) noexcept { return e - n; }); } } // namespace /* Multichannel compression is linked via the absolute maximum of all * channels. */ void Compressor::linkChannels(const uint SamplesToDo, const al::span OutBuffer) { ASSUME(SamplesToDo > 0); ASSUME(SamplesToDo <= BufferLineSize); const auto sideChain = al::span{mSideChain}.subspan(mLookAhead, SamplesToDo); std::fill_n(sideChain.begin(), sideChain.size(), 0.0f); auto fill_max = [sideChain](const FloatBufferLine &input) -> void { const auto buffer = assume_aligned_span<16>(al::span{input}); auto max_abs = [](const float s0, const float s1) noexcept -> float { return std::max(s0, std::fabs(s1)); }; std::transform(sideChain.begin(), sideChain.end(), buffer.begin(), sideChain.begin(), max_abs); }; for(const FloatBufferLine &input : OutBuffer) fill_max(input); } /* This calculates the squared crest factor of the control signal for the * basic automation of the attack/release times. As suggested by the paper, * it uses an instantaneous squared peak detector and a squared RMS detector * both with 200ms release times. */ void Compressor::crestDetector(const uint SamplesToDo) { const float a_crest{mCrestCoeff}; float y2_peak{mLastPeakSq}; float y2_rms{mLastRmsSq}; ASSUME(SamplesToDo > 0); ASSUME(SamplesToDo <= BufferLineSize); auto calc_crest = [&y2_rms,&y2_peak,a_crest](const float x_abs) noexcept -> float { const float x2{std::clamp(x_abs*x_abs, 0.000001f, 1000000.0f)}; y2_peak = std::max(x2, lerpf(x2, y2_peak, a_crest)); y2_rms = lerpf(x2, y2_rms, a_crest); return y2_peak / y2_rms; }; const auto sideChain = al::span{mSideChain}.subspan(mLookAhead, SamplesToDo); std::transform(sideChain.cbegin(), sideChain.cend(), mCrestFactor.begin(), calc_crest); mLastPeakSq = y2_peak; mLastRmsSq = y2_rms; } /* The side-chain starts with a simple peak detector (based on the absolute * value of the incoming signal) and performs most of its operations in the * log domain. */ void Compressor::peakDetector(const uint SamplesToDo) { ASSUME(SamplesToDo > 0); ASSUME(SamplesToDo <= BufferLineSize); /* Clamp the minimum amplitude to near-zero and convert to logarithmic. */ const auto sideChain = al::span{mSideChain}.subspan(mLookAhead, SamplesToDo); std::transform(sideChain.cbegin(), sideChain.cend(), sideChain.begin(), [](float s) { return std::log(std::max(0.000001f, s)); }); } /* An optional hold can be used to extend the peak detector so it can more * solidly detect fast transients. This is best used when operating as a * limiter. */ void Compressor::peakHoldDetector(const uint SamplesToDo) { ASSUME(SamplesToDo > 0); ASSUME(SamplesToDo <= BufferLineSize); SlidingHold *hold{mHold.get()}; uint i{0}; auto detect_peak = [&i,hold](const float x_abs) -> float { const float x_G{std::log(std::max(0.000001f, x_abs))}; return UpdateSlidingHold(hold, i++, x_G); }; auto sideChain = al::span{mSideChain}.subspan(mLookAhead, SamplesToDo); std::transform(sideChain.cbegin(), sideChain.cend(), sideChain.begin(), detect_peak); ShiftSlidingHold(hold, SamplesToDo); } /* This is the heart of the feed-forward compressor. It operates in the log * domain (to better match human hearing) and can apply some basic automation * to knee width, attack/release times, make-up/post gain, and clipping * reduction. */ void Compressor::gainCompressor(const uint SamplesToDo) { const bool autoKnee{mAuto.Knee}; const bool autoAttack{mAuto.Attack}; const bool autoRelease{mAuto.Release}; const bool autoPostGain{mAuto.PostGain}; const bool autoDeclip{mAuto.Declip}; const float threshold{mThreshold}; const float slope{mSlope}; const float attack{mAttack}; const float release{mRelease}; const float c_est{mGainEstimate}; const float a_adp{mAdaptCoeff}; auto lookAhead = mSideChain.cbegin() + mLookAhead; auto crestFactor = mCrestFactor.cbegin(); float postGain{mPostGain}; float knee{mKnee}; float t_att{attack}; float t_rel{release - attack}; float a_att{std::exp(-1.0f / t_att)}; float a_rel{std::exp(-1.0f / t_rel)}; float y_1{mLastRelease}; float y_L{mLastAttack}; float c_dev{mLastGainDev}; ASSUME(SamplesToDo > 0); auto process = [&](const float input) -> float { if(autoKnee) knee = std::max(0.0f, 2.5f * (c_dev + c_est)); const float knee_h{0.5f * knee}; /* This is the gain computer. It applies a static compression curve * to the control signal. */ const float x_over{*(lookAhead++) - threshold}; const float y_G{ (x_over <= -knee_h) ? 0.0f : (std::fabs(x_over) < knee_h) ? (x_over+knee_h) * (x_over+knee_h) / (2.0f * knee) : x_over}; const float y2_crest{*(crestFactor++)}; if(autoAttack) { t_att = 2.0f*attack/y2_crest; a_att = std::exp(-1.0f / t_att); } if(autoRelease) { t_rel = 2.0f*release/y2_crest - t_att; a_rel = std::exp(-1.0f / t_rel); } /* Gain smoothing (ballistics) is done via a smooth decoupled peak * detector. The attack time is subtracted from the release time * above to compensate for the chained operating mode. */ const float x_L{-slope * y_G}; y_1 = std::max(x_L, lerpf(x_L, y_1, a_rel)); y_L = lerpf(y_1, y_L, a_att); /* Knee width and make-up gain automation make use of a smoothed * measurement of deviation between the control signal and estimate. * The estimate is also used to bias the measurement to hot-start its * average. */ c_dev = lerpf(-(y_L+c_est), c_dev, a_adp); if(autoPostGain) { /* Clipping reduction is only viable when make-up gain is being * automated. It modifies the deviation to further attenuate the * control signal when clipping is detected. The adaptation time * is sufficiently long enough to suppress further clipping at the * same output level. */ if(autoDeclip) c_dev = std::max(c_dev, input - y_L - threshold - c_est); postGain = -(c_dev + c_est); } return std::exp(postGain - y_L); }; auto sideChain = al::span{mSideChain}.first(SamplesToDo); std::transform(sideChain.begin(), sideChain.end(), sideChain.begin(), process); mLastRelease = y_1; mLastAttack = y_L; mLastGainDev = c_dev; } /* Combined with the hold time, a look-ahead delay can improve handling of * fast transients by allowing the envelope time to converge prior to * reaching the offending impulse. This is best used when operating as a * limiter. */ void Compressor::signalDelay(const uint SamplesToDo, const al::span OutBuffer) { const auto lookAhead = mLookAhead; ASSUME(SamplesToDo > 0); ASSUME(SamplesToDo <= BufferLineSize); ASSUME(lookAhead > 0); ASSUME(lookAhead < BufferLineSize); auto delays = mDelay.begin(); for(auto &buffer : OutBuffer) { const auto inout = al::span{buffer}.first(SamplesToDo); const auto delaybuf = al::span{*(delays++)}.first(lookAhead); if(SamplesToDo >= delaybuf.size()) LIKELY { const auto inout_start = inout.end() - ptrdiff_t(delaybuf.size()); const auto delay_end = std::rotate(inout.begin(), inout_start, inout.end()); std::swap_ranges(inout.begin(), delay_end, delaybuf.begin()); } else { auto delay_start = std::swap_ranges(inout.begin(), inout.end(), delaybuf.begin()); std::rotate(delaybuf.begin(), delay_start, delaybuf.end()); } } } std::unique_ptr Compressor::Create(const size_t NumChans, const float SampleRate, const FlagBits autoflags, const float LookAheadTime, const float HoldTime, const float PreGainDb, const float PostGainDb, const float ThresholdDb, const float Ratio, const float KneeDb, const float AttackTime, const float ReleaseTime) { const auto lookAhead = static_cast(std::clamp(std::round(LookAheadTime*SampleRate), 0.0f, BufferLineSize-1.0f)); const auto hold = static_cast(std::clamp(std::round(HoldTime*SampleRate), 0.0f, BufferLineSize-1.0f)); auto Comp = CompressorPtr{new Compressor{}}; Comp->mAuto.Knee = autoflags.test(AutoKnee); Comp->mAuto.Attack = autoflags.test(AutoAttack); Comp->mAuto.Release = autoflags.test(AutoRelease); Comp->mAuto.PostGain = autoflags.test(AutoPostGain); Comp->mAuto.Declip = autoflags.test(AutoPostGain) && autoflags.test(AutoDeclip); Comp->mLookAhead = lookAhead; Comp->mPreGain = std::pow(10.0f, PreGainDb / 20.0f); Comp->mPostGain = std::log(10.0f)/20.0f * PostGainDb; Comp->mThreshold = std::log(10.0f)/20.0f * ThresholdDb; Comp->mSlope = 1.0f / std::max(1.0f, Ratio) - 1.0f; Comp->mKnee = std::max(0.0f, std::log(10.0f)/20.0f * KneeDb); Comp->mAttack = std::max(1.0f, AttackTime * SampleRate); Comp->mRelease = std::max(1.0f, ReleaseTime * SampleRate); /* Knee width automation actually treats the compressor as a limiter. By * varying the knee width, it can effectively be seen as applying * compression over a wide range of ratios. */ if(AutoKnee) Comp->mSlope = -1.0f; if(lookAhead > 0) { /* The sliding hold implementation doesn't handle a length of 1. A 1- * sample hold is useless anyway, it would only ever give back what was * just given to it. */ if(hold > 1) { Comp->mHold = std::make_unique(); Comp->mHold->mValues[0] = -std::numeric_limits::infinity(); Comp->mHold->mExpiries[0] = hold; Comp->mHold->mLength = hold; } Comp->mDelay.resize(NumChans, FloatBufferLine{}); } Comp->mCrestCoeff = std::exp(-1.0f / (0.200f * SampleRate)); // 200ms Comp->mGainEstimate = Comp->mThreshold * -0.5f * Comp->mSlope; Comp->mAdaptCoeff = std::exp(-1.0f / (2.0f * SampleRate)); // 2s return Comp; } Compressor::~Compressor() = default; void Compressor::process(const uint SamplesToDo, const al::span InOut) { ASSUME(SamplesToDo > 0); ASSUME(SamplesToDo <= BufferLineSize); const float preGain{mPreGain}; if(preGain != 1.0f) { auto apply_gain = [SamplesToDo,preGain](FloatBufferLine &input) noexcept -> void { const auto buffer = assume_aligned_span<16>(al::span{input}.first(SamplesToDo)); std::transform(buffer.cbegin(), buffer.cend(), buffer.begin(), [preGain](const float s) noexcept { return s * preGain; }); }; std::for_each(InOut.begin(), InOut.end(), apply_gain); } linkChannels(SamplesToDo, InOut); if(mAuto.Attack || mAuto.Release) crestDetector(SamplesToDo); if(mHold) peakHoldDetector(SamplesToDo); else peakDetector(SamplesToDo); gainCompressor(SamplesToDo); if(!mDelay.empty()) signalDelay(SamplesToDo, InOut); const auto gains = assume_aligned_span<16>(al::span{mSideChain}.first(SamplesToDo)); auto apply_comp = [gains](const FloatBufferSpan inout) noexcept -> void { const auto buffer = assume_aligned_span<16>(inout); std::transform(gains.cbegin(), gains.cend(), buffer.cbegin(), buffer.begin(), std::multiplies{}); }; for(const FloatBufferSpan inout : InOut) apply_comp(inout); const auto delayedGains = al::span{mSideChain}.subspan(SamplesToDo, mLookAhead); std::copy(delayedGains.begin(), delayedGains.end(), mSideChain.begin()); } openal-soft-1.24.2/core/mastering.h000066400000000000000000000100361474041540300171230ustar00rootroot00000000000000#ifndef CORE_MASTERING_H #define CORE_MASTERING_H #include #include #include #include "alnumeric.h" #include "alspan.h" #include "bufferline.h" #include "opthelpers.h" #include "vector.h" struct SlidingHold; using uint = unsigned int; /* General topology and basic automation was based on the following paper: * * D. Giannoulis, M. Massberg and J. D. Reiss, * "Parameter Automation in a Dynamic Range Compressor," * Journal of the Audio Engineering Society, v61 (10), Oct. 2013 * * Available (along with supplemental reading) at: * * http://c4dm.eecs.qmul.ac.uk/audioengineering/compressors/ */ class SIMDALIGN Compressor { struct AutoFlags { bool Knee : 1; bool Attack : 1; bool Release : 1; bool PostGain : 1; bool Declip : 1; }; AutoFlags mAuto{}; uint mLookAhead{0}; float mPreGain{0.0f}; float mPostGain{0.0f}; float mThreshold{0.0f}; float mSlope{0.0f}; float mKnee{0.0f}; float mAttack{0.0f}; float mRelease{0.0f}; alignas(16) std::array mSideChain{}; alignas(16) std::array mCrestFactor{}; std::unique_ptr mHold; al::vector mDelay; float mCrestCoeff{0.0f}; float mGainEstimate{0.0f}; float mAdaptCoeff{0.0f}; float mLastPeakSq{0.0f}; float mLastRmsSq{0.0f}; float mLastRelease{0.0f}; float mLastAttack{0.0f}; float mLastGainDev{0.0f}; Compressor() = default; void linkChannels(const uint SamplesToDo, const al::span OutBuffer); void crestDetector(const uint SamplesToDo); void peakDetector(const uint SamplesToDo); void peakHoldDetector(const uint SamplesToDo); void gainCompressor(const uint SamplesToDo); void signalDelay(const uint SamplesToDo, const al::span OutBuffer); public: enum { AutoKnee, AutoAttack, AutoRelease, AutoPostGain, AutoDeclip, FlagsCount }; using FlagBits = std::bitset; ~Compressor(); void process(const uint SamplesToDo, al::span InOut); [[nodiscard]] auto getLookAhead() const noexcept -> uint { return mLookAhead; } /** * The compressor is initialized with the following settings: * * \param NumChans Number of channels to process. * \param SampleRate Sample rate to process. * \param AutoKnee Whether to automate the knee width parameter. * \param AutoAttack Whether to automate the attack time parameter. * \param AutoRelease Whether to automate the release time parameter. * \param AutoPostGain Whether to automate the make-up (post) gain * parameter. * \param AutoDeclip Whether to automate clipping reduction. Ignored * when not automating make-up gain. * \param LookAheadTime Look-ahead time (in seconds). * \param HoldTime Peak hold-time (in seconds). * \param PreGainDb Gain applied before detection (in dB). * \param PostGainDb Make-up gain applied after compression (in dB). * \param ThresholdDb Triggering threshold (in dB). * \param Ratio Compression ratio (x:1). Set to INFINIFTY for true * limiting. Ignored when automating knee width. * \param KneeDb Knee width (in dB). Ignored when automating knee * width. * \param AttackTime Attack time (in seconds). Acts as a maximum when * automating attack time. * \param ReleaseTime Release time (in seconds). Acts as a maximum when * automating release time. */ static std::unique_ptr Create(const size_t NumChans, const float SampleRate, const FlagBits autoflags, const float LookAheadTime, const float HoldTime, const float PreGainDb, const float PostGainDb, const float ThresholdDb, const float Ratio, const float KneeDb, const float AttackTime, const float ReleaseTime); }; using CompressorPtr = std::unique_ptr; #endif /* CORE_MASTERING_H */ openal-soft-1.24.2/core/mixer.cpp000066400000000000000000000062151474041540300166150ustar00rootroot00000000000000 #include "config.h" #include "mixer.h" #include #include #include #include "alnumbers.h" #include "core/ambidefs.h" #include "device.h" #include "mixer/defs.h" struct CTag; MixerOutFunc MixSamplesOut{Mix_}; MixerOneFunc MixSamplesOne{Mix_}; std::array CalcAmbiCoeffs(const float y, const float z, const float x, const float spread) { std::array coeffs{CalcAmbiCoeffs(y, z, x)}; if(spread > 0.0f) { /* Implement the spread by using a spherical source that subtends the * angle spread. See: * http://www.ppsloan.org/publications/StupidSH36.pdf - Appendix A3 * * When adjusted for N3D normalization instead of SN3D, these * calculations are: * * ZH0 = -sqrt(pi) * (-1+ca); * ZH1 = 0.5*sqrt(pi) * sa*sa; * ZH2 = -0.5*sqrt(pi) * ca*(-1+ca)*(ca+1); * ZH3 = -0.125*sqrt(pi) * (-1+ca)*(ca+1)*(5*ca*ca - 1); * ZH4 = -0.125*sqrt(pi) * ca*(-1+ca)*(ca+1)*(7*ca*ca - 3); * ZH5 = -0.0625*sqrt(pi) * (-1+ca)*(ca+1)*(21*ca*ca*ca*ca - 14*ca*ca + 1); * * The gain of the source is compensated for size, so that the * loudness doesn't depend on the spread. Thus: * * ZH0 = 1.0f; * ZH1 = 0.5f * (ca+1.0f); * ZH2 = 0.5f * (ca+1.0f)*ca; * ZH3 = 0.125f * (ca+1.0f)*(5.0f*ca*ca - 1.0f); * ZH4 = 0.125f * (ca+1.0f)*(7.0f*ca*ca - 3.0f)*ca; * ZH5 = 0.0625f * (ca+1.0f)*(21.0f*ca*ca*ca*ca - 14.0f*ca*ca + 1.0f); */ const float ca{std::cos(spread * 0.5f)}; /* Increase the source volume by up to +3dB for a full spread. */ const float scale{std::sqrt(1.0f + al::numbers::inv_pi_v/2.0f*spread)}; const float ZH0_norm{scale}; const float ZH1_norm{scale * 0.5f * (ca+1.f)}; const float ZH2_norm{scale * 0.5f * (ca+1.f)*ca}; const float ZH3_norm{scale * 0.125f * (ca+1.f)*(5.f*ca*ca-1.f)}; /* Zeroth-order */ coeffs[0] *= ZH0_norm; /* First-order */ coeffs[1] *= ZH1_norm; coeffs[2] *= ZH1_norm; coeffs[3] *= ZH1_norm; /* Second-order */ coeffs[4] *= ZH2_norm; coeffs[5] *= ZH2_norm; coeffs[6] *= ZH2_norm; coeffs[7] *= ZH2_norm; coeffs[8] *= ZH2_norm; /* Third-order */ coeffs[9] *= ZH3_norm; coeffs[10] *= ZH3_norm; coeffs[11] *= ZH3_norm; coeffs[12] *= ZH3_norm; coeffs[13] *= ZH3_norm; coeffs[14] *= ZH3_norm; coeffs[15] *= ZH3_norm; } return coeffs; } void ComputePanGains(const MixParams *mix, const al::span coeffs, const float ingain, const al::span gains) { auto ambimap = al::span{std::as_const(mix->AmbiMap)}.first(mix->Buffer.size()); auto iter = std::transform(ambimap.begin(), ambimap.end(), gains.begin(), [coeffs,ingain](const BFChannelConfig &chanmap) noexcept -> float { return chanmap.Scale * coeffs[chanmap.Index] * ingain; }); std::fill(iter, gains.end(), 0.0f); } openal-soft-1.24.2/core/mixer.h000066400000000000000000000075341474041540300162670ustar00rootroot00000000000000#ifndef CORE_MIXER_H #define CORE_MIXER_H #include #include #include #include "alspan.h" #include "ambidefs.h" #include "bufferline.h" #include "opthelpers.h" struct MixParams; /* Mixer functions that handle one input and multiple output channels. */ using MixerOutFunc = void(*)(const al::span InSamples, const al::span OutBuffer, const al::span CurrentGains, const al::span TargetGains, const std::size_t Counter, const std::size_t OutPos); DECL_HIDDEN extern MixerOutFunc MixSamplesOut; inline void MixSamples(const al::span InSamples, const al::span OutBuffer, const al::span CurrentGains, const al::span TargetGains, const std::size_t Counter, const std::size_t OutPos) { MixSamplesOut(InSamples, OutBuffer, CurrentGains, TargetGains, Counter, OutPos); } /* Mixer functions that handle one input and one output channel. */ using MixerOneFunc = void(*)(const al::span InSamples,const al::span OutBuffer, float &CurrentGain, const float TargetGain, const std::size_t Counter); DECL_HIDDEN extern MixerOneFunc MixSamplesOne; inline void MixSamples(const al::span InSamples, const al::span OutBuffer, float &CurrentGain, const float TargetGain, const std::size_t Counter) { MixSamplesOne(InSamples, OutBuffer, CurrentGain, TargetGain, Counter); } /** * Calculates ambisonic encoder coefficients using the X, Y, and Z direction * components, which must represent a normalized (unit length) vector, and the * spread is the angular width of the sound (0...tau). * * NOTE: The components use ambisonic coordinates. As a result: * * Ambisonic Y = OpenAL -X * Ambisonic Z = OpenAL Y * Ambisonic X = OpenAL -Z * * The components are ordered such that OpenAL's X, Y, and Z are the first, * second, and third parameters respectively -- simply negate X and Z. */ std::array CalcAmbiCoeffs(const float y, const float z, const float x, const float spread); /** * CalcDirectionCoeffs * * Calculates ambisonic coefficients based on an OpenAL direction vector. The * vector must be normalized (unit length), and the spread is the angular width * of the sound (0...tau). */ inline std::array CalcDirectionCoeffs(const al::span dir, const float spread) { /* Convert from OpenAL coords to Ambisonics. */ return CalcAmbiCoeffs(-dir[0], dir[1], -dir[2], spread); } /** * CalcDirectionCoeffs * * Calculates ambisonic coefficients based on an OpenAL direction vector. The * vector must be normalized (unit length). */ constexpr std::array CalcDirectionCoeffs(const al::span dir) { /* Convert from OpenAL coords to Ambisonics. */ return CalcAmbiCoeffs(-dir[0], dir[1], -dir[2]); } /** * CalcAngleCoeffs * * Calculates ambisonic coefficients based on azimuth and elevation. The * azimuth and elevation parameters are in radians, going right and up * respectively. */ inline std::array CalcAngleCoeffs(const float azimuth, const float elevation, const float spread) { const float x{-std::sin(azimuth) * std::cos(elevation)}; const float y{ std::sin(elevation)}; const float z{ std::cos(azimuth) * std::cos(elevation)}; return CalcAmbiCoeffs(x, y, z, spread); } /** * ComputePanGains * * Computes panning gains using the given channel decoder coefficients and the * pre-calculated direction or angle coefficients. For B-Format sources, the * coeffs are a 'slice' of a transform matrix for the input channel, used to * scale and orient the sound samples. */ void ComputePanGains(const MixParams *mix, const al::span coeffs, const float ingain, const al::span gains); #endif /* CORE_MIXER_H */ openal-soft-1.24.2/core/mixer/000077500000000000000000000000001474041540300161055ustar00rootroot00000000000000openal-soft-1.24.2/core/mixer/defs.h000066400000000000000000000075631474041540300172120ustar00rootroot00000000000000#ifndef CORE_MIXER_DEFS_H #define CORE_MIXER_DEFS_H #include #include #include #include #include #include "alspan.h" #include "core/bufferline.h" #include "core/cubic_defs.h" struct HrtfChannelState; struct HrtfFilter; struct MixHrtfFilter; using uint = unsigned int; using float2 = std::array; inline constexpr int MixerFracBits{16}; inline constexpr int MixerFracOne{1 << MixerFracBits}; inline constexpr int MixerFracMask{MixerFracOne - 1}; inline constexpr int MixerFracHalf{MixerFracOne >> 1}; inline constexpr float GainSilenceThreshold{0.00001f}; /* -100dB */ enum class Resampler : std::uint8_t { Point, Linear, Spline, Gaussian, FastBSinc12, BSinc12, FastBSinc24, BSinc24, Max = BSinc24 }; /* Interpolator state. Kind of a misnomer since the interpolator itself is * stateless. This just keeps it from having to recompute scale-related * mappings for every sample. */ struct BsincState { float sf; /* Scale interpolation factor. */ uint m; /* Coefficient count. */ uint l; /* Left coefficient offset. */ /* Filter coefficients, followed by the phase, scale, and scale-phase * delta coefficients. Starting at phase index 0, each subsequent phase * index follows contiguously. */ al::span filter; }; struct CubicState { /* Filter coefficients, and coefficient deltas. Starting at phase index 0, * each subsequent phase index follows contiguously. */ al::span filter; explicit CubicState(al::span f) : filter{f} { } }; using InterpState = std::variant; using ResamplerFunc = void(*)(const InterpState *state, const al::span src, uint frac, const uint increment, const al::span dst); ResamplerFunc PrepareResampler(Resampler resampler, uint increment, InterpState *state); template void Resample_(const InterpState *state, const al::span src, uint frac, const uint increment, const al::span dst); template void Mix_(const al::span InSamples, const al::span OutBuffer, const al::span CurrentGains, const al::span TargetGains, const size_t Counter, const size_t OutPos); template void Mix_(const al::span InSamples, const al::span OutBuffer, float &CurrentGain, const float TargetGain, const size_t Counter); template void MixHrtf_(const al::span InSamples, const al::span AccumSamples, const uint IrSize, const MixHrtfFilter *hrtfparams, const size_t SamplesToDo); template void MixHrtfBlend_(const al::span InSamples, const al::span AccumSamples, const uint IrSize, const HrtfFilter *oldparams, const MixHrtfFilter *newparams, const size_t SamplesToDo); template void MixDirectHrtf_(const FloatBufferSpan LeftOut, const FloatBufferSpan RightOut, const al::span InSamples, const al::span AccumSamples, const al::span TempBuf, const al::span ChanState, const size_t IrSize, const size_t SamplesToDo); /* Vectorized resampler helpers */ template constexpr void InitPosArrays(uint pos, uint frac, const uint increment, const al::span frac_arr, const al::span pos_arr) { static_assert(pos_arr.size() == frac_arr.size()); pos_arr[0] = pos; frac_arr[0] = frac; for(size_t i{1};i < pos_arr.size();++i) { const uint frac_tmp{frac_arr[i-1] + increment}; pos_arr[i] = pos_arr[i-1] + (frac_tmp>>MixerFracBits); frac_arr[i] = frac_tmp&MixerFracMask; } } #endif /* CORE_MIXER_DEFS_H */ openal-soft-1.24.2/core/mixer/hrtfbase.h000066400000000000000000000124741474041540300200640ustar00rootroot00000000000000#ifndef CORE_MIXER_HRTFBASE_H #define CORE_MIXER_HRTFBASE_H #include #include #include "defs.h" #include "hrtfdefs.h" #include "opthelpers.h" using uint = unsigned int; using ApplyCoeffsT = void(const al::span Values, const size_t irSize, const ConstHrirSpan Coeffs, const float left, const float right); template inline void MixHrtfBase(const al::span InSamples, const al::span AccumSamples, const size_t IrSize, const MixHrtfFilter *hrtfparams, const size_t SamplesToDo) { ASSUME(SamplesToDo > 0); ASSUME(SamplesToDo <= BufferLineSize); ASSUME(IrSize <= HrirLength); const ConstHrirSpan Coeffs{hrtfparams->Coeffs}; const float gainstep{hrtfparams->GainStep}; const float gain{hrtfparams->Gain}; size_t ldelay{HrtfHistoryLength - hrtfparams->Delay[0]}; size_t rdelay{HrtfHistoryLength - hrtfparams->Delay[1]}; float stepcount{0.0f}; for(size_t i{0u};i < SamplesToDo;++i) { const float g{gain + gainstep*stepcount}; const float left{InSamples[ldelay++] * g}; const float right{InSamples[rdelay++] * g}; ApplyCoeffs(AccumSamples.subspan(i), IrSize, Coeffs, left, right); stepcount += 1.0f; } } template inline void MixHrtfBlendBase(const al::span InSamples, const al::span AccumSamples, const size_t IrSize, const HrtfFilter *oldparams, const MixHrtfFilter *newparams, const size_t SamplesToDo) { ASSUME(SamplesToDo > 0); ASSUME(SamplesToDo <= BufferLineSize); ASSUME(IrSize <= HrirLength); const ConstHrirSpan OldCoeffs{oldparams->Coeffs}; const float oldGainStep{oldparams->Gain / static_cast(SamplesToDo)}; const ConstHrirSpan NewCoeffs{newparams->Coeffs}; const float newGainStep{newparams->GainStep}; if(oldparams->Gain > GainSilenceThreshold) LIKELY { size_t ldelay{HrtfHistoryLength - oldparams->Delay[0]}; size_t rdelay{HrtfHistoryLength - oldparams->Delay[1]}; auto stepcount = static_cast(SamplesToDo); for(size_t i{0u};i < SamplesToDo;++i) { const float g{oldGainStep*stepcount}; const float left{InSamples[ldelay++] * g}; const float right{InSamples[rdelay++] * g}; ApplyCoeffs(AccumSamples.subspan(i), IrSize, OldCoeffs, left, right); stepcount -= 1.0f; } } if(newGainStep*static_cast(SamplesToDo) > GainSilenceThreshold) LIKELY { size_t ldelay{HrtfHistoryLength+1 - newparams->Delay[0]}; size_t rdelay{HrtfHistoryLength+1 - newparams->Delay[1]}; float stepcount{1.0f}; for(size_t i{1u};i < SamplesToDo;++i) { const float g{newGainStep*stepcount}; const float left{InSamples[ldelay++] * g}; const float right{InSamples[rdelay++] * g}; ApplyCoeffs(AccumSamples.subspan(i), IrSize, NewCoeffs, left, right); stepcount += 1.0f; } } } template inline void MixDirectHrtfBase(const FloatBufferSpan LeftOut, const FloatBufferSpan RightOut, const al::span InSamples, const al::span AccumSamples, const al::span TempBuf, const al::span ChannelState, const size_t IrSize, const size_t SamplesToDo) { ASSUME(SamplesToDo > 0); ASSUME(SamplesToDo <= BufferLineSize); ASSUME(IrSize <= HrirLength); assert(ChannelState.size() == InSamples.size()); auto ChanState = ChannelState.begin(); for(const FloatBufferLine &input : InSamples) { /* For dual-band processing, the signal needs extra scaling applied to * the high frequency response. The band-splitter applies this scaling * with a consistent phase shift regardless of the scale amount. */ ChanState->mSplitter.processHfScale(al::span{input}.first(SamplesToDo), TempBuf, ChanState->mHfScale); /* Now apply the HRIR coefficients to this channel. */ const ConstHrirSpan Coeffs{ChanState->mCoeffs}; for(size_t i{0u};i < SamplesToDo;++i) { const float insample{TempBuf[i]}; ApplyCoeffs(AccumSamples.subspan(i), IrSize, Coeffs, insample, insample); } ++ChanState; } /* Add the HRTF signal to the existing "direct" signal. */ const auto left = al::span{al::assume_aligned<16>(LeftOut.data()), SamplesToDo}; std::transform(left.cbegin(), left.cend(), AccumSamples.cbegin(), left.begin(), [](const float sample, const float2 &accum) noexcept -> float { return sample + accum[0]; }); const auto right = al::span{al::assume_aligned<16>(RightOut.data()), SamplesToDo}; std::transform(right.cbegin(), right.cend(), AccumSamples.cbegin(), right.begin(), [](const float sample, const float2 &accum) noexcept -> float { return sample + accum[1]; }); /* Copy the new in-progress accumulation values to the front and clear the * following samples for the next mix. */ const auto accum_inprog = AccumSamples.subspan(SamplesToDo, HrirLength); auto accum_iter = std::copy(accum_inprog.cbegin(), accum_inprog.cend(), AccumSamples.begin()); std::fill_n(accum_iter, SamplesToDo, float2{}); } #endif /* CORE_MIXER_HRTFBASE_H */ openal-soft-1.24.2/core/mixer/hrtfdefs.h000066400000000000000000000022421474041540300200630ustar00rootroot00000000000000#ifndef CORE_MIXER_HRTFDEFS_H #define CORE_MIXER_HRTFDEFS_H #include #include "alspan.h" #include "core/ambidefs.h" #include "core/bufferline.h" #include "core/filters/splitter.h" using float2 = std::array; using ubyte = unsigned char; using ubyte2 = std::array; using ushort = unsigned short; using uint = unsigned int; using uint2 = std::array; constexpr uint HrtfHistoryBits{6}; constexpr uint HrtfHistoryLength{1 << HrtfHistoryBits}; constexpr uint HrtfHistoryMask{HrtfHistoryLength - 1}; constexpr uint HrirBits{7}; constexpr uint HrirLength{1 << HrirBits}; constexpr uint HrirMask{HrirLength - 1}; constexpr uint MinIrLength{8}; using HrirArray = std::array; using HrirSpan = al::span; using ConstHrirSpan = al::span; struct MixHrtfFilter { const ConstHrirSpan Coeffs; uint2 Delay; float Gain; float GainStep; }; struct HrtfFilter { alignas(16) HrirArray Coeffs; uint2 Delay; float Gain; }; struct HrtfChannelState { BandSplitter mSplitter; float mHfScale{}; alignas(16) HrirArray mCoeffs{}; }; #endif /* CORE_MIXER_HRTFDEFS_H */ openal-soft-1.24.2/core/mixer/mixer_c.cpp000066400000000000000000000237141474041540300202460ustar00rootroot00000000000000#include "config.h" #include #include #include #include #include #include "alnumeric.h" #include "alspan.h" #include "core/bsinc_defs.h" #include "core/bufferline.h" #include "core/cubic_defs.h" #include "core/mixer/hrtfdefs.h" #include "core/resampler_limits.h" #include "defs.h" #include "hrtfbase.h" #include "opthelpers.h" struct CTag; struct PointTag; struct LerpTag; struct CubicTag; struct BSincTag; struct FastBSincTag; namespace { constexpr uint BsincPhaseDiffBits{MixerFracBits - BSincPhaseBits}; constexpr uint BsincPhaseDiffOne{1 << BsincPhaseDiffBits}; constexpr uint BsincPhaseDiffMask{BsincPhaseDiffOne - 1u}; constexpr uint CubicPhaseDiffBits{MixerFracBits - CubicPhaseBits}; constexpr uint CubicPhaseDiffOne{1 << CubicPhaseDiffBits}; constexpr uint CubicPhaseDiffMask{CubicPhaseDiffOne - 1u}; using SamplerNST = float(const al::span, const size_t, const uint) noexcept; template using SamplerT = float(const T&,const al::span,const size_t,const uint) noexcept; [[nodiscard]] constexpr auto do_point(const al::span vals, const size_t pos, const uint) noexcept -> float { return vals[pos]; } [[nodiscard]] constexpr auto do_lerp(const al::span vals, const size_t pos, const uint frac) noexcept -> float { return lerpf(vals[pos+0], vals[pos+1], static_cast(frac)*(1.0f/MixerFracOne)); } [[nodiscard]] constexpr auto do_cubic(const CubicState &istate, const al::span vals, const size_t pos, const uint frac) noexcept -> float { /* Calculate the phase index and factor. */ const uint pi{frac >> CubicPhaseDiffBits}; ASSUME(pi < CubicPhaseCount); const float pf{static_cast(frac&CubicPhaseDiffMask) * (1.0f/CubicPhaseDiffOne)}; const auto fil = al::span{istate.filter[pi].mCoeffs}; const auto phd = al::span{istate.filter[pi].mDeltas}; /* Apply the phase interpolated filter. */ return (fil[0] + pf*phd[0])*vals[pos+0] + (fil[1] + pf*phd[1])*vals[pos+1] + (fil[2] + pf*phd[2])*vals[pos+2] + (fil[3] + pf*phd[3])*vals[pos+3]; } [[nodiscard]] constexpr auto do_fastbsinc(const BsincState &bsinc, const al::span vals, const size_t pos, const uint frac) noexcept -> float { const size_t m{bsinc.m}; ASSUME(m > 0); ASSUME(m <= MaxResamplerPadding); /* Calculate the phase index and factor. */ const uint pi{frac >> BsincPhaseDiffBits}; ASSUME(pi < BSincPhaseCount); const float pf{static_cast(frac&BsincPhaseDiffMask) * (1.0f/BsincPhaseDiffOne)}; const auto fil = bsinc.filter.subspan(2_uz*pi*m); const auto phd = fil.subspan(m); /* Apply the phase interpolated filter. */ float r{0.0f}; for(size_t j_f{0};j_f < m;++j_f) r += (fil[j_f] + pf*phd[j_f]) * vals[pos+j_f]; return r; } [[nodiscard]] constexpr auto do_bsinc(const BsincState &bsinc, const al::span vals, const size_t pos, const uint frac) noexcept -> float { const size_t m{bsinc.m}; ASSUME(m > 0); ASSUME(m <= MaxResamplerPadding); /* Calculate the phase index and factor. */ const uint pi{frac >> BsincPhaseDiffBits}; ASSUME(pi < BSincPhaseCount); const float pf{static_cast(frac&BsincPhaseDiffMask) * (1.0f/BsincPhaseDiffOne)}; const auto fil = bsinc.filter.subspan(2_uz*pi*m); const auto phd = fil.subspan(m); const auto scd = fil.subspan(BSincPhaseCount*2_uz*m); const auto spd = scd.subspan(m); /* Apply the scale and phase interpolated filter. */ float r{0.0f}; for(size_t j_f{0};j_f < m;++j_f) r += (fil[j_f] + bsinc.sf*scd[j_f] + pf*(phd[j_f] + bsinc.sf*spd[j_f])) * vals[pos+j_f]; return r; } template void DoResample(const al::span src, uint frac, const uint increment, const al::span dst) { ASSUME(frac < MixerFracOne); size_t pos{0}; std::generate(dst.begin(), dst.end(), [&pos,&frac,src,increment]() -> float { const float output{Sampler(src, pos, frac)}; frac += increment; pos += frac>>MixerFracBits; frac &= MixerFracMask; return output; }); } template Sampler> void DoResample(const U istate, const al::span src, uint frac, const uint increment, const al::span dst) { ASSUME(frac < MixerFracOne); size_t pos{0}; std::generate(dst.begin(), dst.end(), [istate,src,&pos,&frac,increment]() -> float { const float output{Sampler(istate, src, pos, frac)}; frac += increment; pos += frac>>MixerFracBits; frac &= MixerFracMask; return output; }); } inline void ApplyCoeffs(const al::span Values, const size_t IrSize, const ConstHrirSpan Coeffs, const float left, const float right) noexcept { ASSUME(IrSize >= MinIrLength); ASSUME(IrSize <= HrirLength); auto mix_impulse = [left,right](const float2 &value, const float2 &coeff) noexcept -> float2 { return float2{{value[0] + coeff[0]*left, value[1] + coeff[1]*right}}; }; std::transform(Values.cbegin(), Values.cbegin()+ptrdiff_t(IrSize), Coeffs.cbegin(), Values.begin(), mix_impulse); } force_inline void MixLine(al::span InSamples, const al::span dst, float &CurrentGain, const float TargetGain, const float delta, const size_t fade_len, size_t Counter) { const float step{(TargetGain-CurrentGain) * delta}; auto output = dst.begin(); if(std::abs(step) > std::numeric_limits::epsilon()) { auto input = InSamples.first(fade_len); InSamples = InSamples.subspan(fade_len); const float gain{CurrentGain}; float step_count{0.0f}; output = std::transform(input.begin(), input.end(), output, output, [gain,step,&step_count](const float in, float out) noexcept -> float { out += in * (gain + step*step_count); step_count += 1.0f; return out; }); if(fade_len < Counter) { CurrentGain = gain + step*step_count; return; } } CurrentGain = TargetGain; if(!(std::abs(TargetGain) > GainSilenceThreshold)) return; std::transform(InSamples.begin(), InSamples.end(), output, output, [TargetGain](const float in, const float out) noexcept -> float { return out + in*TargetGain; }); } } // namespace template<> void Resample_(const InterpState*, const al::span src, uint frac, const uint increment, const al::span dst) { DoResample(src.subspan(MaxResamplerEdge), frac, increment, dst); } template<> void Resample_(const InterpState*, const al::span src, uint frac, const uint increment, const al::span dst) { DoResample(src.subspan(MaxResamplerEdge), frac, increment, dst); } template<> void Resample_(const InterpState *state, const al::span src, uint frac, const uint increment, const al::span dst) { DoResample(std::get(*state), src.subspan(MaxResamplerEdge-1), frac, increment, dst); } template<> void Resample_(const InterpState *state, const al::span src, uint frac, const uint increment, const al::span dst) { const auto istate = std::get(*state); ASSUME(istate.l <= MaxResamplerEdge); DoResample(istate, src.subspan(MaxResamplerEdge-istate.l), frac, increment, dst); } template<> void Resample_(const InterpState *state, const al::span src, uint frac, const uint increment, const al::span dst) { const auto istate = std::get(*state); ASSUME(istate.l <= MaxResamplerEdge); DoResample(istate, src.subspan(MaxResamplerEdge-istate.l), frac, increment, dst); } template<> void MixHrtf_(const al::span InSamples, const al::span AccumSamples, const uint IrSize, const MixHrtfFilter *hrtfparams, const size_t SamplesToDo) { MixHrtfBase(InSamples, AccumSamples, IrSize, hrtfparams, SamplesToDo); } template<> void MixHrtfBlend_(const al::span InSamples,const al::span AccumSamples, const uint IrSize, const HrtfFilter *oldparams, const MixHrtfFilter *newparams, const size_t SamplesToDo) { MixHrtfBlendBase(InSamples, AccumSamples, IrSize, oldparams, newparams, SamplesToDo); } template<> void MixDirectHrtf_(const FloatBufferSpan LeftOut, const FloatBufferSpan RightOut, const al::span InSamples, const al::span AccumSamples, const al::span TempBuf, const al::span ChanState, const size_t IrSize, const size_t SamplesToDo) { MixDirectHrtfBase(LeftOut, RightOut, InSamples, AccumSamples, TempBuf, ChanState, IrSize, SamplesToDo); } template<> void Mix_(const al::span InSamples, const al::span OutBuffer, const al::span CurrentGains, const al::span TargetGains, const size_t Counter, const size_t OutPos) { const float delta{(Counter > 0) ? 1.0f / static_cast(Counter) : 0.0f}; const auto fade_len = std::min(Counter, InSamples.size()); auto curgains = CurrentGains.begin(); auto targetgains = TargetGains.cbegin(); for(FloatBufferLine &output : OutBuffer) MixLine(InSamples, al::span{output}.subspan(OutPos), *curgains++, *targetgains++, delta, fade_len, Counter); } template<> void Mix_(const al::span InSamples, const al::span OutBuffer, float &CurrentGain, const float TargetGain, const size_t Counter) { const float delta{(Counter > 0) ? 1.0f / static_cast(Counter) : 0.0f}; const auto fade_len = std::min(Counter, InSamples.size()); MixLine(InSamples, OutBuffer, CurrentGain, TargetGain, delta, fade_len, Counter); } openal-soft-1.24.2/core/mixer/mixer_neon.cpp000066400000000000000000000454641474041540300207710ustar00rootroot00000000000000#include "config.h" #include #include #include #include #include #include #include "alnumeric.h" #include "alspan.h" #include "core/bsinc_defs.h" #include "core/bufferline.h" #include "core/cubic_defs.h" #include "core/mixer/hrtfdefs.h" #include "core/resampler_limits.h" #include "defs.h" #include "hrtfbase.h" #include "opthelpers.h" struct CTag; struct NEONTag; struct LerpTag; struct CubicTag; struct BSincTag; struct FastBSincTag; #if defined(__GNUC__) && !defined(__clang__) && !defined(__ARM_NEON) #pragma GCC target("fpu=neon") #endif using uint = unsigned int; namespace { constexpr uint BSincPhaseDiffBits{MixerFracBits - BSincPhaseBits}; constexpr uint BSincPhaseDiffOne{1 << BSincPhaseDiffBits}; constexpr uint BSincPhaseDiffMask{BSincPhaseDiffOne - 1u}; constexpr uint CubicPhaseDiffBits{MixerFracBits - CubicPhaseBits}; constexpr uint CubicPhaseDiffOne{1 << CubicPhaseDiffBits}; constexpr uint CubicPhaseDiffMask{CubicPhaseDiffOne - 1u}; force_inline void vtranspose4(float32x4_t &x0, float32x4_t &x1, float32x4_t &x2, float32x4_t &x3) noexcept { float32x4x2_t t0_{vzipq_f32(x0, x2)}; float32x4x2_t t1_{vzipq_f32(x1, x3)}; float32x4x2_t u0_{vzipq_f32(t0_.val[0], t1_.val[0])}; float32x4x2_t u1_{vzipq_f32(t0_.val[1], t1_.val[1])}; x0 = u0_.val[0]; x1 = u0_.val[1]; x2 = u1_.val[0]; x3 = u1_.val[1]; } inline float32x4_t set_f4(float l0, float l1, float l2, float l3) { float32x4_t ret{vmovq_n_f32(l0)}; ret = vsetq_lane_f32(l1, ret, 1); ret = vsetq_lane_f32(l2, ret, 2); ret = vsetq_lane_f32(l3, ret, 3); return ret; } inline void ApplyCoeffs(const al::span Values, const size_t IrSize, const ConstHrirSpan Coeffs, const float left, const float right) { ASSUME(IrSize >= MinIrLength); ASSUME(IrSize <= HrirLength); auto dup_samples = [left,right]() -> float32x4_t { float32x2_t leftright2{vset_lane_f32(right, vmov_n_f32(left), 1)}; return vcombine_f32(leftright2, leftright2); }; const auto leftright4 = dup_samples(); /* Using a loop here instead of std::transform since some builds seem to * have an issue with accessing an array/span of float32x4_t. */ for(size_t c{0};c < IrSize;c += 2) { auto vals = vld1q_f32(&Values[c][0]); vals = vmlaq_f32(vals, vld1q_f32(&Coeffs[c][0]), leftright4); vst1q_f32(&Values[c][0], vals); } } force_inline void MixLine(const al::span InSamples, const al::span dst, float &CurrentGain, const float TargetGain, const float delta, const size_t fade_len, const size_t realign_len, size_t Counter) { const auto step = float{(TargetGain-CurrentGain) * delta}; auto pos = size_t{0}; if(std::abs(step) > std::numeric_limits::epsilon()) { const auto gain = float{CurrentGain}; auto step_count = float{0.0f}; /* Mix with applying gain steps in aligned multiples of 4. */ if(const size_t todo{fade_len >> 2}) { const auto four4 = vdupq_n_f32(4.0f); const auto step4 = vdupq_n_f32(step); const auto gain4 = vdupq_n_f32(gain); auto step_count4 = set_f4(0.0f, 1.0f, 2.0f, 3.0f); const auto in4 = al::span{reinterpret_cast(InSamples.data()), InSamples.size()/4}.first(todo); const auto out4 = al::span{reinterpret_cast(dst.data()), dst.size()/4}; std::transform(in4.begin(), in4.end(), out4.begin(), out4.begin(), [gain4,step4,four4,&step_count4](const float32x4_t val4, float32x4_t dry4) { /* dry += val * (gain + step*step_count) */ dry4 = vmlaq_f32(dry4, val4, vmlaq_f32(gain4, step4, step_count4)); step_count4 = vaddq_f32(step_count4, four4); return dry4; }); pos += in4.size()*4; /* NOTE: step_count4 now represents the next four counts after the * last four mixed samples, so the lowest element represents the * next step count to apply. */ step_count = vgetq_lane_f32(step_count4, 0); } /* Mix with applying left over gain steps that aren't aligned multiples of 4. */ if(const size_t leftover{fade_len&3}) { const auto in = InSamples.subspan(pos, leftover); const auto out = dst.subspan(pos); std::transform(in.begin(), in.end(), out.begin(), out.begin(), [gain,step,&step_count](const float val, float dry) noexcept -> float { dry += val * (gain + step*step_count); step_count += 1.0f; return dry; }); pos += leftover; } if(pos < Counter) { CurrentGain = gain + step*step_count; return; } /* Mix until pos is aligned with 4 or the mix is done. */ if(const size_t leftover{realign_len&3}) { const auto in = InSamples.subspan(pos, leftover); const auto out = dst.subspan(pos); std::transform(in.begin(), in.end(), out.begin(), out.begin(), [TargetGain](const float val, const float dry) noexcept -> float { return dry + val*TargetGain; }); pos += leftover; } } CurrentGain = TargetGain; if(!(std::abs(TargetGain) > GainSilenceThreshold)) return; if(const size_t todo{(InSamples.size()-pos) >> 2}) { const auto in4 = al::span{reinterpret_cast(InSamples.data()), InSamples.size()/4}.last(todo); const auto out = dst.subspan(pos); const auto out4 = al::span{reinterpret_cast(out.data()), out.size()/4}; const auto gain4 = vdupq_n_f32(TargetGain); std::transform(in4.begin(), in4.end(), out4.begin(), out4.begin(), [gain4](const float32x4_t val4, const float32x4_t dry4) -> float32x4_t { return vmlaq_f32(dry4, val4, gain4); }); pos += in4.size()*4; } if(const size_t leftover{(InSamples.size()-pos)&3}) { const auto in = InSamples.last(leftover); const auto out = dst.subspan(pos); std::transform(in.begin(), in.end(), out.begin(), out.begin(), [TargetGain](const float val, const float dry) noexcept -> float { return dry + val*TargetGain; }); } } } // namespace template<> void Resample_(const InterpState*, const al::span src, uint frac, const uint increment, const al::span dst) { ASSUME(frac < MixerFracOne); const uint32x4_t increment4 = vdupq_n_u32(increment*4u); const float32x4_t fracOne4 = vdupq_n_f32(1.0f/MixerFracOne); const uint32x4_t fracMask4 = vdupq_n_u32(MixerFracMask); alignas(16) std::array pos_{}, frac_{}; InitPosArrays(MaxResamplerEdge, frac, increment, al::span{frac_}, al::span{pos_}); uint32x4_t frac4 = vld1q_u32(frac_.data()); uint32x4_t pos4 = vld1q_u32(pos_.data()); auto vecout = al::span{reinterpret_cast(dst.data()), dst.size()/4}; std::generate(vecout.begin(), vecout.end(), [=,&pos4,&frac4]() -> float32x4_t { const uint pos0{vgetq_lane_u32(pos4, 0)}; const uint pos1{vgetq_lane_u32(pos4, 1)}; const uint pos2{vgetq_lane_u32(pos4, 2)}; const uint pos3{vgetq_lane_u32(pos4, 3)}; ASSUME(pos0 <= pos1); ASSUME(pos1 <= pos2); ASSUME(pos2 <= pos3); const float32x4_t val1{set_f4(src[pos0], src[pos1], src[pos2], src[pos3])}; const float32x4_t val2{set_f4(src[pos0+1_uz], src[pos1+1_uz], src[pos2+1_uz], src[pos3+1_uz])}; /* val1 + (val2-val1)*mu */ const float32x4_t r0{vsubq_f32(val2, val1)}; const float32x4_t mu{vmulq_f32(vcvtq_f32_u32(frac4), fracOne4)}; const float32x4_t out{vmlaq_f32(val1, mu, r0)}; frac4 = vaddq_u32(frac4, increment4); pos4 = vaddq_u32(pos4, vshrq_n_u32(frac4, MixerFracBits)); frac4 = vandq_u32(frac4, fracMask4); return out; }); if(size_t todo{dst.size()&3}) { auto pos = size_t{vgetq_lane_u32(pos4, 0)}; frac = vgetq_lane_u32(frac4, 0); const auto out = dst.last(todo); std::generate(out.begin(), out.end(), [&pos,&frac,src,increment] { const float output{lerpf(src[pos+0], src[pos+1], static_cast(frac) * (1.0f/MixerFracOne))}; frac += increment; pos += frac>>MixerFracBits; frac &= MixerFracMask; return output; }); } } template<> void Resample_(const InterpState *state, const al::span src, uint frac, const uint increment, const al::span dst) { ASSUME(frac < MixerFracOne); const auto filter = std::get(*state).filter; const uint32x4_t increment4{vdupq_n_u32(increment*4u)}; const uint32x4_t fracMask4{vdupq_n_u32(MixerFracMask)}; const float32x4_t fracDiffOne4{vdupq_n_f32(1.0f/CubicPhaseDiffOne)}; const uint32x4_t fracDiffMask4{vdupq_n_u32(CubicPhaseDiffMask)}; alignas(16) std::array pos_{}, frac_{}; InitPosArrays(MaxResamplerEdge-1, frac, increment, al::span{frac_}, al::span{pos_}); uint32x4_t frac4{vld1q_u32(frac_.data())}; uint32x4_t pos4{vld1q_u32(pos_.data())}; auto vecout = al::span{reinterpret_cast(dst.data()), dst.size()/4}; std::generate(vecout.begin(), vecout.end(), [=,&pos4,&frac4] { const uint pos0{vgetq_lane_u32(pos4, 0)}; const uint pos1{vgetq_lane_u32(pos4, 1)}; const uint pos2{vgetq_lane_u32(pos4, 2)}; const uint pos3{vgetq_lane_u32(pos4, 3)}; ASSUME(pos0 <= pos1); ASSUME(pos1 <= pos2); ASSUME(pos2 <= pos3); const float32x4_t val0{vld1q_f32(&src[pos0])}; const float32x4_t val1{vld1q_f32(&src[pos1])}; const float32x4_t val2{vld1q_f32(&src[pos2])}; const float32x4_t val3{vld1q_f32(&src[pos3])}; const uint32x4_t pi4{vshrq_n_u32(frac4, CubicPhaseDiffBits)}; const uint pi0{vgetq_lane_u32(pi4, 0)}; ASSUME(pi0 < CubicPhaseCount); const uint pi1{vgetq_lane_u32(pi4, 1)}; ASSUME(pi1 < CubicPhaseCount); const uint pi2{vgetq_lane_u32(pi4, 2)}; ASSUME(pi2 < CubicPhaseCount); const uint pi3{vgetq_lane_u32(pi4, 3)}; ASSUME(pi3 < CubicPhaseCount); const float32x4_t pf4{vmulq_f32(vcvtq_f32_u32(vandq_u32(frac4, fracDiffMask4)), fracDiffOne4)}; float32x4_t r0{vmulq_f32(val0, vmlaq_f32(vld1q_f32(filter[pi0].mCoeffs.data()), vdupq_lane_f32(vget_low_f32(pf4), 0), vld1q_f32(filter[pi0].mDeltas.data())))}; float32x4_t r1{vmulq_f32(val1, vmlaq_f32(vld1q_f32(filter[pi1].mCoeffs.data()), vdupq_lane_f32(vget_low_f32(pf4), 1), vld1q_f32(filter[pi1].mDeltas.data())))}; float32x4_t r2{vmulq_f32(val2, vmlaq_f32(vld1q_f32(filter[pi2].mCoeffs.data()), vdupq_lane_f32(vget_high_f32(pf4), 0), vld1q_f32(filter[pi2].mDeltas.data())))}; float32x4_t r3{vmulq_f32(val3, vmlaq_f32(vld1q_f32(filter[pi3].mCoeffs.data()), vdupq_lane_f32(vget_high_f32(pf4), 1), vld1q_f32(filter[pi3].mDeltas.data())))}; vtranspose4(r0, r1, r2, r3); r0 = vaddq_f32(vaddq_f32(r0, r1), vaddq_f32(r2, r3)); frac4 = vaddq_u32(frac4, increment4); pos4 = vaddq_u32(pos4, vshrq_n_u32(frac4, MixerFracBits)); frac4 = vandq_u32(frac4, fracMask4); return r0; }); if(const size_t todo{dst.size()&3}) { auto pos = size_t{vgetq_lane_u32(pos4, 0)}; frac = vgetq_lane_u32(frac4, 0); auto out = dst.last(todo); std::generate(out.begin(), out.end(), [&pos,&frac,src,increment,filter] { const uint pi{frac >> CubicPhaseDiffBits}; ASSUME(pi < CubicPhaseCount); const float pf{static_cast(frac&CubicPhaseDiffMask) * (1.0f/CubicPhaseDiffOne)}; const float32x4_t pf4{vdupq_n_f32(pf)}; const float32x4_t f4{vmlaq_f32(vld1q_f32(filter[pi].mCoeffs.data()), pf4, vld1q_f32(filter[pi].mDeltas.data()))}; float32x4_t r4{vmulq_f32(f4, vld1q_f32(&src[pos]))}; r4 = vaddq_f32(r4, vrev64q_f32(r4)); const float output{vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0)}; frac += increment; pos += frac>>MixerFracBits; frac &= MixerFracMask; return output; }); } } template<> void Resample_(const InterpState *state, const al::span src, uint frac, const uint increment, const al::span dst) { const auto &bsinc = std::get(*state); const auto sf4 = vdupq_n_f32(bsinc.sf); const auto m = size_t{bsinc.m}; ASSUME(m > 0); ASSUME(m <= MaxResamplerPadding); ASSUME(frac < MixerFracOne); const auto filter = bsinc.filter.first(4_uz*BSincPhaseCount*m); ASSUME(bsinc.l <= MaxResamplerEdge); auto pos = size_t{MaxResamplerEdge-bsinc.l}; std::generate(dst.begin(), dst.end(), [&pos,&frac,src,increment,sf4,m,filter]() -> float { // Calculate the phase index and factor. const uint pi{frac >> BSincPhaseDiffBits}; ASSUME(pi < BSincPhaseCount); const float pf{static_cast(frac&BSincPhaseDiffMask) * (1.0f/BSincPhaseDiffOne)}; // Apply the scale and phase interpolated filter. float32x4_t r4{vdupq_n_f32(0.0f)}; { const float32x4_t pf4{vdupq_n_f32(pf)}; const auto fil = filter.subspan(2_uz*pi*m); const auto phd = fil.subspan(m); const auto scd = fil.subspan(2_uz*BSincPhaseCount*m); const auto spd = scd.subspan(m); size_t td{m >> 2}; size_t j{0u}; do { /* f = ((fil + sf*scd) + pf*(phd + sf*spd)) */ const float32x4_t f4 = vmlaq_f32( vmlaq_f32(vld1q_f32(&fil[j]), sf4, vld1q_f32(&scd[j])), pf4, vmlaq_f32(vld1q_f32(&phd[j]), sf4, vld1q_f32(&spd[j]))); /* r += f*src */ r4 = vmlaq_f32(r4, f4, vld1q_f32(&src[pos+j])); j += 4; } while(--td); } r4 = vaddq_f32(r4, vrev64q_f32(r4)); const float output{vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0)}; frac += increment; pos += frac>>MixerFracBits; frac &= MixerFracMask; return output; }); } template<> void Resample_(const InterpState *state, const al::span src, uint frac, const uint increment, const al::span dst) { const auto &bsinc = std::get(*state); const auto m = size_t{bsinc.m}; ASSUME(m > 0); ASSUME(m <= MaxResamplerPadding); ASSUME(frac < MixerFracOne); const auto filter = bsinc.filter.first(2_uz*BSincPhaseCount*m); ASSUME(bsinc.l <= MaxResamplerEdge); auto pos = size_t{MaxResamplerEdge-bsinc.l}; std::generate(dst.begin(), dst.end(), [&pos,&frac,src,increment,m,filter]() -> float { // Calculate the phase index and factor. const uint pi{frac >> BSincPhaseDiffBits}; ASSUME(pi < BSincPhaseCount); const float pf{static_cast(frac&BSincPhaseDiffMask) * (1.0f/BSincPhaseDiffOne)}; // Apply the phase interpolated filter. float32x4_t r4{vdupq_n_f32(0.0f)}; { const float32x4_t pf4{vdupq_n_f32(pf)}; const auto fil = filter.subspan(2_uz*pi*m); const auto phd = fil.subspan(m); size_t td{m >> 2}; size_t j{0u}; do { /* f = fil + pf*phd */ const float32x4_t f4 = vmlaq_f32(vld1q_f32(&fil[j]), pf4, vld1q_f32(&phd[j])); /* r += f*src */ r4 = vmlaq_f32(r4, f4, vld1q_f32(&src[pos+j])); j += 4; } while(--td); } r4 = vaddq_f32(r4, vrev64q_f32(r4)); const float output{vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0)}; frac += increment; pos += frac>>MixerFracBits; frac &= MixerFracMask; return output; }); } template<> void MixHrtf_(const al::span InSamples, const al::span AccumSamples, const uint IrSize, const MixHrtfFilter *hrtfparams, const size_t SamplesToDo) { MixHrtfBase(InSamples, AccumSamples, IrSize, hrtfparams, SamplesToDo); } template<> void MixHrtfBlend_(const al::span InSamples, const al::span AccumSamples, const uint IrSize, const HrtfFilter *oldparams, const MixHrtfFilter *newparams, const size_t SamplesToDo) { MixHrtfBlendBase(InSamples, AccumSamples, IrSize, oldparams, newparams, SamplesToDo); } template<> void MixDirectHrtf_(const FloatBufferSpan LeftOut, const FloatBufferSpan RightOut, const al::span InSamples, const al::span AccumSamples, const al::span TempBuf, const al::span ChanState, const size_t IrSize, const size_t SamplesToDo) { MixDirectHrtfBase(LeftOut, RightOut, InSamples, AccumSamples, TempBuf, ChanState, IrSize, SamplesToDo); } template<> void Mix_(const al::span InSamples,const al::span OutBuffer, const al::span CurrentGains, const al::span TargetGains, const size_t Counter, const size_t OutPos) { if((OutPos&3) != 0) UNLIKELY return Mix_(InSamples, OutBuffer, CurrentGains, TargetGains, Counter, OutPos); const float delta{(Counter > 0) ? 1.0f / static_cast(Counter) : 0.0f}; const auto fade_len = std::min(Counter, InSamples.size()); const auto realign_len = std::min((fade_len+3_uz) & ~3_uz, InSamples.size()) - fade_len; auto curgains = CurrentGains.begin(); auto targetgains = TargetGains.cbegin(); for(FloatBufferLine &output : OutBuffer) MixLine(InSamples, al::span{output}.subspan(OutPos), *curgains++, *targetgains++, delta, fade_len, realign_len, Counter); } template<> void Mix_(const al::span InSamples, const al::span OutBuffer, float &CurrentGain, const float TargetGain, const size_t Counter) { if((reinterpret_cast(OutBuffer.data())&15) != 0) UNLIKELY return Mix_(InSamples, OutBuffer, CurrentGain, TargetGain, Counter); const float delta{(Counter > 0) ? 1.0f / static_cast(Counter) : 0.0f}; const auto fade_len = std::min(Counter, InSamples.size()); const auto realign_len = std::min((fade_len+3_uz) & ~3_uz, InSamples.size()) - fade_len; MixLine(InSamples, OutBuffer, CurrentGain, TargetGain, delta, fade_len, realign_len, Counter); } openal-soft-1.24.2/core/mixer/mixer_sse.cpp000066400000000000000000000356521474041540300206220ustar00rootroot00000000000000#include "config.h" #include #include #include #include #include #include #include #include #include "alnumeric.h" #include "alspan.h" #include "core/bsinc_defs.h" #include "core/bufferline.h" #include "core/cubic_defs.h" #include "core/mixer/hrtfdefs.h" #include "core/resampler_limits.h" #include "defs.h" #include "hrtfbase.h" #include "opthelpers.h" struct CTag; struct SSETag; struct CubicTag; struct BSincTag; struct FastBSincTag; #if defined(__GNUC__) && !defined(__clang__) && !defined(__SSE__) #pragma GCC target("sse") #endif namespace { constexpr uint BSincPhaseDiffBits{MixerFracBits - BSincPhaseBits}; constexpr uint BSincPhaseDiffOne{1 << BSincPhaseDiffBits}; constexpr uint BSincPhaseDiffMask{BSincPhaseDiffOne - 1u}; constexpr uint CubicPhaseDiffBits{MixerFracBits - CubicPhaseBits}; constexpr uint CubicPhaseDiffOne{1 << CubicPhaseDiffBits}; constexpr uint CubicPhaseDiffMask{CubicPhaseDiffOne - 1u}; force_inline __m128 vmadd(const __m128 x, const __m128 y, const __m128 z) noexcept { return _mm_add_ps(x, _mm_mul_ps(y, z)); } inline void ApplyCoeffs(const al::span Values, const size_t IrSize, const ConstHrirSpan Coeffs, const float left, const float right) { ASSUME(IrSize >= MinIrLength); ASSUME(IrSize <= HrirLength); const auto lrlr = _mm_setr_ps(left, right, left, right); /* Round up the IR size to a multiple of 2 for SIMD (2 IRs for 2 channels * is 4 floats), to avoid cutting the last sample for odd IR counts. The * underlying HRIR is a fixed-size multiple of 2, any extra samples are * either 0 (silence) or more IR samples that get applied for "free". */ const auto count4 = size_t{(IrSize+1) >> 1}; /* This isn't technically correct to test alignment, but it's true for * systems that support SSE, which is the only one that needs to know the * alignment of Values (which alternates between 8- and 16-byte aligned). */ if(!(reinterpret_cast(Values.data())&15)) { const auto vals4 = al::span{reinterpret_cast<__m128*>(Values[0].data()), count4}; const auto coeffs4 = al::span{reinterpret_cast(Coeffs[0].data()), count4}; std::transform(vals4.cbegin(), vals4.cend(), coeffs4.cbegin(), vals4.begin(), [lrlr](const __m128 &val, const __m128 &coeff) -> __m128 { return vmadd(val, coeff, lrlr); }); } else { auto coeffs = _mm_load_ps(Coeffs[0].data()); auto vals = _mm_loadl_pi(_mm_setzero_ps(), reinterpret_cast<__m64*>(Values[0].data())); auto imp0 = _mm_mul_ps(lrlr, coeffs); vals = _mm_add_ps(imp0, vals); _mm_storel_pi(reinterpret_cast<__m64*>(Values[0].data()), vals); size_t td{count4 - 1}; size_t i{1}; do { coeffs = _mm_load_ps(Coeffs[i+1].data()); vals = _mm_load_ps(Values[i].data()); const auto imp1 = _mm_mul_ps(lrlr, coeffs); imp0 = _mm_shuffle_ps(imp0, imp1, _MM_SHUFFLE(1, 0, 3, 2)); vals = _mm_add_ps(imp0, vals); _mm_store_ps(Values[i].data(), vals); imp0 = imp1; i += 2; } while(--td); vals = _mm_loadl_pi(vals, reinterpret_cast<__m64*>(Values[i].data())); imp0 = _mm_movehl_ps(imp0, imp0); vals = _mm_add_ps(imp0, vals); _mm_storel_pi(reinterpret_cast<__m64*>(Values[i].data()), vals); } } force_inline void MixLine(const al::span InSamples, const al::span dst, float &CurrentGain, const float TargetGain, const float delta, const size_t fade_len, const size_t realign_len, size_t Counter) { const auto step = float{(TargetGain-CurrentGain) * delta}; size_t pos{0}; if(std::abs(step) > std::numeric_limits::epsilon()) { const auto gain = CurrentGain; auto step_count = 0.0f; /* Mix with applying gain steps in aligned multiples of 4. */ if(const size_t todo{fade_len >> 2}) { const auto four4 = _mm_set1_ps(4.0f); const auto step4 = _mm_set1_ps(step); const auto gain4 = _mm_set1_ps(gain); auto step_count4 = _mm_setr_ps(0.0f, 1.0f, 2.0f, 3.0f); const auto in4 = al::span{reinterpret_cast(InSamples.data()), InSamples.size()/4}.first(todo); const auto out4 = al::span{reinterpret_cast<__m128*>(dst.data()), dst.size()/4}; std::transform(in4.begin(), in4.end(), out4.begin(), out4.begin(), [gain4,step4,four4,&step_count4](const __m128 val4, __m128 dry4) -> __m128 { /* dry += val * (gain + step*step_count) */ dry4 = vmadd(dry4, val4, vmadd(gain4, step4, step_count4)); step_count4 = _mm_add_ps(step_count4, four4); return dry4; }); pos += in4.size()*4; /* NOTE: step_count4 now represents the next four counts after the * last four mixed samples, so the lowest element represents the * next step count to apply. */ step_count = _mm_cvtss_f32(step_count4); } /* Mix with applying left over gain steps that aren't aligned multiples of 4. */ if(const size_t leftover{fade_len&3}) { const auto in = InSamples.subspan(pos, leftover); const auto out = dst.subspan(pos); std::transform(in.begin(), in.end(), out.begin(), out.begin(), [gain,step,&step_count](const float val, float dry) noexcept -> float { dry += val * (gain + step*step_count); step_count += 1.0f; return dry; }); pos += leftover; } if(pos < Counter) { CurrentGain = gain + step*step_count; return; } /* Mix until pos is aligned with 4 or the mix is done. */ if(const size_t leftover{realign_len&3}) { const auto in = InSamples.subspan(pos, leftover); const auto out = dst.subspan(pos); std::transform(in.begin(), in.end(), out.begin(), out.begin(), [TargetGain](const float val, const float dry) noexcept -> float { return dry + val*TargetGain; }); pos += leftover; } } CurrentGain = TargetGain; if(!(std::abs(TargetGain) > GainSilenceThreshold)) return; if(size_t todo{(InSamples.size()-pos) >> 2}) { const auto in4 = al::span{reinterpret_cast(InSamples.data()), InSamples.size()/4}.last(todo); const auto out = dst.subspan(pos); const auto out4 = al::span{reinterpret_cast<__m128*>(out.data()), out.size()/4}; const auto gain4 = _mm_set1_ps(TargetGain); std::transform(in4.begin(), in4.end(), out4.begin(), out4.begin(), [gain4](const __m128 val4, const __m128 dry4) -> __m128 { return vmadd(dry4, val4, gain4); }); pos += in4.size()*4; } if(const size_t leftover{(InSamples.size()-pos)&3}) { const auto in = InSamples.last(leftover); const auto out = dst.subspan(pos); std::transform(in.begin(), in.end(), out.begin(), out.begin(), [TargetGain](const float val, const float dry) noexcept -> float { return dry + val*TargetGain; }); } } } // namespace template<> void Resample_(const InterpState *state, const al::span src, uint frac, const uint increment, const al::span dst) { ASSUME(frac < MixerFracOne); const auto filter = std::get(*state).filter; size_t pos{MaxResamplerEdge-1}; std::generate(dst.begin(), dst.end(), [&pos,&frac,src,increment,filter]() -> float { const uint pi{frac >> CubicPhaseDiffBits}; ASSUME(pi < CubicPhaseCount); const float pf{static_cast(frac&CubicPhaseDiffMask) * (1.0f/CubicPhaseDiffOne)}; const __m128 pf4{_mm_set1_ps(pf)}; /* Apply the phase interpolated filter. */ /* f = fil + pf*phd */ const __m128 f4 = vmadd(_mm_load_ps(filter[pi].mCoeffs.data()), pf4, _mm_load_ps(filter[pi].mDeltas.data())); /* r = f*src */ __m128 r4{_mm_mul_ps(f4, _mm_loadu_ps(&src[pos]))}; r4 = _mm_add_ps(r4, _mm_shuffle_ps(r4, r4, _MM_SHUFFLE(0, 1, 2, 3))); r4 = _mm_add_ps(r4, _mm_movehl_ps(r4, r4)); const float output{_mm_cvtss_f32(r4)}; frac += increment; pos += frac>>MixerFracBits; frac &= MixerFracMask; return output; }); } template<> void Resample_(const InterpState *state, const al::span src, uint frac, const uint increment, const al::span dst) { const auto &bsinc = std::get(*state); const auto sf4 = _mm_set1_ps(bsinc.sf); const auto m = size_t{bsinc.m}; ASSUME(m > 0); ASSUME(m <= MaxResamplerPadding); ASSUME(frac < MixerFracOne); const auto filter = bsinc.filter.first(4_uz*BSincPhaseCount*m); ASSUME(bsinc.l <= MaxResamplerEdge); auto pos = size_t{MaxResamplerEdge-bsinc.l}; std::generate(dst.begin(), dst.end(), [&pos,&frac,src,increment,sf4,m,filter]() -> float { // Calculate the phase index and factor. const size_t pi{frac >> BSincPhaseDiffBits}; ASSUME(pi < BSincPhaseCount); const float pf{static_cast(frac&BSincPhaseDiffMask) * (1.0f/BSincPhaseDiffOne)}; // Apply the scale and phase interpolated filter. auto r4 = _mm_setzero_ps(); { const auto pf4 = _mm_set1_ps(pf); const auto fil = filter.subspan(2_uz*pi*m); const auto phd = fil.subspan(m); const auto scd = fil.subspan(2_uz*BSincPhaseCount*m); const auto spd = scd.subspan(m); auto td = size_t{m >> 2}; auto j = size_t{0}; do { /* f = ((fil + sf*scd) + pf*(phd + sf*spd)) */ const __m128 f4 = vmadd( vmadd(_mm_load_ps(&fil[j]), sf4, _mm_load_ps(&scd[j])), pf4, vmadd(_mm_load_ps(&phd[j]), sf4, _mm_load_ps(&spd[j]))); /* r += f*src */ r4 = vmadd(r4, f4, _mm_loadu_ps(&src[pos+j])); j += 4; } while(--td); } r4 = _mm_add_ps(r4, _mm_shuffle_ps(r4, r4, _MM_SHUFFLE(0, 1, 2, 3))); r4 = _mm_add_ps(r4, _mm_movehl_ps(r4, r4)); const auto output = _mm_cvtss_f32(r4); frac += increment; pos += frac>>MixerFracBits; frac &= MixerFracMask; return output; }); } template<> void Resample_(const InterpState *state, const al::span src, uint frac, const uint increment, const al::span dst) { const auto &bsinc = std::get(*state); const auto m = size_t{bsinc.m}; ASSUME(m > 0); ASSUME(m <= MaxResamplerPadding); ASSUME(frac < MixerFracOne); const auto filter = bsinc.filter.first(2_uz*m*BSincPhaseCount); ASSUME(bsinc.l <= MaxResamplerEdge); size_t pos{MaxResamplerEdge-bsinc.l}; std::generate(dst.begin(), dst.end(), [&pos,&frac,src,increment,filter,m]() -> float { // Calculate the phase index and factor. const size_t pi{frac >> BSincPhaseDiffBits}; ASSUME(pi < BSincPhaseCount); const float pf{static_cast(frac&BSincPhaseDiffMask) * (1.0f/BSincPhaseDiffOne)}; // Apply the phase interpolated filter. auto r4 = _mm_setzero_ps(); { const auto pf4 = _mm_set1_ps(pf); const auto fil = filter.subspan(2_uz*m*pi); const auto phd = fil.subspan(m); auto td = size_t{m >> 2}; auto j = size_t{0}; do { /* f = fil + pf*phd */ const auto f4 = vmadd(_mm_load_ps(&fil[j]), pf4, _mm_load_ps(&phd[j])); /* r += f*src */ r4 = vmadd(r4, f4, _mm_loadu_ps(&src[pos+j])); j += 4; } while(--td); } r4 = _mm_add_ps(r4, _mm_shuffle_ps(r4, r4, _MM_SHUFFLE(0, 1, 2, 3))); r4 = _mm_add_ps(r4, _mm_movehl_ps(r4, r4)); const auto output = _mm_cvtss_f32(r4); frac += increment; pos += frac>>MixerFracBits; frac &= MixerFracMask; return output; }); } template<> void MixHrtf_(const al::span InSamples, const al::span AccumSamples, const uint IrSize, const MixHrtfFilter *hrtfparams, const size_t SamplesToDo) { MixHrtfBase(InSamples, AccumSamples, IrSize, hrtfparams, SamplesToDo); } template<> void MixHrtfBlend_(const al::span InSamples, const al::span AccumSamples, const uint IrSize, const HrtfFilter *oldparams, const MixHrtfFilter *newparams, const size_t SamplesToDo) { MixHrtfBlendBase(InSamples, AccumSamples, IrSize, oldparams, newparams, SamplesToDo); } template<> void MixDirectHrtf_(const FloatBufferSpan LeftOut, const FloatBufferSpan RightOut, const al::span InSamples, const al::span AccumSamples, const al::span TempBuf, const al::span ChanState, const size_t IrSize, const size_t SamplesToDo) { MixDirectHrtfBase(LeftOut, RightOut, InSamples, AccumSamples, TempBuf, ChanState, IrSize, SamplesToDo); } template<> void Mix_(const al::span InSamples, const al::span OutBuffer, const al::span CurrentGains, const al::span TargetGains, const size_t Counter, const size_t OutPos) { if((OutPos&3) != 0) UNLIKELY return Mix_(InSamples, OutBuffer, CurrentGains, TargetGains, Counter, OutPos); const float delta{(Counter > 0) ? 1.0f / static_cast(Counter) : 0.0f}; const auto fade_len = std::min(Counter, InSamples.size()); const auto realign_len = std::min((fade_len+3_uz) & ~3_uz, InSamples.size()) - fade_len; auto curgains = CurrentGains.begin(); auto targetgains = TargetGains.cbegin(); for(FloatBufferLine &output : OutBuffer) MixLine(InSamples, al::span{output}.subspan(OutPos), *curgains++, *targetgains++, delta, fade_len, realign_len, Counter); } template<> void Mix_(const al::span InSamples, const al::span OutBuffer, float &CurrentGain, const float TargetGain, const size_t Counter) { if((reinterpret_cast(OutBuffer.data())&15) != 0) UNLIKELY return Mix_(InSamples, OutBuffer, CurrentGain, TargetGain, Counter); const float delta{(Counter > 0) ? 1.0f / static_cast(Counter) : 0.0f}; const auto fade_len = std::min(Counter, InSamples.size()); const auto realign_len = std::min((fade_len+3_uz) & ~3_uz, InSamples.size()) - fade_len; MixLine(InSamples, OutBuffer, CurrentGain, TargetGain, delta, fade_len, realign_len, Counter); } openal-soft-1.24.2/core/mixer/mixer_sse2.cpp000066400000000000000000000214661474041540300207020ustar00rootroot00000000000000/** * OpenAL cross platform audio library * Copyright (C) 2014 by Timothy Arceri . * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * Or go to http://www.gnu.org/copyleft/lgpl.html */ #include "config.h" #include #include #include #include #include #include #include "alnumeric.h" #include "alspan.h" #include "core/cubic_defs.h" #include "core/resampler_limits.h" #include "defs.h" #include "opthelpers.h" struct SSE2Tag; struct LerpTag; struct CubicTag; #if defined(__GNUC__) && !defined(__clang__) && !defined(__SSE2__) #pragma GCC target("sse2") #endif using uint = unsigned int; namespace { constexpr uint CubicPhaseDiffBits{MixerFracBits - CubicPhaseBits}; constexpr uint CubicPhaseDiffOne{1 << CubicPhaseDiffBits}; constexpr uint CubicPhaseDiffMask{CubicPhaseDiffOne - 1u}; force_inline __m128 vmadd(const __m128 x, const __m128 y, const __m128 z) noexcept { return _mm_add_ps(x, _mm_mul_ps(y, z)); } } // namespace template<> void Resample_(const InterpState*, const al::span src, uint frac, const uint increment, const al::span dst) { ASSUME(frac < MixerFracOne); const __m128i increment4{_mm_set1_epi32(static_cast(increment*4))}; const __m128 fracOne4{_mm_set1_ps(1.0f/MixerFracOne)}; const __m128i fracMask4{_mm_set1_epi32(MixerFracMask)}; std::array pos_{}, frac_{}; InitPosArrays(MaxResamplerEdge, frac, increment, al::span{frac_}, al::span{pos_}); __m128i frac4{_mm_setr_epi32(static_cast(frac_[0]), static_cast(frac_[1]), static_cast(frac_[2]), static_cast(frac_[3]))}; __m128i pos4{_mm_setr_epi32(static_cast(pos_[0]), static_cast(pos_[1]), static_cast(pos_[2]), static_cast(pos_[3]))}; auto vecout = al::span{reinterpret_cast<__m128*>(dst.data()), dst.size()/4}; std::generate(vecout.begin(), vecout.end(), [=,&pos4,&frac4]() -> __m128 { const auto pos0 = static_cast(_mm_cvtsi128_si32(pos4)); const auto pos1 = static_cast(_mm_cvtsi128_si32(_mm_srli_si128(pos4, 4))); const auto pos2 = static_cast(_mm_cvtsi128_si32(_mm_srli_si128(pos4, 8))); const auto pos3 = static_cast(_mm_cvtsi128_si32(_mm_srli_si128(pos4, 12))); ASSUME(pos0 <= pos1); ASSUME(pos1 <= pos2); ASSUME(pos2 <= pos3); const __m128 val1{_mm_setr_ps(src[pos0], src[pos1], src[pos2], src[pos3])}; const __m128 val2{_mm_setr_ps(src[pos0+1_uz], src[pos1+1_uz], src[pos2+1_uz], src[pos3+1_uz])}; /* val1 + (val2-val1)*mu */ const __m128 r0{_mm_sub_ps(val2, val1)}; const __m128 mu{_mm_mul_ps(_mm_cvtepi32_ps(frac4), fracOne4)}; const __m128 out{_mm_add_ps(val1, _mm_mul_ps(mu, r0))}; frac4 = _mm_add_epi32(frac4, increment4); pos4 = _mm_add_epi32(pos4, _mm_srli_epi32(frac4, MixerFracBits)); frac4 = _mm_and_si128(frac4, fracMask4); return out; }); if(size_t todo{dst.size()&3}) { auto pos = size_t{static_cast(_mm_cvtsi128_si32(pos4))}; frac = static_cast(_mm_cvtsi128_si32(frac4)); const auto out = dst.last(todo); std::generate(out.begin(), out.end(), [&pos,&frac,src,increment]() { const float smp{lerpf(src[pos+0], src[pos+1], static_cast(frac) * (1.0f/MixerFracOne))}; frac += increment; pos += frac>>MixerFracBits; frac &= MixerFracMask; return smp; }); } } template<> void Resample_(const InterpState *state, const al::span src, uint frac, const uint increment, const al::span dst) { ASSUME(frac < MixerFracOne); const auto filter = std::get(*state).filter; const __m128i increment4{_mm_set1_epi32(static_cast(increment*4))}; const __m128i fracMask4{_mm_set1_epi32(MixerFracMask)}; const __m128 fracDiffOne4{_mm_set1_ps(1.0f/CubicPhaseDiffOne)}; const __m128i fracDiffMask4{_mm_set1_epi32(CubicPhaseDiffMask)}; std::array pos_{}, frac_{}; InitPosArrays(MaxResamplerEdge-1, frac, increment, al::span{frac_}, al::span{pos_}); __m128i frac4{_mm_setr_epi32(static_cast(frac_[0]), static_cast(frac_[1]), static_cast(frac_[2]), static_cast(frac_[3]))}; __m128i pos4{_mm_setr_epi32(static_cast(pos_[0]), static_cast(pos_[1]), static_cast(pos_[2]), static_cast(pos_[3]))}; auto vecout = al::span{reinterpret_cast<__m128*>(dst.data()), dst.size()/4}; std::generate(vecout.begin(), vecout.end(), [=,&pos4,&frac4] { const auto pos0 = static_cast(_mm_cvtsi128_si32(pos4)); const auto pos1 = static_cast(_mm_cvtsi128_si32(_mm_srli_si128(pos4, 4))); const auto pos2 = static_cast(_mm_cvtsi128_si32(_mm_srli_si128(pos4, 8))); const auto pos3 = static_cast(_mm_cvtsi128_si32(_mm_srli_si128(pos4, 12))); ASSUME(pos0 <= pos1); ASSUME(pos1 <= pos2); ASSUME(pos2 <= pos3); const __m128 val0{_mm_loadu_ps(&src[pos0])}; const __m128 val1{_mm_loadu_ps(&src[pos1])}; const __m128 val2{_mm_loadu_ps(&src[pos2])}; const __m128 val3{_mm_loadu_ps(&src[pos3])}; const __m128i pi4{_mm_srli_epi32(frac4, CubicPhaseDiffBits)}; const auto pi0 = static_cast(_mm_cvtsi128_si32(pi4)); const auto pi1 = static_cast(_mm_cvtsi128_si32(_mm_srli_si128(pi4, 4))); const auto pi2 = static_cast(_mm_cvtsi128_si32(_mm_srli_si128(pi4, 8))); const auto pi3 = static_cast(_mm_cvtsi128_si32(_mm_srli_si128(pi4, 12))); ASSUME(pi0 < CubicPhaseCount); ASSUME(pi1 < CubicPhaseCount); ASSUME(pi2 < CubicPhaseCount); ASSUME(pi3 < CubicPhaseCount); const __m128 pf4{_mm_mul_ps(_mm_cvtepi32_ps(_mm_and_si128(frac4, fracDiffMask4)), fracDiffOne4)}; __m128 r0{_mm_mul_ps(val0, vmadd(_mm_load_ps(filter[pi0].mCoeffs.data()), _mm_shuffle_ps(pf4, pf4, _MM_SHUFFLE(0, 0, 0, 0)), _mm_load_ps(filter[pi0].mDeltas.data())))}; __m128 r1{_mm_mul_ps(val1, vmadd(_mm_load_ps(filter[pi1].mCoeffs.data()), _mm_shuffle_ps(pf4, pf4, _MM_SHUFFLE(1, 1, 1, 1)), _mm_load_ps(filter[pi1].mDeltas.data())))}; __m128 r2{_mm_mul_ps(val2, vmadd(_mm_load_ps(filter[pi2].mCoeffs.data()), _mm_shuffle_ps(pf4, pf4, _MM_SHUFFLE(2, 2, 2, 2)), _mm_load_ps(filter[pi2].mDeltas.data())))}; __m128 r3{_mm_mul_ps(val3, vmadd(_mm_load_ps(filter[pi3].mCoeffs.data()), _mm_shuffle_ps(pf4, pf4, _MM_SHUFFLE(3, 3, 3, 3)), _mm_load_ps(filter[pi3].mDeltas.data())))}; _MM_TRANSPOSE4_PS(r0, r1, r2, r3); r0 = _mm_add_ps(_mm_add_ps(r0, r1), _mm_add_ps(r2, r3)); frac4 = _mm_add_epi32(frac4, increment4); pos4 = _mm_add_epi32(pos4, _mm_srli_epi32(frac4, MixerFracBits)); frac4 = _mm_and_si128(frac4, fracMask4); return r0; }); if(const size_t todo{dst.size()&3}) { auto pos = size_t{static_cast(_mm_cvtsi128_si32(pos4))}; frac = static_cast(_mm_cvtsi128_si32(frac4)); auto out = dst.last(todo); std::generate(out.begin(), out.end(), [&pos,&frac,src,increment,filter] { const uint pi{frac >> CubicPhaseDiffBits}; ASSUME(pi < CubicPhaseCount); const float pf{static_cast(frac&CubicPhaseDiffMask) * (1.0f/CubicPhaseDiffOne)}; const __m128 pf4{_mm_set1_ps(pf)}; const __m128 f4 = vmadd(_mm_load_ps(filter[pi].mCoeffs.data()), pf4, _mm_load_ps(filter[pi].mDeltas.data())); __m128 r4{_mm_mul_ps(f4, _mm_loadu_ps(&src[pos]))}; r4 = _mm_add_ps(r4, _mm_shuffle_ps(r4, r4, _MM_SHUFFLE(0, 1, 2, 3))); r4 = _mm_add_ps(r4, _mm_movehl_ps(r4, r4)); const float output{_mm_cvtss_f32(r4)}; frac += increment; pos += frac>>MixerFracBits; frac &= MixerFracMask; return output; }); } } openal-soft-1.24.2/core/mixer/mixer_sse3.cpp000066400000000000000000000000001474041540300206600ustar00rootroot00000000000000openal-soft-1.24.2/core/mixer/mixer_sse41.cpp000066400000000000000000000215501474041540300207570ustar00rootroot00000000000000/** * OpenAL cross platform audio library * Copyright (C) 2014 by Timothy Arceri . * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * Or go to http://www.gnu.org/copyleft/lgpl.html */ #include "config.h" #include #include #include #include #include #include #include #include "alnumeric.h" #include "alspan.h" #include "core/cubic_defs.h" #include "core/resampler_limits.h" #include "defs.h" #include "opthelpers.h" struct SSE4Tag; struct LerpTag; struct CubicTag; #if defined(__GNUC__) && !defined(__clang__) && !defined(__SSE4_1__) #pragma GCC target("sse4.1") #endif using uint = unsigned int; namespace { constexpr uint CubicPhaseDiffBits{MixerFracBits - CubicPhaseBits}; constexpr uint CubicPhaseDiffOne{1 << CubicPhaseDiffBits}; constexpr uint CubicPhaseDiffMask{CubicPhaseDiffOne - 1u}; force_inline __m128 vmadd(const __m128 x, const __m128 y, const __m128 z) noexcept { return _mm_add_ps(x, _mm_mul_ps(y, z)); } } // namespace template<> void Resample_(const InterpState*, const al::span src, uint frac, const uint increment, const al::span dst) { ASSUME(frac < MixerFracOne); const __m128i increment4{_mm_set1_epi32(static_cast(increment*4))}; const __m128 fracOne4{_mm_set1_ps(1.0f/MixerFracOne)}; const __m128i fracMask4{_mm_set1_epi32(MixerFracMask)}; std::array pos_{}, frac_{}; InitPosArrays(MaxResamplerEdge, frac, increment, al::span{frac_}, al::span{pos_}); __m128i frac4{_mm_setr_epi32(static_cast(frac_[0]), static_cast(frac_[1]), static_cast(frac_[2]), static_cast(frac_[3]))}; __m128i pos4{_mm_setr_epi32(static_cast(pos_[0]), static_cast(pos_[1]), static_cast(pos_[2]), static_cast(pos_[3]))}; auto vecout = al::span{reinterpret_cast<__m128*>(dst.data()), dst.size()/4}; std::generate(vecout.begin(), vecout.end(), [=,&pos4,&frac4] { const auto pos0 = static_cast(_mm_extract_epi32(pos4, 0)); const auto pos1 = static_cast(_mm_extract_epi32(pos4, 1)); const auto pos2 = static_cast(_mm_extract_epi32(pos4, 2)); const auto pos3 = static_cast(_mm_extract_epi32(pos4, 3)); ASSUME(pos0 <= pos1); ASSUME(pos1 <= pos2); ASSUME(pos2 <= pos3); const __m128 val1{_mm_setr_ps(src[pos0], src[pos1], src[pos2], src[pos3])}; const __m128 val2{_mm_setr_ps(src[pos0+1_uz], src[pos1+1_uz], src[pos2+1_uz], src[pos3+1_uz])}; /* val1 + (val2-val1)*mu */ const __m128 r0{_mm_sub_ps(val2, val1)}; const __m128 mu{_mm_mul_ps(_mm_cvtepi32_ps(frac4), fracOne4)}; const __m128 out{_mm_add_ps(val1, _mm_mul_ps(mu, r0))}; frac4 = _mm_add_epi32(frac4, increment4); pos4 = _mm_add_epi32(pos4, _mm_srli_epi32(frac4, MixerFracBits)); frac4 = _mm_and_si128(frac4, fracMask4); return out; }); if(size_t todo{dst.size()&3}) { /* NOTE: These four elements represent the position *after* the last * four samples, so the lowest element is the next position to * resample. */ auto pos = size_t{static_cast(_mm_cvtsi128_si32(pos4))}; frac = static_cast(_mm_cvtsi128_si32(frac4)); auto out = dst.last(todo); std::generate(out.begin(), out.end(), [&pos,&frac,src,increment] { const float smp{lerpf(src[pos+0], src[pos+1], static_cast(frac) * (1.0f/MixerFracOne))}; frac += increment; pos += frac>>MixerFracBits; frac &= MixerFracMask; return smp; }); } } template<> void Resample_(const InterpState *state, const al::span src, uint frac, const uint increment, const al::span dst) { ASSUME(frac < MixerFracOne); const auto filter = std::get(*state).filter; const __m128i increment4{_mm_set1_epi32(static_cast(increment*4))}; const __m128i fracMask4{_mm_set1_epi32(MixerFracMask)}; const __m128 fracDiffOne4{_mm_set1_ps(1.0f/CubicPhaseDiffOne)}; const __m128i fracDiffMask4{_mm_set1_epi32(CubicPhaseDiffMask)}; std::array pos_{}, frac_{}; InitPosArrays(MaxResamplerEdge-1, frac, increment, al::span{frac_}, al::span{pos_}); __m128i frac4{_mm_setr_epi32(static_cast(frac_[0]), static_cast(frac_[1]), static_cast(frac_[2]), static_cast(frac_[3]))}; __m128i pos4{_mm_setr_epi32(static_cast(pos_[0]), static_cast(pos_[1]), static_cast(pos_[2]), static_cast(pos_[3]))}; auto vecout = al::span{reinterpret_cast<__m128*>(dst.data()), dst.size()/4}; std::generate(vecout.begin(), vecout.end(), [=,&pos4,&frac4] { const auto pos0 = static_cast(_mm_extract_epi32(pos4, 0)); const auto pos1 = static_cast(_mm_extract_epi32(pos4, 1)); const auto pos2 = static_cast(_mm_extract_epi32(pos4, 2)); const auto pos3 = static_cast(_mm_extract_epi32(pos4, 3)); ASSUME(pos0 <= pos1); ASSUME(pos1 <= pos2); ASSUME(pos2 <= pos3); const __m128 val0{_mm_loadu_ps(&src[pos0])}; const __m128 val1{_mm_loadu_ps(&src[pos1])}; const __m128 val2{_mm_loadu_ps(&src[pos2])}; const __m128 val3{_mm_loadu_ps(&src[pos3])}; const __m128i pi4{_mm_srli_epi32(frac4, CubicPhaseDiffBits)}; const auto pi0 = static_cast(_mm_extract_epi32(pi4, 0)); const auto pi1 = static_cast(_mm_extract_epi32(pi4, 1)); const auto pi2 = static_cast(_mm_extract_epi32(pi4, 2)); const auto pi3 = static_cast(_mm_extract_epi32(pi4, 3)); ASSUME(pi0 < CubicPhaseCount); ASSUME(pi1 < CubicPhaseCount); ASSUME(pi2 < CubicPhaseCount); ASSUME(pi3 < CubicPhaseCount); const __m128 pf4{_mm_mul_ps(_mm_cvtepi32_ps(_mm_and_si128(frac4, fracDiffMask4)), fracDiffOne4)}; __m128 r0{_mm_mul_ps(val0, vmadd(_mm_load_ps(filter[pi0].mCoeffs.data()), _mm_shuffle_ps(pf4, pf4, _MM_SHUFFLE(0, 0, 0, 0)), _mm_load_ps(filter[pi0].mDeltas.data())))}; __m128 r1{_mm_mul_ps(val1, vmadd(_mm_load_ps(filter[pi1].mCoeffs.data()), _mm_shuffle_ps(pf4, pf4, _MM_SHUFFLE(1, 1, 1, 1)), _mm_load_ps(filter[pi1].mDeltas.data())))}; __m128 r2{_mm_mul_ps(val2, vmadd(_mm_load_ps(filter[pi2].mCoeffs.data()), _mm_shuffle_ps(pf4, pf4, _MM_SHUFFLE(2, 2, 2, 2)), _mm_load_ps(filter[pi2].mDeltas.data())))}; __m128 r3{_mm_mul_ps(val3, vmadd(_mm_load_ps(filter[pi3].mCoeffs.data()), _mm_shuffle_ps(pf4, pf4, _MM_SHUFFLE(3, 3, 3, 3)), _mm_load_ps(filter[pi3].mDeltas.data())))}; _MM_TRANSPOSE4_PS(r0, r1, r2, r3); r0 = _mm_add_ps(_mm_add_ps(r0, r1), _mm_add_ps(r2, r3)); frac4 = _mm_add_epi32(frac4, increment4); pos4 = _mm_add_epi32(pos4, _mm_srli_epi32(frac4, MixerFracBits)); frac4 = _mm_and_si128(frac4, fracMask4); return r0; }); if(const size_t todo{dst.size()&3}) { auto pos = size_t{static_cast(_mm_cvtsi128_si32(pos4))}; frac = static_cast(_mm_cvtsi128_si32(frac4)); auto out = dst.last(todo); std::generate(out.begin(), out.end(), [&pos,&frac,src,increment,filter] { const uint pi{frac >> CubicPhaseDiffBits}; ASSUME(pi < CubicPhaseCount); const float pf{static_cast(frac&CubicPhaseDiffMask) * (1.0f/CubicPhaseDiffOne)}; const __m128 pf4{_mm_set1_ps(pf)}; const __m128 f4 = vmadd(_mm_load_ps(filter[pi].mCoeffs.data()), pf4, _mm_load_ps(filter[pi].mDeltas.data())); __m128 r4{_mm_mul_ps(f4, _mm_loadu_ps(&src[pos]))}; r4 = _mm_add_ps(r4, _mm_shuffle_ps(r4, r4, _MM_SHUFFLE(0, 1, 2, 3))); r4 = _mm_add_ps(r4, _mm_movehl_ps(r4, r4)); const float output{_mm_cvtss_f32(r4)}; frac += increment; pos += frac>>MixerFracBits; frac &= MixerFracMask; return output; }); } } openal-soft-1.24.2/core/resampler_limits.h000066400000000000000000000006011474041540300205020ustar00rootroot00000000000000#ifndef CORE_RESAMPLER_LIMITS_H #define CORE_RESAMPLER_LIMITS_H /* Maximum number of samples to pad on the ends of a buffer for resampling. * Note that the padding is symmetric (half at the beginning and half at the * end)! */ constexpr unsigned int MaxResamplerPadding{48}; constexpr unsigned int MaxResamplerEdge{MaxResamplerPadding >> 1}; #endif /* CORE_RESAMPLER_LIMITS_H */ openal-soft-1.24.2/core/rtkit.cpp000066400000000000000000000161141474041540300166250ustar00rootroot00000000000000/*-*- Mode: C; c-basic-offset: 8 -*-*/ /*** Copyright 2009 Lennart Poettering Copyright 2010 David Henningsson Copyright 2021 Chris Robinson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ***/ #include "config.h" #include "rtkit.h" #include #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include #include #include #include #ifdef __linux__ #include #elif defined(__FreeBSD__) #include #endif namespace dbus { constexpr int TypeString{'s'}; constexpr int TypeVariant{'v'}; constexpr int TypeInt32{'i'}; constexpr int TypeUInt32{'u'}; constexpr int TypeInt64{'x'}; constexpr int TypeUInt64{'t'}; constexpr int TypeInvalid{'\0'}; struct MessageDeleter { void operator()(DBusMessage *m) { dbus_message_unref(m); } }; using MessagePtr = std::unique_ptr; } // namespace dbus namespace { inline pid_t _gettid() { #ifdef __linux__ /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) */ return static_cast(syscall(SYS_gettid)); #elif defined(__FreeBSD__) long pid{}; thr_self(&pid); return static_cast(pid); #else #warning gettid not available return 0; #endif } int translate_error(const char *name) { if(strcmp(name, DBUS_ERROR_NO_MEMORY) == 0) return -ENOMEM; if(strcmp(name, DBUS_ERROR_SERVICE_UNKNOWN) == 0 || strcmp(name, DBUS_ERROR_NAME_HAS_NO_OWNER) == 0) return -ENOENT; if(strcmp(name, DBUS_ERROR_ACCESS_DENIED) == 0 || strcmp(name, DBUS_ERROR_AUTH_FAILED) == 0) return -EACCES; return -EIO; } int rtkit_get_int_property(DBusConnection *connection, const char *propname, long long *propval) { dbus::MessagePtr m{dbus_message_new_method_call(RTKIT_SERVICE_NAME, RTKIT_OBJECT_PATH, "org.freedesktop.DBus.Properties", "Get")}; if(!m) return -ENOMEM; const char *interfacestr = RTKIT_SERVICE_NAME; auto ready = dbus_message_append_args(m.get(), dbus::TypeString, &interfacestr, dbus::TypeString, &propname, dbus::TypeInvalid); if(!ready) return -ENOMEM; dbus::Error error; dbus::MessagePtr r{dbus_connection_send_with_reply_and_block(connection, m.get(), -1, &error.get())}; if(!r) return translate_error(error->name); if(dbus_set_error_from_message(&error.get(), r.get())) return translate_error(error->name); int ret{-EBADMSG}; DBusMessageIter iter{}; dbus_message_iter_init(r.get(), &iter); while(int curtype{dbus_message_iter_get_arg_type(&iter)}) { if(curtype == dbus::TypeVariant) { DBusMessageIter subiter{}; dbus_message_iter_recurse(&iter, &subiter); while((curtype=dbus_message_iter_get_arg_type(&subiter)) != dbus::TypeInvalid) { if(curtype == dbus::TypeInt32) { dbus_int32_t i32{}; dbus_message_iter_get_basic(&subiter, &i32); *propval = i32; ret = 0; } if(curtype == dbus::TypeInt64) { dbus_int64_t i64{}; dbus_message_iter_get_basic(&subiter, &i64); *propval = i64; ret = 0; } dbus_message_iter_next(&subiter); } } dbus_message_iter_next(&iter); } return ret; } } // namespace int rtkit_get_max_realtime_priority(DBusConnection *system_bus) { long long retval{}; int err{rtkit_get_int_property(system_bus, "MaxRealtimePriority", &retval)}; return err < 0 ? err : static_cast(retval); } int rtkit_get_min_nice_level(DBusConnection *system_bus, int *min_nice_level) { long long retval{}; int err{rtkit_get_int_property(system_bus, "MinNiceLevel", &retval)}; if(err >= 0) *min_nice_level = static_cast(retval); return err; } long long rtkit_get_rttime_usec_max(DBusConnection *system_bus) { long long retval{}; int err{rtkit_get_int_property(system_bus, "RTTimeUSecMax", &retval)}; return err < 0 ? err : retval; } int rtkit_make_realtime(DBusConnection *system_bus, pid_t thread, int priority) { if(thread == 0) thread = _gettid(); if(thread == 0) return -ENOTSUP; dbus::MessagePtr m{dbus_message_new_method_call(RTKIT_SERVICE_NAME, RTKIT_OBJECT_PATH, "org.freedesktop.RealtimeKit1", "MakeThreadRealtime")}; if(!m) return -ENOMEM; auto u64 = static_cast(thread); auto u32 = static_cast(priority); auto ready = dbus_message_append_args(m.get(), dbus::TypeUInt64, &u64, dbus::TypeUInt32, &u32, dbus::TypeInvalid); if(!ready) return -ENOMEM; dbus::Error error; dbus::MessagePtr r{dbus_connection_send_with_reply_and_block(system_bus, m.get(), -1, &error.get())}; if(!r) return translate_error(error->name); if(dbus_set_error_from_message(&error.get(), r.get())) return translate_error(error->name); return 0; } int rtkit_make_high_priority(DBusConnection *system_bus, pid_t thread, int nice_level) { if(thread == 0) thread = _gettid(); if(thread == 0) return -ENOTSUP; dbus::MessagePtr m{dbus_message_new_method_call(RTKIT_SERVICE_NAME, RTKIT_OBJECT_PATH, "org.freedesktop.RealtimeKit1", "MakeThreadHighPriority")}; if(!m) return -ENOMEM; auto u64 = static_cast(thread); auto s32 = static_cast(nice_level); auto ready = dbus_message_append_args(m.get(), dbus::TypeUInt64, &u64, dbus::TypeInt32, &s32, dbus::TypeInvalid); if(!ready) return -ENOMEM; dbus::Error error; dbus::MessagePtr r{dbus_connection_send_with_reply_and_block(system_bus, m.get(), -1, &error.get())}; if(!r) return translate_error(error->name); if(dbus_set_error_from_message(&error.get(), r.get())) return translate_error(error->name); return 0; } openal-soft-1.24.2/core/rtkit.h000066400000000000000000000061431474041540300162730ustar00rootroot00000000000000/*-*- Mode: C; c-basic-offset: 8 -*-*/ #ifndef foortkithfoo #define foortkithfoo /*** Copyright 2009 Lennart Poettering Copyright 2010 David Henningsson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ***/ #include #include "dbus_wrap.h" /* This is the reference implementation for a client for * RealtimeKit. You don't have to use this, but if do, just copy these * sources into your repository */ #define RTKIT_SERVICE_NAME "org.freedesktop.RealtimeKit1" #define RTKIT_OBJECT_PATH "/org/freedesktop/RealtimeKit1" /* This is mostly equivalent to sched_setparam(thread, SCHED_RR, { * .sched_priority = priority }). 'thread' needs to be a kernel thread * id as returned by gettid(), not a pthread_t! If 'thread' is 0 the * current thread is used. The returned value is a negative errno * style error code, or 0 on success. */ int rtkit_make_realtime(DBusConnection *system_bus, pid_t thread, int priority); /* This is mostly equivalent to setpriority(PRIO_PROCESS, thread, * nice_level). 'thread' needs to be a kernel thread id as returned by * gettid(), not a pthread_t! If 'thread' is 0 the current thread is * used. The returned value is a negative errno style error code, or 0 * on success.*/ int rtkit_make_high_priority(DBusConnection *system_bus, pid_t thread, int nice_level); /* Return the maximum value of realtime priority available. Realtime requests * above this value will fail. A negative value is an errno style error code. */ int rtkit_get_max_realtime_priority(DBusConnection *system_bus); /* Retreive the minimum value of nice level available. High prio requests * below this value will fail. The returned value is a negative errno * style error code, or 0 on success.*/ int rtkit_get_min_nice_level(DBusConnection *system_bus, int *min_nice_level); /* Return the maximum value of RLIMIT_RTTIME to set before attempting a * realtime request. A negative value is an errno style error code. */ long long rtkit_get_rttime_usec_max(DBusConnection *system_bus); #endif openal-soft-1.24.2/core/storage_formats.cpp000066400000000000000000000045141474041540300206700ustar00rootroot00000000000000 #include "config.h" #include "storage_formats.h" #include #include namespace { using namespace std::string_view_literals; } // namespace auto NameFromFormat(FmtType type) noexcept -> std::string_view { switch(type) { case FmtUByte: return "UInt8"sv; case FmtShort: return "Int16"sv; case FmtInt: return "Int32"sv; case FmtFloat: return "Float"sv; case FmtDouble: return "Double"sv; case FmtMulaw: return "muLaw"sv; case FmtAlaw: return "aLaw"sv; case FmtIMA4: return "IMA4 ADPCM"sv; case FmtMSADPCM: return "MS ADPCM"sv; } return ""sv; } auto NameFromFormat(FmtChannels channels) noexcept -> std::string_view { switch(channels) { case FmtMono: return "Mono"sv; case FmtStereo: return "Stereo"sv; case FmtRear: return "Rear"sv; case FmtQuad: return "Quadraphonic"sv; case FmtX51: return "Surround 5.1"sv; case FmtX61: return "Surround 6.1"sv; case FmtX71: return "Surround 7.1"sv; case FmtBFormat2D: return "B-Format 2D"sv; case FmtBFormat3D: return "B-Format 3D"sv; case FmtUHJ2: return "UHJ2"sv; case FmtUHJ3: return "UHJ3"sv; case FmtUHJ4: return "UHJ4"sv; case FmtSuperStereo: return "Super Stereo"sv; case FmtMonoDup: return "Mono (dup)"sv; } return ""sv; } uint BytesFromFmt(FmtType type) noexcept { switch(type) { case FmtUByte: return sizeof(std::uint8_t); case FmtShort: return sizeof(std::int16_t); case FmtInt: return sizeof(std::int32_t); case FmtFloat: return sizeof(float); case FmtDouble: return sizeof(double); case FmtMulaw: return sizeof(std::uint8_t); case FmtAlaw: return sizeof(std::uint8_t); case FmtIMA4: break; case FmtMSADPCM: break; } return 0; } uint ChannelsFromFmt(FmtChannels chans, uint ambiorder) noexcept { switch(chans) { case FmtMono: return 1; case FmtStereo: return 2; case FmtRear: return 2; case FmtQuad: return 4; case FmtX51: return 6; case FmtX61: return 7; case FmtX71: return 8; case FmtBFormat2D: return (ambiorder*2) + 1; case FmtBFormat3D: return (ambiorder+1) * (ambiorder+1); case FmtUHJ2: return 2; case FmtUHJ3: return 3; case FmtUHJ4: return 4; case FmtSuperStereo: return 2; case FmtMonoDup: return 1; } return 0; } openal-soft-1.24.2/core/storage_formats.h000066400000000000000000000025361474041540300203370ustar00rootroot00000000000000#ifndef CORE_STORAGE_FORMATS_H #define CORE_STORAGE_FORMATS_H #include using uint = unsigned int; /* Storable formats */ enum FmtType : unsigned char { FmtUByte, FmtShort, FmtInt, FmtFloat, FmtDouble, FmtMulaw, FmtAlaw, FmtIMA4, FmtMSADPCM, }; enum FmtChannels : unsigned char { FmtMono, FmtStereo, FmtRear, FmtQuad, FmtX51, /* (WFX order) */ FmtX61, /* (WFX order) */ FmtX71, /* (WFX order) */ FmtBFormat2D, FmtBFormat3D, FmtUHJ2, /* 2-channel UHJ, aka "BHJ", stereo-compatible */ FmtUHJ3, /* 3-channel UHJ, aka "THJ" */ FmtUHJ4, /* 4-channel UHJ, aka "PHJ" */ FmtSuperStereo, /* Stereo processed with Super Stereo. */ FmtMonoDup, /* Mono duplicated for left/right separation */ }; enum class AmbiLayout : unsigned char { FuMa, ACN, }; enum class AmbiScaling : unsigned char { FuMa, SN3D, N3D, UHJ, }; auto NameFromFormat(FmtType type) noexcept -> std::string_view; auto NameFromFormat(FmtChannels channels) noexcept -> std::string_view; uint BytesFromFmt(FmtType type) noexcept; uint ChannelsFromFmt(FmtChannels chans, uint ambiorder) noexcept; inline uint FrameSizeFromFmt(FmtChannels chans, FmtType type, uint ambiorder) noexcept { return ChannelsFromFmt(chans, ambiorder) * BytesFromFmt(type); } #endif /* CORE_STORAGE_FORMATS_H */ openal-soft-1.24.2/core/uhjfilter.cpp000066400000000000000000000733611474041540300174730ustar00rootroot00000000000000 #include "config.h" #include "uhjfilter.h" #include #include #include #include #include #include "alcomplex.h" #include "almalloc.h" #include "alnumbers.h" #include "core/bufferline.h" #include "opthelpers.h" #include "pffft.h" #include "phase_shifter.h" #include "vector.h" namespace { template constexpr auto assume_aligned_span(const al::span s) noexcept -> al::span { return al::span{al::assume_aligned(s.data()), s.size()}; } /* Convolution is implemented using a segmented overlap-add method. The filter * response is broken up into multiple segments of 128 samples, and each * segment has an FFT applied with a 256-sample buffer (the latter half left * silent) to get its frequency-domain response. * * Input samples are similarly broken up into 128-sample segments, with a 256- * sample FFT applied to each new incoming segment to get its frequency-domain * response. A history of FFT'd input segments is maintained, equal to the * number of filter response segments. * * To apply the convolution, each filter response segment is convolved with its * paired input segment (using complex multiplies, far cheaper than time-domain * FIRs), accumulating into an FFT buffer. The input history is then shifted to * align with later filter response segments for the next input segment. * * An inverse FFT is then applied to the accumulated FFT buffer to get a 256- * sample time-domain response for output, which is split in two halves. The * first half is the 128-sample output, and the second half is a 128-sample * (really, 127) delayed extension, which gets added to the output next time. * Convolving two time-domain responses of length N results in a time-domain * signal of length N*2 - 1, and this holds true regardless of the convolution * being applied in the frequency domain, so these "overflow" samples need to * be accounted for. */ template struct SegmentedFilter { static constexpr size_t sFftLength{256}; static constexpr size_t sSampleLength{sFftLength / 2}; static constexpr size_t sNumSegments{N/sSampleLength}; static_assert(N >= sFftLength); static_assert((N % sSampleLength) == 0); PFFFTSetup mFft; alignas(16) std::array mFilterData; SegmentedFilter() : mFft{sFftLength, PFFFT_REAL} { static constexpr size_t fft_size{N}; /* To set up the filter, we first need to generate the desired * response (not reversed). */ auto tmpBuffer = std::vector(fft_size, 0.0); for(std::size_t i{0};i < fft_size/2;++i) { const int k{int{fft_size/2} - static_cast(i*2 + 1)}; const double w{2.0*al::numbers::pi * static_cast(i*2 + 1) / double{fft_size}}; const double window{0.3635819 - 0.4891775*std::cos(w) + 0.1365995*std::cos(2.0*w) - 0.0106411*std::cos(3.0*w)}; const double pk{al::numbers::pi * static_cast(k)}; tmpBuffer[i*2 + 1] = window * (1.0-std::cos(pk)) / pk; } /* The segments of the filter are converted back to the frequency * domain, each on their own (0 stuffed). */ using complex_d = std::complex; auto fftBuffer = std::vector(sFftLength); auto fftTmp = al::vector(sFftLength); auto filter = mFilterData.begin(); for(size_t s{0};s < sNumSegments;++s) { const auto tmpspan = al::span{tmpBuffer}.subspan(sSampleLength*s, sSampleLength); auto iter = std::copy_n(tmpspan.cbegin(), tmpspan.size(), fftBuffer.begin()); std::fill(iter, fftBuffer.end(), complex_d{}); forward_fft(fftBuffer); /* Convert to zdomain data for PFFFT, scaled by the FFT length so * the iFFT result will be normalized. */ for(size_t i{0};i < sSampleLength;++i) { fftTmp[i*2 + 0] = static_cast(fftBuffer[i].real()) / float{sFftLength}; fftTmp[i*2 + 1] = static_cast((i == 0) ? fftBuffer[sSampleLength].real() : fftBuffer[i].imag()) / float{sFftLength}; } mFft.zreorder(fftTmp.data(), al::to_address(filter), PFFFT_BACKWARD); filter += sFftLength; } } }; template const SegmentedFilter gSegmentedFilter; template const PhaseShifterT PShifter; /* Filter coefficients for the 'base' all-pass IIR, which applies a frequency- * dependent phase-shift of N degrees. The output of the filter requires a 1- * sample delay. */ constexpr std::array Filter1Coeff{{ 0.479400865589f, 0.876218493539f, 0.976597589508f, 0.997499255936f }}; /* Filter coefficients for the offset all-pass IIR, which applies a frequency- * dependent phase-shift of N+90 degrees. */ constexpr std::array Filter2Coeff{{ 0.161758498368f, 0.733028932341f, 0.945349700329f, 0.990599156684f }}; void processOne(UhjAllPassFilter &self, const al::span coeffs, float x) { auto state = self.mState; for(size_t i{0};i < 4;++i) { const float y{x*coeffs[i] + state[i].z[0]}; state[i].z[0] = state[i].z[1]; state[i].z[1] = y*coeffs[i] - x; x = y; } self.mState = state; } void process(UhjAllPassFilter &self, const al::span coeffs, const al::span src, const bool updateState, const al::span dst) { auto state = self.mState; auto proc_sample = [&state,coeffs](float x) noexcept -> float { for(size_t i{0};i < 4;++i) { const float y{x*coeffs[i] + state[i].z[0]}; state[i].z[0] = state[i].z[1]; state[i].z[1] = y*coeffs[i] - x; x = y; } return x; }; std::transform(src.begin(), src.end(), dst.begin(), proc_sample); if(updateState) LIKELY self.mState = state; } } // namespace /* Encoding UHJ from B-Format is done as: * * S = 0.9396926*W + 0.1855740*X * D = j(-0.3420201*W + 0.5098604*X) + 0.6554516*Y * * Left = (S + D)/2.0 * Right = (S - D)/2.0 * T = j(-0.1432*W + 0.6512*X) - 0.7071068*Y * Q = 0.9772*Z * * where j is a wide-band +90 degree phase shift. 3-channel UHJ excludes Q, * while 2-channel excludes Q and T. * * The phase shift is done using a linear FIR filter derived from an FFT'd * impulse with the desired shift. */ template void UhjEncoder::encode(float *LeftOut, float *RightOut, const al::span InSamples, const size_t SamplesToDo) { static constexpr auto &Filter = gSegmentedFilter; static_assert(sFftLength == Filter.sFftLength); static_assert(sSegmentSize == Filter.sSampleLength); static_assert(sNumSegments == Filter.sNumSegments); ASSUME(SamplesToDo > 0); ASSUME(SamplesToDo <= BufferLineSize); const auto winput = al::span{al::assume_aligned<16>(InSamples[0]), SamplesToDo}; const auto xinput = al::span{al::assume_aligned<16>(InSamples[1]), SamplesToDo}; const auto yinput = al::span{al::assume_aligned<16>(InSamples[2]), SamplesToDo}; std::copy_n(winput.begin(), SamplesToDo, mW.begin()+sFilterDelay); std::copy_n(xinput.begin(), SamplesToDo, mX.begin()+sFilterDelay); std::copy_n(yinput.begin(), SamplesToDo, mY.begin()+sFilterDelay); /* S = 0.9396926*W + 0.1855740*X */ std::transform(mW.begin(), mW.begin()+SamplesToDo, mX.begin(), mS.begin(), [](const float w, const float x) noexcept { return 0.9396926f*w + 0.1855740f*x; }); /* Precompute j(-0.3420201*W + 0.5098604*X) and store in mD. */ auto dstore = mD.begin(); size_t curseg{mCurrentSegment}; for(size_t base{0};base < SamplesToDo;) { const size_t todo{std::min(sSegmentSize-mFifoPos, SamplesToDo-base)}; auto wseg = winput.subspan(base, todo); auto xseg = xinput.subspan(base, todo); /* Some Clang versions don't like calling subspan on an rvalue here. */ const auto wxio_ = al::span{mWXInOut}; auto wxio = wxio_.subspan(mFifoPos, todo); /* Copy out the samples that were previously processed by the FFT. */ dstore = std::copy_n(wxio.begin(), todo, dstore); /* Transform the non-delayed input and store in the front half of the * filter input. */ std::transform(wseg.begin(), wseg.end(), xseg.begin(), wxio.begin(), [](const float w, const float x) noexcept -> float { return -0.3420201f*w + 0.5098604f*x; }); mFifoPos += todo; base += todo; /* Check whether the input buffer is filled with new samples. */ if(mFifoPos < sSegmentSize) break; mFifoPos = 0; /* Copy the new input to the next history segment, clearing the back * half of the segment, and convert to the frequency domain. */ auto input = mWXHistory.begin() + curseg*sFftLength; std::copy_n(mWXInOut.begin(), sSegmentSize, input); std::fill_n(input+sSegmentSize, sSegmentSize, 0.0f); Filter.mFft.transform(al::to_address(input), al::to_address(input), mWorkData.data(), PFFFT_FORWARD); /* Convolve each input segment with its IR filter counterpart (aligned * in time, from newest to oldest). */ mFftBuffer.fill(0.0f); auto filter = Filter.mFilterData.begin(); for(size_t s{curseg};s < sNumSegments;++s) { Filter.mFft.zconvolve_accumulate(al::to_address(input), al::to_address(filter), mFftBuffer.data()); input += sFftLength; filter += sFftLength; } input = mWXHistory.begin(); for(size_t s{0};s < curseg;++s) { Filter.mFft.zconvolve_accumulate(al::to_address(input), al::to_address(filter), mFftBuffer.data()); input += sFftLength; filter += sFftLength; } /* Convert back to samples, writing to the output and storing the extra * for next time. */ Filter.mFft.transform(mFftBuffer.data(), mFftBuffer.data(), mWorkData.data(), PFFFT_BACKWARD); std::transform(mFftBuffer.begin(), mFftBuffer.begin()+sSegmentSize, mWXInOut.begin()+sSegmentSize, mWXInOut.begin(), std::plus{}); std::copy_n(mFftBuffer.begin()+sSegmentSize, sSegmentSize, mWXInOut.begin()+sSegmentSize); /* Shift the input history. */ curseg = curseg ? (curseg-1) : (sNumSegments-1); } mCurrentSegment = curseg; /* D = j(-0.3420201*W + 0.5098604*X) + 0.6554516*Y */ std::transform(mD.begin(), mD.begin()+SamplesToDo, mY.begin(), mD.begin(), [](const float jwx, const float y) noexcept { return jwx + 0.6554516f*y; }); /* Copy the future samples to the front for next time. */ std::copy(mW.cbegin()+SamplesToDo, mW.cbegin()+SamplesToDo+sFilterDelay, mW.begin()); std::copy(mX.cbegin()+SamplesToDo, mX.cbegin()+SamplesToDo+sFilterDelay, mX.begin()); std::copy(mY.cbegin()+SamplesToDo, mY.cbegin()+SamplesToDo+sFilterDelay, mY.begin()); /* Apply a delay to the existing output to align with the input delay. */ auto delayBuffer = mDirectDelay.begin(); for(float *buffer : {LeftOut, RightOut}) { const auto distbuf = assume_aligned_span<16>(al::span{*delayBuffer}); ++delayBuffer; const auto inout = al::span{al::assume_aligned<16>(buffer), SamplesToDo}; if(SamplesToDo >= sFilterDelay) { auto delay_end = std::rotate(inout.begin(), inout.end() - sFilterDelay, inout.end()); std::swap_ranges(inout.begin(), delay_end, distbuf.begin()); } else { auto delay_start = std::swap_ranges(inout.begin(), inout.end(), distbuf.begin()); std::rotate(distbuf.begin(), delay_start, distbuf.begin() + sFilterDelay); } } /* Combine the direct signal with the produced output. */ /* Left = (S + D)/2.0 */ const auto left = al::span{al::assume_aligned<16>(LeftOut), SamplesToDo}; for(size_t i{0};i < SamplesToDo;++i) left[i] += (mS[i] + mD[i]) * 0.5f; /* Right = (S - D)/2.0 */ const auto right = al::span{al::assume_aligned<16>(RightOut), SamplesToDo}; for(size_t i{0};i < SamplesToDo;++i) right[i] += (mS[i] - mD[i]) * 0.5f; } /* This encoding implementation uses two sets of four chained IIR filters to * produce the desired relative phase shift. The first filter chain produces a * phase shift of varying degrees over a wide range of frequencies, while the * second filter chain produces a phase shift 90 degrees ahead of the first * over the same range. Further details are described here: * * https://web.archive.org/web/20060708031958/http://www.biochem.oulu.fi/~oniemita/dsp/hilbert/ * * 2-channel UHJ output requires the use of three filter chains. The S channel * output uses a Filter1 chain on the W and X channel mix, while the D channel * output uses a Filter1 chain on the Y channel plus a Filter2 chain on the W * and X channel mix. This results in the W and X input mix on the D channel * output having the required +90 degree phase shift relative to the other * inputs. */ void UhjEncoderIIR::encode(float *LeftOut, float *RightOut, const al::span InSamples, const size_t SamplesToDo) { ASSUME(SamplesToDo > 0); ASSUME(SamplesToDo <= BufferLineSize); const auto winput = al::span{al::assume_aligned<16>(InSamples[0]), SamplesToDo}; const auto xinput = al::span{al::assume_aligned<16>(InSamples[1]), SamplesToDo}; const auto yinput = al::span{al::assume_aligned<16>(InSamples[2]), SamplesToDo}; /* S = 0.9396926*W + 0.1855740*X */ std::transform(winput.begin(), winput.end(), xinput.begin(), mTemp.begin(), [](const float w, const float x) noexcept { return 0.9396926f*w + 0.1855740f*x; }); process(mFilter1WX, Filter1Coeff, al::span{mTemp}.first(SamplesToDo), true, al::span{mS}.subspan(1)); mS[0] = mDelayWX; mDelayWX = mS[SamplesToDo]; /* Precompute j(-0.3420201*W + 0.5098604*X) and store in mWX. */ std::transform(winput.begin(), winput.end(), xinput.begin(), mTemp.begin(), [](const float w, const float x) noexcept { return -0.3420201f*w + 0.5098604f*x; }); process(mFilter2WX, Filter2Coeff, al::span{mTemp}.first(SamplesToDo), true, mWX); /* Apply filter1 to Y and store in mD. */ process(mFilter1Y, Filter1Coeff, yinput, true, al::span{mD}.subspan(1)); mD[0] = mDelayY; mDelayY = mD[SamplesToDo]; /* D = j(-0.3420201*W + 0.5098604*X) + 0.6554516*Y */ std::transform(mWX.begin(), mWX.begin()+SamplesToDo, mD.begin(), mD.begin(), [](const float jwx, const float y) noexcept { return jwx + 0.6554516f*y; }); /* Apply the base filter to the existing output to align with the processed * signal. */ const auto left = al::span{al::assume_aligned<16>(LeftOut), SamplesToDo}; process(mFilter1Direct[0], Filter1Coeff, left, true, al::span{mTemp}.subspan(1)); mTemp[0] = mDirectDelay[0]; mDirectDelay[0] = mTemp[SamplesToDo]; /* Left = (S + D)/2.0 */ for(size_t i{0};i < SamplesToDo;++i) left[i] = (mS[i] + mD[i])*0.5f + mTemp[i]; const auto right = al::span{al::assume_aligned<16>(RightOut), SamplesToDo}; process(mFilter1Direct[1], Filter1Coeff, right, true, al::span{mTemp}.subspan(1)); mTemp[0] = mDirectDelay[1]; mDirectDelay[1] = mTemp[SamplesToDo]; /* Right = (S - D)/2.0 */ for(size_t i{0};i < SamplesToDo;++i) right[i] = (mS[i] - mD[i])*0.5f + mTemp[i]; } /* Decoding UHJ is done as: * * S = Left + Right * D = Left - Right * * W = 0.981532*S + 0.197484*j(0.828331*D + 0.767820*T) * X = 0.418496*S - j(0.828331*D + 0.767820*T) * Y = 0.795968*D - 0.676392*T + j(0.186633*S) * Z = 1.023332*Q * * where j is a +90 degree phase shift. 3-channel UHJ excludes Q, while 2- * channel excludes Q and T. */ template void UhjDecoder::decode(const al::span samples, const size_t samplesToDo, const bool updateState) { static_assert(sInputPadding <= sMaxPadding, "Filter padding is too large"); constexpr auto &PShift = PShifter; ASSUME(samplesToDo > 0); ASSUME(samplesToDo <= BufferLineSize); { const auto left = al::span{al::assume_aligned<16>(samples[0]), samplesToDo+sInputPadding}; const auto right = al::span{al::assume_aligned<16>(samples[1]), samplesToDo+sInputPadding}; const auto t = al::span{al::assume_aligned<16>(samples[2]), samplesToDo+sInputPadding}; /* S = Left + Right */ std::transform(left.begin(), left.end(), right.begin(), mS.begin(), std::plus{}); /* D = Left - Right */ std::transform(left.begin(), left.end(), right.begin(), mD.begin(), std::minus{}); /* T */ std::copy(t.begin(), t.end(), mT.begin()); } const auto woutput = al::span{al::assume_aligned<16>(samples[0]), samplesToDo}; const auto xoutput = al::span{al::assume_aligned<16>(samples[1]), samplesToDo}; const auto youtput = al::span{al::assume_aligned<16>(samples[2]), samplesToDo}; /* Precompute j(0.828331*D + 0.767820*T) and store in xoutput. */ auto tmpiter = std::copy(mDTHistory.cbegin(), mDTHistory.cend(), mTemp.begin()); std::transform(mD.cbegin(), mD.cbegin()+samplesToDo+sInputPadding, mT.cbegin(), tmpiter, [](const float d, const float t) noexcept { return 0.828331f*d + 0.767820f*t; }); if(updateState) LIKELY std::copy_n(mTemp.cbegin()+samplesToDo, mDTHistory.size(), mDTHistory.begin()); PShift.process(xoutput, mTemp); /* W = 0.981532*S + 0.197484*j(0.828331*D + 0.767820*T) */ std::transform(mS.begin(), mS.begin()+samplesToDo, xoutput.begin(), woutput.begin(), [](const float s, const float jdt) noexcept { return 0.981532f*s + 0.197484f*jdt; }); /* X = 0.418496*S - j(0.828331*D + 0.767820*T) */ std::transform(mS.begin(), mS.begin()+samplesToDo, xoutput.begin(), xoutput.begin(), [](const float s, const float jdt) noexcept { return 0.418496f*s - jdt; }); /* Precompute j*S and store in youtput. */ tmpiter = std::copy(mSHistory.cbegin(), mSHistory.cend(), mTemp.begin()); std::copy_n(mS.cbegin(), samplesToDo+sInputPadding, tmpiter); if(updateState) LIKELY std::copy_n(mTemp.cbegin()+samplesToDo, mSHistory.size(), mSHistory.begin()); PShift.process(youtput, mTemp); /* Y = 0.795968*D - 0.676392*T + j(0.186633*S) */ for(size_t i{0};i < samplesToDo;++i) youtput[i] = 0.795968f*mD[i] - 0.676392f*mT[i] + 0.186633f*youtput[i]; if(samples.size() > 3) { const auto zoutput = al::span{al::assume_aligned<16>(samples[3]), samplesToDo}; /* Z = 1.023332*Q */ std::transform(zoutput.begin(), zoutput.end(), zoutput.begin(), [](const float q) noexcept { return 1.023332f*q; }); } } void UhjDecoderIIR::decode(const al::span samples, const size_t samplesToDo, const bool updateState) { static_assert(sInputPadding <= sMaxPadding, "Filter padding is too large"); ASSUME(samplesToDo > 0); ASSUME(samplesToDo <= BufferLineSize); { const auto left = al::span{al::assume_aligned<16>(samples[0]), samplesToDo+sInputPadding}; const auto right = al::span{al::assume_aligned<16>(samples[1]), samplesToDo+sInputPadding}; /* S = Left + Right */ std::transform(left.begin(), left.end(), right.begin(), mS.begin(), std::plus{}); /* D = Left - Right */ std::transform(left.begin(), left.end(), right.begin(), mD.begin(), std::minus{}); } const auto woutput = al::span{al::assume_aligned<16>(samples[0]), samplesToDo}; const auto xoutput = al::span{al::assume_aligned<16>(samples[1]), samplesToDo}; const auto youtput = al::span{al::assume_aligned<16>(samples[2]), samplesToDo+sInputPadding}; /* Precompute j(0.828331*D + 0.767820*T) and store in xoutput. */ std::transform(mD.cbegin(), mD.cbegin()+sInputPadding+samplesToDo, youtput.begin(), mTemp.begin(), [](const float d, const float t) noexcept { return 0.828331f*d + 0.767820f*t; }); if(mFirstRun) processOne(mFilter2DT, Filter2Coeff, mTemp[0]); process(mFilter2DT, Filter2Coeff, al::span{mTemp}.subspan(1, samplesToDo), updateState, xoutput); /* Apply filter1 to S and store in mTemp. */ process(mFilter1S, Filter1Coeff, al::span{mS}.first(samplesToDo), updateState, mTemp); /* W = 0.981532*S + 0.197484*j(0.828331*D + 0.767820*T) */ std::transform(mTemp.begin(), mTemp.begin()+samplesToDo, xoutput.begin(), woutput.begin(), [](const float s, const float jdt) noexcept { return 0.981532f*s + 0.197484f*jdt; }); /* X = 0.418496*S - j(0.828331*D + 0.767820*T) */ std::transform(mTemp.begin(), mTemp.begin()+samplesToDo, xoutput.begin(), xoutput.begin(), [](const float s, const float jdt) noexcept { return 0.418496f*s - jdt; }); /* Apply filter1 to (0.795968*D - 0.676392*T) and store in mTemp. */ std::transform(mD.cbegin(), mD.cbegin()+samplesToDo, youtput.begin(), youtput.begin(), [](const float d, const float t) noexcept { return 0.795968f*d - 0.676392f*t; }); process(mFilter1DT, Filter1Coeff, youtput.first(samplesToDo), updateState, mTemp); /* Precompute j*S and store in youtput. */ if(mFirstRun) processOne(mFilter2S, Filter2Coeff, mS[0]); process(mFilter2S, Filter2Coeff, al::span{mS}.subspan(1, samplesToDo), updateState, youtput); /* Y = 0.795968*D - 0.676392*T + j(0.186633*S) */ std::transform(mTemp.begin(), mTemp.begin()+samplesToDo, youtput.begin(), youtput.begin(), [](const float dt, const float js) noexcept { return dt + 0.186633f*js; }); if(samples.size() > 3) { const auto zoutput = al::span{al::assume_aligned<16>(samples[3]), samplesToDo}; /* Apply filter1 to Q and store in mTemp. */ process(mFilter1Q, Filter1Coeff, zoutput, updateState, mTemp); /* Z = 1.023332*Q */ std::transform(mTemp.begin(), mTemp.end(), zoutput.begin(), [](const float q) noexcept { return 1.023332f*q; }); } mFirstRun = false; } /* Super Stereo processing is done as: * * S = Left + Right * D = Left - Right * * W = 0.6098637*S + 0.6896511*j*w*D * X = 0.8624776*S - 0.7626955*j*w*D * Y = 1.6822415*w*D + 0.2156194*j*S * * where j is a +90 degree phase shift. w is a variable control for the * resulting stereo width, with the range 0 <= w <= 0.7. */ template void UhjStereoDecoder::decode(const al::span samples, const size_t samplesToDo, const bool updateState) { static_assert(sInputPadding <= sMaxPadding, "Filter padding is too large"); constexpr auto &PShift = PShifter; ASSUME(samplesToDo > 0); ASSUME(samplesToDo <= BufferLineSize); { const auto left = al::span{al::assume_aligned<16>(samples[0]), samplesToDo+sInputPadding}; const auto right = al::span{al::assume_aligned<16>(samples[1]), samplesToDo+sInputPadding}; std::transform(left.begin(), left.end(), right.begin(), mS.begin(), std::plus{}); /* Pre-apply the width factor to the difference signal D. Smoothly * interpolate when it changes. */ const float wtarget{mWidthControl}; const float wcurrent{(mCurrentWidth < 0.0f) ? wtarget : mCurrentWidth}; if(wtarget == wcurrent || !updateState) { std::transform(left.begin(), left.end(), right.begin(), mD.begin(), [wcurrent](const float l, const float r) noexcept { return (l-r) * wcurrent; }); mCurrentWidth = wcurrent; } else { const float wstep{(wtarget - wcurrent) / static_cast(samplesToDo)}; float fi{0.0f}; const auto lfade = left.first(samplesToDo); auto dstore = std::transform(lfade.begin(), lfade.begin(), right.begin(), mD.begin(), [wcurrent,wstep,&fi](const float l, const float r) noexcept { const float ret{(l-r) * (wcurrent + wstep*fi)}; fi += 1.0f; return ret; }); const auto lend = left.subspan(samplesToDo); const auto rend = right.subspan(samplesToDo); std::transform(lend.begin(), lend.end(), rend.begin(), dstore, [wtarget](const float l, const float r) noexcept { return (l-r) * wtarget; }); mCurrentWidth = wtarget; } } const auto woutput = al::span{al::assume_aligned<16>(samples[0]), samplesToDo}; const auto xoutput = al::span{al::assume_aligned<16>(samples[1]), samplesToDo}; const auto youtput = al::span{al::assume_aligned<16>(samples[2]), samplesToDo}; /* Precompute j*D and store in xoutput. */ auto tmpiter = std::copy(mDTHistory.cbegin(), mDTHistory.cend(), mTemp.begin()); std::copy_n(mD.cbegin(), samplesToDo+sInputPadding, tmpiter); if(updateState) LIKELY std::copy_n(mTemp.cbegin()+samplesToDo, mDTHistory.size(), mDTHistory.begin()); PShift.process(xoutput, mTemp); /* W = 0.6098637*S + 0.6896511*j*w*D */ std::transform(mS.begin(), mS.begin()+samplesToDo, xoutput.begin(), woutput.begin(), [](const float s, const float jd) noexcept { return 0.6098637f*s + 0.6896511f*jd; }); /* X = 0.8624776*S - 0.7626955*j*w*D */ std::transform(mS.begin(), mS.begin()+samplesToDo, xoutput.begin(), xoutput.begin(), [](const float s, const float jd) noexcept { return 0.8624776f*s - 0.7626955f*jd; }); /* Precompute j*S and store in youtput. */ tmpiter = std::copy(mSHistory.cbegin(), mSHistory.cend(), mTemp.begin()); std::copy_n(mS.cbegin(), samplesToDo+sInputPadding, tmpiter); if(updateState) LIKELY std::copy_n(mTemp.cbegin()+samplesToDo, mSHistory.size(), mSHistory.begin()); PShift.process(youtput, mTemp); /* Y = 1.6822415*w*D + 0.2156194*j*S */ std::transform(mD.begin(), mD.begin()+samplesToDo, youtput.begin(), youtput.begin(), [](const float d, const float js) noexcept { return 1.6822415f*d + 0.2156194f*js; }); } void UhjStereoDecoderIIR::decode(const al::span samples, const size_t samplesToDo, const bool updateState) { static_assert(sInputPadding <= sMaxPadding, "Filter padding is too large"); ASSUME(samplesToDo > 0); ASSUME(samplesToDo <= BufferLineSize); { const auto left = al::span{al::assume_aligned<16>(samples[0]), samplesToDo+sInputPadding}; const auto right = al::span{al::assume_aligned<16>(samples[1]), samplesToDo+sInputPadding}; std::transform(left.begin(), left.end(), right.begin(), mS.begin(), std::plus{}); /* Pre-apply the width factor to the difference signal D. Smoothly * interpolate when it changes. */ const float wtarget{mWidthControl}; const float wcurrent{(mCurrentWidth < 0.0f) ? wtarget : mCurrentWidth}; if(wtarget == wcurrent || !updateState) { std::transform(left.begin(), left.end(), right.begin(), mD.begin(), [wcurrent](const float l, const float r) noexcept { return (l-r) * wcurrent; }); mCurrentWidth = wcurrent; } else { const float wstep{(wtarget - wcurrent) / static_cast(samplesToDo)}; float fi{0.0f}; const auto lfade = left.first(samplesToDo); auto dstore = std::transform(lfade.begin(), lfade.begin(), right.begin(), mD.begin(), [wcurrent,wstep,&fi](const float l, const float r) noexcept { const float ret{(l-r) * (wcurrent + wstep*fi)}; fi += 1.0f; return ret; }); const auto lend = left.subspan(samplesToDo); const auto rend = right.subspan(samplesToDo); std::transform(lend.begin(), lend.end(), rend.begin(), dstore, [wtarget](const float l, const float r) noexcept { return (l-r) * wtarget; }); mCurrentWidth = wtarget; } } const auto woutput = al::span{al::assume_aligned<16>(samples[0]), samplesToDo}; const auto xoutput = al::span{al::assume_aligned<16>(samples[1]), samplesToDo}; const auto youtput = al::span{al::assume_aligned<16>(samples[2]), samplesToDo}; /* Apply filter1 to S and store in mTemp. */ process(mFilter1S, Filter1Coeff, al::span{mS}.first(samplesToDo), updateState, mTemp); /* Precompute j*D and store in xoutput. */ if(mFirstRun) processOne(mFilter2D, Filter2Coeff, mD[0]); process(mFilter2D, Filter2Coeff, al::span{mD}.subspan(1, samplesToDo), updateState, xoutput); /* W = 0.6098637*S + 0.6896511*j*w*D */ std::transform(mTemp.begin(), mTemp.begin()+samplesToDo, xoutput.begin(), woutput.begin(), [](const float s, const float jd) noexcept { return 0.6098637f*s + 0.6896511f*jd; }); /* X = 0.8624776*S - 0.7626955*j*w*D */ std::transform(mTemp.begin(), mTemp.begin()+samplesToDo, xoutput.begin(), xoutput.begin(), [](const float s, const float jd) noexcept { return 0.8624776f*s - 0.7626955f*jd; }); /* Precompute j*S and store in youtput. */ if(mFirstRun) processOne(mFilter2S, Filter2Coeff, mS[0]); process(mFilter2S, Filter2Coeff, al::span{mS}.subspan(1, samplesToDo), updateState, youtput); /* Apply filter1 to D and store in mTemp. */ process(mFilter1D, Filter1Coeff, al::span{mD}.first(samplesToDo), updateState, mTemp); /* Y = 1.6822415*w*D + 0.2156194*j*S */ std::transform(mTemp.begin(), mTemp.begin()+samplesToDo, youtput.begin(), youtput.begin(), [](const float d, const float js) noexcept { return 1.6822415f*d + 0.2156194f*js; }); mFirstRun = false; } template struct UhjEncoder; template struct UhjDecoder; template struct UhjStereoDecoder; template struct UhjEncoder; template struct UhjDecoder; template struct UhjStereoDecoder; openal-soft-1.24.2/core/uhjfilter.h000066400000000000000000000206741474041540300171370ustar00rootroot00000000000000#ifndef CORE_UHJFILTER_H #define CORE_UHJFILTER_H #include #include #include #include "alspan.h" #include "bufferline.h" #include "opthelpers.h" inline constexpr std::size_t UhjLength256{256}; inline constexpr std::size_t UhjLength512{512}; enum class UhjQualityType : std::uint8_t { IIR = 0, FIR256, FIR512, Default = IIR }; inline UhjQualityType UhjDecodeQuality{UhjQualityType::Default}; inline UhjQualityType UhjEncodeQuality{UhjQualityType::Default}; struct UhjAllPassFilter { struct AllPassState { /* Last two delayed components for direct form II. */ std::array z{}; }; std::array mState; }; struct SIMDALIGN UhjEncoderBase { UhjEncoderBase() = default; UhjEncoderBase(const UhjEncoderBase&) = delete; UhjEncoderBase(UhjEncoderBase&&) = delete; virtual ~UhjEncoderBase() = default; void operator=(const UhjEncoderBase&) = delete; void operator=(UhjEncoderBase&&) = delete; virtual std::size_t getDelay() noexcept = 0; /** * Encodes a 2-channel UHJ (stereo-compatible) signal from a B-Format input * signal. The input must use FuMa channel ordering and UHJ scaling (FuMa * with an additional +3dB boost). */ virtual void encode(float *LeftOut, float *RightOut, const al::span InSamples, const std::size_t SamplesToDo) = 0; }; template struct UhjEncoder final : public UhjEncoderBase { static constexpr std::size_t sFftLength{256}; static constexpr std::size_t sSegmentSize{sFftLength/2}; static constexpr std::size_t sNumSegments{N/sSegmentSize}; static constexpr std::size_t sFilterDelay{N/2 + sSegmentSize}; /* Delays and processing storage for the input signal. */ alignas(16) std::array mW{}; alignas(16) std::array mX{}; alignas(16) std::array mY{}; alignas(16) std::array mS{}; alignas(16) std::array mD{}; /* History and temp storage for the convolution filter. */ std::size_t mFifoPos{}, mCurrentSegment{}; alignas(16) std::array mWXInOut{}; alignas(16) std::array mFftBuffer{}; alignas(16) std::array mWorkData{}; alignas(16) std::array mWXHistory{}; alignas(16) std::array,2> mDirectDelay{}; std::size_t getDelay() noexcept override { return sFilterDelay; } /** * Encodes a 2-channel UHJ (stereo-compatible) signal from a B-Format input * signal. The input must use FuMa channel ordering and UHJ scaling (FuMa * with an additional +3dB boost). */ void encode(float *LeftOut, float *RightOut, const al::span InSamples, const std::size_t SamplesToDo) final; }; struct UhjEncoderIIR final : public UhjEncoderBase { static constexpr std::size_t sFilterDelay{1}; /* Processing storage for the input signal. */ alignas(16) std::array mS{}; alignas(16) std::array mD{}; alignas(16) std::array mWX{}; alignas(16) std::array mTemp{}; float mDelayWX{}, mDelayY{}; UhjAllPassFilter mFilter1WX; UhjAllPassFilter mFilter2WX; UhjAllPassFilter mFilter1Y; std::array mFilter1Direct; std::array mDirectDelay{}; std::size_t getDelay() noexcept override { return sFilterDelay; } /** * Encodes a 2-channel UHJ (stereo-compatible) signal from a B-Format input * signal. The input must use FuMa channel ordering and UHJ scaling (FuMa * with an additional +3dB boost). */ void encode(float *LeftOut, float *RightOut, const al::span InSamples, const std::size_t SamplesToDo) final; }; struct SIMDALIGN DecoderBase { static constexpr std::size_t sMaxPadding{256}; /* For 2-channel UHJ, shelf filters should use these LF responses. */ static constexpr float sWLFScale{0.661f}; static constexpr float sXYLFScale{1.293f}; DecoderBase() = default; DecoderBase(const DecoderBase&) = delete; DecoderBase(DecoderBase&&) = delete; virtual ~DecoderBase() = default; void operator=(const DecoderBase&) = delete; void operator=(DecoderBase&&) = delete; virtual void decode(const al::span samples, const std::size_t samplesToDo, const bool updateState) = 0; /** * The width factor for Super Stereo processing. Can be changed in between * calls to decode, with valid values being between 0...0.7. */ float mWidthControl{0.593f}; }; template struct UhjDecoder final : public DecoderBase { /* The number of extra sample frames needed for input. */ static constexpr std::size_t sInputPadding{N/2}; alignas(16) std::array mS{}; alignas(16) std::array mD{}; alignas(16) std::array mT{}; alignas(16) std::array mDTHistory{}; alignas(16) std::array mSHistory{}; alignas(16) std::array mTemp{}; /** * Decodes a 3- or 4-channel UHJ signal into a B-Format signal with FuMa * channel ordering and UHJ scaling. For 3-channel, the 3rd channel may be * attenuated by 'n', where 0 <= n <= 1. So to decode 2-channel UHJ, supply * 3 channels with the 3rd channel silent (n=0). The B-Format signal * reconstructed from 2-channel UHJ should not be run through a normal * B-Format decoder, as it needs different shelf filters. */ void decode(const al::span samples, const std::size_t samplesToDo, const bool updateState) final; }; struct UhjDecoderIIR final : public DecoderBase { /* These IIR decoder filters normally have a 1-sample delay on the non- * filtered components. However, the filtered components are made to skip * the first output sample and take one future sample, which puts it ahead * by one sample. The first filtered output sample is cut to align it with * the first non-filtered sample, similar to the FIR filters. */ static constexpr std::size_t sInputPadding{1}; bool mFirstRun{true}; alignas(16) std::array mS{}; alignas(16) std::array mD{}; alignas(16) std::array mTemp{}; UhjAllPassFilter mFilter1S; UhjAllPassFilter mFilter2DT; UhjAllPassFilter mFilter1DT; UhjAllPassFilter mFilter2S; UhjAllPassFilter mFilter1Q; void decode(const al::span samples, const std::size_t samplesToDo, const bool updateState) final; }; template struct UhjStereoDecoder final : public DecoderBase { static constexpr std::size_t sInputPadding{N/2}; float mCurrentWidth{-1.0f}; alignas(16) std::array mS{}; alignas(16) std::array mD{}; alignas(16) std::array mDTHistory{}; alignas(16) std::array mSHistory{}; alignas(16) std::array mTemp{}; /** * Applies Super Stereo processing on a stereo signal to create a B-Format * signal with FuMa channel ordering and UHJ scaling. The samples span * should contain 3 channels, the first two being the left and right stereo * channels, and the third left empty. */ void decode(const al::span samples, const std::size_t samplesToDo, const bool updateState) final; }; struct UhjStereoDecoderIIR final : public DecoderBase { static constexpr std::size_t sInputPadding{1}; bool mFirstRun{true}; float mCurrentWidth{-1.0f}; alignas(16) std::array mS{}; alignas(16) std::array mD{}; alignas(16) std::array mTemp{}; UhjAllPassFilter mFilter1S; UhjAllPassFilter mFilter2D; UhjAllPassFilter mFilter1D; UhjAllPassFilter mFilter2S; void decode(const al::span samples, const std::size_t samplesToDo, const bool updateState) final; }; #endif /* CORE_UHJFILTER_H */ openal-soft-1.24.2/core/uiddefs.cpp000066400000000000000000000021441474041540300171110ustar00rootroot00000000000000 #include "config.h" #include "config_backends.h" #ifndef AL_NO_UID_DEFS #if defined(HAVE_GUIDDEF_H) #define INITGUID #include #include DEFINE_GUID(KSDATAFORMAT_SUBTYPE_PCM, 0x00000001, 0x0000, 0x0010, 0x80,0x00, 0x00,0xaa,0x00,0x38,0x9b,0x71); DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, 0x00000003, 0x0000, 0x0010, 0x80,0x00, 0x00,0xaa,0x00,0x38,0x9b,0x71); DEFINE_GUID(IID_IDirectSoundNotify, 0xb0210783, 0x89cd, 0x11d0, 0xaf,0x08, 0x00,0xa0,0xc9,0x25,0xcd,0x16); DEFINE_GUID(CLSID_MMDeviceEnumerator, 0xbcde0395, 0xe52f, 0x467c, 0x8e,0x3d, 0xc4,0x57,0x92,0x91,0x69,0x2e); #if HAVE_WASAPI && !ALSOFT_UWP #include #include #include DEFINE_DEVPROPKEY(DEVPKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80,0x20, 0x67,0xd1,0x46,0xa8,0x50,0xe0, 14); DEFINE_PROPERTYKEY(PKEY_AudioEndpoint_FormFactor, 0x1da5d803, 0xd492, 0x4edd, 0x8c,0x23, 0xe0,0xc0,0xff,0xee,0x7f,0x0e, 0); DEFINE_PROPERTYKEY(PKEY_AudioEndpoint_GUID, 0x1da5d803, 0xd492, 0x4edd, 0x8c, 0x23,0xe0, 0xc0,0xff,0xee,0x7f,0x0e, 4 ); #endif #endif #endif /* AL_NO_UID_DEFS */ openal-soft-1.24.2/core/voice.cpp000066400000000000000000001444311474041540300166010ustar00rootroot00000000000000 #include "config.h" #include "config_simd.h" #include "voice.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "alnumeric.h" #include "alspan.h" #include "alstring.h" #include "ambidefs.h" #include "async_event.h" #include "buffer_storage.h" #include "context.h" #include "cpu_caps.h" #include "devformat.h" #include "device.h" #include "filters/biquad.h" #include "filters/nfc.h" #include "filters/splitter.h" #include "fmt_traits.h" #include "logging.h" #include "mixer.h" #include "mixer/defs.h" #include "mixer/hrtfdefs.h" #include "opthelpers.h" #include "resampler_limits.h" #include "ringbuffer.h" #include "vector.h" #include "voice_change.h" struct CTag; #if HAVE_SSE struct SSETag; #endif #if HAVE_NEON struct NEONTag; #endif static_assert(!(DeviceBase::MixerLineSize&3), "MixerLineSize must be a multiple of 4"); static_assert(!(MaxResamplerEdge&3), "MaxResamplerEdge is not a multiple of 4"); static_assert((BufferLineSize-1)/MaxPitch > 0, "MaxPitch is too large for BufferLineSize!"); static_assert((INT_MAX>>MixerFracBits)/MaxPitch > BufferLineSize, "MaxPitch and/or BufferLineSize are too large for MixerFracBits!"); namespace { using uint = unsigned int; using namespace std::chrono; using namespace std::string_view_literals; using HrtfMixerFunc = void(*)(const al::span InSamples, const al::span AccumSamples, const uint IrSize, const MixHrtfFilter *hrtfparams, const size_t SamplesToDo); using HrtfMixerBlendFunc = void(*)(const al::span InSamples, const al::span AccumSamples, const uint IrSize, const HrtfFilter *oldparams, const MixHrtfFilter *newparams, const size_t SamplesToDo); HrtfMixerFunc MixHrtfSamples{MixHrtf_}; HrtfMixerBlendFunc MixHrtfBlendSamples{MixHrtfBlend_}; inline MixerOutFunc SelectMixer() { #if HAVE_NEON if((CPUCapFlags&CPU_CAP_NEON)) return Mix_; #endif #if HAVE_SSE if((CPUCapFlags&CPU_CAP_SSE)) return Mix_; #endif return Mix_; } inline MixerOneFunc SelectMixerOne() { #if HAVE_NEON if((CPUCapFlags&CPU_CAP_NEON)) return Mix_; #endif #if HAVE_SSE if((CPUCapFlags&CPU_CAP_SSE)) return Mix_; #endif return Mix_; } inline HrtfMixerFunc SelectHrtfMixer() { #if HAVE_NEON if((CPUCapFlags&CPU_CAP_NEON)) return MixHrtf_; #endif #if HAVE_SSE if((CPUCapFlags&CPU_CAP_SSE)) return MixHrtf_; #endif return MixHrtf_; } inline HrtfMixerBlendFunc SelectHrtfBlendMixer() { #if HAVE_NEON if((CPUCapFlags&CPU_CAP_NEON)) return MixHrtfBlend_; #endif #if HAVE_SSE if((CPUCapFlags&CPU_CAP_SSE)) return MixHrtfBlend_; #endif return MixHrtfBlend_; } } // namespace void Voice::InitMixer(std::optional resopt) { if(resopt) { struct ResamplerEntry { const std::string_view name; const Resampler resampler; }; constexpr std::array ResamplerList{ ResamplerEntry{"none"sv, Resampler::Point}, ResamplerEntry{"point"sv, Resampler::Point}, ResamplerEntry{"linear"sv, Resampler::Linear}, ResamplerEntry{"spline"sv, Resampler::Spline}, ResamplerEntry{"gaussian"sv, Resampler::Gaussian}, ResamplerEntry{"bsinc12"sv, Resampler::BSinc12}, ResamplerEntry{"fast_bsinc12"sv, Resampler::FastBSinc12}, ResamplerEntry{"bsinc24"sv, Resampler::BSinc24}, ResamplerEntry{"fast_bsinc24"sv, Resampler::FastBSinc24}, }; std::string_view resampler{*resopt}; if (al::case_compare(resampler, "cubic"sv) == 0) { WARN("Resampler option \"{}\" is deprecated, using spline", *resopt); resampler = "spline"sv; } else if(al::case_compare(resampler, "sinc4"sv) == 0 || al::case_compare(resampler, "sinc8"sv) == 0) { WARN("Resampler option \"{}\" is deprecated, using gaussian", *resopt); resampler = "gaussian"sv; } else if(al::case_compare(resampler, "bsinc"sv) == 0) { WARN("Resampler option \"{}\" is deprecated, using bsinc12", *resopt); resampler = "bsinc12"sv; } auto iter = std::find_if(ResamplerList.begin(), ResamplerList.end(), [resampler](const ResamplerEntry &entry) -> bool { return al::case_compare(resampler, entry.name) == 0; }); if(iter == ResamplerList.end()) ERR("Invalid resampler: {}", *resopt); else ResamplerDefault = iter->resampler; } MixSamplesOut = SelectMixer(); MixSamplesOne = SelectMixerOne(); MixHrtfBlendSamples = SelectHrtfBlendMixer(); MixHrtfSamples = SelectHrtfMixer(); } namespace { /* IMA ADPCM Stepsize table */ constexpr std::array IMAStep_size{{ 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 19, 21, 23, 25, 28, 31, 34, 37, 41, 45, 50, 55, 60, 66, 73, 80, 88, 97, 107, 118, 130, 143, 157, 173, 190, 209, 230, 253, 279, 307, 337, 371, 408, 449, 494, 544, 598, 658, 724, 796, 876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358, 5894, 6484, 7132, 7845, 8630, 9493,10442, 11487,12635,13899,15289,16818,18500,20350,22358,24633,27086,29794, 32767 }}; /* IMA4 ADPCM Codeword decode table */ constexpr std::array IMA4Codeword{{ 1, 3, 5, 7, 9, 11, 13, 15, -1,-3,-5,-7,-9,-11,-13,-15, }}; /* IMA4 ADPCM Step index adjust decode table */ constexpr std::arrayIMA4Index_adjust{{ -1,-1,-1,-1, 2, 4, 6, 8, -1,-1,-1,-1, 2, 4, 6, 8 }}; /* MSADPCM Adaption table */ constexpr std::array MSADPCMAdaption{{ 230, 230, 230, 230, 307, 409, 512, 614, 768, 614, 512, 409, 307, 230, 230, 230 }}; /* MSADPCM Adaption Coefficient tables */ constexpr std::array MSADPCMAdaptionCoeff{ std::array{256, 0}, std::array{512, -256}, std::array{ 0, 0}, std::array{192, 64}, std::array{240, 0}, std::array{460, -208}, std::array{392, -232} }; void SendSourceStoppedEvent(ContextBase *context, uint id) { RingBuffer *ring{context->mAsyncEvents.get()}; auto evt_vec = ring->getWriteVector(); if(evt_vec[0].len < 1) return; auto &evt = InitAsyncEvent(evt_vec[0].buf); evt.mId = id; evt.mState = AsyncSrcState::Stop; ring->writeAdvance(1); } al::span DoFilters(BiquadFilter &lpfilter, BiquadFilter &hpfilter, const al::span dst, const al::span src, int type) { switch(type) { case AF_None: lpfilter.clear(); hpfilter.clear(); break; case AF_LowPass: lpfilter.process(src, dst); hpfilter.clear(); return dst.first(src.size()); case AF_HighPass: lpfilter.clear(); hpfilter.process(src, dst); return dst.first(src.size()); case AF_BandPass: DualBiquad{lpfilter, hpfilter}.process(src, dst); return dst.first(src.size()); } return src; } template inline void LoadSamples(const al::span dstSamples, const al::span srcData, const size_t srcChan, const size_t srcOffset, const size_t srcStep, const size_t samplesPerBlock [[maybe_unused]]) noexcept { using TypeTraits = al::FmtTypeTraits; using SampleType = typename TypeTraits::Type; static constexpr size_t sampleSize{sizeof(SampleType)}; assert(srcChan < srcStep); auto converter = TypeTraits{}; al::span src{reinterpret_cast(srcData.data()), srcData.size()/sampleSize}; auto ssrc = src.cbegin() + ptrdiff_t(srcOffset*srcStep); std::generate(dstSamples.begin(), dstSamples.end(), [&ssrc,srcChan,srcStep,converter] { auto ret = converter(ssrc[srcChan]); ssrc += ptrdiff_t(srcStep); return ret; }); } template<> inline void LoadSamples(al::span dstSamples, al::span src, const size_t srcChan, const size_t srcOffset, const size_t srcStep, const size_t samplesPerBlock) noexcept { static constexpr int MaxStepIndex{static_cast(IMAStep_size.size()) - 1}; assert(srcStep > 0 || srcStep <= 2); assert(srcChan < srcStep); assert(samplesPerBlock > 1); const size_t blockBytes{((samplesPerBlock-1)/2 + 4)*srcStep}; /* Skip to the ADPCM block containing the srcOffset sample. */ src = src.subspan(srcOffset/samplesPerBlock*blockBytes); /* Calculate how many samples need to be skipped in the block. */ size_t skip{srcOffset % samplesPerBlock}; /* NOTE: This could probably be optimized better. */ auto dst = dstSamples.begin(); while(dst != dstSamples.end()) { /* Each IMA4 block starts with a signed 16-bit sample, and a signed * 16-bit table index. The table index needs to be clamped. */ int sample{int(src[srcChan*4 + 0]) | (int(src[srcChan*4 + 1]) << 8)}; int index{int(src[srcChan*4 + 2]) | (int(src[srcChan*4 + 3]) << 8)}; auto nibbleData = src.subspan((srcStep+srcChan)*4); src = src.subspan(blockBytes); sample = (sample^0x8000) - 32768; index = std::clamp((index^0x8000) - 32768, 0, MaxStepIndex); if(skip == 0) { *dst = static_cast(sample) / 32768.0f; if(++dst == dstSamples.end()) return; } else --skip; auto decode_sample = [&sample,&index](const uint8_t nibble) { sample += IMA4Codeword[nibble] * IMAStep_size[static_cast(index)] / 8; sample = std::clamp(sample, -32768, 32767); index += IMA4Index_adjust[nibble]; index = std::clamp(index, 0, MaxStepIndex); return sample; }; /* The rest of the block is arranged as a series of nibbles, contained * in 4 *bytes* per channel interleaved. So every 8 nibbles we need to * skip 4 bytes per channel to get the next nibbles for this channel. * * First, decode the samples that we need to skip in the block (will * always be less than the block size). They need to be decoded despite * being ignored for proper state on the remaining samples. */ static constexpr auto NibbleMask = std::byte{0xf}; size_t nibbleOffset{0}; const size_t startOffset{skip + 1}; for(;skip;--skip) { const size_t byteShift{(nibbleOffset&1) * 4}; const size_t wordOffset{(nibbleOffset>>1) & ~3_uz}; const size_t byteOffset{wordOffset*srcStep + ((nibbleOffset>>1)&3u)}; ++nibbleOffset; const auto nval = (nibbleData[byteOffset]>>byteShift) & NibbleMask; std::ignore = decode_sample(al::to_underlying(nval)); } /* Second, decode the rest of the block and write to the output, until * the end of the block or the end of output. */ const size_t todo{std::min(samplesPerBlock-startOffset, size_t(dstSamples.end()-dst))}; dst = std::generate_n(dst, todo, [&] { const size_t byteShift{(nibbleOffset&1) * 4}; const size_t wordOffset{(nibbleOffset>>1) & ~3_uz}; const size_t byteOffset{wordOffset*srcStep + ((nibbleOffset>>1)&3u)}; ++nibbleOffset; const auto nval = (nibbleData[byteOffset]>>byteShift) & NibbleMask; return static_cast(decode_sample(al::to_underlying(nval))) / 32768.0f; }); } } template<> inline void LoadSamples(al::span dstSamples, al::span src, const size_t srcChan, const size_t srcOffset, const size_t srcStep, const size_t samplesPerBlock) noexcept { assert(srcStep > 0 || srcStep <= 2); assert(srcChan < srcStep); assert(samplesPerBlock > 2); const size_t blockBytes{((samplesPerBlock-2)/2 + 7)*srcStep}; src = src.subspan(srcOffset/samplesPerBlock*blockBytes); size_t skip{srcOffset % samplesPerBlock}; auto dst = dstSamples.begin(); while(dst != dstSamples.end()) { /* Each MS ADPCM block starts with an 8-bit block predictor, used to * dictate how the two sample history values are mixed with the decoded * sample, and an initial signed 16-bit delta value which scales the * nibble sample value. This is followed by the two initial 16-bit * sample history values. */ const uint8_t blockpred{std::min(uint8_t(src[srcChan]), uint8_t{6})}; int delta{int(src[srcStep + 2*srcChan + 0]) | (int(src[srcStep + 2*srcChan + 1]) << 8)}; auto sampleHistory = std::array{ int(src[3*srcStep + 2*srcChan + 0]) | (int(src[3*srcStep + 2*srcChan + 1])<<8), int(src[5*srcStep + 2*srcChan + 0]) | (int(src[5*srcStep + 2*srcChan + 1])<<8)}; const auto input = src.subspan(7*srcStep); src = src.subspan(blockBytes); const auto coeffs = al::span{MSADPCMAdaptionCoeff[blockpred]}; delta = (delta^0x8000) - 32768; sampleHistory[0] = (sampleHistory[0]^0x8000) - 32768; sampleHistory[1] = (sampleHistory[1]^0x8000) - 32768; /* The second history sample is "older", so it's the first to be * written out. */ if(skip == 0) { *dst = static_cast(sampleHistory[1]) / 32768.0f; if(++dst == dstSamples.end()) return; *dst = static_cast(sampleHistory[0]) / 32768.0f; if(++dst == dstSamples.end()) return; } else if(skip == 1) { --skip; *dst = static_cast(sampleHistory[0]) / 32768.0f; if(++dst == dstSamples.end()) return; } else skip -= 2; auto decode_sample = [&sampleHistory,&delta,coeffs](const uint8_t nibble) { int pred{(sampleHistory[0]*coeffs[0] + sampleHistory[1]*coeffs[1]) / 256}; pred += ((nibble^0x08) - 0x08) * delta; pred = std::clamp(pred, -32768, 32767); sampleHistory[1] = sampleHistory[0]; sampleHistory[0] = pred; delta = (MSADPCMAdaption[nibble] * delta) / 256; delta = std::max(16, delta); return pred; }; /* The rest of the block is a series of nibbles, interleaved per- * channel. First, skip samples. */ static constexpr auto NibbleMask = std::byte{0xf}; const size_t startOffset{skip + 2}; size_t nibbleOffset{srcChan}; for(;skip;--skip) { const size_t byteOffset{nibbleOffset>>1}; const size_t byteShift{((nibbleOffset&1)^1) * 4}; nibbleOffset += srcStep; const auto nval = (input[byteOffset]>>byteShift) & NibbleMask; std::ignore = decode_sample(al::to_underlying(nval)); } /* Now decode the rest of the block, until the end of the block or the * dst buffer is filled. */ const size_t todo{std::min(samplesPerBlock-startOffset, size_t(dstSamples.end()-dst))}; dst = std::generate_n(dst, todo, [&] { const size_t byteOffset{nibbleOffset>>1}; const size_t byteShift{((nibbleOffset&1)^1) * 4}; nibbleOffset += srcStep; const auto nval = (input[byteOffset]>>byteShift) & NibbleMask; return static_cast(decode_sample(al::to_underlying(nval))) / 32768.0f; }); } } void LoadSamples(const al::span dstSamples, const al::span src, const size_t srcChan, const size_t srcOffset, const FmtType srcType, const size_t srcStep, const size_t samplesPerBlock) noexcept { #define HANDLE_FMT(T) case T: \ LoadSamples(dstSamples, src, srcChan, srcOffset, srcStep, \ samplesPerBlock); \ break switch(srcType) { HANDLE_FMT(FmtUByte); HANDLE_FMT(FmtShort); HANDLE_FMT(FmtInt); HANDLE_FMT(FmtFloat); HANDLE_FMT(FmtDouble); HANDLE_FMT(FmtMulaw); HANDLE_FMT(FmtAlaw); HANDLE_FMT(FmtIMA4); HANDLE_FMT(FmtMSADPCM); } #undef HANDLE_FMT } void LoadBufferStatic(VoiceBufferItem *buffer, VoiceBufferItem *bufferLoopItem, const size_t dataPosInt, const FmtType sampleType, const size_t srcChannel, const size_t srcStep, al::span voiceSamples) { if(!bufferLoopItem) { float lastSample{0.0f}; /* Load what's left to play from the buffer */ if(buffer->mSampleLen > dataPosInt) LIKELY { const size_t buffer_remaining{buffer->mSampleLen - dataPosInt}; const size_t remaining{std::min(voiceSamples.size(), buffer_remaining)}; LoadSamples(voiceSamples.first(remaining), buffer->mSamples, srcChannel, dataPosInt, sampleType, srcStep, buffer->mBlockAlign); lastSample = voiceSamples[remaining-1]; voiceSamples = voiceSamples.subspan(remaining); } if(const size_t toFill{voiceSamples.size()}) std::fill_n(voiceSamples.begin(), toFill, lastSample); } else { const size_t loopStart{buffer->mLoopStart}; const size_t loopEnd{buffer->mLoopEnd}; ASSUME(loopEnd > loopStart); const size_t intPos{(dataPosInt < loopEnd) ? dataPosInt : (((dataPosInt-loopStart)%(loopEnd-loopStart)) + loopStart)}; /* Load what's left of this loop iteration */ const size_t remaining{std::min(voiceSamples.size(), loopEnd-dataPosInt)}; LoadSamples(voiceSamples.first(remaining), buffer->mSamples, srcChannel, intPos, sampleType, srcStep, buffer->mBlockAlign); voiceSamples = voiceSamples.subspan(remaining); /* Load repeats of the loop to fill the buffer. */ const size_t loopSize{loopEnd - loopStart}; while(const size_t toFill{std::min(voiceSamples.size(), loopSize)}) { LoadSamples(voiceSamples.first(toFill), buffer->mSamples, srcChannel, loopStart, sampleType, srcStep, buffer->mBlockAlign); voiceSamples = voiceSamples.subspan(toFill); } } } void LoadBufferCallback(VoiceBufferItem *buffer, const size_t dataPosInt, const size_t numCallbackSamples, const FmtType sampleType, const size_t srcChannel, const size_t srcStep, al::span voiceSamples) { float lastSample{0.0f}; if(numCallbackSamples > dataPosInt) LIKELY { const size_t remaining{std::min(voiceSamples.size(), numCallbackSamples-dataPosInt)}; LoadSamples(voiceSamples.first(remaining), buffer->mSamples, srcChannel, dataPosInt, sampleType, srcStep, buffer->mBlockAlign); lastSample = voiceSamples[remaining-1]; voiceSamples = voiceSamples.subspan(remaining); } if(const size_t toFill{voiceSamples.size()}) std::fill_n(voiceSamples.begin(), toFill, lastSample); } void LoadBufferQueue(VoiceBufferItem *buffer, VoiceBufferItem *bufferLoopItem, size_t dataPosInt, const FmtType sampleType, const size_t srcChannel, const size_t srcStep, al::span voiceSamples) { float lastSample{0.0f}; /* Crawl the buffer queue to fill in the temp buffer */ while(buffer && !voiceSamples.empty()) { if(dataPosInt >= buffer->mSampleLen) { dataPosInt -= buffer->mSampleLen; buffer = buffer->mNext.load(std::memory_order_acquire); if(!buffer) buffer = bufferLoopItem; continue; } const size_t remaining{std::min(voiceSamples.size(), buffer->mSampleLen-dataPosInt)}; LoadSamples(voiceSamples.first(remaining), buffer->mSamples, srcChannel, dataPosInt, sampleType, srcStep, buffer->mBlockAlign); lastSample = voiceSamples[remaining-1]; voiceSamples = voiceSamples.subspan(remaining); if(voiceSamples.empty()) break; dataPosInt = 0; buffer = buffer->mNext.load(std::memory_order_acquire); if(!buffer) buffer = bufferLoopItem; } if(const size_t toFill{voiceSamples.size()}) std::fill_n(voiceSamples.begin(), toFill, lastSample); } void DoHrtfMix(const al::span samples, DirectParams &parms, const float TargetGain, const size_t Counter, size_t OutPos, const bool IsPlaying, DeviceBase *Device) { const uint IrSize{Device->mIrSize}; const auto HrtfSamples = al::span{Device->ExtraSampleData}; const auto AccumSamples = al::span{Device->HrtfAccumData}; /* Copy the HRTF history and new input samples into a temp buffer. */ auto src_iter = std::copy(parms.Hrtf.History.begin(), parms.Hrtf.History.end(), HrtfSamples.begin()); std::copy_n(samples.begin(), samples.size(), src_iter); /* Copy the last used samples back into the history buffer for later. */ if(IsPlaying) LIKELY { const auto endsamples = HrtfSamples.subspan(samples.size(), parms.Hrtf.History.size()); std::copy_n(endsamples.cbegin(), endsamples.size(), parms.Hrtf.History.begin()); } /* If fading and this is the first mixing pass, fade between the IRs. */ size_t fademix{0}; if(Counter && OutPos == 0) { fademix = std::min(samples.size(), Counter); float gain{TargetGain}; /* The new coefficients need to fade in completely since they're * replacing the old ones. To keep the gain fading consistent, * interpolate between the old and new target gains given how much of * the fade time this mix handles. */ if(Counter > fademix) { const float a{static_cast(fademix) / static_cast(Counter)}; gain = lerpf(parms.Hrtf.Old.Gain, TargetGain, a); } MixHrtfFilter hrtfparams{ parms.Hrtf.Target.Coeffs, parms.Hrtf.Target.Delay, 0.0f, gain / static_cast(fademix)}; MixHrtfBlendSamples(HrtfSamples, AccumSamples.subspan(OutPos), IrSize, &parms.Hrtf.Old, &hrtfparams, fademix); /* Update the old parameters with the result. */ parms.Hrtf.Old = parms.Hrtf.Target; parms.Hrtf.Old.Gain = gain; OutPos += fademix; } if(fademix < samples.size()) { const size_t todo{samples.size() - fademix}; float gain{TargetGain}; /* Interpolate the target gain if the gain fading lasts longer than * this mix. */ if(Counter > samples.size()) { const float a{static_cast(todo) / static_cast(Counter-fademix)}; gain = lerpf(parms.Hrtf.Old.Gain, TargetGain, a); } MixHrtfFilter hrtfparams{ parms.Hrtf.Target.Coeffs, parms.Hrtf.Target.Delay, parms.Hrtf.Old.Gain, (gain - parms.Hrtf.Old.Gain) / static_cast(todo)}; MixHrtfSamples(HrtfSamples.subspan(fademix), AccumSamples.subspan(OutPos), IrSize, &hrtfparams, todo); /* Store the now-current gain for next time. */ parms.Hrtf.Old.Gain = gain; } } void DoNfcMix(const al::span samples, al::span OutBuffer, DirectParams &parms, const al::span OutGains, const uint Counter, const uint OutPos, DeviceBase *Device) { using FilterProc = void (NfcFilter::*)(const al::span, const al::span); static constexpr std::array NfcProcess{{ nullptr, &NfcFilter::process1, &NfcFilter::process2, &NfcFilter::process3}}; MixSamples(samples, al::span{OutBuffer[0]}.subspan(OutPos), parms.Gains.Current[0], OutGains[0], Counter); OutBuffer = OutBuffer.subspan(1); auto CurrentGains = al::span{parms.Gains.Current}.subspan(1); auto TargetGains = OutGains.subspan(1); const auto nfcsamples = al::span{Device->ExtraSampleData}.first(samples.size()); size_t order{1}; while(const size_t chancount{Device->NumChannelsPerOrder[order]}) { (parms.NFCtrlFilter.*NfcProcess[order])(samples, nfcsamples); MixSamples(nfcsamples, OutBuffer.first(chancount), CurrentGains, TargetGains, Counter, OutPos); if(++order == MaxAmbiOrder+1) break; OutBuffer = OutBuffer.subspan(chancount); CurrentGains = CurrentGains.subspan(chancount); TargetGains = TargetGains.subspan(chancount); } } } // namespace void Voice::mix(const State vstate, ContextBase *Context, const nanoseconds deviceTime, const uint SamplesToDo) { static constexpr std::array SilentTarget{}; ASSUME(SamplesToDo > 0); DeviceBase *Device{Context->mDevice}; const uint NumSends{Device->NumAuxSends}; /* Get voice info */ int DataPosInt{mPosition.load(std::memory_order_relaxed)}; uint DataPosFrac{mPositionFrac.load(std::memory_order_relaxed)}; VoiceBufferItem *BufferListItem{mCurrentBuffer.load(std::memory_order_relaxed)}; VoiceBufferItem *BufferLoopItem{mLoopBuffer.load(std::memory_order_relaxed)}; const uint increment{mStep}; if(increment < 1) UNLIKELY { /* If the voice is supposed to be stopping but can't be mixed, just * stop it before bailing. */ if(vstate == Stopping) mPlayState.store(Stopped, std::memory_order_release); return; } /* If the static voice's current position is beyond the buffer loop end * position, disable looping. */ if(mFlags.test(VoiceIsStatic) && BufferLoopItem) { if(DataPosInt >= 0 && static_cast(DataPosInt) >= BufferListItem->mLoopEnd) BufferLoopItem = nullptr; } uint OutPos{0u}; /* Check if we're doing a delayed start, and we start in this update. */ if(mStartTime > deviceTime) UNLIKELY { /* If the voice is supposed to be stopping but hasn't actually started * yet, make sure its stopped. */ if(vstate == Stopping) { mPlayState.store(Stopped, std::memory_order_release); return; } /* If the start time is too far ahead, don't bother. */ auto diff = mStartTime - deviceTime; if(diff >= seconds{1}) return; /* Get the number of samples ahead of the current time that output * should start at. Skip this update if it's beyond the output sample * count. */ OutPos = static_cast(round(diff * Device->mSampleRate).count()); if(OutPos >= SamplesToDo) return; } /* Calculate the number of samples to mix, and the number of (resampled) * samples that need to be loaded (mixing samples and decoder padding). */ const uint samplesToMix{SamplesToDo - OutPos}; const uint samplesToLoad{samplesToMix + mDecoderPadding}; /* Get a span of pointers to hold the floating point, deinterlaced, * resampled buffer data to be mixed. */ auto SamplePointers = std::array{}; const auto MixingSamples = al::span{SamplePointers}.first(mChans.size()); { const uint channelStep{(samplesToLoad+3u)&~3u}; auto base = Device->mSampleData.end() - MixingSamples.size()*channelStep; std::generate(MixingSamples.begin(), MixingSamples.end(), [&base,channelStep] { const auto ret = base; base += channelStep; return al::to_address(ret); }); } /* UHJ2 and SuperStereo only have 2 buffer channels, but 3 mixing channels * (3rd channel is generated from decoding). MonoDup only has 1 buffer * channel, but 2 mixing channels (2nd channel is just duplicated). */ const size_t realChannels{(mFmtChannels == FmtMonoDup) ? 1u : (mFmtChannels == FmtUHJ2 || mFmtChannels == FmtSuperStereo) ? 2u : MixingSamples.size()}; for(size_t chan{0};chan < realChannels;++chan) { static constexpr uint ResBufSize{std::tuple_size_v}; static constexpr uint srcSizeMax{ResBufSize - MaxResamplerEdge}; const al::span prevSamples{mPrevSamples[chan]}; std::copy(prevSamples.cbegin(), prevSamples.cend(), Device->mResampleData.begin()); const auto resampleBuffer = al::span{Device->mResampleData}.subspan(); int intPos{DataPosInt}; uint fracPos{DataPosFrac}; /* Load samples for this channel from the available buffer(s), with * resampling. */ for(uint samplesLoaded{0};samplesLoaded < samplesToLoad;) { /* Calculate the number of dst samples that can be loaded this * iteration, given the available resampler buffer size, and the * number of src samples that are needed to load it. */ auto calc_buffer_sizes = [fracPos,increment](uint dstBufferSize) { /* If ext=true, calculate the last written dst pos from the dst * count, convert to the last read src pos, then add one to get * the src count. * * If ext=false, convert the dst count to src count directly. * * Without this, the src count could be short by one when * increment < 1.0, or not have a full src at the end when * increment > 1.0. */ const bool ext{increment <= MixerFracOne}; uint64_t dataSize64{dstBufferSize - ext}; dataSize64 = (dataSize64*increment + fracPos) >> MixerFracBits; /* Also include resampler padding. */ dataSize64 += ext + MaxResamplerEdge; if(dataSize64 <= srcSizeMax) return std::array{dstBufferSize, static_cast(dataSize64)}; /* If the source size got saturated, we can't fill the desired * dst size. Figure out how many dst samples we can fill. */ dataSize64 = srcSizeMax - MaxResamplerEdge; dataSize64 = ((dataSize64<(dataSize64) & ~3u; } return std::array{dstBufferSize, srcSizeMax}; }; const auto [dstBufferSize, srcBufferSize] = calc_buffer_sizes( samplesToLoad - samplesLoaded); size_t srcSampleDelay{0}; if(intPos < 0) UNLIKELY { /* If the current position is negative, there's that many * silent samples to load before using the buffer. */ srcSampleDelay = static_cast(-intPos); if(srcSampleDelay >= srcBufferSize) { /* If the number of silent source samples exceeds the * number to load, the output will be silent. */ std::fill_n(MixingSamples[chan]+samplesLoaded, dstBufferSize, 0.0f); std::fill_n(resampleBuffer.begin(), srcBufferSize, 0.0f); goto skip_resample; } std::fill_n(resampleBuffer.begin(), srcSampleDelay, 0.0f); } /* Load the necessary samples from the given buffer(s). */ if(!BufferListItem) UNLIKELY { const uint avail{std::min(srcBufferSize, MaxResamplerEdge)}; const uint tofill{std::max(srcBufferSize, MaxResamplerEdge)}; const auto srcbuf = resampleBuffer.first(tofill); /* When loading from a voice that ended prematurely, only take * the samples that get closest to 0 amplitude. This helps * certain sounds fade out better. */ auto srciter = std::min_element(srcbuf.begin(), srcbuf.begin()+ptrdiff_t(avail), [](const float l, const float r) { return std::abs(l) < std::abs(r); }); std::fill(srciter+1, srcbuf.end(), *srciter); } else if(mFlags.test(VoiceIsStatic)) { const auto uintPos = static_cast(std::max(intPos, 0)); const auto bufferSamples = resampleBuffer.subspan(srcSampleDelay, srcBufferSize-srcSampleDelay); LoadBufferStatic(BufferListItem, BufferLoopItem, uintPos, mFmtType, chan, mFrameStep, bufferSamples); } else if(mFlags.test(VoiceIsCallback)) { const auto uintPos = static_cast(std::max(intPos, 0)); const uint callbackBase{mCallbackBlockBase * mSamplesPerBlock}; const size_t bufferOffset{uintPos - callbackBase}; const size_t needSamples{bufferOffset + srcBufferSize - srcSampleDelay}; const size_t needBlocks{(needSamples + mSamplesPerBlock-1) / mSamplesPerBlock}; if(!mFlags.test(VoiceCallbackStopped) && needBlocks > mNumCallbackBlocks) { const size_t byteOffset{mNumCallbackBlocks*size_t{mBytesPerBlock}}; const size_t needBytes{(needBlocks-mNumCallbackBlocks)*size_t{mBytesPerBlock}}; const int gotBytes{BufferListItem->mCallback(BufferListItem->mUserData, &BufferListItem->mSamples[byteOffset], static_cast(needBytes))}; if(gotBytes < 0) mFlags.set(VoiceCallbackStopped); else if(static_cast(gotBytes) < needBytes) { mFlags.set(VoiceCallbackStopped); mNumCallbackBlocks += static_cast(gotBytes) / mBytesPerBlock; } else mNumCallbackBlocks = static_cast(needBlocks); } const size_t numSamples{size_t{mNumCallbackBlocks} * mSamplesPerBlock}; const auto bufferSamples = resampleBuffer.subspan(srcSampleDelay, srcBufferSize-srcSampleDelay); LoadBufferCallback(BufferListItem, bufferOffset, numSamples, mFmtType, chan, mFrameStep, bufferSamples); } else { const auto uintPos = static_cast(std::max(intPos, 0)); const auto bufferSamples = resampleBuffer.subspan(srcSampleDelay, srcBufferSize-srcSampleDelay); LoadBufferQueue(BufferListItem, BufferLoopItem, uintPos, mFmtType, chan, mFrameStep, bufferSamples); } /* If there's a matching sample step and no phase offset, use a * simple copy for resampling. */ if(increment == MixerFracOne && fracPos == 0) std::copy_n(resampleBuffer.cbegin(), dstBufferSize, MixingSamples[chan]+samplesLoaded); else mResampler(&mResampleState, Device->mResampleData, fracPos, increment, {MixingSamples[chan]+samplesLoaded, dstBufferSize}); /* Store the last source samples used for next time. */ if(vstate == Playing) LIKELY { /* Only store samples for the end of the mix, excluding what * gets loaded for decoder padding. */ const uint loadEnd{samplesLoaded + dstBufferSize}; if(samplesToMix > samplesLoaded && samplesToMix <= loadEnd) LIKELY { const size_t dstOffset{samplesToMix - samplesLoaded}; const size_t srcOffset{(dstOffset*increment + fracPos) >> MixerFracBits}; std::copy_n(Device->mResampleData.cbegin()+srcOffset, prevSamples.size(), prevSamples.begin()); } } skip_resample: samplesLoaded += dstBufferSize; if(samplesLoaded < samplesToLoad) { fracPos += dstBufferSize*increment; const uint srcOffset{fracPos >> MixerFracBits}; fracPos &= MixerFracMask; intPos += static_cast(srcOffset); /* If more samples need to be loaded, copy the back of the * resampleBuffer to the front to reuse it. prevSamples isn't * reliable since it's only updated for the end of the mix. */ std::copy_n(Device->mResampleData.cbegin()+srcOffset, MaxResamplerPadding, Device->mResampleData.begin()); } } } if(mFmtChannels == FmtMonoDup) { /* NOTE: a mono source shouldn't have a decoder or the VoiceIsAmbisonic * flag, so aliasing instead of copying to the second channel shouldn't * be a problem. */ MixingSamples[1] = MixingSamples[0]; } else for(auto &samples : MixingSamples.subspan(realChannels)) std::fill_n(samples, samplesToLoad, 0.0f); if(mDecoder) mDecoder->decode(MixingSamples, samplesToMix, (vstate==Playing)); if(mFlags.test(VoiceIsAmbisonic)) { auto voiceSamples = MixingSamples.begin(); for(auto &chandata : mChans) { chandata.mAmbiSplitter.processScale({*voiceSamples, samplesToMix}, chandata.mAmbiHFScale, chandata.mAmbiLFScale); ++voiceSamples; } } const uint Counter{mFlags.test(VoiceIsFading) ? std::min(samplesToMix, 64u) : 0u}; if(!Counter) { /* No fading, just overwrite the old/current params. */ for(auto &chandata : mChans) { { DirectParams &parms = chandata.mDryParams; if(!mFlags.test(VoiceHasHrtf)) parms.Gains.Current = parms.Gains.Target; else parms.Hrtf.Old = parms.Hrtf.Target; } for(uint send{0};send < NumSends;++send) { if(mSend[send].Buffer.empty()) continue; SendParams &parms = chandata.mWetParams[send]; parms.Gains.Current = parms.Gains.Target; } } } auto voiceSamples = MixingSamples.begin(); for(auto &chandata : mChans) { /* Now filter and mix to the appropriate outputs. */ const al::span FilterBuf{Device->FilteredData}; { DirectParams &parms = chandata.mDryParams; const auto samples = DoFilters(parms.LowPass, parms.HighPass, FilterBuf, {*voiceSamples, samplesToMix}, mDirect.FilterType); if(mFlags.test(VoiceHasHrtf)) { const float TargetGain{parms.Hrtf.Target.Gain * float(vstate == Playing)}; DoHrtfMix(samples, parms, TargetGain, Counter, OutPos, (vstate == Playing), Device); } else { const auto TargetGains = (vstate == Playing) ? al::span{parms.Gains.Target} : al::span{SilentTarget}; if(mFlags.test(VoiceHasNfc)) DoNfcMix(samples, mDirect.Buffer, parms, TargetGains, Counter, OutPos, Device); else MixSamples(samples, mDirect.Buffer, parms.Gains.Current, TargetGains, Counter, OutPos); } } for(uint send{0};send < NumSends;++send) { if(mSend[send].Buffer.empty()) continue; SendParams &parms = chandata.mWetParams[send]; const auto samples = DoFilters(parms.LowPass, parms.HighPass, FilterBuf, {*voiceSamples, samplesToMix}, mSend[send].FilterType); const auto TargetGains = (vstate == Playing) ? al::span{parms.Gains.Target} : al::span{SilentTarget}; MixSamples(samples, mSend[send].Buffer, parms.Gains.Current, TargetGains, Counter, OutPos); } ++voiceSamples; } mFlags.set(VoiceIsFading); /* Don't update positions and buffers if we were stopping. */ if(vstate == Stopping) UNLIKELY { mPlayState.store(Stopped, std::memory_order_release); return; } /* Update voice positions and buffers as needed. */ DataPosFrac += increment*samplesToMix; DataPosInt += static_cast(DataPosFrac>>MixerFracBits); DataPosFrac &= MixerFracMask; uint buffers_done{0u}; if(BufferListItem && DataPosInt > 0) LIKELY { if(mFlags.test(VoiceIsStatic)) { if(BufferLoopItem) { /* Handle looping static source */ const uint LoopStart{BufferListItem->mLoopStart}; const uint LoopEnd{BufferListItem->mLoopEnd}; uint DataPosUInt{static_cast(DataPosInt)}; if(DataPosUInt >= LoopEnd) { assert(LoopEnd > LoopStart); DataPosUInt = ((DataPosUInt-LoopStart)%(LoopEnd-LoopStart)) + LoopStart; DataPosInt = static_cast(DataPosUInt); } } else { /* Handle non-looping static source */ if(static_cast(DataPosInt) >= BufferListItem->mSampleLen) BufferListItem = nullptr; } } else if(mFlags.test(VoiceIsCallback)) { /* Handle callback buffer source */ const uint currentBlock{static_cast(DataPosInt) / mSamplesPerBlock}; const uint blocksDone{currentBlock - mCallbackBlockBase}; if(blocksDone < mNumCallbackBlocks) { const size_t byteOffset{blocksDone*size_t{mBytesPerBlock}}; const size_t byteEnd{mNumCallbackBlocks*size_t{mBytesPerBlock}}; const al::span data{BufferListItem->mSamples}; std::copy(data.cbegin()+ptrdiff_t(byteOffset), data.cbegin()+ptrdiff_t(byteEnd), data.begin()); mNumCallbackBlocks -= blocksDone; mCallbackBlockBase += blocksDone; } else { BufferListItem = nullptr; mNumCallbackBlocks = 0; mCallbackBlockBase += blocksDone; } } else { /* Handle streaming source */ do { if(BufferListItem->mSampleLen > static_cast(DataPosInt)) break; DataPosInt -= static_cast(BufferListItem->mSampleLen); ++buffers_done; BufferListItem = BufferListItem->mNext.load(std::memory_order_relaxed); if(!BufferListItem) BufferListItem = BufferLoopItem; } while(BufferListItem); } } /* Capture the source ID in case it gets reset for stopping. */ const uint SourceID{mSourceID.load(std::memory_order_relaxed)}; /* Update voice info */ mPosition.store(DataPosInt, std::memory_order_relaxed); mPositionFrac.store(DataPosFrac, std::memory_order_relaxed); mCurrentBuffer.store(BufferListItem, std::memory_order_relaxed); if(!BufferListItem) { mLoopBuffer.store(nullptr, std::memory_order_relaxed); mSourceID.store(0u, std::memory_order_relaxed); } std::atomic_thread_fence(std::memory_order_release); /* Send any events now, after the position/buffer info was updated. */ const auto enabledevt = Context->mEnabledEvts.load(std::memory_order_acquire); if(buffers_done > 0 && enabledevt.test(al::to_underlying(AsyncEnableBits::BufferCompleted))) { RingBuffer *ring{Context->mAsyncEvents.get()}; auto evt_vec = ring->getWriteVector(); if(evt_vec[0].len > 0) { auto &evt = InitAsyncEvent(evt_vec[0].buf); evt.mId = SourceID; evt.mCount = buffers_done; ring->writeAdvance(1); } } if(!BufferListItem) { /* If the voice just ended, set it to Stopping so the next render * ensures any residual noise fades to 0 amplitude. */ mPlayState.store(Stopping, std::memory_order_release); if(enabledevt.test(al::to_underlying(AsyncEnableBits::SourceState))) SendSourceStoppedEvent(Context, SourceID); } } void Voice::prepare(DeviceBase *device) { /* Even if storing really high order ambisonics, we only mix channels for * orders up to the device order. The rest are simply dropped. */ uint num_channels{(mFmtChannels == FmtMonoDup) ? 2 : (mFmtChannels == FmtUHJ2 || mFmtChannels == FmtSuperStereo) ? 3 : ChannelsFromFmt(mFmtChannels, std::min(mAmbiOrder, device->mAmbiOrder))}; if(num_channels > device->MixerChannelsMax) UNLIKELY { ERR("Unexpected channel count: {} (limit: {}, {} : {})", num_channels, device->MixerChannelsMax, NameFromFormat(mFmtChannels), mAmbiOrder); num_channels = device->MixerChannelsMax; } if(mChans.capacity() > 2 && num_channels < mChans.capacity()) { decltype(mChans){}.swap(mChans); decltype(mPrevSamples){}.swap(mPrevSamples); } mChans.reserve(std::max(2u, num_channels)); mChans.resize(num_channels); mPrevSamples.reserve(std::max(2u, num_channels)); mPrevSamples.resize(num_channels); mDecoder = nullptr; mDecoderPadding = 0; if(mFmtChannels == FmtSuperStereo) { switch(UhjDecodeQuality) { case UhjQualityType::IIR: mDecoder = std::make_unique(); mDecoderPadding = UhjStereoDecoderIIR::sInputPadding; break; case UhjQualityType::FIR256: mDecoder = std::make_unique>(); mDecoderPadding = UhjStereoDecoder::sInputPadding; break; case UhjQualityType::FIR512: mDecoder = std::make_unique>(); mDecoderPadding = UhjStereoDecoder::sInputPadding; break; } } else if(IsUHJ(mFmtChannels)) { switch(UhjDecodeQuality) { case UhjQualityType::IIR: mDecoder = std::make_unique(); mDecoderPadding = UhjDecoderIIR::sInputPadding; break; case UhjQualityType::FIR256: mDecoder = std::make_unique>(); mDecoderPadding = UhjDecoder::sInputPadding; break; case UhjQualityType::FIR512: mDecoder = std::make_unique>(); mDecoderPadding = UhjDecoder::sInputPadding; break; } } /* Clear the stepping value explicitly so the mixer knows not to mix this * until the update gets applied. */ mStep = 0; /* Make sure the sample history is cleared. */ std::fill(mPrevSamples.begin(), mPrevSamples.end(), HistoryLine{}); if(mFmtChannels == FmtUHJ2 && !device->mUhjEncoder) { /* 2-channel UHJ needs different shelf filters. However, we can't just * use different shelf filters after mixing it, given any old speaker * setup the user has. To make this work, we apply the expected shelf * filters for decoding UHJ2 to quad (only needs LF scaling), and act * as if those 4 quad channels are encoded right back into B-Format. * * This isn't perfect, but without an entirely separate and limited * UHJ2 path, it's better than nothing. * * Note this isn't needed with UHJ output (UHJ2->B-Format->UHJ2 is * identity, so don't mess with it). */ const BandSplitter splitter{device->mXOverFreq / static_cast(device->mSampleRate)}; for(auto &chandata : mChans) { chandata.mAmbiHFScale = 1.0f; chandata.mAmbiLFScale = 1.0f; chandata.mAmbiSplitter = splitter; chandata.mDryParams = DirectParams{}; chandata.mDryParams.NFCtrlFilter = device->mNFCtrlFilter; std::fill_n(chandata.mWetParams.begin(), device->NumAuxSends, SendParams{}); } mChans[0].mAmbiLFScale = DecoderBase::sWLFScale; mChans[1].mAmbiLFScale = DecoderBase::sXYLFScale; mChans[2].mAmbiLFScale = DecoderBase::sXYLFScale; mFlags.set(VoiceIsAmbisonic); } /* Don't need to set the VoiceIsAmbisonic flag if the device is not higher * order than the voice. No HF scaling is necessary to mix it. */ else if(mAmbiOrder && device->mAmbiOrder > mAmbiOrder) { auto OrdersSpan = Is2DAmbisonic(mFmtChannels) ? al::span{AmbiIndex::OrderFrom2DChannel} : al::span{AmbiIndex::OrderFromChannel}; auto OrderFromChan = OrdersSpan.cbegin(); const auto scales = AmbiScale::GetHFOrderScales(mAmbiOrder, device->mAmbiOrder, device->m2DMixing); const BandSplitter splitter{device->mXOverFreq / static_cast(device->mSampleRate)}; for(auto &chandata : mChans) { chandata.mAmbiHFScale = scales[*(OrderFromChan++)]; chandata.mAmbiLFScale = 1.0f; chandata.mAmbiSplitter = splitter; chandata.mDryParams = DirectParams{}; chandata.mDryParams.NFCtrlFilter = device->mNFCtrlFilter; std::fill_n(chandata.mWetParams.begin(), device->NumAuxSends, SendParams{}); } mFlags.set(VoiceIsAmbisonic); } else { for(auto &chandata : mChans) { chandata.mDryParams = DirectParams{}; chandata.mDryParams.NFCtrlFilter = device->mNFCtrlFilter; std::fill_n(chandata.mWetParams.begin(), device->NumAuxSends, SendParams{}); } mFlags.reset(VoiceIsAmbisonic); } } openal-soft-1.24.2/core/voice.h000066400000000000000000000144061474041540300162440ustar00rootroot00000000000000#ifndef CORE_VOICE_H #define CORE_VOICE_H #include #include #include #include #include #include #include #include #include "alspan.h" #include "bufferline.h" #include "buffer_storage.h" #include "devformat.h" #include "filters/biquad.h" #include "filters/nfc.h" #include "filters/splitter.h" #include "mixer/defs.h" #include "mixer/hrtfdefs.h" #include "opthelpers.h" #include "resampler_limits.h" #include "uhjfilter.h" #include "vector.h" struct ContextBase; struct DeviceBase; struct EffectSlot; enum class DistanceModel : unsigned char; using uint = unsigned int; inline constexpr size_t MaxSendCount{6}; enum class SpatializeMode : unsigned char { Off, On, Auto }; enum class DirectMode : unsigned char { Off, DropMismatch, RemixMismatch }; inline constexpr uint MaxPitch{10}; enum { AF_None = 0, AF_LowPass = 1, AF_HighPass = 2, AF_BandPass = AF_LowPass | AF_HighPass }; struct DirectParams { BiquadFilter LowPass; BiquadFilter HighPass; NfcFilter NFCtrlFilter; struct HrtfParams { HrtfFilter Old{}; HrtfFilter Target{}; alignas(16) std::array History{}; }; HrtfParams Hrtf; struct GainParams { std::array Current{}; std::array Target{}; }; GainParams Gains; }; struct SendParams { BiquadFilter LowPass; BiquadFilter HighPass; struct GainParams { std::array Current{}; std::array Target{}; }; GainParams Gains; }; struct VoiceBufferItem { std::atomic mNext{nullptr}; CallbackType mCallback{nullptr}; void *mUserData{nullptr}; uint mBlockAlign{0u}; uint mSampleLen{0u}; uint mLoopStart{0u}; uint mLoopEnd{0u}; al::span mSamples; protected: ~VoiceBufferItem() = default; }; struct VoiceProps { float Pitch; float Gain; float OuterGain; float MinGain; float MaxGain; float InnerAngle; float OuterAngle; float RefDistance; float MaxDistance; float RolloffFactor; std::array Position; std::array Velocity; std::array Direction; std::array OrientAt; std::array OrientUp; bool HeadRelative; DistanceModel mDistanceModel; Resampler mResampler; DirectMode DirectChannels; SpatializeMode mSpatializeMode; bool DryGainHFAuto; bool WetGainAuto; bool WetGainHFAuto; float OuterGainHF; float AirAbsorptionFactor; float RoomRolloffFactor; float DopplerFactor; std::array StereoPan; float Radius; float EnhWidth; float Panning; /** Direct filter and auxiliary send info. */ struct DirectData { float Gain; float GainHF; float HFReference; float GainLF; float LFReference; }; DirectData Direct; struct SendData { EffectSlot *Slot; float Gain; float GainHF; float HFReference; float GainLF; float LFReference; }; std::array Send; }; struct VoicePropsItem : public VoiceProps { std::atomic next{nullptr}; }; enum : uint { VoiceIsStatic, VoiceIsCallback, VoiceIsAmbisonic, VoiceCallbackStopped, VoiceIsFading, VoiceHasHrtf, VoiceHasNfc, VoiceFlagCount }; struct SIMDALIGN Voice { enum State { Stopped, Playing, Stopping, Pending }; std::atomic mUpdate{nullptr}; VoiceProps mProps{}; std::atomic mSourceID{0u}; std::atomic mPlayState{Stopped}; std::atomic mPendingChange{false}; /** * Source offset in samples, relative to the currently playing buffer, NOT * the whole queue. */ std::atomic mPosition{}; /** Fractional (fixed-point) offset to the next sample. */ std::atomic mPositionFrac{}; /* Current buffer queue item being played. */ std::atomic mCurrentBuffer{}; /* Buffer queue item to loop to at end of queue (will be NULL for non- * looping voices). */ std::atomic mLoopBuffer{}; std::chrono::nanoseconds mStartTime{}; /* Properties for the attached buffer(s). */ FmtChannels mFmtChannels{}; FmtType mFmtType{}; uint mFrequency{}; uint mFrameStep{}; /**< In steps of the sample type size. */ uint mBytesPerBlock{}; /**< Or for PCM formats, BytesPerFrame. */ uint mSamplesPerBlock{}; /**< Always 1 for PCM formats. */ AmbiLayout mAmbiLayout{}; AmbiScaling mAmbiScaling{}; uint mAmbiOrder{}; std::unique_ptr mDecoder; uint mDecoderPadding{}; /** Current target parameters used for mixing. */ uint mStep{0}; ResamplerFunc mResampler{}; InterpState mResampleState; std::bitset mFlags; uint mNumCallbackBlocks{0}; uint mCallbackBlockBase{0}; struct TargetData { int FilterType{}; al::span Buffer; }; TargetData mDirect; std::array mSend; /* The first MaxResamplerPadding/2 elements are the sample history from the * previous mix, with an additional MaxResamplerPadding/2 elements that are * now current (which may be overwritten if the buffer data is still * available). */ using HistoryLine = std::array; al::vector mPrevSamples{2}; struct ChannelData { float mAmbiHFScale{}, mAmbiLFScale{}; BandSplitter mAmbiSplitter; DirectParams mDryParams; std::array mWetParams; }; al::vector mChans{2}; Voice() = default; ~Voice() = default; Voice(const Voice&) = delete; Voice& operator=(const Voice&) = delete; void mix(const State vstate, ContextBase *Context, const std::chrono::nanoseconds deviceTime, const uint SamplesToDo); void prepare(DeviceBase *device); static void InitMixer(std::optional resopt); }; inline Resampler ResamplerDefault{Resampler::Spline}; #endif /* CORE_VOICE_H */ openal-soft-1.24.2/core/voice_change.h000066400000000000000000000006221474041540300175440ustar00rootroot00000000000000#ifndef VOICE_CHANGE_H #define VOICE_CHANGE_H #include struct Voice; using uint = unsigned int; enum class VChangeState { Reset, Stop, Play, Pause, Restart }; struct VoiceChange { Voice *mOldVoice{nullptr}; Voice *mVoice{nullptr}; uint mSourceID{0}; VChangeState mState{}; std::atomic mNext{nullptr}; }; #endif /* VOICE_CHANGE_H */ openal-soft-1.24.2/docs/000077500000000000000000000000001474041540300147615ustar00rootroot00000000000000openal-soft-1.24.2/docs/3D7.1.txt000066400000000000000000000075441474041540300162300ustar00rootroot00000000000000Overview ======== 3D7.1 is a custom speaker layout designed by Simon Goodwin at Codemasters[1]. Typical surround sound setups, like quad, 5.1, 6.1, and 7.1, only produce audio on a 2D horizontal plane with no verticality, which means the envelopment of "surround" sound is limited to left, right, front, and back panning. Sounds that should come from above or below will still only play in 2D since there is no height difference in the speaker array. To work around this, 3D7.1 was designed so that some speakers are placed higher than the listener while others are lower, in a particular configuration that tries to provide balanced output and maintain some compatibility with existing audio content and software. Software that recognizes this setup, or can be configured for it, can then take advantage of the height difference and increase the perception of verticality for true 3D audio. The result is that sounds can be perceived as coming from left, right, front, and back, as well as up and down. [1] http://www.codemasters.com/research/3D_sound_for_3D_games.pdf Hardware Setup ============== Setting up 3D7.1 requires an audio device capable of raw 8-channel or 7.1 output, along with a 7.1 speaker kit. The speakers should be hooked up to the device in the usual way, with front-left and front-right output going to the front-left and front-right speakers, etc. The placement of the speakers should be set up according to the table below. Azimuth is the horizontal angle in degrees, with 0 directly in front and positive values go /left/, and elevation is the vertical angle in degrees, with 0 at head level and positive values go /up/. ------------------------------------------------------------ - Speaker label | Azimuth | Elevation | New label - ------------------------------------------------------------ - Front left | 51 | 24 | Upper front left - - Front right | -51 | 24 | Upper front right - - Front center | 0 | 0 | Front center - - Subwoofer/LFE | N/A | N/A | Subwoofer/LFE - - Side left | 129 | -24 | Lower back left - - Side right | -129 | -24 | Lower back right - - Back left | 180 | 55 | Upper back center - - Back right | 0 | -55 | Lower front center - ------------------------------------------------------------ Note that this speaker layout *IS NOT* compatible with standard 7.1 content. Audio that should be played from the back will come out at the wrong location since the back speakers are placed in the lower front and upper back positions. However, this speaker layout *IS* more or less compatible with standard 5.1 content. Though slightly tilted, to a listener sitting a bit further back from the center, the front and side speakers will be close enough to their intended locations that the output won't be too off. Software Setup ============== To enable 3D7.1 on OpenAL Soft, first make sure the audio device is configured for 7.1 output. Then in the alsoft-config utility, for the Channels setting choose "3D7.1 Surround" from the drop-down list. And that's it. Any application using OpenAL Soft can take advantage of fully 3D audio, and multi-channel sounds will be properly remixed for the speaker layout. Note that care must be taken that the audio device is not treated as a "true" 7.1 device by non-3D7.1-capable applications. In particular, the audio server should not try to upmix stereo and 5.1 content to "fill out" the back speakers, and non-3D7.1 apps should be set to either stereo or 5.1 output. As such, if your system is capable of it, it may be useful to define a virtual 5.1 device that maps the front, side, and LFE channels to the main device for output and disables upmixing, then use that virtual 5.1 device for apps that do normal stereo or surround sound output, and use the main device for apps that understand 3D7.1 output. openal-soft-1.24.2/docs/ambdec.txt000066400000000000000000000210341474041540300167350ustar00rootroot00000000000000AmbDec Configuration Files ========================== AmbDec configuration files were developed by Fons Adriaensen as part of the AmbDec program . The file works by specifying a decoder matrix or matrices which transform ambisonic channels into speaker feeds. Single-band decoders specify a single matrix that transforms all frequencies, while dual-band decoders specifies two matrices where one transforms low frequency sounds and the other transforms high frequency sounds. See docs/ambisonics.txt for more general information about ambisonics. Starting with OpenAL Soft 1.18, version 3 of the file format is supported as a means of specifying custom surround sound speaker layouts. These configuration files are also used to enable per-speaker distance compensation. File Format =========== As of this writing, there is no official documentation of the .ambdec file format. However, the format as OpenAL Soft sees it is as follows: The file is plain text. Comments start with a hash/pound character (#). There may be any amount of whitespace in between the option and parameter values. Strings are *not* enclosed in quotation marks. /description Specifies a text description of the configuration. Ignored by OpenAL Soft. /version Declares the format version used by the configuration file. OpenAL Soft currently only supports version 3. /dec/chan_mask Specifies a hexadecimal mask value of ambisonic input channels used by this decoder. Counting up from the least significant bit, bit 0 maps to Ambisonic Channel Number (ACN) 0, bit 1 maps to ACN 1, etc. As an example, a value of 'b' enables bits 0, 1, and 3 (1011 in binary), which correspond to ACN 0, 1, and 3 (first-order horizontal). /dec/freq_bands Specifies the number of frequency bands used by the decoder. This must be 1 for single-band or 2 for dual-band. /dec/speakers Specifies the number of output speakers to decode to. /dec/coeff_scale Specifies the scaling used by the decoder coefficients. Currently recognized types are fuma, sn3d, and n3d, for Furse-Malham (FuMa), semi-normalized (SN3D), and fully normalized (N3D) scaling, respectively. /opt/input_scale Specifies the scaling used by the ambisonic input data. As OpenAL Soft renders the data itself and knows the scaling, this is ignored. /opt/nfeff_comp Specifies whether near-field effect compensation is off (not applied at all), applied on input (faster, less accurate with varying speaker distances) or output (slower, more accurate with varying speaker distances). Ignored by OpenAL Soft. /opt/delay_comp Specifies whether delay compensation is applied for output. This is used to correct for time variations caused by different speaker distances. As OpenAL Soft has its own config option for this, this is ignored. /opt/level_comp Specifies whether gain compensation is applied for output. This is used to correct for volume variations caused by different speaker distances. As OpenAL Soft has its own config option for this, this is ignored. /opt/xover_freq Specifies the crossover frequency for dual-band decoders. Frequencies less than this are fed to the low-frequency matrix, and frequencies greater than this are fed to the high-frequency matrix. Unused for single-band decoders. /opt/xover_ratio Specifies the volume ratio between the frequency bands. Values greater than 0 decrease the low-frequency output by half the specified value and increase the high-frequency output by half the specified value, while values less than 0 increase the low-frequency output and decrease the high-frequency output to similar effect. Unused for single-band decoders. /speakers/{ Begins the output speaker definitions. A speaker is defined using the add_spkr command, and there must be a matching number of speaker definitions as the specified speaker count. The definitions are ended with a "/}". add_spkr Defines an output speaker. The ID is a string identifier for the output speaker (see Speaker IDs below). The distance is in meters from the center-point of the physical speaker array. The azimuth is the horizontal angle of the speaker, in degrees, where 0 is directly front and positive values go left. The elevation is the vertical angle of the speaker, in degrees, where 0 is directly front and positive goes upward. The connection string is the JACK port name the speaker should connect to. Currently, OpenAL Soft uses the ID and distance, and ignores the rest. /lfmatrix/{ Begins the low-frequency decoder matrix definition. The definition should include an order_gain command to specify the base gain for the ambisonic orders. Each matrix row is defined using the add_row command, and there must be a matching number of rows as the number of speakers. Additionally the row definitions are in the same order as the speaker definitions. The definitions are ended with a "/}". Only valid for dual-band decoders. /hfmatrix/{ Begins the high-frequency decoder matrix definition. The definition should include an order_gain command to specify the base gain for the ambisonic orders. Each matrix row is defined using the add_row command, and there must be a matching number of rows as the number of speakers, Additionally the row definitions are in the same order as the speaker definitions. The definitions are ended with a "/}". Only valid for dual-band decoders. /matrix/{ Begins the decoder matrix definition. The definition should include an order_gain command to specify the base gain for the ambisonic orders. Each matrix row is defined using the add_row command, and there must be a matching number of rows as the number of speakers. Additionally the row definitions are in the same order as the speaker definitions. The definitions are ended with a "/}". Only valid for single-band decoders. order_gain Specifies the base gain for the zeroth-, first-, second-, and third-order coefficients in the given matrix, automatically scaling the related coefficients. This should be specified at the beginning of the matrix definition. add_row ... Specifies a row of coefficients for the matrix. There should be one coefficient for each enabled bit in the channel mask, and corresponds to the matching ACN channel. /end Marks the end of the configuration file. Speaker IDs =========== The AmbDec program uses the speaker ID as a label to display in its config dialog, but does not otherwise use it for any particular purpose. However, since OpenAL Soft needs to match a speaker definition to an output channel, the speaker ID is used to identify what output channel it correspond to. Therefore, OpenAL Soft requires these channel labels to be recognized: LF = Front left RF = Front right LS = Side left RS = Side right LB = Back left RB = Back right CE = Front center CB = Back center LFT = Top front left RFT = Top front right LBT = Top back left RBT = Top back right Additionally, configuration files for surround51 will acknowledge back speakers for side channels, to avoid issues with a configuration expecting 5.1 to use the side channels when the device is configured for back, or vice-versa. Furthermore, OpenAL Soft does not require a speaker definition for each output channel the configuration is used with. So for example a 5.1 configuration may omit a front center speaker definition, in which case the front center output channel will not contribute to the ambisonic decode (though OpenAL Soft will still use it in certain scenarios, such as the AL_EFFECT_DEDICATED_DIALOGUE effect). Creating Configuration Files ============================ Configuration files can be created or modified by hand in a text editor. The AmbDec program also has a GUI for creating and editing them. However, these methods rely on you having the coefficients to fill in... they won't be generated for you. Another option is to use the Ambisonic Decoder Toolbox . This is a collection of MATLAB and GNU Octave scripts that can generate AmbDec configuration files from an array of speaker definitions (labels and positions). If you're familiar with using MATLAB or GNU Octave, this may be a good option. There are plans for OpenAL Soft to include a utility to generate coefficients and make configuration files. However, calculating proper coefficients for anything other than regular or semi-regular speaker setups is somewhat of a black art, so may take some time. openal-soft-1.24.2/docs/ambisonics.txt000066400000000000000000000144071474041540300176570ustar00rootroot00000000000000OpenAL Soft's renderer has advanced quite a bit since its start with panned stereo output. Among these advancements is support for surround sound output, using psychoacoustic modeling and more accurate plane wave reconstruction. The concepts in use may not be immediately obvious to people just getting into 3D audio, or people who only have more indirect experience through the use of 3D audio APIs, so this document aims to introduce the ideas and purpose of Ambisonics as used by OpenAL Soft. What Is It? =========== Originally developed in the 1970s by Michael Gerzon and a team others, Ambisonics was created as a means of recording and playing back 3D sound. Taking advantage of the way sound waves propagate, it is possible to record a fully 3D soundfield using as few as 4 channels (or even just 3, if you don't mind dropping down to 2 dimensions like many surround sound systems are). This representation is called B-Format. It was designed to handle audio independent of any specific speaker layout, so with a proper decoder the same recording can be played back on a variety of speaker setups, from quadraphonic and hexagonal to cubic and other periphonic (with height) layouts. Although it was developed decades ago, various factors held ambisonics back from really taking hold in the consumer market. However, given the solid theories backing it, as well as the potential and practical benefits on offer, it continued to be a topic of research over the years, with improvements being made over the original design. One of the improvements made is the use of Spherical Harmonics to increase the number of channels for greater spatial definition. Where the original 4-channel design is termed as "First-Order Ambisonics", or FOA, the increased channel count through the use of Spherical Harmonics is termed as "Higher-Order Ambisonics", or HOA. The details of higher order ambisonics are out of the scope of this document, but know that the added channels are still independent of any speaker layout, and aim to further improve the spatial detail for playback. Today, the processing power available on even low-end computers means real-time Ambisonics processing is possible. Not only can decoders be implemented in software, but so can encoders, synthesizing a soundfield using multiple panned sources, thus taking advantage of what ambisonics offers in a virtual audio environment. How Does It Help? ================= Positional sound has come a long way from pan-pot stereo (aka pair-wise). Although useful at the time, the issues became readily apparent when trying to extend it for surround sound. Pan-pot doesn't work as well for depth (front- back) or vertical panning, it has a rather small "sweet spot" (the area the head needs to be in to perceive the sound in its intended direction), and it misses key distance-related details of sound waves. Ambisonics takes a different approach. It uses all available speakers to help localize a sound, and it also takes into account how the brain localizes low frequency sounds compared to high frequency ones -- a so-called psychoacoustic model. It may seem counter-intuitive (if a sound is coming from the front-left, surely just play it on the front-left speaker?), but to properly model a sound coming from where a speaker doesn't exist, more needs to be done to construct a proper sound wave that's perceived to come from the intended direction. Doing this creates a larger sweet spot, allowing the perceived sound direction to remain correct over a larger area around the center of the speakers. In addition, Ambisonics can encode the near-field effect of sounds, effectively capturing the sound distance. The near-field effect is a subtle low-frequency boost as a result of wave-front curvature, and properly compensating for this occurring with the output speakers (as well as emulating it with a synthesized soundfield) can create an improved sense of distance for sounds that move near or far. How Is It Used? =============== As a 3D audio API, OpenAL is tasked with playing 3D sound as best it can with the speaker setup the user has. Since the OpenAL API doesn't expose discrete playback speaker feeds, an implementation has a lot of leeway with how to deal with the audio before it's played back for the user to hear. Consequently, OpenAL Soft (or any other OpenAL implementation that wishes to) can render using Ambisonics and decode the ambisonic mix for a high level of accuracy over what simple pan-pot could provide. In addition to surround sound output, Ambisonics also has benefits with stereo output. 2-channel UHJ is a stereo-compatible format that encodes some surround sound information using a wide-band 90-degree phase shift filter. This is generated by taking the ambisonic mix and deriving a front-stereo mix with with the rear sounds filtered in with it. Although the result is not as good as 3-channel (2D) B-Format, it has the distinct advantage of only using 2 channels and being compatible with stereo output. This means it will sound just fine when played as-is through a normal stereo device, or it may optionally be fed to a properly configured surround sound receiver which can extract the encoded information and restore some of the original surround sound signal. What Are Its Limitations? ========================= As good as Ambisonics is, it's not a magic bullet that can overcome all problems. One of the bigger issues it has is dealing with irregular speaker setups, such as 5.1 surround sound. The problem mainly lies in the imbalanced speaker positioning -- there are three speakers within the front 60-degree area (meaning only 30-degree gaps in between each of the three speakers), while only two speakers cover the back 140-degree area, leaving 80-degree gaps on the sides. It should be noted that this problem is inherent to the speaker layout itself; there isn't much that can be done to get an optimal surround sound response, with ambisonics or not. It will do the best it can, but there are trade-offs between detail and accuracy. Another issue lies with HRTF. While it's certainly possible to play an ambisonic mix using HRTF and retain a sense of 3D sound, doing so with a high degree of spatial detail requires a fair amount of resources, in both memory and processing time. And even with it, mixing sounds with HRTF directly will still be better for positional accuracy. openal-soft-1.24.2/docs/env-vars.txt000066400000000000000000000113141474041540300172630ustar00rootroot00000000000000Useful Environment Variables Below is a list of environment variables that can be set to aid with running or debugging apps that use OpenAL Soft. They should be set before the app is run. *** Logging *** ALSOFT_LOGLEVEL Specifies the amount of logging OpenAL Soft will write out: 0 - Effectively disables all logging 1 - Prints out errors only 2 - Prints out warnings and errors 3 - Prints out additional information, as well as warnings and errors ALSOFT_LOGFILE Specifies a filename that logged output will be written to. Note that the file will be first cleared when logging is initialized. *** Overrides *** ALSOFT_CONF Specifies an additional configuration file to load settings from. These settings will take precedence over the global and user configs, but not other environment variable settings. ALSOFT_DRIVERS Overrides the drivers config option. This specifies which backend drivers to consider or not consider for use. Please see the drivers option in alsoftrc.sample for a list of available drivers. ALSOFT_DEFAULT_REVERB Specifies the default reverb preset to apply to sources. Please see the default-reverb option in alsoftrc.sample for additional information and a list of available presets. ALSOFT_TRAP_AL_ERROR Set to "true" or "1" to force trapping AL errors. Like the trap-al-error config option, this will raise a SIGTRAP signal (or a breakpoint exception under Windows) when a context-level error is generated. Useful when run under a debugger as it will break execution right when the error occurs, making it easier to track the cause. ALSOFT_TRAP_ALC_ERROR Set to "true" or "1" to force trapping ALC errors. Like the trap-alc-error config option, this will raise a SIGTRAP signal (or a breakpoint exception under Windows) when a device-level error is generated. Useful when run under a debugger as it will break execution right when the error occurs, making it easier to track the cause. ALSOFT_TRAP_ERROR Set to "true" or "1" to force trapping both ALC and AL errors. *** Compatibility *** __ALSOFT_HALF_ANGLE_CONES Older versions of OpenAL Soft incorrectly calculated the cone angles to range between 0 and 180 degrees, instead of the expected range of 0 to 360 degrees. Setting this to "true" or "1" restores the old buggy behavior, for apps that were written to expect the incorrect range. __ALSOFT_ENABLE_SUB_DATA_EXT The more widely used AL_EXT_SOURCE_RADIUS extension is incompatible with the now-defunct AL_SOFT_buffer_sub_data extension. Setting this to "true" or "1" restores the AL_SOFT_buffer_sub_data extension for apps that require it, disabling AL_EXT_SOURCE_RADIUS. __ALSOFT_REVERSE_Z Applications that don't natively use OpenAL's coordinate system have to convert to it before passing in 3D coordinates. Depending on how exactly this is done, it can cause correct output for stereo but incorrect Z panning for surround sound (i.e., sounds that are supposed to be behind you sound like they're in front, and vice-versa). Setting this to "true" or "1" will negate the localized Z coordinate to flip front/back panning for 3D sources. __ALSOFT_REVERSE_Y Same as for __ALSOFT_REVERSE_Z, but for Y (up/down) panning. __ALSOFT_REVERSE_X Same as for __ALSOFT_REVERSE_Z, but for X (left/right) panning. __ALSOFT_VENDOR_OVERRIDE Overrides the value returned by alGetString(AL_VENDOR), for apps that misbehave without particular values. __ALSOFT_VERSION_OVERRIDE Overrides the value returned by alGetString(AL_VERSION), for apps that misbehave without particular values. __ALSOFT_RENDERER_OVERRIDE Overrides the value returned by alGetString(AL_RENDERER), for apps that misbehave without particular values. __ALSOFT_DEFAULT_ERROR Applications that erroneously call alGetError prior to setting a context as current may not like that OpenAL Soft returns 0xA004 (AL_INVALID_OPERATION), indicating that the call could not be executed as there's no context to get the error value from. This can be set to 0 (AL_NO_ERROR) to let such apps pass the check despite the problem. Other applications, however, may see AL_NO_ERROR returned and assume any previous AL calls succeeded when they actually failed, so this should only be set when necessary. __ALSOFT_SUSPEND_CONTEXT Due to the OpenAL spec not being very clear about them, behavior of the alcSuspendContext and alcProcessContext methods has varied, and because of that, previous versions of OpenAL Soft had them no-op. Creative's hardware drivers and the Rapture3D driver, however, use these methods to batch changes, which some applications make use of to protect against partial updates. In an attempt to standardize on that behavior, OpenAL Soft has changed those methods accordingly. Setting this to "ignore" restores the previous no-op behavior for applications that interact poorly with the new behavior. openal-soft-1.24.2/docs/hrtf.txt000066400000000000000000000072621474041540300164740ustar00rootroot00000000000000HRTF Support ============ Starting with OpenAL Soft 1.14, HRTFs can be used to enable enhanced spatialization for both 3D (mono) and multi-channel sources, when used with headphones/stereo output. This can be enabled using the 'hrtf' config option. For multi-channel sources this creates a virtual speaker effect, making it sound as if speakers provide a discrete position for each channel around the listener. For mono sources this provides much more versatility in the perceived placement of sounds, making it seem as though they are coming from all around, including above and below the listener, instead of just to the front, back, and sides. The default data set is based on the KEMAR HRTF data provided by MIT, which can be found at . Custom HRTF Data Sets ===================== OpenAL Soft also provides an option to use user-specified data sets, in addition to or in place of the default set. This allows users to provide data sets that could be better suited for their heads, or to work with stereo speakers instead of headphones, for example. The file format is specified below. It uses little-endian byte order. == ALchar magic[8] = "MinPHR03"; ALuint sampleRate; ALubyte channelType; /* Can be 0 (mono) or 1 (stereo). */ ALubyte hrirSize; /* Can be 8 to 128 in steps of 8. */ ALubyte fdCount; /* Can be 1 to 16. */ struct { ALushort distance; /* Can be 50mm to 2500mm. */ ALubyte evCount; /* Can be 5 to 128. */ ALubyte azCount[evCount]; /* Each can be 1 to 128. */ } fields[fdCount]; /* NOTE: ALbyte3 is a packed 24-bit sample type, * hrirCount is the sum of all azCounts. * channels can be 1 (mono) or 2 (stereo) depending on channelType. */ ALbyte3 coefficients[hrirCount][hrirSize][channels]; ALubyte delays[hrirCount][channels]; /* Each can be 0 to 63. */ == The data layout is as follows: The file first starts with the 8-byte marker, "MinPHR03", to identify it as an HRTF data set. This is followed by an unsigned 32-bit integer, specifying the sample rate the data set is designed for (OpenAL Soft will resample the HRIRs if the output device's playback rate doesn't match). Afterward, an unsigned 8-bit integer specifies the channel type, which can be 0 (mono, single-channel) or 1 (stereo, dual-channel). After this is another 8-bit integer which specifies how many sample points (or finite impulse response filter coefficients) make up each HRIR. The following unsigned 8-bit integer specifies the number of fields used by the data set, which must be in descending order (farthest first, closest last). Then for each field an unsigned 16-bit short specifies the distance for that field in millimeters, followed by an 8-bit integer for the number of elevations. These elevations start at the bottom (-90 degrees), and increment upwards. Following this is an array of unsigned 8-bit integers, one for each elevation which specifies the number of azimuths (and thus HRIRs) that make up each elevation. Azimuths start clockwise from the front, constructing a full circle. Mono HRTFs use the same HRIRs for both ears by reversing the azimuth calculation (ie. left = angle, right = 360-angle). The actual coefficients follow. Each coefficient is a signed 24-bit sample. Stereo HRTFs interleave left/right ear coefficients. The HRIRs must be minimum-phase. This allows the use of a smaller filter length, reducing computation. After the coefficients is an array of unsigned 8-bit delay values as 6.2 fixed- point integers, one for each HRIR (with stereo HRTFs interleaving left/right ear delays). This is the propagation delay in samples a signal must wait before being convolved with the corresponding minimum-phase HRIR filter. openal-soft-1.24.2/examples/000077500000000000000000000000001474041540300156475ustar00rootroot00000000000000openal-soft-1.24.2/examples/alconvolve.c000066400000000000000000000442571474041540300201770ustar00rootroot00000000000000/* * OpenAL Convolution Reverb Example * * Copyright (c) 2020 by Chris Robinson * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ /* This file contains an example for applying convolution to a source. */ #include #include #include #include #include #include #include "sndfile.h" #include "AL/al.h" #include "AL/alext.h" #include "common/alhelpers.h" #include "win_main_utf8.h" #ifndef AL_SOFT_convolution_effect #define AL_SOFT_convolution_effect #define AL_EFFECT_CONVOLUTION_SOFT 0xA000 #endif /* Filter object functions */ static LPALGENFILTERS alGenFilters; static LPALDELETEFILTERS alDeleteFilters; static LPALISFILTER alIsFilter; static LPALFILTERI alFilteri; static LPALFILTERIV alFilteriv; static LPALFILTERF alFilterf; static LPALFILTERFV alFilterfv; static LPALGETFILTERI alGetFilteri; static LPALGETFILTERIV alGetFilteriv; static LPALGETFILTERF alGetFilterf; static LPALGETFILTERFV alGetFilterfv; /* Effect object functions */ static LPALGENEFFECTS alGenEffects; static LPALDELETEEFFECTS alDeleteEffects; static LPALISEFFECT alIsEffect; static LPALEFFECTI alEffecti; static LPALEFFECTIV alEffectiv; static LPALEFFECTF alEffectf; static LPALEFFECTFV alEffectfv; static LPALGETEFFECTI alGetEffecti; static LPALGETEFFECTIV alGetEffectiv; static LPALGETEFFECTF alGetEffectf; static LPALGETEFFECTFV alGetEffectfv; /* Auxiliary Effect Slot object functions */ static LPALGENAUXILIARYEFFECTSLOTS alGenAuxiliaryEffectSlots; static LPALDELETEAUXILIARYEFFECTSLOTS alDeleteAuxiliaryEffectSlots; static LPALISAUXILIARYEFFECTSLOT alIsAuxiliaryEffectSlot; static LPALAUXILIARYEFFECTSLOTI alAuxiliaryEffectSloti; static LPALAUXILIARYEFFECTSLOTIV alAuxiliaryEffectSlotiv; static LPALAUXILIARYEFFECTSLOTF alAuxiliaryEffectSlotf; static LPALAUXILIARYEFFECTSLOTFV alAuxiliaryEffectSlotfv; static LPALGETAUXILIARYEFFECTSLOTI alGetAuxiliaryEffectSloti; static LPALGETAUXILIARYEFFECTSLOTIV alGetAuxiliaryEffectSlotiv; static LPALGETAUXILIARYEFFECTSLOTF alGetAuxiliaryEffectSlotf; static LPALGETAUXILIARYEFFECTSLOTFV alGetAuxiliaryEffectSlotfv; /* This stuff defines a simple streaming player object, the same as alstream.c. * Comments are removed for brevity, see alstream.c for more details. */ enum { NumBuffers = 4 }; enum { BufferSamples = 8192 }; typedef struct StreamPlayer { ALuint buffers[NumBuffers]; ALuint source; SNDFILE *sndfile; SF_INFO sfinfo; float *membuf; ALenum format; } StreamPlayer; static StreamPlayer *NewPlayer(void) { StreamPlayer *player; player = calloc(1, sizeof(*player)); assert(player != NULL); alGenBuffers(NumBuffers, player->buffers); assert(alGetError() == AL_NO_ERROR && "Could not create buffers"); alGenSources(1, &player->source); assert(alGetError() == AL_NO_ERROR && "Could not create source"); alSource3i(player->source, AL_POSITION, 0, 0, -1); alSourcei(player->source, AL_SOURCE_RELATIVE, AL_TRUE); alSourcei(player->source, AL_ROLLOFF_FACTOR, 0); assert(alGetError() == AL_NO_ERROR && "Could not set source parameters"); return player; } static void ClosePlayerFile(StreamPlayer *player) { if(player->sndfile) sf_close(player->sndfile); player->sndfile = NULL; free(player->membuf); player->membuf = NULL; } static void DeletePlayer(StreamPlayer *player) { ClosePlayerFile(player); alDeleteSources(1, &player->source); alDeleteBuffers(NumBuffers, player->buffers); if(alGetError() != AL_NO_ERROR) fprintf(stderr, "Failed to delete object IDs\n"); memset(player, 0, sizeof(*player)); /* NOLINT(clang-analyzer-security.insecureAPI.*) */ free(player); } static int OpenPlayerFile(StreamPlayer *player, const char *filename) { size_t frame_size; ClosePlayerFile(player); player->sndfile = sf_open(filename, SFM_READ, &player->sfinfo); if(!player->sndfile) { fprintf(stderr, "Could not open audio in %s: %s\n", filename, sf_strerror(NULL)); return 0; } player->format = AL_NONE; if(player->sfinfo.channels == 1) player->format = AL_FORMAT_MONO_FLOAT32; else if(player->sfinfo.channels == 2) player->format = AL_FORMAT_STEREO_FLOAT32; else if(player->sfinfo.channels == 6) player->format = AL_FORMAT_51CHN32; else if(player->sfinfo.channels == 3) { if(sf_command(player->sndfile, SFC_WAVEX_GET_AMBISONIC, NULL, 0) == SF_AMBISONIC_B_FORMAT) player->format = AL_FORMAT_BFORMAT2D_FLOAT32; } else if(player->sfinfo.channels == 4) { if(sf_command(player->sndfile, SFC_WAVEX_GET_AMBISONIC, NULL, 0) == SF_AMBISONIC_B_FORMAT) player->format = AL_FORMAT_BFORMAT3D_FLOAT32; } if(!player->format) { fprintf(stderr, "Unsupported channel count: %d\n", player->sfinfo.channels); sf_close(player->sndfile); player->sndfile = NULL; return 0; } frame_size = (size_t)(BufferSamples * player->sfinfo.channels) * sizeof(float); player->membuf = malloc(frame_size); return 1; } static int StartPlayer(StreamPlayer *player) { ALsizei i; alSourceRewind(player->source); alSourcei(player->source, AL_BUFFER, 0); for(i = 0;i < NumBuffers;i++) { sf_count_t slen = sf_readf_float(player->sndfile, player->membuf, BufferSamples); if(slen < 1) break; slen *= player->sfinfo.channels * (sf_count_t)sizeof(float); alBufferData(player->buffers[i], player->format, player->membuf, (ALsizei)slen, player->sfinfo.samplerate); } if(alGetError() != AL_NO_ERROR) { fprintf(stderr, "Error buffering for playback\n"); return 0; } alSourceQueueBuffers(player->source, i, player->buffers); alSourcePlay(player->source); if(alGetError() != AL_NO_ERROR) { fprintf(stderr, "Error starting playback\n"); return 0; } return 1; } static int UpdatePlayer(StreamPlayer *player) { ALint processed, state; alGetSourcei(player->source, AL_SOURCE_STATE, &state); alGetSourcei(player->source, AL_BUFFERS_PROCESSED, &processed); if(alGetError() != AL_NO_ERROR) { fprintf(stderr, "Error checking source state\n"); return 0; } while(processed > 0) { ALuint bufid; sf_count_t slen; alSourceUnqueueBuffers(player->source, 1, &bufid); processed--; slen = sf_readf_float(player->sndfile, player->membuf, BufferSamples); if(slen > 0) { slen *= player->sfinfo.channels * (sf_count_t)sizeof(float); alBufferData(bufid, player->format, player->membuf, (ALsizei)slen, player->sfinfo.samplerate); alSourceQueueBuffers(player->source, 1, &bufid); } if(alGetError() != AL_NO_ERROR) { fprintf(stderr, "Error buffering data\n"); return 0; } } if(state != AL_PLAYING && state != AL_PAUSED) { ALint queued; alGetSourcei(player->source, AL_BUFFERS_QUEUED, &queued); if(queued == 0) return 0; alSourcePlay(player->source); if(alGetError() != AL_NO_ERROR) { fprintf(stderr, "Error restarting playback\n"); return 0; } } return 1; } /* CreateEffect creates a new OpenAL effect object with a convolution type, and * returns the new effect ID. */ static ALuint CreateEffect(void) { ALuint effect = 0; ALenum err; printf("Using Convolution\n"); /* Create the effect object and set the convolution effect type. */ alGenEffects(1, &effect); alEffecti(effect, AL_EFFECT_TYPE, AL_EFFECT_CONVOLUTION_SOFT); /* Check if an error occurred, and clean up if so. */ err = alGetError(); if(err != AL_NO_ERROR) { fprintf(stderr, "OpenAL error: %s\n", alGetString(err)); if(alIsEffect(effect)) alDeleteEffects(1, &effect); return 0; } return effect; } /* LoadBuffer loads the named audio file into an OpenAL buffer object, and * returns the new buffer ID. */ static ALuint LoadSound(const char *filename) { const char *namepart; ALenum err, format; ALuint buffer; SNDFILE *sndfile; SF_INFO sfinfo; float *membuf; sf_count_t num_frames; ALsizei num_bytes; /* Open the audio file and check that it's usable. */ sndfile = sf_open(filename, SFM_READ, &sfinfo); if(!sndfile) { fprintf(stderr, "Could not open audio in %s: %s\n", filename, sf_strerror(sndfile)); return 0; } if(sfinfo.frames < 1 || sfinfo.frames > (sf_count_t)(INT_MAX/sizeof(float))/sfinfo.channels) { fprintf(stderr, "Bad sample count in %s (%" PRId64 ")\n", filename, sfinfo.frames); sf_close(sndfile); return 0; } /* Get the sound format, and figure out the OpenAL format. Use floats since * impulse responses will usually have more than 16-bit precision. */ format = AL_NONE; if(sfinfo.channels == 1) format = AL_FORMAT_MONO_FLOAT32; else if(sfinfo.channels == 2) format = AL_FORMAT_STEREO_FLOAT32; else if(sfinfo.channels == 3) { if(sf_command(sndfile, SFC_WAVEX_GET_AMBISONIC, NULL, 0) == SF_AMBISONIC_B_FORMAT) format = AL_FORMAT_BFORMAT2D_FLOAT32; } else if(sfinfo.channels == 4) { if(sf_command(sndfile, SFC_WAVEX_GET_AMBISONIC, NULL, 0) == SF_AMBISONIC_B_FORMAT) format = AL_FORMAT_BFORMAT3D_FLOAT32; } if(!format) { fprintf(stderr, "Unsupported channel count: %d\n", sfinfo.channels); sf_close(sndfile); return 0; } namepart = strrchr(filename, '/'); if(!namepart) namepart = strrchr(filename, '\\'); if(!namepart) namepart = filename; else namepart++; printf("Loading: %s (%s, %dhz, %" PRId64 " samples / %.2f seconds)\n", namepart, FormatName(format), sfinfo.samplerate, sfinfo.frames, (double)sfinfo.frames / sfinfo.samplerate); fflush(stdout); /* Decode the whole audio file to a buffer. */ membuf = malloc((size_t)(sfinfo.frames * sfinfo.channels) * sizeof(float)); num_frames = sf_readf_float(sndfile, membuf, sfinfo.frames); if(num_frames < 1) { free(membuf); sf_close(sndfile); fprintf(stderr, "Failed to read samples in %s (%" PRId64 ")\n", filename, num_frames); return 0; } num_bytes = (ALsizei)(num_frames * sfinfo.channels) * (ALsizei)sizeof(float); /* Buffer the audio data into a new buffer object, then free the data and * close the file. */ buffer = 0; alGenBuffers(1, &buffer); alBufferData(buffer, format, membuf, num_bytes, sfinfo.samplerate); free(membuf); sf_close(sndfile); /* Check if an error occurred, and clean up if so. */ err = alGetError(); if(err != AL_NO_ERROR) { fprintf(stderr, "OpenAL Error: %s\n", alGetString(err)); if(buffer && alIsBuffer(buffer)) alDeleteBuffers(1, &buffer); return 0; } return buffer; } int main(int argc, char **argv) { ALuint ir_buffer, filter, effect, slot; StreamPlayer *player; int i; /* Print out usage if no arguments were specified */ if(argc < 2) { fprintf(stderr, "Usage: %s [-device ] " "<[-dry | -nodry] filename>...\n", argv[0]); return 1; } argv++; argc--; if(InitAL(&argv, &argc) != 0) return 1; if(!alIsExtensionPresent("AL_SOFTX_convolution_effect")) { CloseAL(); fprintf(stderr, "Error: Convolution effect not supported\n"); return 1; } if(argc < 2) { CloseAL(); fprintf(stderr, "Error: Missing impulse response or sound files\n"); return 1; } /* Define a macro to help load the function pointers. */ #define LOAD_PROC(T, x) ((x) = FUNCTION_CAST(T, alGetProcAddress(#x))) LOAD_PROC(LPALGENFILTERS, alGenFilters); LOAD_PROC(LPALDELETEFILTERS, alDeleteFilters); LOAD_PROC(LPALISFILTER, alIsFilter); LOAD_PROC(LPALFILTERI, alFilteri); LOAD_PROC(LPALFILTERIV, alFilteriv); LOAD_PROC(LPALFILTERF, alFilterf); LOAD_PROC(LPALFILTERFV, alFilterfv); LOAD_PROC(LPALGETFILTERI, alGetFilteri); LOAD_PROC(LPALGETFILTERIV, alGetFilteriv); LOAD_PROC(LPALGETFILTERF, alGetFilterf); LOAD_PROC(LPALGETFILTERFV, alGetFilterfv); LOAD_PROC(LPALGENEFFECTS, alGenEffects); LOAD_PROC(LPALDELETEEFFECTS, alDeleteEffects); LOAD_PROC(LPALISEFFECT, alIsEffect); LOAD_PROC(LPALEFFECTI, alEffecti); LOAD_PROC(LPALEFFECTIV, alEffectiv); LOAD_PROC(LPALEFFECTF, alEffectf); LOAD_PROC(LPALEFFECTFV, alEffectfv); LOAD_PROC(LPALGETEFFECTI, alGetEffecti); LOAD_PROC(LPALGETEFFECTIV, alGetEffectiv); LOAD_PROC(LPALGETEFFECTF, alGetEffectf); LOAD_PROC(LPALGETEFFECTFV, alGetEffectfv); LOAD_PROC(LPALGENAUXILIARYEFFECTSLOTS, alGenAuxiliaryEffectSlots); LOAD_PROC(LPALDELETEAUXILIARYEFFECTSLOTS, alDeleteAuxiliaryEffectSlots); LOAD_PROC(LPALISAUXILIARYEFFECTSLOT, alIsAuxiliaryEffectSlot); LOAD_PROC(LPALAUXILIARYEFFECTSLOTI, alAuxiliaryEffectSloti); LOAD_PROC(LPALAUXILIARYEFFECTSLOTIV, alAuxiliaryEffectSlotiv); LOAD_PROC(LPALAUXILIARYEFFECTSLOTF, alAuxiliaryEffectSlotf); LOAD_PROC(LPALAUXILIARYEFFECTSLOTFV, alAuxiliaryEffectSlotfv); LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTI, alGetAuxiliaryEffectSloti); LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTIV, alGetAuxiliaryEffectSlotiv); LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTF, alGetAuxiliaryEffectSlotf); LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTFV, alGetAuxiliaryEffectSlotfv); #undef LOAD_PROC /* Load the reverb into an effect. */ effect = CreateEffect(); if(!effect) { CloseAL(); return 1; } /* Load the impulse response sound into a buffer. */ ir_buffer = LoadSound(argv[0]); if(!ir_buffer) { alDeleteEffects(1, &effect); CloseAL(); return 1; } /* Create the effect slot object. This is what "plays" an effect on sources * that connect to it. */ slot = 0; alGenAuxiliaryEffectSlots(1, &slot); /* Set the impulse response sound buffer on the effect slot. This allows * effects to access it as needed. In this case, convolution uses it as the * filter source. NOTE: Unlike the effect object, the buffer *is* kept * referenced and may not be changed or deleted as long as it's set, just * like with a source. When another buffer is set, or the effect slot is * deleted, the buffer reference is released. * * The effect slot's gain is reduced because the impulse responses I've * tested with result in excessively loud reverb. Is that normal? Even with * this, it seems a bit on the loud side. * * Also note: unlike standard or EAX reverb, there is no automatic * attenuation of a source's reverb response with distance, so the reverb * will remain full volume regardless of a given sound's distance from the * listener. You can use a send filter to alter a given source's * contribution to reverb. */ alAuxiliaryEffectSloti(slot, AL_BUFFER, (ALint)ir_buffer); alAuxiliaryEffectSlotf(slot, AL_EFFECTSLOT_GAIN, 1.0f / 16.0f); alAuxiliaryEffectSloti(slot, AL_EFFECTSLOT_EFFECT, (ALint)effect); assert(alGetError()==AL_NO_ERROR && "Failed to set effect slot"); /* Create a filter that can silence the dry path. */ filter = 0; alGenFilters(1, &filter); alFilteri(filter, AL_FILTER_TYPE, AL_FILTER_LOWPASS); alFilterf(filter, AL_LOWPASS_GAIN, 0.0f); player = NewPlayer(); /* Connect the player's source to the effect slot. */ alSource3i(player->source, AL_AUXILIARY_SEND_FILTER, (ALint)slot, 0, AL_FILTER_NULL); assert(alGetError()==AL_NO_ERROR && "Failed to setup sound source"); /* Play each file listed on the command line */ for(i = 1;i < argc;i++) { const char *namepart; if(argc-i > 1) { if(strcasecmp(argv[i], "-nodry") == 0) { alSourcei(player->source, AL_DIRECT_FILTER, (ALint)filter); ++i; } else if(strcasecmp(argv[i], "-dry") == 0) { alSourcei(player->source, AL_DIRECT_FILTER, AL_FILTER_NULL); ++i; } } if(!OpenPlayerFile(player, argv[i])) continue; namepart = strrchr(argv[i], '/'); if(!namepart) namepart = strrchr(argv[i], '\\'); if(!namepart) namepart = argv[i]; else namepart++; printf("Playing: %s (%s, %dhz)\n", namepart, FormatName(player->format), player->sfinfo.samplerate); fflush(stdout); if(!StartPlayer(player)) { ClosePlayerFile(player); continue; } while(UpdatePlayer(player)) al_nssleep(10000000); ClosePlayerFile(player); } printf("Done.\n"); /* All files done. Delete the player and effect resources, and close down * OpenAL. */ DeletePlayer(player); player = NULL; alDeleteAuxiliaryEffectSlots(1, &slot); alDeleteEffects(1, &effect); alDeleteFilters(1, &filter); alDeleteBuffers(1, &ir_buffer); CloseAL(); return 0; } openal-soft-1.24.2/examples/aldebug.cpp000066400000000000000000000265351474041540300177710ustar00rootroot00000000000000/* * OpenAL Debug Context Example * * Copyright (c) 2024 by Chris Robinson * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ /* This file contains an example for using the debug extension. */ #include #include #include #include #include #include #include #include #include "AL/al.h" #include "AL/alc.h" #include "AL/alext.h" #include "alnumeric.h" #include "alspan.h" #include "fmt/core.h" #include "win_main_utf8.h" namespace { using namespace std::string_view_literals; struct DeviceCloser { void operator()(ALCdevice *device) const noexcept { alcCloseDevice(device); } }; using DevicePtr = std::unique_ptr; struct ContextDestroyer { void operator()(ALCcontext *context) const noexcept { alcDestroyContext(context); } }; using ContextPtr = std::unique_ptr; constexpr auto GetDebugSourceName(ALenum source) noexcept -> std::string_view { switch(source) { case AL_DEBUG_SOURCE_API_EXT: return "API"sv; case AL_DEBUG_SOURCE_AUDIO_SYSTEM_EXT: return "Audio System"sv; case AL_DEBUG_SOURCE_THIRD_PARTY_EXT: return "Third Party"sv; case AL_DEBUG_SOURCE_APPLICATION_EXT: return "Application"sv; case AL_DEBUG_SOURCE_OTHER_EXT: return "Other"sv; } return ""sv; } constexpr auto GetDebugTypeName(ALenum type) noexcept -> std::string_view { switch(type) { case AL_DEBUG_TYPE_ERROR_EXT: return "Error"sv; case AL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_EXT: return "Deprecated Behavior"sv; case AL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_EXT: return "Undefined Behavior"sv; case AL_DEBUG_TYPE_PORTABILITY_EXT: return "Portability"sv; case AL_DEBUG_TYPE_PERFORMANCE_EXT: return "Performance"sv; case AL_DEBUG_TYPE_MARKER_EXT: return "Marker"sv; case AL_DEBUG_TYPE_PUSH_GROUP_EXT: return "Push Group"sv; case AL_DEBUG_TYPE_POP_GROUP_EXT: return "Pop Group"sv; case AL_DEBUG_TYPE_OTHER_EXT: return "Other"sv; } return ""sv; } constexpr auto GetDebugSeverityName(ALenum severity) noexcept -> std::string_view { switch(severity) { case AL_DEBUG_SEVERITY_HIGH_EXT: return "High"sv; case AL_DEBUG_SEVERITY_MEDIUM_EXT: return "Medium"sv; case AL_DEBUG_SEVERITY_LOW_EXT: return "Low"sv; case AL_DEBUG_SEVERITY_NOTIFICATION_EXT: return "Notification"sv; } return ""sv; } auto alDebugMessageCallbackEXT = LPALDEBUGMESSAGECALLBACKEXT{}; auto alDebugMessageInsertEXT = LPALDEBUGMESSAGEINSERTEXT{}; auto alDebugMessageControlEXT = LPALDEBUGMESSAGECONTROLEXT{}; auto alPushDebugGroupEXT = LPALPUSHDEBUGGROUPEXT{}; auto alPopDebugGroupEXT = LPALPOPDEBUGGROUPEXT{}; auto alGetDebugMessageLogEXT = LPALGETDEBUGMESSAGELOGEXT{}; auto alObjectLabelEXT = LPALOBJECTLABELEXT{}; auto alGetObjectLabelEXT = LPALGETOBJECTLABELEXT{}; auto alGetPointerEXT = LPALGETPOINTEREXT{}; auto alGetPointervEXT = LPALGETPOINTERVEXT{}; int main(al::span args) { /* Print out usage if -h was specified */ if(args.size() > 1 && (args[1] == "-h" || args[1] == "--help")) { fmt::println(stderr, "Usage: {} [-device ] [-nodebug]", args[0]); return 1; } /* Initialize OpenAL. */ args = args.subspan(1); auto device = DevicePtr{}; if(args.size() > 1 && args[0] == "-device") { device = DevicePtr{alcOpenDevice(std::string{args[1]}.c_str())}; if(!device) fmt::println(stderr, "Failed to open \"{}\", trying default", args[1]); args = args.subspan(2); } if(!device) device = DevicePtr{alcOpenDevice(nullptr)}; if(!device) { fmt::println(stderr, "Could not open a device!"); return 1; } if(!alcIsExtensionPresent(device.get(), "ALC_EXT_debug")) { fmt::println(stderr, "ALC_EXT_debug not supported on device"); return 1; } /* Load the Debug API functions we're using. */ #define LOAD_PROC(N) N = reinterpret_cast(alcGetProcAddress(device.get(), #N)) LOAD_PROC(alDebugMessageCallbackEXT); LOAD_PROC(alDebugMessageInsertEXT); LOAD_PROC(alDebugMessageControlEXT); LOAD_PROC(alPushDebugGroupEXT); LOAD_PROC(alPopDebugGroupEXT); LOAD_PROC(alGetDebugMessageLogEXT); LOAD_PROC(alObjectLabelEXT); LOAD_PROC(alGetObjectLabelEXT); LOAD_PROC(alGetPointerEXT); LOAD_PROC(alGetPointervEXT); #undef LOAD_PROC /* Create a debug context and set it as current. If -nodebug was specified, * create a non-debug context (to see how debug messages react). */ auto flags = ALCint{ALC_CONTEXT_DEBUG_BIT_EXT}; if(!args.empty() && args[0] == "-nodebug") flags &= ~ALC_CONTEXT_DEBUG_BIT_EXT; const auto attribs = std::array{{ ALC_CONTEXT_FLAGS_EXT, flags, 0 /* end-of-list */ }}; auto context = ContextPtr{alcCreateContext(device.get(), attribs.data())}; if(!context || alcMakeContextCurrent(context.get()) == ALC_FALSE) { fmt::println(stderr, "Could not create and set a context!"); return 1; } /* Enable low-severity debug messages, which are disabled by default. */ alDebugMessageControlEXT(AL_DONT_CARE_EXT, AL_DONT_CARE_EXT, AL_DEBUG_SEVERITY_LOW_EXT, 0, nullptr, AL_TRUE); fmt::println("Context flags: {:#010x}", as_unsigned(alGetInteger(AL_CONTEXT_FLAGS_EXT))); /* A debug context has debug output enabled by default. But in case this * isn't a debug context, explicitly enable it (probably won't get much, if * anything, in that case). */ fmt::println("Default debug state is: {}", alIsEnabled(AL_DEBUG_OUTPUT_EXT) ? "enabled"sv : "disabled"sv); alEnable(AL_DEBUG_OUTPUT_EXT); /* The max debug message length property will allow us to define message * storage of sufficient length. This includes space for the null * terminator. */ const auto maxloglength = alGetInteger(AL_MAX_DEBUG_MESSAGE_LENGTH_EXT); fmt::println("Max debug message length: {}", maxloglength); fmt::println(""); /* Doppler Velocity is deprecated since AL 1.1, so this should generate a * deprecation debug message. We'll first handle debug messages through the * message log, meaning we'll query for and read it afterward. */ fmt::println("Calling alDopplerVelocity(0.5f)..."); alDopplerVelocity(0.5f); for(auto numlogs = alGetInteger(AL_DEBUG_LOGGED_MESSAGES_EXT);numlogs > 0;--numlogs) { auto message = std::vector(static_cast(maxloglength), '\0'); auto source = ALenum{}; auto type = ALenum{}; auto id = ALuint{}; auto severity = ALenum{}; auto msglength = ALsizei{}; /* Getting the message removes it from the log. */ const auto read = alGetDebugMessageLogEXT(1, maxloglength, &source, &type, &id, &severity, &msglength, message.data()); if(read != 1) { fmt::println(stderr, "Read {} debug messages, expected to read 1", read); break; } /* The message lengths returned by alGetDebugMessageLogEXT include the * null terminator, so subtract one for the string_view length. If we * read more than one message at a time, the length could be used as * the offset to the next message. */ const auto msgstr = std::string_view{message.data(), static_cast(msglength ? msglength-1 : 0)}; fmt::println("Got message from log:\n" " Source: {}\n" " Type: {}\n" " ID: {}\n" " Severity: {}\n" " Message: \"{}\"", GetDebugSourceName(source), GetDebugTypeName(type), id, GetDebugSeverityName(severity), msgstr); } fmt::println(""); /* Now set up a callback function. This lets us print the debug messages as * they happen without having to explicitly query and get them. */ static constexpr auto debug_callback = [](ALenum source, ALenum type, ALuint id, ALenum severity, ALsizei length, const ALchar *message, void *userParam [[maybe_unused]]) noexcept -> void { /* The message length provided to the callback does not include the * null terminator. */ const auto msgstr = std::string_view{message, static_cast(length)}; fmt::println("Got message from callback:\n" " Source: {}\n" " Type: {}\n" " ID: {}\n" " Severity: {}\n" " Message: \"{}\"", GetDebugSourceName(source), GetDebugTypeName(type), id, GetDebugSeverityName(severity), msgstr); }; alDebugMessageCallbackEXT(debug_callback, nullptr); if(const auto numlogs = alGetInteger(AL_DEBUG_LOGGED_MESSAGES_EXT)) fmt::println(stderr, "{} left over logged message{}!", numlogs, (numlogs==1)?"":"s"); /* This should also generate a deprecation debug message, which will now go * through the callback. */ fmt::println("Calling alGetInteger(AL_DOPPLER_VELOCITY)..."); auto dv [[maybe_unused]] = alGetInteger(AL_DOPPLER_VELOCITY); fmt::println(""); /* These functions are notoriously unreliable for their behavior, they will * likely generate portability debug messages. */ fmt::println("Calling alcSuspendContext and alcProcessContext..."); alcSuspendContext(context.get()); alcProcessContext(context.get()); fputs("\n", stdout); fmt::println("Pushing a debug group, making some invalid calls, and popping the debug group..."); alPushDebugGroupEXT(AL_DEBUG_SOURCE_APPLICATION_EXT, 0, -1, "Error test group"); alSpeedOfSound(0.0f); /* Can't set the label of the null buffer. */ alObjectLabelEXT(AL_BUFFER, 0, -1, "The null buffer"); alPopDebugGroupEXT(); fmt::println(""); /* All done, insert a custom message and unset the callback. The context * and device will clean themselves up. */ alDebugMessageInsertEXT(AL_DEBUG_SOURCE_APPLICATION_EXT, AL_DEBUG_TYPE_MARKER_EXT, 0, AL_DEBUG_SEVERITY_NOTIFICATION_EXT, -1, "End of run, cleaning up"); alDebugMessageCallbackEXT(nullptr, nullptr); return 0; } } // namespace int main(int argc, char **argv) { assert(argc >= 0); auto args = std::vector(static_cast(argc)); std::copy_n(argv, args.size(), args.begin()); return main(al::span{args}); } openal-soft-1.24.2/examples/aldirect.cpp000066400000000000000000000416071474041540300201520ustar00rootroot00000000000000/* * OpenAL Direct Context Example * * Copyright (c) 2024 by Chris Robinson * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ /* This file contains an example for playing a sound buffer with the Direct API * extension. */ #include #include #include #include #include #include #include #include #include #include "sndfile.h" #include "AL/al.h" #include "AL/alc.h" #include "AL/alext.h" #include "alspan.h" #include "common/alhelpers.h" #include "fmt/core.h" #include "win_main_utf8.h" namespace { /* On Windows when using Creative's router, we need to override the ALC * functions and access the driver functions directly. This isn't needed when * not using the router, or on other OSs. */ LPALCOPENDEVICE p_alcOpenDevice{alcOpenDevice}; LPALCCLOSEDEVICE p_alcCloseDevice{alcCloseDevice}; LPALCISEXTENSIONPRESENT p_alcIsExtensionPresent{alcIsExtensionPresent}; LPALCCREATECONTEXT p_alcCreateContext{alcCreateContext}; LPALCDESTROYCONTEXT p_alcDestroyContext{alcDestroyContext}; LPALCGETPROCADDRESS p_alcGetProcAddress{alcGetProcAddress}; LPALGETSTRINGDIRECT alGetStringDirect{}; LPALGETERRORDIRECT alGetErrorDirect{}; LPALISEXTENSIONPRESENTDIRECT alIsExtensionPresentDirect{}; LPALGENBUFFERSDIRECT alGenBuffersDirect{}; LPALDELETEBUFFERSDIRECT alDeleteBuffersDirect{}; LPALISBUFFERDIRECT alIsBufferDirect{}; LPALBUFFERIDIRECT alBufferiDirect{}; LPALBUFFERDATADIRECT alBufferDataDirect{}; LPALGENSOURCESDIRECT alGenSourcesDirect{}; LPALDELETESOURCESDIRECT alDeleteSourcesDirect{}; LPALSOURCEIDIRECT alSourceiDirect{}; LPALGETSOURCEIDIRECT alGetSourceiDirect{}; LPALGETSOURCEFDIRECT alGetSourcefDirect{}; LPALSOURCEPLAYDIRECT alSourcePlayDirect{}; struct SndFileDeleter { void operator()(SNDFILE *sndfile) { sf_close(sndfile); } }; using SndFilePtr = std::unique_ptr; enum class FormatType { Int16, Float, IMA4, MSADPCM }; /* LoadBuffer loads the named audio file into an OpenAL buffer object, and * returns the new buffer ID. */ ALuint LoadSound(ALCcontext *context, const std::string_view filename) { /* Open the audio file and check that it's usable. */ SF_INFO sfinfo{}; SndFilePtr sndfile{sf_open(std::string{filename}.c_str(), SFM_READ, &sfinfo)}; if(!sndfile) { fmt::println(stderr, "Could not open audio in {}: {}", filename, sf_strerror(sndfile.get())); return 0; } if(sfinfo.frames < 1) { fmt::println(stderr, "Bad sample count in {} ({})", filename, sfinfo.frames); return 0; } /* Detect a suitable format to load. Formats like Vorbis and Opus use float * natively, so load as float to avoid clipping when possible. Formats * larger than 16-bit can also use float to preserve a bit more precision. */ FormatType sample_format{FormatType::Int16}; switch((sfinfo.format&SF_FORMAT_SUBMASK)) { case SF_FORMAT_PCM_24: case SF_FORMAT_PCM_32: case SF_FORMAT_FLOAT: case SF_FORMAT_DOUBLE: case SF_FORMAT_VORBIS: case SF_FORMAT_OPUS: case SF_FORMAT_ALAC_20: case SF_FORMAT_ALAC_24: case SF_FORMAT_ALAC_32: case 0x0080/*SF_FORMAT_MPEG_LAYER_I*/: case 0x0081/*SF_FORMAT_MPEG_LAYER_II*/: case 0x0082/*SF_FORMAT_MPEG_LAYER_III*/: if(alIsExtensionPresentDirect(context, "AL_EXT_FLOAT32")) sample_format = FormatType::Float; break; case SF_FORMAT_IMA_ADPCM: /* ADPCM formats require setting a block alignment as specified in the * file, which needs to be read from the wave 'fmt ' chunk manually * since libsndfile doesn't provide it in a format-agnostic way. */ if(sfinfo.channels <= 2 && (sfinfo.format&SF_FORMAT_TYPEMASK) == SF_FORMAT_WAV && alIsExtensionPresentDirect(context, "AL_EXT_IMA4") && alIsExtensionPresentDirect(context, "AL_SOFT_block_alignment")) sample_format = FormatType::IMA4; break; case SF_FORMAT_MS_ADPCM: if(sfinfo.channels <= 2 && (sfinfo.format&SF_FORMAT_TYPEMASK) == SF_FORMAT_WAV && alIsExtensionPresentDirect(context, "AL_SOFT_MSADPCM") && alIsExtensionPresentDirect(context, "AL_SOFT_block_alignment")) sample_format = FormatType::MSADPCM; break; } ALint byteblockalign{0}, splblockalign{0}; if(sample_format == FormatType::IMA4 || sample_format == FormatType::MSADPCM) { /* For ADPCM, lookup the wave file's "fmt " chunk, which is a * WAVEFORMATEX-based structure for the audio format. */ SF_CHUNK_INFO inf{"fmt ", 4, 0, nullptr}; SF_CHUNK_ITERATOR *iter{sf_get_chunk_iterator(sndfile.get(), &inf)}; /* If there's an issue getting the chunk or block alignment, load as * 16-bit and have libsndfile do the conversion. */ if(!iter || sf_get_chunk_size(iter, &inf) != SF_ERR_NO_ERROR || inf.datalen < 14) sample_format = FormatType::Int16; else { auto fmtbuf = std::vector(inf.datalen, ALubyte{0}); inf.data = fmtbuf.data(); if(sf_get_chunk_data(iter, &inf) != SF_ERR_NO_ERROR) sample_format = FormatType::Int16; else { /* Read the nBlockAlign field, and convert from bytes- to * samples-per-block (verifying it's valid by converting back * and comparing to the original value). */ byteblockalign = fmtbuf[12] | (fmtbuf[13]<<8); if(sample_format == FormatType::IMA4) { splblockalign = (byteblockalign/sfinfo.channels - 4)/4*8 + 1; if(splblockalign < 1 || ((splblockalign-1)/2 + 4)*sfinfo.channels != byteblockalign) sample_format = FormatType::Int16; } else if(sample_format == FormatType::MSADPCM) { splblockalign = (byteblockalign/sfinfo.channels - 7)*2 + 2; if(splblockalign < 2 || ((splblockalign-2)/2 + 7)*sfinfo.channels != byteblockalign) sample_format = FormatType::Int16; } else sample_format = FormatType::Int16; } } } if(sample_format == FormatType::Int16) { splblockalign = 1; byteblockalign = sfinfo.channels * 2; } else if(sample_format == FormatType::Float) { splblockalign = 1; byteblockalign = sfinfo.channels * 4; } /* Figure out the OpenAL format from the file and desired sample type. */ ALenum format{AL_NONE}; if(sfinfo.channels == 1) { if(sample_format == FormatType::Int16) format = AL_FORMAT_MONO16; else if(sample_format == FormatType::Float) format = AL_FORMAT_MONO_FLOAT32; else if(sample_format == FormatType::IMA4) format = AL_FORMAT_MONO_IMA4; else if(sample_format == FormatType::MSADPCM) format = AL_FORMAT_MONO_MSADPCM_SOFT; } else if(sfinfo.channels == 2) { if(sample_format == FormatType::Int16) format = AL_FORMAT_STEREO16; else if(sample_format == FormatType::Float) format = AL_FORMAT_STEREO_FLOAT32; else if(sample_format == FormatType::IMA4) format = AL_FORMAT_STEREO_IMA4; else if(sample_format == FormatType::MSADPCM) format = AL_FORMAT_STEREO_MSADPCM_SOFT; } else if(sfinfo.channels == 3) { if(sf_command(sndfile.get(), SFC_WAVEX_GET_AMBISONIC, nullptr, 0) == SF_AMBISONIC_B_FORMAT) { if(sample_format == FormatType::Int16) format = AL_FORMAT_BFORMAT2D_16; else if(sample_format == FormatType::Float) format = AL_FORMAT_BFORMAT2D_FLOAT32; } } else if(sfinfo.channels == 4) { if(sf_command(sndfile.get(), SFC_WAVEX_GET_AMBISONIC, nullptr, 0) == SF_AMBISONIC_B_FORMAT) { if(sample_format == FormatType::Int16) format = AL_FORMAT_BFORMAT3D_16; else if(sample_format == FormatType::Float) format = AL_FORMAT_BFORMAT3D_FLOAT32; } } if(!format) { fmt::println(stderr, "Unsupported channel count: {}", sfinfo.channels); return 0; } if(sfinfo.frames/splblockalign > sf_count_t{std::numeric_limits::max()}/byteblockalign) { fmt::println(stderr, "Too many sample frames in {} ({})", filename, sfinfo.frames); return 0; } /* Decode the whole audio file to a buffer. */ auto membuf = std::vector(static_cast(sfinfo.frames / splblockalign * byteblockalign)); sf_count_t num_frames{}; if(sample_format == FormatType::Int16) num_frames = sf_readf_short(sndfile.get(), reinterpret_cast(membuf.data()), sfinfo.frames); else if(sample_format == FormatType::Float) num_frames = sf_readf_float(sndfile.get(), reinterpret_cast(membuf.data()), sfinfo.frames); else { const sf_count_t count{sfinfo.frames / splblockalign * byteblockalign}; num_frames = sf_read_raw(sndfile.get(), membuf.data(), count); if(num_frames > 0) num_frames = num_frames / byteblockalign * splblockalign; } if(num_frames < 1) { fmt::println(stderr, "Failed to read samples in {} ({})", filename, num_frames); return 0; } const auto num_bytes = static_cast(num_frames / splblockalign * byteblockalign); fmt::println("Loading: {} ({}, {}hz)", filename, FormatName(format), sfinfo.samplerate); ALuint buffer{}; alGenBuffersDirect(context, 1, &buffer); if(splblockalign > 1) alBufferiDirect(context, buffer, AL_UNPACK_BLOCK_ALIGNMENT_SOFT, splblockalign); alBufferDataDirect(context, buffer, format, membuf.data(), num_bytes, sfinfo.samplerate); /* Check if an error occurred, and clean up if so. */ if(ALenum err{alGetErrorDirect(context)}; err != AL_NO_ERROR) { fmt::println(stderr, "OpenAL Error: {}", alGetStringDirect(context, err)); if(buffer && alIsBufferDirect(context, buffer)) alDeleteBuffersDirect(context, 1, &buffer); return 0; } return buffer; } int main(al::span args) { /* Print out usage if no arguments were specified */ if(args.size() < 2) { fmt::println(stderr, "Usage: {} [-device ] ", args[0]); return 1; } /* Initialize OpenAL. */ args = args.subspan(1); ALCdevice *device{}; if(args.size() > 1 && args[0] == "-device") { device = p_alcOpenDevice(std::string{args[1]}.c_str()); if(!device) fmt::println(stderr, "Failed to open \"{}\", trying default", args[1]); args = args.subspan(2); } if(!device) device = p_alcOpenDevice(nullptr); if(!device) { fmt::println(stderr, "Could not open a device!"); return 1; } if(!p_alcIsExtensionPresent(device, "ALC_EXT_direct_context")) { fmt::println(stderr, "ALC_EXT_direct_context not supported on device"); p_alcCloseDevice(device); return 1; } /* On Windows with Creative's router, the device needs to be bootstrapped * to use it through the driver directly. Otherwise the Direct functions * aren't able to recognize the router's ALCcontexts. To handle this, we * use the router's alcOpenDevice, alcGetProcAddress, and alcCloseDevice * functions to open the device with the router, get the device driver's * alcGetProcAddress2 function, and close the device with the router. Then * call alcGetProcAddress2 with the null device handle to get the driver's * functions. Afterward, we can open the device back up using the driver * functions directly and continue on. * * Note that this will allow using other devices from the same driver just * fine, but switching to a device on another driver will require using the * original functions from the router (and require re-bootstrapping to use * that driver's functions, if applicable). If controlling multiple devices * with Direct functions from separate drivers simultaneously is desired, a * good strategy may be to associate the driver's ALC and Direct functions * with the ALCdevice and ALCcontext handles created from them. * * This is all unnecessary when not using Creative's router, including on * non-Windows OSs or when using OpenAL Soft's router, where the original * ALC functions can be used as normal. */ { const std::string devname{alcGetString(device, ALC_ALL_DEVICES_SPECIFIER)}; auto p_alcGetProcAddress2 = reinterpret_cast( p_alcGetProcAddress(device, "alcGetProcAddress2")); p_alcCloseDevice(device); /* Load the driver-specific ALC functions we'll be using. */ #define LOAD_PROC(N) p_##N = reinterpret_cast(p_alcGetProcAddress2(nullptr, #N)) LOAD_PROC(alcOpenDevice); LOAD_PROC(alcCloseDevice); LOAD_PROC(alcIsExtensionPresent); LOAD_PROC(alcGetProcAddress); LOAD_PROC(alcCreateContext); LOAD_PROC(alcDestroyContext); LOAD_PROC(alcGetProcAddress); #undef LOAD_PROC device = p_alcOpenDevice(devname.c_str()); assert(device != nullptr); } /* Load the Direct API functions we're using. */ #define LOAD_PROC(N) N = reinterpret_cast(p_alcGetProcAddress(device, #N)) LOAD_PROC(alGetStringDirect); LOAD_PROC(alGetErrorDirect); LOAD_PROC(alIsExtensionPresentDirect); LOAD_PROC(alGenBuffersDirect); LOAD_PROC(alDeleteBuffersDirect); LOAD_PROC(alIsBufferDirect); LOAD_PROC(alBufferiDirect); LOAD_PROC(alBufferDataDirect); LOAD_PROC(alGenSourcesDirect); LOAD_PROC(alDeleteSourcesDirect); LOAD_PROC(alSourceiDirect); LOAD_PROC(alGetSourceiDirect); LOAD_PROC(alGetSourcefDirect); LOAD_PROC(alSourcePlayDirect); #undef LOAD_PROC /* Create the context. It doesn't need to be set as current to use with the * Direct API functions. */ ALCcontext *context{p_alcCreateContext(device, nullptr)}; if(!context) { p_alcCloseDevice(device); fmt::println(stderr, "Could not create a context!"); return 1; } /* Load the sound into a buffer. */ const ALuint buffer{LoadSound(context, args[0])}; if(!buffer) { p_alcDestroyContext(context); p_alcCloseDevice(device); return 1; } /* Create the source to play the sound with. */ ALuint source{0}; alGenSourcesDirect(context, 1, &source); alSourceiDirect(context, source, AL_BUFFER, static_cast(buffer)); assert(alGetErrorDirect(context)==AL_NO_ERROR && "Failed to setup sound source"); /* Play the sound until it finishes. */ alSourcePlayDirect(context, source); ALenum state{}; do { al_nssleep(10000000); alGetSourceiDirect(context, source, AL_SOURCE_STATE, &state); /* Get the source offset. */ ALfloat offset{}; alGetSourcefDirect(context, source, AL_SEC_OFFSET, &offset); fmt::print(" \rOffset: {:.02f}", offset); fflush(stdout); } while(alGetErrorDirect(context) == AL_NO_ERROR && state == AL_PLAYING); fmt::println(""); /* All done. Delete resources, and close down OpenAL. */ alDeleteSourcesDirect(context, 1, &source); alDeleteBuffersDirect(context, 1, &buffer); p_alcDestroyContext(context); p_alcCloseDevice(device); return 0; } } // namespace int main(int argc, char **argv) { assert(argc >= 0); auto args = std::vector(static_cast(argc)); std::copy_n(argv, args.size(), args.begin()); return main(al::span{args}); } openal-soft-1.24.2/examples/alffplay.cpp000066400000000000000000002157311474041540300201620ustar00rootroot00000000000000/* * An example showing how to play a stream sync'd to video, using ffmpeg. * * Requires C++14. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __GNUC__ _Pragma("GCC diagnostic push") _Pragma("GCC diagnostic ignored \"-Wconversion\"") _Pragma("GCC diagnostic ignored \"-Wold-style-cast\"") #endif extern "C" { #include "libavcodec/avcodec.h" #include "libavformat/avformat.h" #include "libavformat/avio.h" #include "libavutil/avutil.h" #include "libavutil/error.h" #include "libavutil/frame.h" #include "libavutil/mem.h" #include "libavutil/pixfmt.h" #include "libavutil/rational.h" #include "libavutil/samplefmt.h" #include "libavutil/time.h" #include "libavutil/channel_layout.h" #include "libswscale/swscale.h" #include "libswresample/swresample.h" struct SwsContext; } #define SDL_MAIN_HANDLED #include "SDL3/SDL_events.h" #include "SDL3/SDL_main.h" #include "SDL3/SDL_render.h" #include "SDL3/SDL_video.h" #ifdef __GNUC__ _Pragma("GCC diagnostic pop") #endif #include "AL/alc.h" #include "AL/al.h" #include "AL/alext.h" #include "almalloc.h" #include "alnumbers.h" #include "alnumeric.h" #include "alspan.h" #include "common/alhelpers.h" #include "fmt/core.h" #include "fmt/format.h" namespace { using voidp = void*; using fixed32 = std::chrono::duration>; using nanoseconds = std::chrono::nanoseconds; using microseconds = std::chrono::microseconds; using milliseconds = std::chrono::milliseconds; using seconds = std::chrono::seconds; using seconds_d64 = std::chrono::duration; using std::chrono::duration_cast; #ifdef __GNUC__ _Pragma("GCC diagnostic push") _Pragma("GCC diagnostic ignored \"-Wold-style-cast\"") #endif constexpr auto AVNoPtsValue = AV_NOPTS_VALUE; constexpr auto AVErrorEOF = AVERROR_EOF; #ifdef __GNUC__ _Pragma("GCC diagnostic pop") #endif const std::string AppName{"alffplay"}; ALenum DirectOutMode{AL_FALSE}; bool EnableWideStereo{false}; bool EnableUhj{false}; bool EnableSuperStereo{false}; bool DisableVideo{false}; LPALGETSOURCEI64VSOFT alGetSourcei64vSOFT; LPALCGETINTEGER64VSOFT alcGetInteger64vSOFT; LPALEVENTCONTROLSOFT alEventControlSOFT; LPALEVENTCALLBACKSOFT alEventCallbackSOFT; LPALBUFFERCALLBACKSOFT alBufferCallbackSOFT; const seconds AVNoSyncThreshold{10}; #define VIDEO_PICTURE_QUEUE_SIZE 24 const seconds_d64 AudioSyncThreshold{0.03}; const milliseconds AudioSampleCorrectionMax{50}; /* Averaging filter coefficient for audio sync. */ #define AUDIO_DIFF_AVG_NB 20 const double AudioAvgFilterCoeff{std::pow(0.01, 1.0/AUDIO_DIFF_AVG_NB)}; /* Per-buffer size, in time */ constexpr milliseconds AudioBufferTime{20}; /* Buffer total size, in time (should be divisible by the buffer time) */ constexpr milliseconds AudioBufferTotalTime{800}; constexpr auto AudioBufferCount = AudioBufferTotalTime / AudioBufferTime; enum { FF_MOVIE_DONE_EVENT = SDL_EVENT_USER }; enum class SyncMaster { Audio, Video, External, Default = Audio }; inline microseconds get_avtime() { return microseconds{av_gettime()}; } /* Define unique_ptrs to auto-cleanup associated ffmpeg objects. */ struct AVIOContextDeleter { void operator()(AVIOContext *ptr) { avio_closep(&ptr); } }; using AVIOContextPtr = std::unique_ptr; struct AVFormatCtxDeleter { void operator()(AVFormatContext *ptr) { avformat_close_input(&ptr); } }; using AVFormatCtxPtr = std::unique_ptr; struct AVCodecCtxDeleter { void operator()(AVCodecContext *ptr) { avcodec_free_context(&ptr); } }; using AVCodecCtxPtr = std::unique_ptr; struct AVPacketDeleter { void operator()(AVPacket *pkt) { av_packet_free(&pkt); } }; using AVPacketPtr = std::unique_ptr; struct AVFrameDeleter { void operator()(AVFrame *ptr) { av_frame_free(&ptr); } }; using AVFramePtr = std::unique_ptr; struct SwrContextDeleter { void operator()(SwrContext *ptr) { swr_free(&ptr); } }; using SwrContextPtr = std::unique_ptr; struct SwsContextDeleter { void operator()(SwsContext *ptr) { sws_freeContext(ptr); } }; using SwsContextPtr = std::unique_ptr; struct ChannelLayout : public AVChannelLayout { ChannelLayout() : AVChannelLayout{} { } ChannelLayout(const ChannelLayout &rhs) : AVChannelLayout{} { av_channel_layout_copy(this, &rhs); } ~ChannelLayout() { av_channel_layout_uninit(this); } auto operator=(const ChannelLayout &rhs) -> ChannelLayout& { av_channel_layout_copy(this, &rhs); return *this; } }; class DataQueue { const size_t mSizeLimit; std::mutex mPacketMutex, mFrameMutex; std::condition_variable mPacketCond; std::condition_variable mInFrameCond, mOutFrameCond; std::deque mPackets; size_t mTotalSize{0}; bool mFinished{false}; AVPacketPtr getPacket() { std::unique_lock plock{mPacketMutex}; while(mPackets.empty() && !mFinished) mPacketCond.wait(plock); if(mPackets.empty()) return nullptr; auto ret = std::move(mPackets.front()); mPackets.pop_front(); mTotalSize -= static_cast(ret->size); return ret; } public: explicit DataQueue(size_t size_limit) : mSizeLimit{size_limit} { } int sendPacket(AVCodecContext *codecctx) { auto packet = AVPacketPtr{getPacket()}; auto ret = int{}; { auto flock = std::unique_lock{mFrameMutex}; mInFrameCond.wait(flock, [codecctx,pkt=packet.get(),&ret]() { ret = avcodec_send_packet(codecctx, pkt); return ret != AVERROR(EAGAIN); }); } mOutFrameCond.notify_one(); if(!packet) { if(!ret) return AVErrorEOF; fmt::println(stderr, "Failed to send flush packet: {}", ret); return ret; } if(ret < 0) fmt::println(stderr, "Failed to send packet: {}", ret); return ret; } int receiveFrame(AVCodecContext *codecctx, AVFrame *frame) { auto ret = int{}; { auto flock = std::unique_lock{mFrameMutex}; mOutFrameCond.wait(flock, [codecctx,frame,&ret]() { ret = avcodec_receive_frame(codecctx, frame); return ret != AVERROR(EAGAIN); }); } mInFrameCond.notify_one(); return ret; } void setFinished() { { std::lock_guard packetlock{mPacketMutex}; mFinished = true; } mPacketCond.notify_one(); } void flush() { { std::lock_guard packetlock{mPacketMutex}; mFinished = true; mPackets.clear(); mTotalSize = 0; } mPacketCond.notify_one(); } bool put(const AVPacket *pkt) { { std::lock_guard packet_lock{mPacketMutex}; if(mTotalSize >= mSizeLimit || mFinished) return false; mPackets.push_back(AVPacketPtr{av_packet_alloc()}); if(av_packet_ref(mPackets.back().get(), pkt) != 0) { mPackets.pop_back(); return true; } mTotalSize += static_cast(mPackets.back()->size); } mPacketCond.notify_one(); return true; } }; struct MovieState; struct AudioState { MovieState &mMovie; AVStream *mStream{nullptr}; AVCodecCtxPtr mCodecCtx; DataQueue mQueue{2_uz*1024_uz*1024_uz}; /* Used for clock difference average computation */ seconds_d64 mClockDiffAvg{0}; /* Time of the next sample to be buffered */ nanoseconds mCurrentPts{0}; /* Device clock time that the stream started at. */ nanoseconds mDeviceStartTime{nanoseconds::min()}; /* Decompressed sample frame, and swresample context for conversion */ AVFramePtr mDecodedFrame; SwrContextPtr mSwresCtx; /* Conversion format, for what gets fed to OpenAL */ uint64_t mDstChanLayout{0}; AVSampleFormat mDstSampleFmt{AV_SAMPLE_FMT_NONE}; /* Storage of converted samples */ std::array mSamples{}; al::span mSamplesSpan; int mSamplesLen{0}; /* In samples */ int mSamplesPos{0}; int mSamplesMax{0}; std::vector mBufferData; std::atomic mReadPos{0}; std::atomic mWritePos{0}; /* OpenAL format */ ALenum mFormat{AL_NONE}; ALuint mFrameSize{0}; std::mutex mSrcMutex; std::condition_variable mSrcCond; std::atomic_flag mConnected{}; ALuint mSource{0}; std::array mBuffers{}; ALuint mBufferIdx{0}; explicit AudioState(MovieState &movie) : mMovie(movie) { mConnected.test_and_set(std::memory_order_relaxed); } ~AudioState() { if(mSource) alDeleteSources(1, &mSource); if(mBuffers[0]) alDeleteBuffers(static_cast(mBuffers.size()), mBuffers.data()); av_freep(static_cast(mSamples.data())); } static void AL_APIENTRY eventCallbackC(ALenum eventType, ALuint object, ALuint param, ALsizei length, const ALchar *message, void *userParam) noexcept { static_cast(userParam)->eventCallback(eventType, object, param, length, message); } void eventCallback(ALenum eventType, ALuint object, ALuint param, ALsizei length, const ALchar *message) noexcept; static ALsizei AL_APIENTRY bufferCallbackC(void *userptr, void *data, ALsizei size) noexcept { return static_cast(userptr)->bufferCallback(data, size); } ALsizei bufferCallback(void *data, ALsizei size) noexcept; nanoseconds getClockNoLock(); nanoseconds getClock() { std::lock_guard lock{mSrcMutex}; return getClockNoLock(); } bool startPlayback(); int getSync(); int decodeFrame(); bool readAudio(al::span samples, unsigned int length, int &sample_skip); bool readAudio(int sample_skip); int handler(); }; struct VideoState { MovieState &mMovie; AVStream *mStream{nullptr}; AVCodecCtxPtr mCodecCtx; DataQueue mQueue{14_uz*1024_uz*1024_uz}; /* The pts of the currently displayed frame, and the time (av_gettime) it * was last updated - used to have running video pts */ nanoseconds mDisplayPts{0}; microseconds mDisplayPtsTime{microseconds::min()}; std::mutex mDispPtsMutex; /* Swscale context for format conversion */ SwsContextPtr mSwscaleCtx; struct Picture { AVFramePtr mFrame; nanoseconds mPts{nanoseconds::min()}; }; std::array mPictQ; std::atomic mPictQRead{0u}, mPictQWrite{1u}; std::mutex mPictQMutex; std::condition_variable mPictQCond; SDL_Texture *mImage{nullptr}; int mWidth{0}, mHeight{0}; /* Full texture size */ bool mFirstUpdate{true}; std::atomic mEOS{false}; std::atomic mFinalUpdate{false}; explicit VideoState(MovieState &movie) : mMovie(movie) { } ~VideoState() { if(mImage) SDL_DestroyTexture(mImage); mImage = nullptr; } nanoseconds getClock(); void display(SDL_Renderer *renderer, AVFrame *frame) const; void updateVideo(SDL_Window *screen, SDL_Renderer *renderer, bool redraw); int handler(); }; struct MovieState { AVIOContextPtr mIOContext; AVFormatCtxPtr mFormatCtx; SyncMaster mAVSyncType{SyncMaster::Default}; microseconds mClockBase{microseconds::min()}; std::atomic mQuit{false}; AudioState mAudio; VideoState mVideo; std::mutex mStartupMutex; std::condition_variable mStartupCond; bool mStartupDone{false}; std::thread mParseThread; std::thread mAudioThread; std::thread mVideoThread; std::string mFilename; explicit MovieState(std::string_view fname) : mAudio{*this}, mVideo{*this}, mFilename{fname} { } ~MovieState() { stop(); if(mParseThread.joinable()) mParseThread.join(); } static int decode_interrupt_cb(void *ctx); bool prepare(); void setTitle(SDL_Window *window) const; void stop(); [[nodiscard]] nanoseconds getClock() const; [[nodiscard]] nanoseconds getMasterClock(); [[nodiscard]] nanoseconds getDuration() const; bool streamComponentOpen(AVStream *stream); int parse_handler(); }; nanoseconds AudioState::getClockNoLock() { // The audio clock is the timestamp of the sample currently being heard. if(alcGetInteger64vSOFT) { // If device start time = min, we aren't playing yet. if(mDeviceStartTime == nanoseconds::min()) return nanoseconds::zero(); // Get the current device clock time and latency. auto device = alcGetContextsDevice(alcGetCurrentContext()); std::array devtimes{}; alcGetInteger64vSOFT(device, ALC_DEVICE_CLOCK_LATENCY_SOFT, 2, devtimes.data()); auto latency = nanoseconds{devtimes[1]}; auto device_time = nanoseconds{devtimes[0]}; // The clock is simply the current device time relative to the recorded // start time. We can also subtract the latency to get more a accurate // position of where the audio device actually is in the output stream. return device_time - mDeviceStartTime - latency; } if(!mBufferData.empty()) { if(mDeviceStartTime == nanoseconds::min()) return nanoseconds::zero(); /* With a callback buffer and no device clock, mDeviceStartTime is * actually the timestamp of the first sample frame played. The audio * clock, then, is that plus the current source offset. */ std::array offset{}; if(alGetSourcei64vSOFT) alGetSourcei64vSOFT(mSource, AL_SAMPLE_OFFSET_LATENCY_SOFT, offset.data()); else { ALint ioffset; alGetSourcei(mSource, AL_SAMPLE_OFFSET, &ioffset); offset[0] = ALint64SOFT{ioffset} << 32; } /* NOTE: The source state must be checked last, in case an underrun * occurs and the source stops between getting the state and retrieving * the offset+latency. */ ALint status; alGetSourcei(mSource, AL_SOURCE_STATE, &status); nanoseconds pts{}; if(status == AL_PLAYING || status == AL_PAUSED) pts = mDeviceStartTime - nanoseconds{offset[1]} + duration_cast(fixed32{offset[0] / mCodecCtx->sample_rate}); else { /* If the source is stopped, the pts of the next sample to be heard * is the pts of the next sample to be buffered, minus the amount * already in the buffer ready to play. */ const size_t woffset{mWritePos.load(std::memory_order_acquire)}; const size_t roffset{mReadPos.load(std::memory_order_relaxed)}; const size_t readable{((woffset>=roffset) ? woffset : (mBufferData.size()+woffset)) - roffset}; pts = mCurrentPts - nanoseconds{seconds{readable/mFrameSize}}/mCodecCtx->sample_rate; } return pts; } /* The source-based clock is based on 4 components: * 1 - The timestamp of the next sample to buffer (mCurrentPts) * 2 - The length of the source's buffer queue * (AudioBufferTime*AL_BUFFERS_QUEUED) * 3 - The offset OpenAL is currently at in the source (the first value * from AL_SAMPLE_OFFSET_LATENCY_SOFT) * 4 - The latency between OpenAL and the DAC (the second value from * AL_SAMPLE_OFFSET_LATENCY_SOFT) * * Subtracting the length of the source queue from the next sample's * timestamp gives the timestamp of the sample at the start of the source * queue. Adding the source offset to that results in the timestamp for the * sample at OpenAL's current position, and subtracting the source latency * from that gives the timestamp of the sample currently at the DAC. */ nanoseconds pts{mCurrentPts}; if(mSource) { std::array offset{}; if(alGetSourcei64vSOFT) alGetSourcei64vSOFT(mSource, AL_SAMPLE_OFFSET_LATENCY_SOFT, offset.data()); else { ALint ioffset; alGetSourcei(mSource, AL_SAMPLE_OFFSET, &ioffset); offset[0] = ALint64SOFT{ioffset} << 32; } ALint queued, status; alGetSourcei(mSource, AL_BUFFERS_QUEUED, &queued); alGetSourcei(mSource, AL_SOURCE_STATE, &status); /* If the source is AL_STOPPED, then there was an underrun and all * buffers are processed, so ignore the source queue. The audio thread * will put the source into an AL_INITIAL state and clear the queue * when it starts recovery. */ if(status != AL_STOPPED) { pts -= AudioBufferTime*queued; pts += duration_cast(fixed32{offset[0] / mCodecCtx->sample_rate}); } /* Don't offset by the latency if the source isn't playing. */ if(status == AL_PLAYING) pts -= nanoseconds{offset[1]}; } return std::max(pts, nanoseconds::zero()); } bool AudioState::startPlayback() { const size_t woffset{mWritePos.load(std::memory_order_acquire)}; const size_t roffset{mReadPos.load(std::memory_order_relaxed)}; const size_t readable{((woffset >= roffset) ? woffset : (mBufferData.size()+woffset)) - roffset}; if(!mBufferData.empty()) { if(readable == 0) return false; if(!alcGetInteger64vSOFT) mDeviceStartTime = mCurrentPts - nanoseconds{seconds{readable/mFrameSize}}/mCodecCtx->sample_rate; } else { ALint queued{}; alGetSourcei(mSource, AL_BUFFERS_QUEUED, &queued); if(queued == 0) return false; } alSourcePlay(mSource); if(alcGetInteger64vSOFT) { /* Subtract the total buffer queue time from the current pts to get the * pts of the start of the queue. */ std::array srctimes{}; alGetSourcei64vSOFT(mSource, AL_SAMPLE_OFFSET_CLOCK_SOFT, srctimes.data()); auto device_time = nanoseconds{srctimes[1]}; auto src_offset = duration_cast(fixed32{srctimes[0]}) / mCodecCtx->sample_rate; /* The mixer may have ticked and incremented the device time and sample * offset, so subtract the source offset from the device time to get * the device time the source started at. Also subtract startpts to get * the device time the stream would have started at to reach where it * is now. */ if(!mBufferData.empty()) { nanoseconds startpts{mCurrentPts - nanoseconds{seconds{readable/mFrameSize}}/mCodecCtx->sample_rate}; mDeviceStartTime = device_time - src_offset - startpts; } else { nanoseconds startpts{mCurrentPts - AudioBufferTotalTime}; mDeviceStartTime = device_time - src_offset - startpts; } } return true; } int AudioState::getSync() { if(mMovie.mAVSyncType == SyncMaster::Audio) return 0; auto ref_clock = mMovie.getMasterClock(); auto diff = ref_clock - getClockNoLock(); if(!(diff < AVNoSyncThreshold && diff > -AVNoSyncThreshold)) { /* Difference is TOO big; reset accumulated average */ mClockDiffAvg = seconds_d64::zero(); return 0; } /* Accumulate the diffs */ mClockDiffAvg = mClockDiffAvg*AudioAvgFilterCoeff + diff; auto avg_diff = mClockDiffAvg*(1.0 - AudioAvgFilterCoeff); if(avg_diff < AudioSyncThreshold/2.0 && avg_diff > -AudioSyncThreshold) return 0; /* Constrain the per-update difference to avoid exceedingly large skips */ diff = std::min(diff, AudioSampleCorrectionMax); return static_cast(duration_cast(diff*mCodecCtx->sample_rate).count()); } int AudioState::decodeFrame() { do { while(int ret{mQueue.receiveFrame(mCodecCtx.get(), mDecodedFrame.get())}) { if(ret == AVErrorEOF) return 0; fmt::println(stderr, "Failed to receive frame: {}", ret); } } while(mDecodedFrame->nb_samples <= 0); /* If provided, update w/ pts */ if(mDecodedFrame->best_effort_timestamp != AVNoPtsValue) mCurrentPts = duration_cast(seconds_d64{av_q2d(mStream->time_base) * static_cast(mDecodedFrame->best_effort_timestamp)}); if(mDecodedFrame->nb_samples > mSamplesMax) { av_freep(static_cast(mSamples.data())); av_samples_alloc(mSamples.data(), nullptr, mCodecCtx->ch_layout.nb_channels, mDecodedFrame->nb_samples, mDstSampleFmt, 0); mSamplesMax = mDecodedFrame->nb_samples; mSamplesSpan = {mSamples[0], static_cast(mSamplesMax)*mFrameSize}; } /* Return the amount of sample frames converted */ const int data_size{swr_convert(mSwresCtx.get(), mSamples.data(), mDecodedFrame->nb_samples, mDecodedFrame->extended_data, mDecodedFrame->nb_samples)}; av_frame_unref(mDecodedFrame.get()); return data_size; } /* Duplicates the sample at in to out, count times. The frame size is a * multiple of the template type size. */ template void sample_dup(al::span out, al::span in, size_t count, size_t frame_size) { auto sample = al::span{reinterpret_cast(in.data()), in.size()/sizeof(T)}; auto dst = al::span{reinterpret_cast(out.data()), out.size()/sizeof(T)}; /* NOTE: frame_size is a multiple of sizeof(T). */ const size_t type_mult{frame_size / sizeof(T)}; if(type_mult == 1) std::fill_n(dst.begin(), count, sample.front()); else for(size_t i{0};i < count;++i) { for(size_t j{0};j < type_mult;++j) dst[i*type_mult + j] = sample[j]; } } void sample_dup(al::span out, al::span in, size_t count, size_t frame_size) { if((frame_size&7) == 0) sample_dup(out, in, count, frame_size); else if((frame_size&3) == 0) sample_dup(out, in, count, frame_size); else if((frame_size&1) == 0) sample_dup(out, in, count, frame_size); else sample_dup(out, in, count, frame_size); } bool AudioState::readAudio(al::span samples, unsigned int length, int &sample_skip) { unsigned int audio_size{0}; /* Read the next chunk of data, refill the buffer, and queue it * on the source */ length /= mFrameSize; while(mSamplesLen > 0 && audio_size < length) { unsigned int rem{length - audio_size}; if(mSamplesPos >= 0) { rem = std::min(rem, static_cast(mSamplesLen - mSamplesPos)); const size_t boffset{static_cast(mSamplesPos) * size_t{mFrameSize}}; std::copy_n(mSamplesSpan.cbegin()+ptrdiff_t(boffset), rem*size_t{mFrameSize}, samples.begin()); } else { rem = std::min(rem, static_cast(-mSamplesPos)); /* Add samples by copying the first sample */ sample_dup(samples, mSamplesSpan, rem, mFrameSize); } mSamplesPos += static_cast(rem); mCurrentPts += nanoseconds{seconds{rem}} / mCodecCtx->sample_rate; samples = samples.subspan(rem*size_t{mFrameSize}); audio_size += rem; while(mSamplesPos >= mSamplesLen) { mSamplesLen = decodeFrame(); mSamplesPos = std::min(mSamplesLen, sample_skip); if(mSamplesLen <= 0) break; sample_skip -= mSamplesPos; // Adjust the device start time and current pts by the amount we're // skipping/duplicating, so that the clock remains correct for the // current stream position. auto skip = nanoseconds{seconds{mSamplesPos}} / mCodecCtx->sample_rate; mDeviceStartTime -= skip; mCurrentPts += skip; } } if(audio_size <= 0) return false; if(audio_size < length) { const unsigned int rem{length - audio_size}; std::fill_n(samples.begin(), rem*mFrameSize, (mDstSampleFmt == AV_SAMPLE_FMT_U8) ? 0x80 : 0x00); mCurrentPts += nanoseconds{seconds{rem}} / mCodecCtx->sample_rate; } return true; } bool AudioState::readAudio(int sample_skip) { size_t woffset{mWritePos.load(std::memory_order_acquire)}; const size_t roffset{mReadPos.load(std::memory_order_relaxed)}; while(mSamplesLen > 0) { const size_t nsamples{((roffset > woffset) ? roffset-woffset-1 : (roffset == 0) ? (mBufferData.size()-woffset-1) : (mBufferData.size()-woffset)) / mFrameSize}; if(!nsamples) break; if(mSamplesPos < 0) { const size_t rem{std::min(nsamples, static_cast(-mSamplesPos))}; sample_dup(al::span{mBufferData}.subspan(woffset), mSamplesSpan, rem, mFrameSize); woffset += rem * mFrameSize; if(woffset == mBufferData.size()) woffset = 0; mWritePos.store(woffset, std::memory_order_release); mCurrentPts += nanoseconds{seconds{rem}} / mCodecCtx->sample_rate; mSamplesPos += static_cast(rem); continue; } const size_t rem{std::min(nsamples, static_cast(mSamplesLen-mSamplesPos))}; const size_t boffset{static_cast(mSamplesPos) * size_t{mFrameSize}}; const size_t nbytes{rem * mFrameSize}; std::copy_n(mSamplesSpan.cbegin()+ptrdiff_t(boffset), nbytes, mBufferData.begin()+ptrdiff_t(woffset)); woffset += nbytes; if(woffset == mBufferData.size()) woffset = 0; mWritePos.store(woffset, std::memory_order_release); mCurrentPts += nanoseconds{seconds{rem}} / mCodecCtx->sample_rate; mSamplesPos += static_cast(rem); while(mSamplesPos >= mSamplesLen) { mSamplesLen = decodeFrame(); mSamplesPos = std::min(mSamplesLen, sample_skip); if(mSamplesLen <= 0) return false; sample_skip -= mSamplesPos; auto skip = nanoseconds{seconds{mSamplesPos}} / mCodecCtx->sample_rate; mDeviceStartTime -= skip; mCurrentPts += skip; } } return true; } void AL_APIENTRY AudioState::eventCallback(ALenum eventType, ALuint object, ALuint param, ALsizei length, const ALchar *message) noexcept { if(eventType == AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT) { /* Temporarily lock the source mutex to ensure it's not between * checking the processed count and going to sleep. */ std::unique_lock{mSrcMutex}.unlock(); mSrcCond.notify_one(); return; } fmt::print("\n---- AL Event on AudioState {:p} ----\nEvent: ", voidp{this}); switch(eventType) { case AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT: fmt::print("Buffer completed"); break; case AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT: fmt::print("Source state changed"); break; case AL_EVENT_TYPE_DISCONNECTED_SOFT: fmt::print("Disconnected"); break; default: fmt::print("{:#x}", as_unsigned(eventType)); break; } fmt::println("\n" "Object ID: {}\n" "Parameter: {}\n" "Message: {}\n----", object, param, std::string_view{message, static_cast(length)}); if(eventType == AL_EVENT_TYPE_DISCONNECTED_SOFT) { { std::lock_guard lock{mSrcMutex}; mConnected.clear(std::memory_order_release); } mSrcCond.notify_one(); } } ALsizei AudioState::bufferCallback(void *data, ALsizei size) noexcept { auto dst = al::span{static_cast(data), static_cast(size)}; ALsizei got{0}; size_t roffset{mReadPos.load(std::memory_order_acquire)}; while(!dst.empty()) { const size_t woffset{mWritePos.load(std::memory_order_relaxed)}; if(woffset == roffset) break; size_t todo{((woffset < roffset) ? mBufferData.size() : woffset) - roffset}; todo = std::min(todo, dst.size()); std::copy_n(mBufferData.cbegin()+ptrdiff_t(roffset), todo, dst.begin()); dst = dst.subspan(todo); got += static_cast(todo); roffset += todo; if(roffset == mBufferData.size()) roffset = 0; } mReadPos.store(roffset, std::memory_order_release); return got; } int AudioState::handler() { std::unique_lock srclock{mSrcMutex, std::defer_lock}; milliseconds sleep_time{AudioBufferTime / 3}; struct EventControlManager { const std::array evt_types{{ AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT, AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT, AL_EVENT_TYPE_DISCONNECTED_SOFT}}; explicit EventControlManager(milliseconds &sleep_time) { if(alEventControlSOFT) { alEventControlSOFT(static_cast(evt_types.size()), evt_types.data(), AL_TRUE); alEventCallbackSOFT(&AudioState::eventCallbackC, this); sleep_time = AudioBufferTotalTime; } } ~EventControlManager() { if(alEventControlSOFT) { alEventControlSOFT(static_cast(evt_types.size()), evt_types.data(), AL_FALSE); alEventCallbackSOFT(nullptr, nullptr); } } }; EventControlManager event_controller{sleep_time}; std::vector samples; ALsizei buffer_len{0}; /* Note that ffmpeg assumes AmbiX (ACN layout, SN3D normalization). Only * support HOA when OpenAL can take AmbiX natively (if AmbiX -> FuMa * conversion is needed, we don't bother with higher order channels). */ const auto has_bfmt = bool{alIsExtensionPresent("AL_EXT_BFORMAT") != AL_FALSE}; const auto has_bfmt_ex = bool{alIsExtensionPresent("AL_SOFT_bformat_ex") != AL_FALSE}; const auto has_bfmt_hoa = bool{has_bfmt_ex && alIsExtensionPresent("AL_SOFT_bformat_hoa") != AL_FALSE}; /* AL_SOFT_bformat_hoa supports up to 14th order (225 channels). */ static constexpr auto max_ambi_order = 14; auto ambi_order = 0; /* Find a suitable format for OpenAL. */ const auto layoutmask = mCodecCtx->ch_layout.u.mask; /* NOLINT(*-union-access) */ mDstChanLayout = 0; mFormat = AL_NONE; if((mCodecCtx->sample_fmt == AV_SAMPLE_FMT_FLT || mCodecCtx->sample_fmt == AV_SAMPLE_FMT_FLTP || mCodecCtx->sample_fmt == AV_SAMPLE_FMT_DBL || mCodecCtx->sample_fmt == AV_SAMPLE_FMT_DBLP || mCodecCtx->sample_fmt == AV_SAMPLE_FMT_S32 || mCodecCtx->sample_fmt == AV_SAMPLE_FMT_S32P || mCodecCtx->sample_fmt == AV_SAMPLE_FMT_S64 || mCodecCtx->sample_fmt == AV_SAMPLE_FMT_S64P) && alIsExtensionPresent("AL_EXT_FLOAT32")) { mDstSampleFmt = AV_SAMPLE_FMT_FLT; mFrameSize = 4; if(mCodecCtx->ch_layout.order == AV_CHANNEL_ORDER_NATIVE) { if(alIsExtensionPresent("AL_EXT_MCFORMATS")) { if(layoutmask == AV_CH_LAYOUT_7POINT1) { mDstChanLayout = layoutmask; mFrameSize *= 8; mFormat = alGetEnumValue("AL_FORMAT_71CHN32"); } if(layoutmask == AV_CH_LAYOUT_5POINT1 || layoutmask == AV_CH_LAYOUT_5POINT1_BACK) { mDstChanLayout = layoutmask; mFrameSize *= 6; mFormat = alGetEnumValue("AL_FORMAT_51CHN32"); } if(layoutmask == AV_CH_LAYOUT_QUAD) { mDstChanLayout = layoutmask; mFrameSize *= 4; mFormat = EnableUhj ? AL_FORMAT_UHJ4CHN_FLOAT32_SOFT : alGetEnumValue("AL_FORMAT_QUAD32"); } } if(layoutmask == AV_CH_LAYOUT_MONO) { mDstChanLayout = layoutmask; mFrameSize *= 1; mFormat = AL_FORMAT_MONO_FLOAT32; } } else if(mCodecCtx->ch_layout.order == AV_CHANNEL_ORDER_AMBISONIC && has_bfmt) { /* Calculate what should be the ambisonic order from the number of * channels, and confirm that's the number of channels. Opus allows * an optional non-diegetic stereo stream with the B-Format stream, * which we can ignore, so check for that too. */ auto order = static_cast(std::sqrt(mCodecCtx->ch_layout.nb_channels)) - 1; auto channels = ALuint(order+1) * ALuint(order+1); if(channels == ALuint(mCodecCtx->ch_layout.nb_channels) || channels+2 == ALuint(mCodecCtx->ch_layout.nb_channels)) { /* OpenAL only supports first-order with AL_EXT_BFORMAT, which * is 4 channels for 3D buffers, unless AL_SOFT_bformat_hoa is * also supported. */ ambi_order = has_bfmt_hoa ? std::min(order, max_ambi_order) : 1; mFrameSize *= ALuint(ambi_order+1) * ALuint(ambi_order+1); mFormat = alGetEnumValue("AL_FORMAT_BFORMAT3D_FLOAT32"); } } if(!mFormat || mFormat == -1) { mDstChanLayout = AV_CH_LAYOUT_STEREO; mFrameSize *= 2; mFormat = EnableUhj ? AL_FORMAT_UHJ2CHN_FLOAT32_SOFT : AL_FORMAT_STEREO_FLOAT32; } } if(mCodecCtx->sample_fmt == AV_SAMPLE_FMT_U8 || mCodecCtx->sample_fmt == AV_SAMPLE_FMT_U8P) { mDstSampleFmt = AV_SAMPLE_FMT_U8; mFrameSize = 1; if(mCodecCtx->ch_layout.order == AV_CHANNEL_ORDER_NATIVE) { if(alIsExtensionPresent("AL_EXT_MCFORMATS")) { if(layoutmask == AV_CH_LAYOUT_7POINT1) { mDstChanLayout = layoutmask; mFrameSize *= 8; mFormat = alGetEnumValue("AL_FORMAT_71CHN8"); } if(layoutmask == AV_CH_LAYOUT_5POINT1 || layoutmask == AV_CH_LAYOUT_5POINT1_BACK) { mDstChanLayout = layoutmask; mFrameSize *= 6; mFormat = alGetEnumValue("AL_FORMAT_51CHN8"); } if(layoutmask == AV_CH_LAYOUT_QUAD) { mDstChanLayout = layoutmask; mFrameSize *= 4; mFormat = EnableUhj ? AL_FORMAT_UHJ4CHN8_SOFT : alGetEnumValue("AL_FORMAT_QUAD8"); } } if(layoutmask == AV_CH_LAYOUT_MONO) { mDstChanLayout = layoutmask; mFrameSize *= 1; mFormat = AL_FORMAT_MONO8; } } else if(mCodecCtx->ch_layout.order == AV_CHANNEL_ORDER_AMBISONIC && has_bfmt) { auto order = static_cast(std::sqrt(mCodecCtx->ch_layout.nb_channels)) - 1; auto channels = (order+1) * (order+1); if(channels == mCodecCtx->ch_layout.nb_channels || channels+2 == mCodecCtx->ch_layout.nb_channels) { ambi_order = has_bfmt_hoa ? std::min(order, max_ambi_order) : 1; mFrameSize *= ALuint(ambi_order+1) * ALuint(ambi_order+1); mFormat = alGetEnumValue("AL_FORMAT_BFORMAT3D_8"); } } if(!mFormat || mFormat == -1) { mDstChanLayout = AV_CH_LAYOUT_STEREO; mFrameSize *= 2; mFormat = EnableUhj ? AL_FORMAT_UHJ2CHN8_SOFT : AL_FORMAT_STEREO8; } } if(!mFormat || mFormat == -1) { mDstSampleFmt = AV_SAMPLE_FMT_S16; mFrameSize = 2; if(mCodecCtx->ch_layout.order == AV_CHANNEL_ORDER_NATIVE) { if(alIsExtensionPresent("AL_EXT_MCFORMATS")) { if(layoutmask == AV_CH_LAYOUT_7POINT1) { mDstChanLayout = layoutmask; mFrameSize *= 8; mFormat = alGetEnumValue("AL_FORMAT_71CHN16"); } if(layoutmask == AV_CH_LAYOUT_5POINT1 || layoutmask == AV_CH_LAYOUT_5POINT1_BACK) { mDstChanLayout = layoutmask; mFrameSize *= 6; mFormat = alGetEnumValue("AL_FORMAT_51CHN16"); } if(layoutmask == AV_CH_LAYOUT_QUAD) { mDstChanLayout = layoutmask; mFrameSize *= 4; mFormat = EnableUhj ? AL_FORMAT_UHJ4CHN16_SOFT : alGetEnumValue("AL_FORMAT_QUAD16"); } } if(layoutmask == AV_CH_LAYOUT_MONO) { mDstChanLayout = layoutmask; mFrameSize *= 1; mFormat = AL_FORMAT_MONO16; } } else if(mCodecCtx->ch_layout.order == AV_CHANNEL_ORDER_AMBISONIC && has_bfmt) { auto order = static_cast(std::sqrt(mCodecCtx->ch_layout.nb_channels)) - 1; auto channels = (order+1) * (order+1); if(channels == mCodecCtx->ch_layout.nb_channels || channels+2 == mCodecCtx->ch_layout.nb_channels) { ambi_order = has_bfmt_hoa ? std::min(order, max_ambi_order) : 1; mFrameSize *= ALuint(ambi_order+1) * ALuint(ambi_order+1); mFormat = alGetEnumValue("AL_FORMAT_BFORMAT3D_16"); } } if(!mFormat || mFormat == -1) { mDstChanLayout = AV_CH_LAYOUT_STEREO; mFrameSize *= 2; mFormat = EnableUhj ? AL_FORMAT_UHJ2CHN16_SOFT : AL_FORMAT_STEREO16; } } mSamples.fill(nullptr); mSamplesSpan = {}; mSamplesMax = 0; mSamplesPos = 0; mSamplesLen = 0; mDecodedFrame.reset(av_frame_alloc()); if(!mDecodedFrame) { fmt::println(stderr, "Failed to allocate audio frame"); return 0; } if(!mDstChanLayout) { auto layout = ChannelLayout{}; av_channel_layout_from_string(&layout, fmt::format("ambisonic {}", ambi_order).c_str()); const auto err = swr_alloc_set_opts2(al::out_ptr(mSwresCtx), &layout, mDstSampleFmt, mCodecCtx->sample_rate, &mCodecCtx->ch_layout, mCodecCtx->sample_fmt, mCodecCtx->sample_rate, 0, nullptr); if(err != 0) { std::array errstr{}; fmt::println(stderr, "Failed to allocate SwrContext: {}", av_make_error_string(errstr.data(), AV_ERROR_MAX_STRING_SIZE, err)); return 0; } if(has_bfmt_hoa && ambi_order > 1) fmt::println("Found AL_SOFT_bformat_hoa (order {})", ambi_order); else if(has_bfmt_ex) fmt::println("Found AL_SOFT_bformat_ex"); else { fmt::println("Found AL_EXT_BFORMAT"); /* Without AL_SOFT_bformat_ex, OpenAL only supports FuMa channel * ordering and normalization, so a custom matrix is needed to * scale and reorder the source from AmbiX. */ std::vector mtx(64_uz*64_uz, 0.0); mtx[0 + 0*64] = std::sqrt(0.5); mtx[3 + 1*64] = 1.0; mtx[1 + 2*64] = 1.0; mtx[2 + 3*64] = 1.0; swr_set_matrix(mSwresCtx.get(), mtx.data(), 64); } } else { ChannelLayout layout{}; av_channel_layout_from_mask(&layout, mDstChanLayout); int err{swr_alloc_set_opts2(al::out_ptr(mSwresCtx), &layout, mDstSampleFmt, mCodecCtx->sample_rate, &mCodecCtx->ch_layout, mCodecCtx->sample_fmt, mCodecCtx->sample_rate, 0, nullptr)}; if(err != 0) { std::array errstr{}; fmt::println(stderr, "Failed to allocate SwrContext: {}", av_make_error_string(errstr.data(), AV_ERROR_MAX_STRING_SIZE, err)); return 0; } } if(int err{swr_init(mSwresCtx.get())}) { std::array errstr{}; fmt::println(stderr, "Failed to initialize audio converter: {}", av_make_error_string(errstr.data(), AV_ERROR_MAX_STRING_SIZE, err)); return 0; } alGenBuffers(static_cast(mBuffers.size()), mBuffers.data()); alGenSources(1, &mSource); if(DirectOutMode) alSourcei(mSource, AL_DIRECT_CHANNELS_SOFT, DirectOutMode); if(EnableWideStereo) { static constexpr std::array angles{static_cast(al::numbers::pi / 3.0), static_cast(-al::numbers::pi / 3.0)}; alSourcefv(mSource, AL_STEREO_ANGLES, angles.data()); } if(has_bfmt_ex) { for(ALuint bufid : mBuffers) { alBufferi(bufid, AL_AMBISONIC_LAYOUT_SOFT, AL_ACN_SOFT); alBufferi(bufid, AL_AMBISONIC_SCALING_SOFT, AL_SN3D_SOFT); } } if(ambi_order > 1) { for(ALuint bufid : mBuffers) alBufferi(bufid, AL_UNPACK_AMBISONIC_ORDER_SOFT, ambi_order); } #ifdef AL_SOFT_UHJ if(EnableSuperStereo) alSourcei(mSource, AL_STEREO_MODE_SOFT, AL_SUPER_STEREO_SOFT); #endif if(alGetError() != AL_NO_ERROR) return 0; bool callback_ok{false}; if(alBufferCallbackSOFT) { alBufferCallbackSOFT(mBuffers[0], mFormat, mCodecCtx->sample_rate, bufferCallbackC, this); alSourcei(mSource, AL_BUFFER, static_cast(mBuffers[0])); if(alGetError() != AL_NO_ERROR) { fmt::println(stderr, "Failed to set buffer callback"); alSourcei(mSource, AL_BUFFER, 0); } else { mBufferData.resize(static_cast(duration_cast(mCodecCtx->sample_rate * AudioBufferTotalTime).count()) * mFrameSize); std::fill(mBufferData.begin(), mBufferData.end(), uint8_t{}); mReadPos.store(0, std::memory_order_relaxed); mWritePos.store(mBufferData.size()/mFrameSize/2*mFrameSize, std::memory_order_relaxed); ALCint refresh{}; alcGetIntegerv(alcGetContextsDevice(alcGetCurrentContext()), ALC_REFRESH, 1, &refresh); sleep_time = milliseconds{seconds{1}} / refresh; callback_ok = true; } } if(!callback_ok) buffer_len = static_cast(duration_cast(mCodecCtx->sample_rate * AudioBufferTime).count() * mFrameSize); if(buffer_len > 0) samples.resize(static_cast(buffer_len)); /* Prefill the codec buffer. */ auto packet_sender = [this]() { while(true) { const int ret{mQueue.sendPacket(mCodecCtx.get())}; if(ret == AVErrorEOF) break; } }; auto sender [[maybe_unused]] = std::async(std::launch::async, packet_sender); srclock.lock(); if(alcGetInteger64vSOFT) { int64_t devtime{}; alcGetInteger64vSOFT(alcGetContextsDevice(alcGetCurrentContext()), ALC_DEVICE_CLOCK_SOFT, 1, &devtime); mDeviceStartTime = nanoseconds{devtime} - mCurrentPts; } mSamplesLen = decodeFrame(); if(mSamplesLen > 0) { mSamplesPos = std::min(mSamplesLen, getSync()); auto skip = nanoseconds{seconds{mSamplesPos}} / mCodecCtx->sample_rate; mDeviceStartTime -= skip; mCurrentPts += skip; } while(true) { if(mMovie.mQuit.load(std::memory_order_relaxed)) { /* If mQuit is set, drain frames until we can't get more audio, * indicating we've reached the flush packet and the packet sender * will also quit. */ do { mSamplesLen = decodeFrame(); mSamplesPos = mSamplesLen; } while(mSamplesLen > 0); break; } ALenum state; if(!mBufferData.empty()) { alGetSourcei(mSource, AL_SOURCE_STATE, &state); /* If mQuit is not set, don't quit even if there's no more audio, * so what's buffered has a chance to play to the real end. */ readAudio(getSync()); } else { ALint processed, queued; /* First remove any processed buffers. */ alGetSourcei(mSource, AL_BUFFERS_PROCESSED, &processed); while(processed > 0) { ALuint bid; alSourceUnqueueBuffers(mSource, 1, &bid); --processed; } /* Refill the buffer queue. */ int sync_skip{getSync()}; alGetSourcei(mSource, AL_BUFFERS_QUEUED, &queued); while(static_cast(queued) < mBuffers.size()) { /* Read the next chunk of data, filling the buffer, and queue * it on the source. */ if(!readAudio(samples, static_cast(buffer_len), sync_skip)) break; const ALuint bufid{mBuffers[mBufferIdx]}; mBufferIdx = static_cast((mBufferIdx+1) % mBuffers.size()); alBufferData(bufid, mFormat, samples.data(), buffer_len, mCodecCtx->sample_rate); alSourceQueueBuffers(mSource, 1, &bufid); ++queued; } /* Check that the source is playing. */ alGetSourcei(mSource, AL_SOURCE_STATE, &state); if(state == AL_STOPPED) { /* AL_STOPPED means there was an underrun. Clear the buffer * queue since this likely means we're late, and rewind the * source to get it back into an AL_INITIAL state. */ alSourceRewind(mSource); alSourcei(mSource, AL_BUFFER, 0); if(alcGetInteger64vSOFT) { /* Also update the device start time with the current * device clock, so the decoder knows we're running behind. */ int64_t devtime{}; alcGetInteger64vSOFT(alcGetContextsDevice(alcGetCurrentContext()), ALC_DEVICE_CLOCK_SOFT, 1, &devtime); mDeviceStartTime = nanoseconds{devtime} - mCurrentPts; } continue; } } /* (re)start the source if needed, and wait for a buffer to finish */ if(state != AL_PLAYING && state != AL_PAUSED) { if(!startPlayback()) break; } if(ALenum err{alGetError()}) fmt::println(stderr, "Got AL error: {:#x} ({})", as_unsigned(err), alGetString(err)); mSrcCond.wait_for(srclock, sleep_time); } alSourceRewind(mSource); alSourcei(mSource, AL_BUFFER, 0); srclock.unlock(); return 0; } nanoseconds VideoState::getClock() { /* NOTE: This returns incorrect times while not playing. */ std::lock_guard displock{mDispPtsMutex}; if(mDisplayPtsTime == microseconds::min()) return nanoseconds::zero(); auto delta = get_avtime() - mDisplayPtsTime; return mDisplayPts + delta; } /* Called by VideoState::updateVideo to display the next video frame. */ void VideoState::display(SDL_Renderer *renderer, AVFrame *frame) const { if(!mImage) return; auto frame_width = frame->width - static_cast(frame->crop_left + frame->crop_right); auto frame_height = frame->height - static_cast(frame->crop_top + frame->crop_bottom); const auto src_rect = SDL_FRect{ static_cast(frame->crop_left), static_cast(frame->crop_top), static_cast(frame_width), static_cast(frame_height) }; SDL_RenderTexture(renderer, mImage, &src_rect, nullptr); SDL_RenderPresent(renderer); } /* Called regularly on the main thread where the SDL_Renderer was created. It * handles updating the textures of decoded frames and displaying the latest * frame. */ void VideoState::updateVideo(SDL_Window *screen, SDL_Renderer *renderer, bool redraw) { size_t read_idx{mPictQRead.load(std::memory_order_relaxed)}; Picture *vp{&mPictQ[read_idx]}; auto clocktime = mMovie.getMasterClock(); bool updated{false}; while(true) { size_t next_idx{(read_idx+1)%mPictQ.size()}; if(next_idx == mPictQWrite.load(std::memory_order_acquire)) break; Picture *nextvp{&mPictQ[next_idx]}; if(clocktime < nextvp->mPts && !mMovie.mQuit.load(std::memory_order_relaxed)) { /* For the first update, ensure the first frame gets shown. */ if(!mFirstUpdate || updated) break; } vp = nextvp; updated = true; read_idx = next_idx; } if(mMovie.mQuit.load(std::memory_order_relaxed)) { if(mEOS) mFinalUpdate = true; mPictQRead.store(read_idx, std::memory_order_release); std::unique_lock{mPictQMutex}.unlock(); mPictQCond.notify_one(); return; } AVFrame *frame{vp->mFrame.get()}; if(updated) { mPictQRead.store(read_idx, std::memory_order_release); std::unique_lock{mPictQMutex}.unlock(); mPictQCond.notify_one(); /* allocate or resize the buffer! */ bool fmt_updated{false}; if(!mImage || mWidth != frame->width || mHeight != frame->height) { fmt_updated = true; if(mImage) SDL_DestroyTexture(mImage); mImage = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, frame->width, frame->height); if(!mImage) fmt::println(stderr, "Failed to create YV12 texture!"); mWidth = frame->width; mHeight = frame->height; } int frame_width{frame->width - static_cast(frame->crop_left + frame->crop_right)}; int frame_height{frame->height - static_cast(frame->crop_top + frame->crop_bottom)}; if(mFirstUpdate && frame_width > 0 && frame_height > 0) { /* For the first update, set the window size to the video size. */ mFirstUpdate = false; if(frame->sample_aspect_ratio.den != 0) { double aspect_ratio = av_q2d(frame->sample_aspect_ratio); if(aspect_ratio >= 1.0) frame_width = static_cast(std::lround(frame_width * aspect_ratio)); else if(aspect_ratio > 0.0) frame_height = static_cast(std::lround(frame_height / aspect_ratio)); } if(SDL_SetWindowSize(screen, frame_width, frame_height)) SDL_SyncWindow(screen); SDL_SetRenderLogicalPresentation(renderer, frame_width, frame_height, SDL_LOGICAL_PRESENTATION_LETTERBOX); } if(mImage) { void *pixels{nullptr}; int pitch{0}; if(mCodecCtx->pix_fmt == AV_PIX_FMT_YUV420P) SDL_UpdateYUVTexture(mImage, nullptr, frame->data[0], frame->linesize[0], frame->data[1], frame->linesize[1], frame->data[2], frame->linesize[2] ); else if(!SDL_LockTexture(mImage, nullptr, &pixels, &pitch)) fmt::println(stderr, "Failed to lock texture: {}", SDL_GetError()); else { // Convert the image into YUV format that SDL uses int w{frame->width}; int h{frame->height}; if(!mSwscaleCtx || fmt_updated) { mSwscaleCtx.reset(sws_getContext( w, h, mCodecCtx->pix_fmt, w, h, AV_PIX_FMT_YUV420P, 0, nullptr, nullptr, nullptr )); } /* point pict at the queue */ const auto framesize = static_cast(w)*static_cast(h); const auto pixelspan = al::span{static_cast(pixels), framesize*3/2}; const std::array pict_data{ al::to_address(pixelspan.begin()), al::to_address(pixelspan.begin() + ptrdiff_t{w}*h), al::to_address(pixelspan.begin() + ptrdiff_t{w}*h + ptrdiff_t{w}*h/4) }; const std::array pict_linesize{pitch, pitch/2, pitch/2}; sws_scale(mSwscaleCtx.get(), std::data(frame->data), std::data(frame->linesize), 0, h, pict_data.data(), pict_linesize.data()); SDL_UnlockTexture(mImage); } redraw = true; } } if(redraw) { /* Show the picture! */ display(renderer, frame); } if(updated) { auto disp_time = get_avtime(); std::lock_guard displock{mDispPtsMutex}; mDisplayPts = vp->mPts; mDisplayPtsTime = disp_time; } if(mEOS.load(std::memory_order_acquire)) { if((read_idx+1)%mPictQ.size() == mPictQWrite.load(std::memory_order_acquire)) { mFinalUpdate = true; std::unique_lock{mPictQMutex}.unlock(); mPictQCond.notify_one(); } } } int VideoState::handler() { std::for_each(mPictQ.begin(), mPictQ.end(), [](Picture &pict) -> void { pict.mFrame = AVFramePtr{av_frame_alloc()}; }); /* Prefill the codec buffer. */ auto sender [[maybe_unused]] = std::async(std::launch::async, [this]() { while(true) { const int ret{mQueue.sendPacket(mCodecCtx.get())}; if(ret == AVErrorEOF) break; } }); { std::lock_guard displock{mDispPtsMutex}; mDisplayPtsTime = get_avtime(); } auto current_pts = nanoseconds::zero(); while(true) { size_t write_idx{mPictQWrite.load(std::memory_order_relaxed)}; Picture *vp{&mPictQ[write_idx]}; /* Retrieve video frame. */ auto get_frame = [this](AVFrame *frame) -> AVFrame* { while(int ret{mQueue.receiveFrame(mCodecCtx.get(), frame)}) { if(ret == AVErrorEOF) return nullptr; fmt::println(stderr, "Failed to receive frame: {}", ret); } return frame; }; auto *decoded_frame = get_frame(vp->mFrame.get()); if(!decoded_frame) break; /* Get the PTS for this frame. */ if(decoded_frame->best_effort_timestamp != AVNoPtsValue) current_pts = duration_cast(seconds_d64{av_q2d(mStream->time_base) * static_cast(decoded_frame->best_effort_timestamp)}); vp->mPts = current_pts; /* Update the video clock to the next expected PTS. */ auto frame_delay = av_q2d(mCodecCtx->time_base); frame_delay += decoded_frame->repeat_pict * (frame_delay * 0.5); current_pts += duration_cast(seconds_d64{frame_delay}); /* Put the frame in the queue to be loaded into a texture and displayed * by the rendering thread. */ write_idx = (write_idx+1)%mPictQ.size(); mPictQWrite.store(write_idx, std::memory_order_release); if(write_idx == mPictQRead.load(std::memory_order_acquire)) { /* Wait until we have space for a new pic */ auto lock = std::unique_lock{mPictQMutex}; mPictQCond.wait(lock, [write_idx,this]() noexcept { return write_idx != mPictQRead.load(std::memory_order_acquire); }); } } mEOS = true; auto lock = std::unique_lock{mPictQMutex}; mPictQCond.wait(lock, [this]() noexcept { return mFinalUpdate.load(); }); return 0; } int MovieState::decode_interrupt_cb(void *ctx) { return static_cast(ctx)->mQuit.load(std::memory_order_relaxed); } bool MovieState::prepare() { AVIOContext *avioctx{nullptr}; AVIOInterruptCB intcb{decode_interrupt_cb, this}; if(avio_open2(&avioctx, mFilename.c_str(), AVIO_FLAG_READ, &intcb, nullptr)) { fmt::println(stderr, "Failed to open {}", mFilename); return false; } mIOContext.reset(avioctx); /* Open movie file. If avformat_open_input fails it will automatically free * this context, so don't set it onto a smart pointer yet. */ AVFormatContext *fmtctx{avformat_alloc_context()}; fmtctx->pb = mIOContext.get(); fmtctx->interrupt_callback = intcb; if(avformat_open_input(&fmtctx, mFilename.c_str(), nullptr, nullptr) != 0) { fmt::println(stderr, "Failed to open {}", mFilename); return false; } mFormatCtx.reset(fmtctx); /* Retrieve stream information */ if(avformat_find_stream_info(mFormatCtx.get(), nullptr) < 0) { fmt::println(stderr, "{}: failed to find stream info", mFilename); return false; } /* Dump information about file onto standard error */ av_dump_format(mFormatCtx.get(), 0, mFilename.c_str(), 0); mParseThread = std::thread{&MovieState::parse_handler, this}; std::unique_lock slock{mStartupMutex}; while(!mStartupDone) mStartupCond.wait(slock); return true; } void MovieState::setTitle(SDL_Window *window) const { /* rfind returns npos if the char isn't found, and npos+1==0, which will * give the desired result for finding the filename portion. */ const auto fpos = std::max(mFilename.rfind('/')+1, mFilename.rfind('\\')+1); const auto title = fmt::format("{} - {}", std::string_view{mFilename}.substr(fpos), AppName); SDL_SetWindowTitle(window, title.c_str()); } nanoseconds MovieState::getClock() const { if(mClockBase == microseconds::min()) return nanoseconds::zero(); return get_avtime() - mClockBase; } nanoseconds MovieState::getMasterClock() { if(mAVSyncType == SyncMaster::Video && mVideo.mStream) return mVideo.getClock(); if(mAVSyncType == SyncMaster::Audio && mAudio.mStream) return mAudio.getClock(); return getClock(); } nanoseconds MovieState::getDuration() const { return std::chrono::duration>(mFormatCtx->duration); } bool MovieState::streamComponentOpen(AVStream *stream) { /* Get a pointer to the codec context for the stream, and open the * associated codec. */ AVCodecCtxPtr avctx{avcodec_alloc_context3(nullptr)}; if(!avctx) return false; if(avcodec_parameters_to_context(avctx.get(), stream->codecpar)) return false; const AVCodec *codec{avcodec_find_decoder(avctx->codec_id)}; if(!codec || avcodec_open2(avctx.get(), codec, nullptr) < 0) { fmt::println(stderr, "Unsupported codec: {} ({:#x})", avcodec_get_name(avctx->codec_id), al::to_underlying(avctx->codec_id)); return false; } /* Initialize and start the media type handler */ switch(avctx->codec_type) { case AVMEDIA_TYPE_AUDIO: mAudio.mStream = stream; mAudio.mCodecCtx = std::move(avctx); return true; case AVMEDIA_TYPE_VIDEO: mVideo.mStream = stream; mVideo.mCodecCtx = std::move(avctx); return true; default: break; } return false; } int MovieState::parse_handler() { auto &audio_queue = mAudio.mQueue; auto &video_queue = mVideo.mQueue; int video_index{-1}; int audio_index{-1}; /* Find the first video and audio streams */ const auto ctxstreams = al::span{mFormatCtx->streams, mFormatCtx->nb_streams}; for(size_t i{0};i < ctxstreams.size();++i) { auto codecpar = ctxstreams[i]->codecpar; if(codecpar->codec_type == AVMEDIA_TYPE_VIDEO && !DisableVideo && video_index < 0 && streamComponentOpen(ctxstreams[i])) video_index = static_cast(i); else if(codecpar->codec_type == AVMEDIA_TYPE_AUDIO && audio_index < 0 && streamComponentOpen(ctxstreams[i])) audio_index = static_cast(i); } { std::unique_lock slock{mStartupMutex}; mStartupDone = true; } mStartupCond.notify_all(); if(video_index < 0 && audio_index < 0) { fmt::println(stderr, "{}: could not open codecs", mFilename); mQuit = true; } /* Set the base time 750ms ahead of the current av time. */ mClockBase = get_avtime() + milliseconds{750}; if(audio_index >= 0) mAudioThread = std::thread{&AudioState::handler, &mAudio}; if(video_index >= 0) mVideoThread = std::thread{&VideoState::handler, &mVideo}; /* Main packet reading/dispatching loop */ AVPacketPtr packet{av_packet_alloc()}; while(!mQuit.load(std::memory_order_relaxed)) { if(av_read_frame(mFormatCtx.get(), packet.get()) < 0) break; /* Copy the packet into the queue it's meant for. */ if(packet->stream_index == video_index) { while(!mQuit.load(std::memory_order_acquire) && !video_queue.put(packet.get())) std::this_thread::sleep_for(milliseconds{100}); } else if(packet->stream_index == audio_index) { while(!mQuit.load(std::memory_order_acquire) && !audio_queue.put(packet.get())) std::this_thread::sleep_for(milliseconds{100}); } av_packet_unref(packet.get()); } /* Finish the queues so the receivers know nothing more is coming. */ video_queue.setFinished(); audio_queue.setFinished(); /* all done - wait for it */ if(mVideoThread.joinable()) mVideoThread.join(); if(mAudioThread.joinable()) mAudioThread.join(); mVideo.mEOS = true; std::unique_lock lock{mVideo.mPictQMutex}; while(!mVideo.mFinalUpdate) mVideo.mPictQCond.wait(lock); lock.unlock(); SDL_Event evt{}; evt.user.type = FF_MOVIE_DONE_EVENT; SDL_PushEvent(&evt); return 0; } void MovieState::stop() { mQuit = true; mAudio.mQueue.flush(); mVideo.mQueue.flush(); } // Helper method to print the time with human-readable formatting. auto PrettyTime(seconds t) -> std::string { using minutes = std::chrono::minutes; using hours = std::chrono::hours; if(t.count() < 0) return "0s"; // Only handle up to hour formatting if(t >= hours{1}) return fmt::format("{}h{:02}m{:02}s", duration_cast(t).count(), duration_cast(t).count()%60, t.count()%60); return fmt::format("{}m{:02}s", duration_cast(t).count(), t.count()%60); } int main(al::span args) { SDL_SetMainReady(); if(args.size() < 2) { fmt::println(stderr, "Usage: {} [-device ] [-direct] ", args[0]); return 1; } /* Initialize networking protocols */ avformat_network_init(); if(!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS)) { fmt::println(stderr, "Could not initialize SDL - {}", SDL_GetError()); return 1; } /* Make a window to put our video */ auto *screen = SDL_CreateWindow(AppName.c_str(), 640, 480, SDL_WINDOW_RESIZABLE); if(!screen) { fmt::println(stderr, "SDL: could not set video mode - exiting"); return 1; } SDL_SetWindowSurfaceVSync(screen, 1); /* Make a renderer to handle the texture image surface and rendering. */ auto *renderer = SDL_CreateRenderer(screen, nullptr); if(!renderer) { fmt::println(stderr, "SDL: could not create renderer - exiting"); return 1; } SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); SDL_RenderFillRect(renderer, nullptr); SDL_RenderPresent(renderer); /* Open an audio device */ args = args.subspan(1); if(InitAL(args) != 0) return 1; { ALCdevice *device{alcGetContextsDevice(alcGetCurrentContext())}; if(alcIsExtensionPresent(device,"ALC_SOFT_device_clock")) { fmt::println("Found ALC_SOFT_device_clock"); alcGetInteger64vSOFT = reinterpret_cast( alcGetProcAddress(device, "alcGetInteger64vSOFT")); } } if(alIsExtensionPresent("AL_SOFT_source_latency")) { fmt::println("Found AL_SOFT_source_latency"); alGetSourcei64vSOFT = reinterpret_cast( alGetProcAddress("alGetSourcei64vSOFT")); } if(alIsExtensionPresent("AL_SOFT_events")) { fmt::println("Found AL_SOFT_events"); alEventControlSOFT = reinterpret_cast( alGetProcAddress("alEventControlSOFT")); alEventCallbackSOFT = reinterpret_cast( alGetProcAddress("alEventCallbackSOFT")); } if(alIsExtensionPresent("AL_SOFT_callback_buffer")) { fmt::println("Found AL_SOFT_callback_buffer"); alBufferCallbackSOFT = reinterpret_cast( alGetProcAddress("alBufferCallbackSOFT")); } size_t fileidx{0}; for(;fileidx < args.size();++fileidx) { if(args[fileidx] == "-direct") { if(alIsExtensionPresent("AL_SOFT_direct_channels_remix")) { fmt::println("Found AL_SOFT_direct_channels_remix"); DirectOutMode = AL_REMIX_UNMATCHED_SOFT; } else if(alIsExtensionPresent("AL_SOFT_direct_channels")) { fmt::println("Found AL_SOFT_direct_channels"); DirectOutMode = AL_DROP_UNMATCHED_SOFT; } else fmt::println(stderr, "AL_SOFT_direct_channels not supported for direct output"); } else if(args[fileidx] == "-wide") { if(!alIsExtensionPresent("AL_EXT_STEREO_ANGLES")) fmt::println(stderr, "AL_EXT_STEREO_ANGLES not supported for wide stereo"); else { fmt::println("Found AL_EXT_STEREO_ANGLES"); EnableWideStereo = true; } } else if(args[fileidx] == "-uhj") { if(!alIsExtensionPresent("AL_SOFT_UHJ")) fmt::println(stderr, "AL_SOFT_UHJ not supported for UHJ decoding"); else { fmt::println("Found AL_SOFT_UHJ"); EnableUhj = true; } } else if(args[fileidx] == "-superstereo") { if(!alIsExtensionPresent("AL_SOFT_UHJ")) fmt::println(stderr, "AL_SOFT_UHJ not supported for Super Stereo decoding"); else { fmt::println("Found AL_SOFT_UHJ (Super Stereo)"); EnableSuperStereo = true; } } else if(args[fileidx] == "-novideo") DisableVideo = true; else break; } auto movState = std::unique_ptr{}; while(fileidx < args.size() && !movState) { movState = std::make_unique(args[fileidx++]); if(!movState->prepare()) movState = nullptr; } if(!movState) { fmt::println(stderr, "Could not start a video"); return 1; } movState->setTitle(screen); /* Default to going to the next movie at the end of one. */ enum class EomAction { Next, Quit } eom_action{EomAction::Next}; seconds last_time{seconds::min()}; while(true) { auto event = SDL_Event{}; auto have_event = SDL_WaitEventTimeout(&event, 10); const auto cur_time = duration_cast(movState->getMasterClock()); if(cur_time != last_time) { const auto end_time = duration_cast(movState->getDuration()); fmt::print(" \r {} / {}", PrettyTime(cur_time), PrettyTime(end_time)); fflush(stdout); last_time = cur_time; } auto force_redraw = false; while(have_event) { switch(event.type) { case SDL_EVENT_KEY_DOWN: switch(event.key.key) { case SDLK_ESCAPE: movState->stop(); eom_action = EomAction::Quit; break; case SDLK_N: movState->stop(); eom_action = EomAction::Next; break; default: break; } break; case SDL_EVENT_WINDOW_SHOWN: case SDL_EVENT_WINDOW_EXPOSED: case SDL_EVENT_WINDOW_RESIZED: case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED: case SDL_EVENT_WINDOW_SAFE_AREA_CHANGED: case SDL_EVENT_RENDER_TARGETS_RESET: SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); SDL_RenderFillRect(renderer, nullptr); force_redraw = true; break; case SDL_EVENT_QUIT: movState->stop(); eom_action = EomAction::Quit; break; case FF_MOVIE_DONE_EVENT: fmt::println(""); last_time = seconds::min(); if(eom_action != EomAction::Quit) { movState = nullptr; while(fileidx < args.size() && !movState) { movState = std::make_unique(args[fileidx++]); if(!movState->prepare()) movState = nullptr; } if(movState) { movState->setTitle(screen); break; } } /* Nothing more to play. Shut everything down and quit. */ movState = nullptr; CloseAL(); SDL_DestroyRenderer(renderer); renderer = nullptr; SDL_DestroyWindow(screen); screen = nullptr; SDL_QuitSubSystem(SDL_INIT_VIDEO | SDL_INIT_EVENTS); exit(0); default: break; } have_event = SDL_PollEvent(&event); } movState->mVideo.updateVideo(screen, renderer, force_redraw); } fmt::println(stderr, "SDL_WaitEvent error - {}", SDL_GetError()); return 1; } } // namespace int main(int argc, char *argv[]) { assert(argc >= 0); auto args = std::vector(static_cast(argc)); std::copy_n(argv, args.size(), args.begin()); return main(al::span{args}); } openal-soft-1.24.2/examples/alhrtf.c000066400000000000000000000217361474041540300173040ustar00rootroot00000000000000/* * OpenAL HRTF Example * * Copyright (c) 2015 by Chris Robinson * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ /* This file contains an example for selecting an HRTF. */ #include #include #include #include #include #include #include #include "sndfile.h" #include "AL/al.h" #include "AL/alc.h" #include "AL/alext.h" #include "common/alhelpers.h" #include "win_main_utf8.h" #ifndef M_PI #define M_PI (3.14159265358979323846) #endif static LPALCGETSTRINGISOFT alcGetStringiSOFT; static LPALCRESETDEVICESOFT alcResetDeviceSOFT; /* LoadBuffer loads the named audio file into an OpenAL buffer object, and * returns the new buffer ID. */ static ALuint LoadSound(const char *filename) { ALenum err, format; ALuint buffer; SNDFILE *sndfile; SF_INFO sfinfo; short *membuf; sf_count_t num_frames; ALsizei num_bytes; /* Open the audio file and check that it's usable. */ sndfile = sf_open(filename, SFM_READ, &sfinfo); if(!sndfile) { fprintf(stderr, "Could not open audio in %s: %s\n", filename, sf_strerror(sndfile)); return 0; } if(sfinfo.frames < 1 || sfinfo.frames > (sf_count_t)(INT_MAX/sizeof(short))/sfinfo.channels) { fprintf(stderr, "Bad sample count in %s (%" PRId64 ")\n", filename, sfinfo.frames); sf_close(sndfile); return 0; } /* Get the sound format, and figure out the OpenAL format */ format = AL_NONE; if(sfinfo.channels == 1) format = AL_FORMAT_MONO16; else if(sfinfo.channels == 2) format = AL_FORMAT_STEREO16; else if(sfinfo.channels == 3) { if(sf_command(sndfile, SFC_WAVEX_GET_AMBISONIC, NULL, 0) == SF_AMBISONIC_B_FORMAT) format = AL_FORMAT_BFORMAT2D_16; } else if(sfinfo.channels == 4) { if(sf_command(sndfile, SFC_WAVEX_GET_AMBISONIC, NULL, 0) == SF_AMBISONIC_B_FORMAT) format = AL_FORMAT_BFORMAT3D_16; } if(!format) { fprintf(stderr, "Unsupported channel count: %d\n", sfinfo.channels); sf_close(sndfile); return 0; } /* Decode the whole audio file to a buffer. */ membuf = malloc((size_t)(sfinfo.frames * sfinfo.channels) * sizeof(short)); num_frames = sf_readf_short(sndfile, membuf, sfinfo.frames); if(num_frames < 1) { free(membuf); sf_close(sndfile); fprintf(stderr, "Failed to read samples in %s (%" PRId64 ")\n", filename, num_frames); return 0; } num_bytes = (ALsizei)(num_frames * sfinfo.channels) * (ALsizei)sizeof(short); /* Buffer the audio data into a new buffer object, then free the data and * close the file. */ buffer = 0; alGenBuffers(1, &buffer); alBufferData(buffer, format, membuf, num_bytes, sfinfo.samplerate); free(membuf); sf_close(sndfile); /* Check if an error occurred, and clean up if so. */ err = alGetError(); if(err != AL_NO_ERROR) { fprintf(stderr, "OpenAL Error: %s\n", alGetString(err)); if(buffer && alIsBuffer(buffer)) alDeleteBuffers(1, &buffer); return 0; } return buffer; } int main(int argc, char **argv) { ALCdevice *device; ALCcontext *context; ALboolean has_angle_ext; ALuint source, buffer; const char *soundname; const char *hrtfname; ALCint hrtf_state; ALCint num_hrtf; ALdouble angle; ALenum state; /* Print out usage if no arguments were specified */ if(argc < 2) { fprintf(stderr, "Usage: %s [-device ] [-hrtf ] \n", argv[0]); return 1; } /* Initialize OpenAL, and check for HRTF support. */ argv++; argc--; if(InitAL(&argv, &argc) != 0) return 1; context = alcGetCurrentContext(); device = alcGetContextsDevice(context); if(!alcIsExtensionPresent(device, "ALC_SOFT_HRTF")) { fprintf(stderr, "Error: ALC_SOFT_HRTF not supported\n"); CloseAL(); return 1; } /* Define a macro to help load the function pointers. */ #define LOAD_PROC(d, T, x) ((x) = FUNCTION_CAST(T, alcGetProcAddress((d), #x))) LOAD_PROC(device, LPALCGETSTRINGISOFT, alcGetStringiSOFT); LOAD_PROC(device, LPALCRESETDEVICESOFT, alcResetDeviceSOFT); #undef LOAD_PROC /* Check for the AL_EXT_STEREO_ANGLES extension to be able to also rotate * stereo sources. */ has_angle_ext = alIsExtensionPresent("AL_EXT_STEREO_ANGLES"); printf("AL_EXT_STEREO_ANGLES %sfound\n", has_angle_ext?"":"not "); /* Check for user-preferred HRTF */ if(strcmp(argv[0], "-hrtf") == 0) { hrtfname = argv[1]; soundname = argv[2]; } else { hrtfname = NULL; soundname = argv[0]; } /* Enumerate available HRTFs, and reset the device using one. */ alcGetIntegerv(device, ALC_NUM_HRTF_SPECIFIERS_SOFT, 1, &num_hrtf); if(!num_hrtf) printf("No HRTFs found\n"); else { ALCint attr[5]; ALCint index = -1; ALCint i; printf("Available HRTFs:\n"); for(i = 0;i < num_hrtf;i++) { const ALCchar *name = alcGetStringiSOFT(device, ALC_HRTF_SPECIFIER_SOFT, i); printf(" %d: %s\n", i, name); /* Check if this is the HRTF the user requested. */ if(hrtfname && strcmp(name, hrtfname) == 0) index = i; } i = 0; attr[i++] = ALC_HRTF_SOFT; attr[i++] = ALC_TRUE; if(index == -1) { if(hrtfname) printf("HRTF \"%s\" not found\n", hrtfname); printf("Using default HRTF...\n"); } else { printf("Selecting HRTF %d...\n", index); attr[i++] = ALC_HRTF_ID_SOFT; attr[i++] = index; } attr[i] = 0; if(!alcResetDeviceSOFT(device, attr)) printf("Failed to reset device: %s\n", alcGetString(device, alcGetError(device))); } /* Check if HRTF is enabled, and show which is being used. */ alcGetIntegerv(device, ALC_HRTF_SOFT, 1, &hrtf_state); if(!hrtf_state) printf("HRTF not enabled!\n"); else { const ALchar *name = alcGetString(device, ALC_HRTF_SPECIFIER_SOFT); printf("HRTF enabled, using %s\n", name); } fflush(stdout); /* Load the sound into a buffer. */ buffer = LoadSound(soundname); if(!buffer) { CloseAL(); return 1; } /* Create the source to play the sound with. */ source = 0; alGenSources(1, &source); alSourcei(source, AL_SOURCE_RELATIVE, AL_TRUE); alSource3f(source, AL_POSITION, 0.0f, 0.0f, -1.0f); alSourcei(source, AL_BUFFER, (ALint)buffer); assert(alGetError()==AL_NO_ERROR && "Failed to setup sound source"); /* Play the sound until it finishes. */ angle = 0.0; alSourcePlay(source); do { al_nssleep(10000000); alcSuspendContext(context); /* Rotate the source around the listener by about 1/4 cycle per second, * and keep it within -pi...+pi. */ angle += 0.01 * M_PI * 0.5; if(angle > M_PI) angle -= M_PI*2.0; /* This only rotates mono sounds. */ alSource3f(source, AL_POSITION, (ALfloat)sin(angle), 0.0f, -(ALfloat)cos(angle)); if(has_angle_ext) { /* This rotates stereo sounds with the AL_EXT_STEREO_ANGLES * extension. Angles are specified counter-clockwise in radians. */ ALfloat angles[2] = { (ALfloat)(M_PI/6.0 - angle), (ALfloat)(-M_PI/6.0 - angle) }; alSourcefv(source, AL_STEREO_ANGLES, angles); } alcProcessContext(context); alGetSourcei(source, AL_SOURCE_STATE, &state); } while(alGetError() == AL_NO_ERROR && state == AL_PLAYING); /* All done. Delete resources, and close down OpenAL. */ alDeleteSources(1, &source); alDeleteBuffers(1, &buffer); CloseAL(); return 0; } openal-soft-1.24.2/examples/allafplay.cpp000066400000000000000000001121061474041540300203210ustar00rootroot00000000000000/* * OpenAL LAF Playback Example * * Copyright (c) 2024 by Chris Robinson * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ /* This file contains an example for playback of Limitless Audio Format files. * * Some current shortcomings: * * - 256 track limit. Could be made higher, but making it too flexible would * necessitate more micro-allocations. * * - "Objects" mode only supports sample rates that are a multiple of 48. Since * positions are specified as samples in extra channels/tracks, and 3*16 * samples are needed per track to specify the full set of positions, and * each chunk is exactly one second long, other sample rates would result in * the positions being split across chunks, causing the source playback * offset to go out of sync with the offset used to look up the current * spatial positions. Fixing this will require slightly more work to update * and synchronize the spatial position arrays against the playback offset. * * - Updates are specified as fast as the app can detect and react to the * reported source offset (that in turn depends on how often OpenAL renders). * This can cause some positions to be a touch late and lose some granular * temporal movement. In practice, this should probably be good enough for * most use-cases. Fixing this would need either a new extension to queue * position changes to apply when needed, or use a separate loopback device * to render with and control the number of samples rendered between updates * (with a second device to do the actual playback). * * - The LAF documentation doesn't prohibit object position tracks from being * separated with audio tracks in between, or from being the first tracks * followed by the audio tracks. It's not known if this is intended to be * allowed, but it's not supported. Object position tracks must be last. * * Some remaining issues: * * - There are bursts of static on some channels. This doesn't appear to be a * parsing error since the bursts last less than the chunk size, and it never * loses sync with the remaining chunks. Might be an encoding error with the * files tested. * * - Positions are specified in left-handed coordinates, despite the LAF * documentation saying it's right-handed. Might be an encoding error with * the files tested, or might be a misunderstanding about which is which. How * to proceed may depend on how wide-spread this issue ends up being, but for * now, they're treated as left-handed here. * * - The LAF documentation doesn't specify the range or direction for the * channels' X and Y axis rotation in Channels mode. Presumably X rotation * (elevation) goes from -pi/2...+pi/2 and Y rotation (azimuth) goes from * either -pi...+pi or 0...pi*2, but the direction of movement isn't * specified. Currently positive azimuth moves from center rightward and * positive elevation moves from head-level upward. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "AL/alc.h" #include "AL/al.h" #include "AL/alext.h" #include "albit.h" #include "almalloc.h" #include "alnumeric.h" #include "alspan.h" #include "alstring.h" #include "common/alhelpers.h" #include "filesystem.h" #include "fmt/core.h" #include "fmt/std.h" #include "win_main_utf8.h" namespace { /* Filter object functions */ auto alGenFilters = LPALGENFILTERS{}; auto alDeleteFilters = LPALDELETEFILTERS{}; auto alIsFilter = LPALISFILTER{}; auto alFilteri = LPALFILTERI{}; auto alFilteriv = LPALFILTERIV{}; auto alFilterf = LPALFILTERF{}; auto alFilterfv = LPALFILTERFV{}; auto alGetFilteri = LPALGETFILTERI{}; auto alGetFilteriv = LPALGETFILTERIV{}; auto alGetFilterf = LPALGETFILTERF{}; auto alGetFilterfv = LPALGETFILTERFV{}; /* Effect object functions */ auto alGenEffects = LPALGENEFFECTS{}; auto alDeleteEffects = LPALDELETEEFFECTS{}; auto alIsEffect = LPALISEFFECT{}; auto alEffecti = LPALEFFECTI{}; auto alEffectiv = LPALEFFECTIV{}; auto alEffectf = LPALEFFECTF{}; auto alEffectfv = LPALEFFECTFV{}; auto alGetEffecti = LPALGETEFFECTI{}; auto alGetEffectiv = LPALGETEFFECTIV{}; auto alGetEffectf = LPALGETEFFECTF{}; auto alGetEffectfv = LPALGETEFFECTFV{}; /* Auxiliary Effect Slot object functions */ auto alGenAuxiliaryEffectSlots = LPALGENAUXILIARYEFFECTSLOTS{}; auto alDeleteAuxiliaryEffectSlots = LPALDELETEAUXILIARYEFFECTSLOTS{}; auto alIsAuxiliaryEffectSlot = LPALISAUXILIARYEFFECTSLOT{}; auto alAuxiliaryEffectSloti = LPALAUXILIARYEFFECTSLOTI{}; auto alAuxiliaryEffectSlotiv = LPALAUXILIARYEFFECTSLOTIV{}; auto alAuxiliaryEffectSlotf = LPALAUXILIARYEFFECTSLOTF{}; auto alAuxiliaryEffectSlotfv = LPALAUXILIARYEFFECTSLOTFV{}; auto alGetAuxiliaryEffectSloti = LPALGETAUXILIARYEFFECTSLOTI{}; auto alGetAuxiliaryEffectSlotiv = LPALGETAUXILIARYEFFECTSLOTIV{}; auto alGetAuxiliaryEffectSlotf = LPALGETAUXILIARYEFFECTSLOTF{}; auto alGetAuxiliaryEffectSlotfv = LPALGETAUXILIARYEFFECTSLOTFV{}; auto MuteFilterID = ALuint{}; auto LowFrequencyEffectID = ALuint{}; auto LfeSlotID = ALuint{}; using namespace std::string_view_literals; [[noreturn]] void do_assert(const char *message, int linenum, const char *filename, const char *funcname) { auto errstr = fmt::format("{}:{}: {}: {}", filename, linenum, funcname, message); throw std::runtime_error{errstr}; } #define MyAssert(cond) do { \ if(!(cond)) UNLIKELY \ do_assert("Assertion '" #cond "' failed", __LINE__, __FILE__, \ std::data(__func__)); \ } while(0) enum class Quality : std::uint8_t { s8, s16, f32, s24 }; enum class Mode : bool { Channels, Objects }; auto GetQualityName(Quality quality) noexcept -> std::string_view { switch(quality) { case Quality::s8: return "8-bit int"sv; case Quality::s16: return "16-bit int"sv; case Quality::f32: return "32-bit float"sv; case Quality::s24: return "24-bit int"sv; } return ""sv; } auto GetModeName(Mode mode) noexcept -> std::string_view { switch(mode) { case Mode::Channels: return "channels"sv; case Mode::Objects: return "objects"sv; } return ""sv; } auto BytesFromQuality(Quality quality) noexcept -> size_t { switch(quality) { case Quality::s8: return 1; case Quality::s16: return 2; case Quality::f32: return 4; case Quality::s24: return 3; } return 4; } auto BufferBytesFromQuality(Quality quality) noexcept -> size_t { switch(quality) { case Quality::s8: return 1; case Quality::s16: return 2; case Quality::f32: return 4; /* 24-bit samples are converted to 32-bit for OpenAL. */ case Quality::s24: return 4; } return 4; } /* Helper class for reading little-endian samples on big-endian targets, or * convert 24-bit samples. */ template struct SampleReader; template<> struct SampleReader { using src_t = int8_t; using dst_t = int8_t; [[nodiscard]] static auto read(const src_t &in) noexcept -> dst_t { return in; } }; template<> struct SampleReader { using src_t = int16_t; using dst_t = int16_t; [[nodiscard]] static auto read(const src_t &in) noexcept -> dst_t { if constexpr(al::endian::native == al::endian::little) return in; else return al::byteswap(in); } }; template<> struct SampleReader { /* 32-bit float samples are read as 32-bit integer on big-endian systems, * so that they can be byteswapped before being reinterpreted as float. */ using src_t = std::conditional_t; using dst_t = float; [[nodiscard]] static auto read(const src_t &in) noexcept -> dst_t { if constexpr(al::endian::native == al::endian::little) return in; else return al::bit_cast(al::byteswap(static_cast(in))); } }; template<> struct SampleReader { /* 24-bit samples are converted to 32-bit integer. */ using src_t = std::array; using dst_t = int32_t; [[nodiscard]] static auto read(const src_t &in) noexcept -> dst_t { return static_cast((uint32_t{in[0]}<<8) | (uint32_t{in[1]}<<16) | (uint32_t{in[2]}<<24)); } }; /* Each track with position data consists of a set of 3 samples per 16 audio * channels, resulting in a full set of positions being specified over 48 * sample frames. */ constexpr auto FramesPerPos = 48_uz; struct Channel { ALuint mSource{}; std::array mBuffers{}; float mAzimuth{}; float mElevation{}; bool mIsLfe{}; Channel() = default; Channel(const Channel&) = delete; Channel(Channel&& rhs) : mSource{rhs.mSource}, mBuffers{rhs.mBuffers}, mAzimuth{rhs.mAzimuth} , mElevation{rhs.mElevation}, mIsLfe{rhs.mIsLfe} { rhs.mSource = 0; rhs.mBuffers.fill(0); } ~Channel() { if(mSource) alDeleteSources(1, &mSource); if(mBuffers[0]) alDeleteBuffers(ALsizei(mBuffers.size()), mBuffers.data()); } auto operator=(const Channel&) -> Channel& = delete; auto operator=(Channel&& rhs) -> Channel& { std::swap(mSource, rhs.mSource); std::swap(mBuffers, rhs.mBuffers); std::swap(mAzimuth, rhs.mAzimuth); std::swap(mElevation, rhs.mElevation); std::swap(mIsLfe, rhs.mIsLfe); return *this; } }; struct LafStream { std::filebuf mInFile; Quality mQuality{}; Mode mMode{}; uint32_t mNumTracks{}; uint32_t mSampleRate{}; ALenum mALFormat{}; uint64_t mSampleCount{}; uint64_t mCurrentSample{}; std::array mEnabledTracks{}; uint32_t mNumEnabled{}; std::vector mSampleChunk; al::span mSampleLine; std::vector mChannels; std::vector> mPosTracks; LafStream() = default; LafStream(const LafStream&) = delete; ~LafStream() = default; auto operator=(const LafStream&) -> LafStream& = delete; [[nodiscard]] auto readChunk() -> uint32_t; void convertSamples(const al::span samples) const; void convertPositions(const al::span dst, const al::span src) const; template void copySamples(char *dst, const char *src, size_t idx, size_t count) const; [[nodiscard]] auto prepareTrack(size_t trackidx, size_t count) -> al::span; [[nodiscard]] auto isAtEnd() const noexcept -> bool { return mCurrentSample >= mSampleCount; } }; auto LafStream::readChunk() -> uint32_t { mEnabledTracks.fill(0); mInFile.sgetn(reinterpret_cast(mEnabledTracks.data()), (mNumTracks+7_z)>>3); mNumEnabled = std::accumulate(mEnabledTracks.cbegin(), mEnabledTracks.cend(), 0u, [](const unsigned int val, const uint8_t in) { return val + unsigned(al::popcount(unsigned(in))); }); /* Make sure enable bits aren't set for non-existent tracks. */ if(mEnabledTracks[((mNumTracks+7_uz)>>3) - 1] >= (1u<<(mNumTracks&7))) throw std::runtime_error{"Invalid channel enable bits"}; /* Each chunk is exactly one second long, with samples interleaved for each * enabled track. The last chunk may be shorter if there isn't enough time * remaining for a full second. */ const auto numsamples = std::min(uint64_t{mSampleRate}, mSampleCount-mCurrentSample); const auto toread = std::streamsize(numsamples * BytesFromQuality(mQuality) * mNumEnabled); if(mInFile.sgetn(mSampleChunk.data(), toread) != toread) throw std::runtime_error{"Failed to read sample chunk"}; std::fill(mSampleChunk.begin()+toread, mSampleChunk.end(), char{}); mCurrentSample += numsamples; return static_cast(numsamples); } void LafStream::convertSamples(const al::span samples) const { /* OpenAL uses unsigned 8-bit samples (0...255), so signed 8-bit samples * (-128...+127) need conversion. The other formats are fine. */ if(mQuality == Quality::s8) std::transform(samples.begin(), samples.end(), samples.begin(), [](const char sample) noexcept { return char(sample^0x80); }); } void LafStream::convertPositions(const al::span dst, const al::span src) const { switch(mQuality) { case Quality::s8: std::transform(src.begin(), src.end(), dst.begin(), [](const int8_t in) { return float(in) / 127.0f; }); break; case Quality::s16: { auto i16src = al::span{reinterpret_cast(src.data()), src.size()/sizeof(int16_t)}; std::transform(i16src.begin(), i16src.end(), dst.begin(), [](const int16_t in) { return float(in) / 32767.0f; }); } break; case Quality::f32: { auto f32src = al::span{reinterpret_cast(src.data()), src.size()/sizeof(float)}; std::copy(f32src.begin(), f32src.end(), dst.begin()); } break; case Quality::s24: { /* 24-bit samples are converted to 32-bit in copySamples. */ auto i32src = al::span{reinterpret_cast(src.data()), src.size()/sizeof(int32_t)}; std::transform(i32src.begin(), i32src.end(), dst.begin(), [](const int32_t in) { return float(in>>8) / 8388607.0f; }); } break; } } template void LafStream::copySamples(char *dst, const char *src, const size_t idx, const size_t count) const { using reader_t = SampleReader; using src_t = typename reader_t::src_t; using dst_t = typename reader_t::dst_t; const auto step = size_t{mNumEnabled}; assert(idx < step); auto input = al::span{reinterpret_cast(src), count*step}; auto output = al::span{reinterpret_cast(dst), count}; auto inptr = input.begin(); std::generate_n(output.begin(), output.size(), [&inptr,idx,step] { auto ret = reader_t::read(inptr[idx]); inptr += ptrdiff_t(step); return ret; }); } auto LafStream::prepareTrack(const size_t trackidx, const size_t count) -> al::span { const auto todo = std::min(size_t{mSampleRate}, count); if((mEnabledTracks[trackidx>>3] & (1_uz<<(trackidx&7)))) { /* If the track is enabled, get the real index (skipping disabled * tracks), and deinterlace it into the mono line. */ const auto idx = [this,trackidx]() -> unsigned int { const auto bits = al::span{mEnabledTracks}.first(trackidx>>3); const auto res = std::accumulate(bits.begin(), bits.end(), 0u, [](const unsigned int val, const uint8_t in) { return val + unsigned(al::popcount(unsigned(in))); }); return unsigned(al::popcount(mEnabledTracks[trackidx>>3] & ((1u<<(trackidx&7))-1))) + res; }(); switch(mQuality) { case Quality::s8: copySamples(mSampleLine.data(), mSampleChunk.data(), idx, todo); break; case Quality::s16: copySamples(mSampleLine.data(), mSampleChunk.data(), idx, todo); break; case Quality::f32: copySamples(mSampleLine.data(), mSampleChunk.data(), idx, todo); break; case Quality::s24: copySamples(mSampleLine.data(), mSampleChunk.data(), idx, todo); break; } } else { /* If the track is disabled, provide silence. */ std::fill_n(mSampleLine.begin(), mSampleLine.size(), char{}); } return mSampleLine.first(todo * BufferBytesFromQuality(mQuality)); } auto LoadLAF(const fs::path &fname) -> std::unique_ptr { auto laf = std::make_unique(); if(!laf->mInFile.open(fname, std::ios_base::binary | std::ios_base::in)) throw std::runtime_error{"Could not open file"}; auto marker = std::array{}; if(laf->mInFile.sgetn(marker.data(), marker.size()) != marker.size()) throw std::runtime_error{"Failed to read file marker"}; if(std::string_view{marker.data(), marker.size()} != "LIMITLESS"sv) throw std::runtime_error{"Not an LAF file"}; auto header = std::array{}; if(laf->mInFile.sgetn(header.data(), header.size()) != header.size()) throw std::runtime_error{"Failed to read header"}; while(std::string_view{header.data(), 4} != "HEAD"sv) { auto headview = std::string_view{header.data(), header.size()}; auto hiter = header.begin(); if(const auto hpos = std::min(headview.find("HEAD"sv), headview.size()); hpos < headview.size()) { /* Found the HEAD marker. Copy what was read of the header to the * front, fill in the rest of the header, and continue loading. */ hiter = std::copy(header.begin()+hpos, header.end(), hiter); } else if(al::ends_with(headview, "HEA"sv)) { /* Found what might be the HEAD marker at the end. Copy it to the * front, refill the header, and check again. */ hiter = std::copy_n(header.end()-3, 3, hiter); } else if(al::ends_with(headview, "HE"sv)) hiter = std::copy_n(header.end()-2, 2, hiter); else if(headview.back() == 'H') hiter = std::copy_n(header.end()-1, 1, hiter); const auto toread = std::distance(hiter, header.end()); if(laf->mInFile.sgetn(al::to_address(hiter), toread) != toread) throw std::runtime_error{"Failed to read header"}; } laf->mQuality = [stype=int{header[4]}] { if(stype == 0) return Quality::s8; if(stype == 1) return Quality::s16; if(stype == 2) return Quality::f32; if(stype == 3) return Quality::s24; throw std::runtime_error{fmt::format("Invalid quality type: {}", stype)}; }(); laf->mMode = [mode=int{header[5]}] { if(mode == 0) return Mode::Channels; if(mode == 1) return Mode::Objects; throw std::runtime_error{fmt::format("Invalid mode: {}", mode)}; }(); laf->mNumTracks = [input=al::span{header}.subspan<6,4>()] { return uint32_t{uint8_t(input[0])} | (uint32_t{uint8_t(input[1])}<<8u) | (uint32_t{uint8_t(input[2])}<<16u) | (uint32_t{uint8_t(input[3])}<<24u); }(); fmt::println("Filename: {}", fname.string()); fmt::println(" quality: {}", GetQualityName(laf->mQuality)); fmt::println(" mode: {}", GetModeName(laf->mMode)); fmt::println(" track count: {}", laf->mNumTracks); if(laf->mNumTracks == 0) throw std::runtime_error{"No tracks"}; if(laf->mNumTracks > 256) throw std::runtime_error{fmt::format("Too many tracks: {}", laf->mNumTracks)}; auto chandata = std::vector(laf->mNumTracks*9_uz); auto headersize = std::streamsize(chandata.size()); if(laf->mInFile.sgetn(chandata.data(), headersize) != headersize) throw std::runtime_error{"Failed to read channel header data"}; if(laf->mMode == Mode::Channels) laf->mChannels.reserve(laf->mNumTracks); else { if(laf->mNumTracks < 2) throw std::runtime_error{"Not enough tracks"}; auto numchans = uint32_t{laf->mNumTracks - 1}; auto numpostracks = uint32_t{1}; while(numpostracks*16 < numchans) { --numchans; ++numpostracks; } laf->mChannels.reserve(numchans); laf->mPosTracks.reserve(numpostracks); } for(uint32_t i{0};i < laf->mNumTracks;++i) { static constexpr auto read_float = [](al::span input) { const auto value = uint32_t{uint8_t(input[0])} | (uint32_t{uint8_t(input[1])}<<8u) | (uint32_t{uint8_t(input[2])}<<16u) | (uint32_t{uint8_t(input[3])}<<24u); return al::bit_cast(value); }; auto chan = al::span{chandata}.subspan(i*9_uz, 9); auto x_axis = read_float(chan.first<4>()); auto y_axis = read_float(chan.subspan<4,4>()); auto lfe_flag = int{chan[8]}; fmt::println("Track {}: E={:f}, A={:f} (LFE: {})", i, x_axis, y_axis, lfe_flag); if(x_axis != x_axis && y_axis == 0.0) { MyAssert(laf->mMode == Mode::Objects); MyAssert(i != 0); laf->mPosTracks.emplace_back(); } else { MyAssert(laf->mPosTracks.empty()); MyAssert(std::isfinite(x_axis) && std::isfinite(y_axis)); auto &channel = laf->mChannels.emplace_back(); channel.mAzimuth = y_axis; channel.mElevation = x_axis; channel.mIsLfe = lfe_flag != 0; } } fmt::println("Channels: {}", laf->mChannels.size()); /* For "objects" mode, ensure there's enough tracks with position data to * handle the audio channels. */ if(laf->mMode == Mode::Objects) MyAssert(((laf->mChannels.size()-1)>>4) == laf->mPosTracks.size()-1); auto footer = std::array{}; if(laf->mInFile.sgetn(footer.data(), footer.size()) != footer.size()) throw std::runtime_error{"Failed to read sample header data"}; laf->mSampleRate = [input=al::span{footer}.first<4>()] { return uint32_t{uint8_t(input[0])} | (uint32_t{uint8_t(input[1])}<<8u) | (uint32_t{uint8_t(input[2])}<<16u) | (uint32_t{uint8_t(input[3])}<<24u); }(); laf->mSampleCount = [input=al::span{footer}.last<8>()] { return uint64_t{uint8_t(input[0])} | (uint64_t{uint8_t(input[1])}<<8) | (uint64_t{uint8_t(input[2])}<<16u) | (uint64_t{uint8_t(input[3])}<<24u) | (uint64_t{uint8_t(input[4])}<<32u) | (uint64_t{uint8_t(input[5])}<<40u) | (uint64_t{uint8_t(input[6])}<<48u) | (uint64_t{uint8_t(input[7])}<<56u); }(); fmt::println("Sample rate: {}", laf->mSampleRate); fmt::println("Length: {} samples ({:.2f} sec)", laf->mSampleCount, static_cast(laf->mSampleCount)/static_cast(laf->mSampleRate)); /* Position vectors get split across the PCM chunks if the sample rate * isn't a multiple of 48. Each PCM chunk is exactly one second (the sample * rate in sample frames). Each track with position data consists of a set * of 3 samples for 16 audio channels, resuling in 48 sample frames for a * full set of positions. Extra logic will be needed to manage the position * frame offset separate from each chunk. */ MyAssert(laf->mMode == Mode::Channels || (laf->mSampleRate%FramesPerPos) == 0); for(size_t i{0};i < laf->mPosTracks.size();++i) laf->mPosTracks[i].resize(laf->mSampleRate*2_uz, 0.0f); laf->mSampleChunk.resize(laf->mSampleRate*BytesFromQuality(laf->mQuality)*laf->mNumTracks + laf->mSampleRate*BufferBytesFromQuality(laf->mQuality)); laf->mSampleLine = al::span{laf->mSampleChunk}.last(laf->mSampleRate * BufferBytesFromQuality(laf->mQuality)); return laf; } void PlayLAF(std::string_view fname) try { auto laf = LoadLAF(fs::u8path(fname)); switch(laf->mQuality) { case Quality::s8: laf->mALFormat = AL_FORMAT_MONO8; break; case Quality::s16: laf->mALFormat = AL_FORMAT_MONO16; break; case Quality::f32: if(alIsExtensionPresent("AL_EXT_FLOAT32")) laf->mALFormat = AL_FORMAT_MONO_FLOAT32; break; case Quality::s24: laf->mALFormat = alGetEnumValue("AL_FORMAT_MONO32"); if(!laf->mALFormat || laf->mALFormat == -1) laf->mALFormat = alGetEnumValue("AL_FORMAT_MONO_I32"); break; } if(!laf->mALFormat || laf->mALFormat == -1) throw std::runtime_error{fmt::format("No supported format for {} samples", GetQualityName(laf->mQuality))}; static constexpr auto alloc_channel = [](Channel &channel) { alGenSources(1, &channel.mSource); alGenBuffers(ALsizei(channel.mBuffers.size()), channel.mBuffers.data()); /* Disable distance attenuation, and make sure the source stays locked * relative to the listener. */ alSourcef(channel.mSource, AL_ROLLOFF_FACTOR, 0.0f); alSourcei(channel.mSource, AL_SOURCE_RELATIVE, AL_TRUE); /* FIXME: Is the Y rotation/azimuth clockwise or counter-clockwise? * Does +azimuth move a front sound right or left? */ const auto x = std::sin(channel.mAzimuth) * std::cos(channel.mElevation); const auto y = std::sin(channel.mElevation); const auto z = -std::cos(channel.mAzimuth) * std::cos(channel.mElevation); alSource3f(channel.mSource, AL_POSITION, x, y, z); if(channel.mIsLfe) { if(LfeSlotID) { /* For LFE, silence the direct/dry path and connect the LFE aux * slot on send 0. */ alSourcei(channel.mSource, AL_DIRECT_FILTER, ALint(MuteFilterID)); alSource3i(channel.mSource, AL_AUXILIARY_SEND_FILTER, ALint(LfeSlotID), 0, AL_FILTER_NULL); } else { /* If AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT isn't available, * silence LFE channels since they may not be appropriate to * play normally. */ alSourcef(channel.mSource, AL_GAIN, 0.0f); } } if(auto err=alGetError()) throw std::runtime_error{fmt::format("OpenAL error: {}", alGetString(err))}; }; std::for_each(laf->mChannels.begin(), laf->mChannels.end(), alloc_channel); while(!laf->isAtEnd()) { auto state = ALenum{}; auto offset = ALint{}; auto processed = ALint{}; /* All sources are played in sync, so they'll all be at the same offset * with the same state and number of processed buffers. Query the back * source just in case the previous update ran really late and missed * updating only some sources on time (in which case, the latter ones * will underrun, which this will detect and restart them all as * needed). */ alGetSourcei(laf->mChannels.back().mSource, AL_BUFFERS_PROCESSED, &processed); alGetSourcei(laf->mChannels.back().mSource, AL_SAMPLE_OFFSET, &offset); alGetSourcei(laf->mChannels.back().mSource, AL_SOURCE_STATE, &state); if(state == AL_PLAYING || state == AL_PAUSED) { if(!laf->mPosTracks.empty()) { alcSuspendContext(alcGetCurrentContext()); for(size_t i{0};i < laf->mChannels.size();++i) { const auto trackidx = i>>4; const auto posoffset = unsigned(offset)/FramesPerPos*16_uz + (i&15); const auto x = laf->mPosTracks[trackidx][posoffset*3 + 0]; const auto y = laf->mPosTracks[trackidx][posoffset*3 + 1]; const auto z = laf->mPosTracks[trackidx][posoffset*3 + 2]; /* Contrary to the docs, the position is left-handed and * needs to be converted to right-handed. */ alSource3f(laf->mChannels[i].mSource, AL_POSITION, x, y, -z); } alcProcessContext(alcGetCurrentContext()); } if(processed > 0) { const auto numsamples = laf->readChunk(); for(size_t i{0};i < laf->mChannels.size();++i) { const auto samples = laf->prepareTrack(i, numsamples); laf->convertSamples(samples); auto bufid = ALuint{}; alSourceUnqueueBuffers(laf->mChannels[i].mSource, 1, &bufid); alBufferData(bufid, laf->mALFormat, samples.data(), ALsizei(samples.size()), ALsizei(laf->mSampleRate)); alSourceQueueBuffers(laf->mChannels[i].mSource, 1, &bufid); } for(size_t i{0};i < laf->mPosTracks.size();++i) { std::copy(laf->mPosTracks[i].begin() + ptrdiff_t(laf->mSampleRate), laf->mPosTracks[i].end(), laf->mPosTracks[i].begin()); const auto positions = laf->prepareTrack(laf->mChannels.size()+i, numsamples); laf->convertPositions(al::span{laf->mPosTracks[i]}.last(laf->mSampleRate), positions); } } else std::this_thread::sleep_for(std::chrono::milliseconds{10}); } else if(state == AL_STOPPED) { auto sources = std::array{}; for(size_t i{0};i < laf->mChannels.size();++i) sources[i] = laf->mChannels[i].mSource; alSourcePlayv(ALsizei(laf->mChannels.size()), sources.data()); } else if(state == AL_INITIAL) { auto sources = std::array{}; auto numsamples = laf->readChunk(); for(size_t i{0};i < laf->mChannels.size();++i) { const auto samples = laf->prepareTrack(i, numsamples); laf->convertSamples(samples); alBufferData(laf->mChannels[i].mBuffers[0], laf->mALFormat, samples.data(), ALsizei(samples.size()), ALsizei(laf->mSampleRate)); } for(size_t i{0};i < laf->mPosTracks.size();++i) { const auto positions = laf->prepareTrack(laf->mChannels.size()+i, numsamples); laf->convertPositions(al::span{laf->mPosTracks[i]}.first(laf->mSampleRate), positions); } numsamples = laf->readChunk(); for(size_t i{0};i < laf->mChannels.size();++i) { const auto samples = laf->prepareTrack(i, numsamples); laf->convertSamples(samples); alBufferData(laf->mChannels[i].mBuffers[1], laf->mALFormat, samples.data(), ALsizei(samples.size()), ALsizei(laf->mSampleRate)); alSourceQueueBuffers(laf->mChannels[i].mSource, ALsizei(laf->mChannels[i].mBuffers.size()), laf->mChannels[i].mBuffers.data()); sources[i] = laf->mChannels[i].mSource; } for(size_t i{0};i < laf->mPosTracks.size();++i) { const auto positions = laf->prepareTrack(laf->mChannels.size()+i, numsamples); laf->convertPositions(al::span{laf->mPosTracks[i]}.last(laf->mSampleRate), positions); } if(!laf->mPosTracks.empty()) { for(size_t i{0};i < laf->mChannels.size();++i) { const auto trackidx = i>>4; const auto x = laf->mPosTracks[trackidx][(i&15)*3 + 0]; const auto y = laf->mPosTracks[trackidx][(i&15)*3 + 1]; const auto z = laf->mPosTracks[trackidx][(i&15)*3 + 2]; alSource3f(laf->mChannels[i].mSource, AL_POSITION, x, y, -z); } } alSourcePlayv(ALsizei(laf->mChannels.size()), sources.data()); } else break; } auto state = ALenum{}; auto offset = ALint{}; alGetSourcei(laf->mChannels.back().mSource, AL_SAMPLE_OFFSET, &offset); alGetSourcei(laf->mChannels.back().mSource, AL_SOURCE_STATE, &state); while(alGetError() == AL_NO_ERROR && state == AL_PLAYING) { if(!laf->mPosTracks.empty()) { alcSuspendContext(alcGetCurrentContext()); for(size_t i{0};i < laf->mChannels.size();++i) { const auto trackidx = i>>4; const auto posoffset = unsigned(offset)/FramesPerPos*16_uz + (i&15); const auto x = laf->mPosTracks[trackidx][posoffset*3 + 0]; const auto y = laf->mPosTracks[trackidx][posoffset*3 + 1]; const auto z = laf->mPosTracks[trackidx][posoffset*3 + 2]; alSource3f(laf->mChannels[i].mSource, AL_POSITION, x, y, -z); } alcProcessContext(alcGetCurrentContext()); } std::this_thread::sleep_for(std::chrono::milliseconds{10}); alGetSourcei(laf->mChannels.back().mSource, AL_SAMPLE_OFFSET, &offset); alGetSourcei(laf->mChannels.back().mSource, AL_SOURCE_STATE, &state); } } catch(std::exception& e) { fmt::println(stderr, "Error playing {}:\n {}", fname, e.what()); } auto main(al::span args) -> int { /* Print out usage if no arguments were specified */ if(args.size() < 2) { fmt::println(stderr, "Usage: {} [-device ] \n", args[0]); return 1; } args = args.subspan(1); if(InitAL(args) != 0) throw std::runtime_error{"Failed to initialize OpenAL"}; /* A simple RAII container for automating OpenAL shutdown. */ struct AudioManager { AudioManager() = default; AudioManager(const AudioManager&) = delete; auto operator=(const AudioManager&) -> AudioManager& = delete; ~AudioManager() { if(LfeSlotID) { alDeleteAuxiliaryEffectSlots(1, &LfeSlotID); alDeleteEffects(1, &LowFrequencyEffectID); alDeleteFilters(1, &MuteFilterID); } CloseAL(); } }; AudioManager almgr; if(auto *device = alcGetContextsDevice(alcGetCurrentContext()); alcIsExtensionPresent(device, "ALC_EXT_EFX") && alcIsExtensionPresent(device, "ALC_EXT_DEDICATED")) { #define LOAD_PROC(x) do { \ x = reinterpret_cast(alGetProcAddress(#x)); \ if(!x) fmt::println(stderr, "Failed to find function '{}'\n", #x##sv);\ } while(0) LOAD_PROC(alGenFilters); LOAD_PROC(alDeleteFilters); LOAD_PROC(alIsFilter); LOAD_PROC(alFilterf); LOAD_PROC(alFilterfv); LOAD_PROC(alFilteri); LOAD_PROC(alFilteriv); LOAD_PROC(alGetFilterf); LOAD_PROC(alGetFilterfv); LOAD_PROC(alGetFilteri); LOAD_PROC(alGetFilteriv); LOAD_PROC(alGenEffects); LOAD_PROC(alDeleteEffects); LOAD_PROC(alIsEffect); LOAD_PROC(alEffectf); LOAD_PROC(alEffectfv); LOAD_PROC(alEffecti); LOAD_PROC(alEffectiv); LOAD_PROC(alGetEffectf); LOAD_PROC(alGetEffectfv); LOAD_PROC(alGetEffecti); LOAD_PROC(alGetEffectiv); LOAD_PROC(alGenAuxiliaryEffectSlots); LOAD_PROC(alDeleteAuxiliaryEffectSlots); LOAD_PROC(alIsAuxiliaryEffectSlot); LOAD_PROC(alAuxiliaryEffectSlotf); LOAD_PROC(alAuxiliaryEffectSlotfv); LOAD_PROC(alAuxiliaryEffectSloti); LOAD_PROC(alAuxiliaryEffectSlotiv); LOAD_PROC(alGetAuxiliaryEffectSlotf); LOAD_PROC(alGetAuxiliaryEffectSlotfv); LOAD_PROC(alGetAuxiliaryEffectSloti); LOAD_PROC(alGetAuxiliaryEffectSlotiv); #undef LOAD_PROC alGenFilters(1, &MuteFilterID); alFilteri(MuteFilterID, AL_FILTER_TYPE, AL_FILTER_LOWPASS); alFilterf(MuteFilterID, AL_LOWPASS_GAIN, 0.0f); MyAssert(alGetError() == AL_NO_ERROR); alGenEffects(1, &LowFrequencyEffectID); alEffecti(LowFrequencyEffectID, AL_EFFECT_TYPE, AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT); MyAssert(alGetError() == AL_NO_ERROR); alGenAuxiliaryEffectSlots(1, &LfeSlotID); alAuxiliaryEffectSloti(LfeSlotID, AL_EFFECTSLOT_EFFECT, ALint(LowFrequencyEffectID)); MyAssert(alGetError() == AL_NO_ERROR); } std::for_each(args.begin(), args.end(), PlayLAF); return 0; } } // namespace int main(int argc, char **argv) { MyAssert(argc >= 0); auto args = std::vector(static_cast(argc)); std::copy_n(argv, args.size(), args.begin()); return main(al::span{args}); } openal-soft-1.24.2/examples/allatency.c000066400000000000000000000160271474041540300177750ustar00rootroot00000000000000/* * OpenAL Source Latency Example * * Copyright (c) 2012 by Chris Robinson * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ /* This file contains an example for checking the latency of a sound. */ #include #include #include #include #include #include "sndfile.h" #include "AL/al.h" #include "AL/alext.h" #include "common/alhelpers.h" #include "win_main_utf8.h" static LPALSOURCEDSOFT alSourcedSOFT; static LPALSOURCE3DSOFT alSource3dSOFT; static LPALSOURCEDVSOFT alSourcedvSOFT; static LPALGETSOURCEDSOFT alGetSourcedSOFT; static LPALGETSOURCE3DSOFT alGetSource3dSOFT; static LPALGETSOURCEDVSOFT alGetSourcedvSOFT; static LPALSOURCEI64SOFT alSourcei64SOFT; static LPALSOURCE3I64SOFT alSource3i64SOFT; static LPALSOURCEI64VSOFT alSourcei64vSOFT; static LPALGETSOURCEI64SOFT alGetSourcei64SOFT; static LPALGETSOURCE3I64SOFT alGetSource3i64SOFT; static LPALGETSOURCEI64VSOFT alGetSourcei64vSOFT; /* LoadBuffer loads the named audio file into an OpenAL buffer object, and * returns the new buffer ID. */ static ALuint LoadSound(const char *filename) { ALenum err, format; ALuint buffer; SNDFILE *sndfile; SF_INFO sfinfo; short *membuf; sf_count_t num_frames; ALsizei num_bytes; /* Open the audio file and check that it's usable. */ sndfile = sf_open(filename, SFM_READ, &sfinfo); if(!sndfile) { fprintf(stderr, "Could not open audio in %s: %s\n", filename, sf_strerror(sndfile)); return 0; } if(sfinfo.frames < 1 || sfinfo.frames > (sf_count_t)(INT_MAX/sizeof(short))/sfinfo.channels) { fprintf(stderr, "Bad sample count in %s (%" PRId64 ")\n", filename, sfinfo.frames); sf_close(sndfile); return 0; } /* Get the sound format, and figure out the OpenAL format */ format = AL_NONE; if(sfinfo.channels == 1) format = AL_FORMAT_MONO16; else if(sfinfo.channels == 2) format = AL_FORMAT_STEREO16; else if(sfinfo.channels == 3) { if(sf_command(sndfile, SFC_WAVEX_GET_AMBISONIC, NULL, 0) == SF_AMBISONIC_B_FORMAT) format = AL_FORMAT_BFORMAT2D_16; } else if(sfinfo.channels == 4) { if(sf_command(sndfile, SFC_WAVEX_GET_AMBISONIC, NULL, 0) == SF_AMBISONIC_B_FORMAT) format = AL_FORMAT_BFORMAT3D_16; } if(!format) { fprintf(stderr, "Unsupported channel count: %d\n", sfinfo.channels); sf_close(sndfile); return 0; } /* Decode the whole audio file to a buffer. */ membuf = malloc((size_t)(sfinfo.frames * sfinfo.channels) * sizeof(short)); num_frames = sf_readf_short(sndfile, membuf, sfinfo.frames); if(num_frames < 1) { free(membuf); sf_close(sndfile); fprintf(stderr, "Failed to read samples in %s (%" PRId64 ")\n", filename, num_frames); return 0; } num_bytes = (ALsizei)(num_frames * sfinfo.channels) * (ALsizei)sizeof(short); /* Buffer the audio data into a new buffer object, then free the data and * close the file. */ buffer = 0; alGenBuffers(1, &buffer); alBufferData(buffer, format, membuf, num_bytes, sfinfo.samplerate); free(membuf); sf_close(sndfile); /* Check if an error occurred, and clean up if so. */ err = alGetError(); if(err != AL_NO_ERROR) { fprintf(stderr, "OpenAL Error: %s\n", alGetString(err)); if(buffer && alIsBuffer(buffer)) alDeleteBuffers(1, &buffer); return 0; } return buffer; } int main(int argc, char **argv) { ALuint source, buffer; ALdouble offsets[2]; ALenum state; /* Print out usage if no arguments were specified */ if(argc < 2) { fprintf(stderr, "Usage: %s [-device ] \n", argv[0]); return 1; } /* Initialize OpenAL, and check for source_latency support. */ argv++; argc--; if(InitAL(&argv, &argc) != 0) return 1; if(!alIsExtensionPresent("AL_SOFT_source_latency")) { fprintf(stderr, "Error: AL_SOFT_source_latency not supported\n"); CloseAL(); return 1; } /* Define a macro to help load the function pointers. */ #define LOAD_PROC(T, x) ((x) = FUNCTION_CAST(T, alGetProcAddress(#x))) LOAD_PROC(LPALSOURCEDSOFT, alSourcedSOFT); LOAD_PROC(LPALSOURCE3DSOFT, alSource3dSOFT); LOAD_PROC(LPALSOURCEDVSOFT, alSourcedvSOFT); LOAD_PROC(LPALGETSOURCEDSOFT, alGetSourcedSOFT); LOAD_PROC(LPALGETSOURCE3DSOFT, alGetSource3dSOFT); LOAD_PROC(LPALGETSOURCEDVSOFT, alGetSourcedvSOFT); LOAD_PROC(LPALSOURCEI64SOFT, alSourcei64SOFT); LOAD_PROC(LPALSOURCE3I64SOFT, alSource3i64SOFT); LOAD_PROC(LPALSOURCEI64VSOFT, alSourcei64vSOFT); LOAD_PROC(LPALGETSOURCEI64SOFT, alGetSourcei64SOFT); LOAD_PROC(LPALGETSOURCE3I64SOFT, alGetSource3i64SOFT); LOAD_PROC(LPALGETSOURCEI64VSOFT, alGetSourcei64vSOFT); #undef LOAD_PROC /* Load the sound into a buffer. */ buffer = LoadSound(argv[0]); if(!buffer) { CloseAL(); return 1; } /* Create the source to play the sound with. */ source = 0; alGenSources(1, &source); alSourcei(source, AL_BUFFER, (ALint)buffer); assert(alGetError()==AL_NO_ERROR && "Failed to setup sound source"); /* Play the sound until it finishes. */ alSourcePlay(source); do { al_nssleep(10000000); alGetSourcei(source, AL_SOURCE_STATE, &state); /* Get the source offset and latency. AL_SEC_OFFSET_LATENCY_SOFT will * place the offset (in seconds) in offsets[0], and the time until that * offset will be heard (in seconds) in offsets[1]. */ alGetSourcedvSOFT(source, AL_SEC_OFFSET_LATENCY_SOFT, offsets); printf("\rOffset: %f - Latency:%3u ms ", offsets[0], (ALuint)(offsets[1]*1000)); fflush(stdout); } while(alGetError() == AL_NO_ERROR && state == AL_PLAYING); printf("\n"); /* All done. Delete resources, and close down OpenAL. */ alDeleteSources(1, &source); alDeleteBuffers(1, &buffer); CloseAL(); return 0; } openal-soft-1.24.2/examples/alloopback.c000066400000000000000000000225101474041540300201220ustar00rootroot00000000000000/* * OpenAL Loopback Example * * Copyright (c) 2013 by Chris Robinson * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ /* This file contains an example for using the loopback device for custom * output handling. */ #include #include #include #include #include #define SDL_MAIN_HANDLED #include "SDL3/SDL_audio.h" #include "SDL3/SDL_error.h" #include "SDL3/SDL_main.h" #include "SDL3/SDL_stdinc.h" #include "AL/al.h" #include "AL/alc.h" #include "AL/alext.h" #include "common/alhelpers.h" #ifndef M_PI #define M_PI (3.14159265358979323846) #endif typedef struct { ALCdevice *Device; ALCcontext *Context; ALCsizei FrameSize; void *Buffer; int BufferSize; } PlaybackInfo; static LPALCLOOPBACKOPENDEVICESOFT alcLoopbackOpenDeviceSOFT; static LPALCISRENDERFORMATSUPPORTEDSOFT alcIsRenderFormatSupportedSOFT; static LPALCRENDERSAMPLESSOFT alcRenderSamplesSOFT; void SDLCALL RenderSDLSamples(void *userdata, SDL_AudioStream *stream, int additional_amount, int total_amount) { PlaybackInfo *playback = (PlaybackInfo*)userdata; if(additional_amount < 0) additional_amount = total_amount; if(additional_amount <= 0) return; if(additional_amount > playback->BufferSize) { free(playback->Buffer); playback->Buffer = malloc((unsigned int)additional_amount); playback->BufferSize = additional_amount; } alcRenderSamplesSOFT(playback->Device, playback->Buffer, additional_amount/playback->FrameSize); SDL_PutAudioStreamData(stream, playback->Buffer, additional_amount); } static const char *ChannelsName(ALCenum chans) { switch(chans) { case ALC_MONO_SOFT: return "Mono"; case ALC_STEREO_SOFT: return "Stereo"; case ALC_QUAD_SOFT: return "Quadraphonic"; case ALC_5POINT1_SOFT: return "5.1 Surround"; case ALC_6POINT1_SOFT: return "6.1 Surround"; case ALC_7POINT1_SOFT: return "7.1 Surround"; } return "Unknown Channels"; } static const char *TypeName(ALCenum type) { switch(type) { case ALC_BYTE_SOFT: return "S8"; case ALC_UNSIGNED_BYTE_SOFT: return "U8"; case ALC_SHORT_SOFT: return "S16"; case ALC_UNSIGNED_SHORT_SOFT: return "U16"; case ALC_INT_SOFT: return "S32"; case ALC_UNSIGNED_INT_SOFT: return "U32"; case ALC_FLOAT_SOFT: return "Float32"; } return "Unknown Type"; } /* Creates a one second buffer containing a sine wave, and returns the new * buffer ID. */ static ALuint CreateSineWave(void) { ALshort data[44100*4]; ALuint buffer; ALenum err; ALuint i; for(i = 0;i < 44100*4;i++) data[i] = (ALshort)(sin(i/44100.0 * 1000.0 * 2.0*M_PI) * 32767.0); /* Buffer the audio data into a new buffer object. */ buffer = 0; alGenBuffers(1, &buffer); alBufferData(buffer, AL_FORMAT_MONO16, data, sizeof(data), 44100); /* Check if an error occurred, and clean up if so. */ err = alGetError(); if(err != AL_NO_ERROR) { fprintf(stderr, "OpenAL Error: %s\n", alGetString(err)); if(alIsBuffer(buffer)) alDeleteBuffers(1, &buffer); return 0; } return buffer; } int main(int argc, char *argv[]) { PlaybackInfo playback = { NULL, NULL, 0, NULL, 0 }; SDL_AudioStream *stream = NULL; SDL_AudioSpec obtained; ALuint source, buffer; ALCint attrs[16]; ALenum state; (void)argc; (void)argv; SDL_SetMainReady(); /* Print out error if extension is missing. */ if(!alcIsExtensionPresent(NULL, "ALC_SOFT_loopback")) { fprintf(stderr, "Error: ALC_SOFT_loopback not supported!\n"); return 1; } /* Define a macro to help load the function pointers. */ #define LOAD_PROC(T, x) ((x) = FUNCTION_CAST(T, alcGetProcAddress(NULL, #x))) LOAD_PROC(LPALCLOOPBACKOPENDEVICESOFT, alcLoopbackOpenDeviceSOFT); LOAD_PROC(LPALCISRENDERFORMATSUPPORTEDSOFT, alcIsRenderFormatSupportedSOFT); LOAD_PROC(LPALCRENDERSAMPLESSOFT, alcRenderSamplesSOFT); #undef LOAD_PROC if(!SDL_Init(SDL_INIT_AUDIO)) { fprintf(stderr, "Failed to init SDL audio: %s\n", SDL_GetError()); return 1; } /* Set up SDL audio with our callback, and get the stream format. */ stream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, NULL, RenderSDLSamples, &playback); if(!stream) { fprintf(stderr, "Failed to open SDL audio: %s\n", SDL_GetError()); goto error; } if(!SDL_GetAudioStreamFormat(stream, &obtained, NULL)) { fprintf(stderr, "Failed to query SDL audio format: %s\n", SDL_GetError()); goto error; } /* Set up our OpenAL attributes based on what we got from SDL. */ attrs[0] = ALC_FORMAT_CHANNELS_SOFT; if(obtained.channels == 1) attrs[1] = ALC_MONO_SOFT; else if(obtained.channels == 2) attrs[1] = ALC_STEREO_SOFT; else if(obtained.channels == 4) attrs[1] = ALC_QUAD_SOFT; else if(obtained.channels == 6) attrs[1] = ALC_5POINT1_SOFT; else if(obtained.channels == 7) attrs[1] = ALC_6POINT1_SOFT; else if(obtained.channels == 8) attrs[1] = ALC_7POINT1_SOFT; else { fprintf(stderr, "Unhandled SDL channel count: %d\n", obtained.channels); goto error; } attrs[2] = ALC_FORMAT_TYPE_SOFT; if(obtained.format == SDL_AUDIO_U8) attrs[3] = ALC_UNSIGNED_BYTE_SOFT; else if(obtained.format == SDL_AUDIO_S8) attrs[3] = ALC_BYTE_SOFT; else if(obtained.format == SDL_AUDIO_S16) attrs[3] = ALC_SHORT_SOFT; else if(obtained.format == SDL_AUDIO_S32) attrs[3] = ALC_INT_SOFT; else if(obtained.format == SDL_AUDIO_F32) attrs[3] = ALC_FLOAT_SOFT; else { fprintf(stderr, "Unhandled SDL format: 0x%04x\n", obtained.format); goto error; } attrs[4] = ALC_FREQUENCY; attrs[5] = obtained.freq; attrs[6] = 0; /* end of list */ playback.FrameSize = obtained.channels * (int)SDL_AUDIO_BITSIZE(obtained.format) / 8; /* Initialize OpenAL loopback device, using our format attributes. */ playback.Device = alcLoopbackOpenDeviceSOFT(NULL); if(!playback.Device) { fprintf(stderr, "Failed to open loopback device!\n"); goto error; } /* Make sure the format is supported before setting them on the device. */ if(alcIsRenderFormatSupportedSOFT(playback.Device, attrs[5], attrs[1], attrs[3]) == ALC_FALSE) { fprintf(stderr, "Render format not supported: %s, %s, %dhz\n", ChannelsName(attrs[1]), TypeName(attrs[3]), attrs[5]); goto error; } playback.Context = alcCreateContext(playback.Device, attrs); if(!playback.Context || alcMakeContextCurrent(playback.Context) == ALC_FALSE) { fprintf(stderr, "Failed to set an OpenAL audio context\n"); goto error; } printf("Got render format from SDL stream: %s, %s, %dhz\n", ChannelsName(attrs[1]), TypeName(attrs[3]), attrs[5]); /* Start SDL playing. Our callback (thus alcRenderSamplesSOFT) will now * start being called regularly to update the AL playback state. */ SDL_ResumeAudioStreamDevice(stream); /* Load the sound into a buffer. */ buffer = CreateSineWave(); if(!buffer) goto error; /* Create the source to play the sound with. */ source = 0; alGenSources(1, &source); alSourcei(source, AL_BUFFER, (ALint)buffer); assert(alGetError()==AL_NO_ERROR && "Failed to setup sound source"); /* Play the sound until it finishes. */ alSourcePlay(source); do { al_nssleep(10000000); alGetSourcei(source, AL_SOURCE_STATE, &state); } while(alGetError() == AL_NO_ERROR && state == AL_PLAYING); /* All done. Delete resources, and close OpenAL. */ alDeleteSources(1, &source); alDeleteBuffers(1, &buffer); /* Stop SDL playing. */ SDL_PauseAudioStreamDevice(stream); /* Close up OpenAL and SDL. */ SDL_DestroyAudioStream(stream); alcDestroyContext(playback.Context); alcCloseDevice(playback.Device); SDL_QuitSubSystem(SDL_INIT_AUDIO); return 0; error: if(stream) SDL_DestroyAudioStream(stream); if(playback.Context) alcDestroyContext(playback.Context); if(playback.Device) alcCloseDevice(playback.Device); SDL_QuitSubSystem(SDL_INIT_AUDIO); return 1; } openal-soft-1.24.2/examples/almultireverb.c000066400000000000000000000642431474041540300207010ustar00rootroot00000000000000/* * OpenAL Multi-Zone Reverb Example * * Copyright (c) 2018 by Chris Robinson * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ /* This file contains an example for controlling multiple reverb zones to * smoothly transition between reverb environments. The general concept is to * extend single-reverb by also tracking the closest adjacent environment, and * utilize EAX Reverb's panning vectors to position them relative to the * listener. */ #include #include #include #include #include #include #include #include "sndfile.h" #include "AL/al.h" #include "AL/alc.h" #include "AL/efx.h" #include "AL/efx-presets.h" #include "common/alhelpers.h" #include "win_main_utf8.h" #ifndef M_PI #define M_PI 3.14159265358979323846 #endif /* Filter object functions */ static LPALGENFILTERS alGenFilters; static LPALDELETEFILTERS alDeleteFilters; static LPALISFILTER alIsFilter; static LPALFILTERI alFilteri; static LPALFILTERIV alFilteriv; static LPALFILTERF alFilterf; static LPALFILTERFV alFilterfv; static LPALGETFILTERI alGetFilteri; static LPALGETFILTERIV alGetFilteriv; static LPALGETFILTERF alGetFilterf; static LPALGETFILTERFV alGetFilterfv; /* Effect object functions */ static LPALGENEFFECTS alGenEffects; static LPALDELETEEFFECTS alDeleteEffects; static LPALISEFFECT alIsEffect; static LPALEFFECTI alEffecti; static LPALEFFECTIV alEffectiv; static LPALEFFECTF alEffectf; static LPALEFFECTFV alEffectfv; static LPALGETEFFECTI alGetEffecti; static LPALGETEFFECTIV alGetEffectiv; static LPALGETEFFECTF alGetEffectf; static LPALGETEFFECTFV alGetEffectfv; /* Auxiliary Effect Slot object functions */ static LPALGENAUXILIARYEFFECTSLOTS alGenAuxiliaryEffectSlots; static LPALDELETEAUXILIARYEFFECTSLOTS alDeleteAuxiliaryEffectSlots; static LPALISAUXILIARYEFFECTSLOT alIsAuxiliaryEffectSlot; static LPALAUXILIARYEFFECTSLOTI alAuxiliaryEffectSloti; static LPALAUXILIARYEFFECTSLOTIV alAuxiliaryEffectSlotiv; static LPALAUXILIARYEFFECTSLOTF alAuxiliaryEffectSlotf; static LPALAUXILIARYEFFECTSLOTFV alAuxiliaryEffectSlotfv; static LPALGETAUXILIARYEFFECTSLOTI alGetAuxiliaryEffectSloti; static LPALGETAUXILIARYEFFECTSLOTIV alGetAuxiliaryEffectSlotiv; static LPALGETAUXILIARYEFFECTSLOTF alGetAuxiliaryEffectSlotf; static LPALGETAUXILIARYEFFECTSLOTFV alGetAuxiliaryEffectSlotfv; /* LoadEffect loads the given initial reverb properties into the given OpenAL * effect object, and returns non-zero on success. */ static int LoadEffect(ALuint effect, const EFXEAXREVERBPROPERTIES *reverb) { ALenum err; alGetError(); /* Prepare the effect for EAX Reverb (standard reverb doesn't contain * the needed panning vectors). */ alEffecti(effect, AL_EFFECT_TYPE, AL_EFFECT_EAXREVERB); err = alGetError(); if(err != AL_NO_ERROR) { fprintf(stderr, "Failed to set EAX Reverb: %s (0x%04x)\n", alGetString(err), err); return 0; } /* Load the reverb properties. */ alEffectf(effect, AL_EAXREVERB_DENSITY, reverb->flDensity); alEffectf(effect, AL_EAXREVERB_DIFFUSION, reverb->flDiffusion); alEffectf(effect, AL_EAXREVERB_GAIN, reverb->flGain); alEffectf(effect, AL_EAXREVERB_GAINHF, reverb->flGainHF); alEffectf(effect, AL_EAXREVERB_GAINLF, reverb->flGainLF); alEffectf(effect, AL_EAXREVERB_DECAY_TIME, reverb->flDecayTime); alEffectf(effect, AL_EAXREVERB_DECAY_HFRATIO, reverb->flDecayHFRatio); alEffectf(effect, AL_EAXREVERB_DECAY_LFRATIO, reverb->flDecayLFRatio); alEffectf(effect, AL_EAXREVERB_REFLECTIONS_GAIN, reverb->flReflectionsGain); alEffectf(effect, AL_EAXREVERB_REFLECTIONS_DELAY, reverb->flReflectionsDelay); alEffectfv(effect, AL_EAXREVERB_REFLECTIONS_PAN, reverb->flReflectionsPan); alEffectf(effect, AL_EAXREVERB_LATE_REVERB_GAIN, reverb->flLateReverbGain); alEffectf(effect, AL_EAXREVERB_LATE_REVERB_DELAY, reverb->flLateReverbDelay); alEffectfv(effect, AL_EAXREVERB_LATE_REVERB_PAN, reverb->flLateReverbPan); alEffectf(effect, AL_EAXREVERB_ECHO_TIME, reverb->flEchoTime); alEffectf(effect, AL_EAXREVERB_ECHO_DEPTH, reverb->flEchoDepth); alEffectf(effect, AL_EAXREVERB_MODULATION_TIME, reverb->flModulationTime); alEffectf(effect, AL_EAXREVERB_MODULATION_DEPTH, reverb->flModulationDepth); alEffectf(effect, AL_EAXREVERB_AIR_ABSORPTION_GAINHF, reverb->flAirAbsorptionGainHF); alEffectf(effect, AL_EAXREVERB_HFREFERENCE, reverb->flHFReference); alEffectf(effect, AL_EAXREVERB_LFREFERENCE, reverb->flLFReference); alEffectf(effect, AL_EAXREVERB_ROOM_ROLLOFF_FACTOR, reverb->flRoomRolloffFactor); alEffecti(effect, AL_EAXREVERB_DECAY_HFLIMIT, reverb->iDecayHFLimit); /* Check if an error occurred, and return failure if so. */ err = alGetError(); if(err != AL_NO_ERROR) { fprintf(stderr, "Error setting up reverb: %s\n", alGetString(err)); return 0; } return 1; } /* LoadBuffer loads the named audio file into an OpenAL buffer object, and * returns the new buffer ID. */ static ALuint LoadSound(const char *filename) { ALenum err, format; ALuint buffer; SNDFILE *sndfile; SF_INFO sfinfo; short *membuf; sf_count_t num_frames; ALsizei num_bytes; /* Open the audio file and check that it's usable. */ sndfile = sf_open(filename, SFM_READ, &sfinfo); if(!sndfile) { fprintf(stderr, "Could not open audio in %s: %s\n", filename, sf_strerror(sndfile)); return 0; } if(sfinfo.frames < 1 || sfinfo.frames > (sf_count_t)(INT_MAX/sizeof(short))/sfinfo.channels) { fprintf(stderr, "Bad sample count in %s (%" PRId64 ")\n", filename, sfinfo.frames); sf_close(sndfile); return 0; } /* Get the sound format, and figure out the OpenAL format */ if(sfinfo.channels == 1) format = AL_FORMAT_MONO16; else if(sfinfo.channels == 2) format = AL_FORMAT_STEREO16; else { fprintf(stderr, "Unsupported channel count: %d\n", sfinfo.channels); sf_close(sndfile); return 0; } /* Decode the whole audio file to a buffer. */ membuf = malloc((size_t)(sfinfo.frames * sfinfo.channels) * sizeof(short)); num_frames = sf_readf_short(sndfile, membuf, sfinfo.frames); if(num_frames < 1) { free(membuf); sf_close(sndfile); fprintf(stderr, "Failed to read samples in %s (%" PRId64 ")\n", filename, num_frames); return 0; } num_bytes = (ALsizei)(num_frames * sfinfo.channels) * (ALsizei)sizeof(short); /* Buffer the audio data into a new buffer object, then free the data and * close the file. */ buffer = 0; alGenBuffers(1, &buffer); alBufferData(buffer, format, membuf, num_bytes, sfinfo.samplerate); free(membuf); sf_close(sndfile); /* Check if an error occurred, and clean up if so. */ err = alGetError(); if(err != AL_NO_ERROR) { fprintf(stderr, "OpenAL Error: %s\n", alGetString(err)); if(buffer && alIsBuffer(buffer)) alDeleteBuffers(1, &buffer); return 0; } return buffer; } /* Helper to calculate the dot-product of the two given vectors. */ static ALfloat dot_product(const ALfloat vec0[3], const ALfloat vec1[3]) { return vec0[0]*vec1[0] + vec0[1]*vec1[1] + vec0[2]*vec1[2]; } /* Helper to normalize a given vector. */ static void normalize(ALfloat vec[3]) { ALfloat mag = sqrtf(dot_product(vec, vec)); if(mag > 0.00001f) { vec[0] /= mag; vec[1] /= mag; vec[2] /= mag; } else { vec[0] = 0.0f; vec[1] = 0.0f; vec[2] = 0.0f; } } /* The main update function to update the listener and environment effects. */ static void UpdateListenerAndEffects(float timediff, const ALuint slots[2], const ALuint effects[2], const EFXEAXREVERBPROPERTIES reverbs[2]) { static const ALfloat listener_move_scale = 10.0f; /* Individual reverb zones are connected via "portals". Each portal has a * position (center point of the connecting area), a normal (facing * direction), and a radius (approximate size of the connecting area). */ const ALfloat portal_pos[3] = { 0.0f, 0.0f, 0.0f }; const ALfloat portal_norm[3] = { sqrtf(0.5f), 0.0f, -sqrtf(0.5f) }; const ALfloat portal_radius = 2.5f; ALfloat other_dir[3], this_dir[3]; ALfloat listener_pos[3]; ALfloat local_norm[3]; ALfloat local_dir[3]; ALfloat near_edge[3]; ALfloat far_edge[3]; ALfloat dist, edist; /* Update the listener position for the amount of time passed. This uses a * simple triangular LFO to offset the position (moves along the X axis * between -listener_move_scale and +listener_move_scale for each * transition). */ listener_pos[0] = (fabsf(2.0f - timediff/2.0f) - 1.0f) * listener_move_scale; listener_pos[1] = 0.0f; listener_pos[2] = 0.0f; alListenerfv(AL_POSITION, listener_pos); /* Calculate local_dir, which represents the listener-relative point to the * adjacent zone (should also include orientation). Because EAX Reverb uses * left-handed coordinates instead of right-handed like the rest of OpenAL, * negate Z for the local values. */ local_dir[0] = portal_pos[0] - listener_pos[0]; local_dir[1] = portal_pos[1] - listener_pos[1]; local_dir[2] = -(portal_pos[2] - listener_pos[2]); /* A normal application would also rotate the portal's normal given the * listener orientation, to get the listener-relative normal. */ local_norm[0] = portal_norm[0]; local_norm[1] = portal_norm[1]; local_norm[2] = -portal_norm[2]; /* Calculate the distance from the listener to the portal, and ensure it's * far enough away to not suffer severe floating-point precision issues. */ dist = sqrtf(dot_product(local_dir, local_dir)); if(dist > 0.00001f) { const EFXEAXREVERBPROPERTIES *other_reverb, *this_reverb; ALuint other_effect, this_effect; ALfloat magnitude, dir_dot_norm; /* Normalize the direction to the portal. */ local_dir[0] /= dist; local_dir[1] /= dist; local_dir[2] /= dist; /* Calculate the dot product of the portal's local direction and local * normal, which is used for angular and side checks later on. */ dir_dot_norm = dot_product(local_dir, local_norm); /* Figure out which zone we're in. */ if(dir_dot_norm <= 0.0f) { /* We're in front of the portal, so we're in Zone 0. */ this_effect = effects[0]; other_effect = effects[1]; this_reverb = &reverbs[0]; other_reverb = &reverbs[1]; } else { /* We're behind the portal, so we're in Zone 1. */ this_effect = effects[1]; other_effect = effects[0]; this_reverb = &reverbs[1]; other_reverb = &reverbs[0]; } /* Calculate the listener-relative extents of the portal. */ /* First, project the listener-to-portal vector onto the portal's plane * to get the portal-relative direction along the plane that goes away * from the listener (toward the farthest edge of the portal). */ far_edge[0] = local_dir[0] - local_norm[0]*dir_dot_norm; far_edge[1] = local_dir[1] - local_norm[1]*dir_dot_norm; far_edge[2] = local_dir[2] - local_norm[2]*dir_dot_norm; edist = sqrtf(dot_product(far_edge, far_edge)); if(edist > 0.0001f) { /* Rescale the portal-relative vector to be at the radius edge. */ ALfloat mag = portal_radius / edist; far_edge[0] *= mag; far_edge[1] *= mag; far_edge[2] *= mag; /* Calculate the closest edge of the portal by negating the * farthest, and add an offset to make them both relative to the * listener. */ near_edge[0] = local_dir[0]*dist - far_edge[0]; near_edge[1] = local_dir[1]*dist - far_edge[1]; near_edge[2] = local_dir[2]*dist - far_edge[2]; far_edge[0] += local_dir[0]*dist; far_edge[1] += local_dir[1]*dist; far_edge[2] += local_dir[2]*dist; /* Normalize the listener-relative extents of the portal, then * calculate the panning magnitude for the other zone given the * apparent size of the opening. The panning magnitude affects the * envelopment of the environment, with 1 being a point, 0.5 being * half coverage around the listener, and 0 being full coverage. */ normalize(far_edge); normalize(near_edge); magnitude = 1.0f - acosf(dot_product(far_edge, near_edge))/(float)(M_PI*2.0); /* Recalculate the panning direction, to be directly between the * direction of the two extents. */ local_dir[0] = far_edge[0] + near_edge[0]; local_dir[1] = far_edge[1] + near_edge[1]; local_dir[2] = far_edge[2] + near_edge[2]; normalize(local_dir); } else { /* If we get here, the listener is directly in front of or behind * the center of the portal, making all aperture edges effectively * equidistant. Calculating the panning magnitude is simplified, * using the arctangent of the radius and distance. */ magnitude = 1.0f - (atan2f(portal_radius, dist) / (float)M_PI); } /* Scale the other zone's panning vector. */ other_dir[0] = local_dir[0] * magnitude; other_dir[1] = local_dir[1] * magnitude; other_dir[2] = local_dir[2] * magnitude; /* Pan the current zone to the opposite direction of the portal, and * take the remaining percentage of the portal's magnitude. */ this_dir[0] = local_dir[0] * (magnitude-1.0f); this_dir[1] = local_dir[1] * (magnitude-1.0f); this_dir[2] = local_dir[2] * (magnitude-1.0f); /* Now set the effects' panning vectors and gain. Energy is shared * between environments, so attenuate according to each zone's * contribution (note: gain^2 = energy). */ alEffectf(this_effect, AL_EAXREVERB_REFLECTIONS_GAIN, this_reverb->flReflectionsGain * sqrtf(magnitude)); alEffectf(this_effect, AL_EAXREVERB_LATE_REVERB_GAIN, this_reverb->flLateReverbGain * sqrtf(magnitude)); alEffectfv(this_effect, AL_EAXREVERB_REFLECTIONS_PAN, this_dir); alEffectfv(this_effect, AL_EAXREVERB_LATE_REVERB_PAN, this_dir); alEffectf(other_effect, AL_EAXREVERB_REFLECTIONS_GAIN, other_reverb->flReflectionsGain * sqrtf(1.0f-magnitude)); alEffectf(other_effect, AL_EAXREVERB_LATE_REVERB_GAIN, other_reverb->flLateReverbGain * sqrtf(1.0f-magnitude)); alEffectfv(other_effect, AL_EAXREVERB_REFLECTIONS_PAN, other_dir); alEffectfv(other_effect, AL_EAXREVERB_LATE_REVERB_PAN, other_dir); } else { /* We're practically in the center of the portal. Give the panning * vectors a 50/50 split, with Zone 0 covering the half in front of * the normal, and Zone 1 covering the half behind. */ this_dir[0] = local_norm[0] / 2.0f; this_dir[1] = local_norm[1] / 2.0f; this_dir[2] = local_norm[2] / 2.0f; other_dir[0] = local_norm[0] / -2.0f; other_dir[1] = local_norm[1] / -2.0f; other_dir[2] = local_norm[2] / -2.0f; alEffectf(effects[0], AL_EAXREVERB_REFLECTIONS_GAIN, reverbs[0].flReflectionsGain * sqrtf(0.5f)); alEffectf(effects[0], AL_EAXREVERB_LATE_REVERB_GAIN, reverbs[0].flLateReverbGain * sqrtf(0.5f)); alEffectfv(effects[0], AL_EAXREVERB_REFLECTIONS_PAN, this_dir); alEffectfv(effects[0], AL_EAXREVERB_LATE_REVERB_PAN, this_dir); alEffectf(effects[1], AL_EAXREVERB_REFLECTIONS_GAIN, reverbs[1].flReflectionsGain * sqrtf(0.5f)); alEffectf(effects[1], AL_EAXREVERB_LATE_REVERB_GAIN, reverbs[1].flLateReverbGain * sqrtf(0.5f)); alEffectfv(effects[1], AL_EAXREVERB_REFLECTIONS_PAN, other_dir); alEffectfv(effects[1], AL_EAXREVERB_LATE_REVERB_PAN, other_dir); } /* Finally, update the effect slots with the updated effect parameters. */ alAuxiliaryEffectSloti(slots[0], AL_EFFECTSLOT_EFFECT, (ALint)effects[0]); alAuxiliaryEffectSloti(slots[1], AL_EFFECTSLOT_EFFECT, (ALint)effects[1]); } int main(int argc, char **argv) { static const int MaxTransitions = 8; EFXEAXREVERBPROPERTIES reverbs[2] = { EFX_REVERB_PRESET_CARPETEDHALLWAY, EFX_REVERB_PRESET_BATHROOM }; ALCdevice *device = NULL; ALCcontext *context = NULL; ALuint effects[2] = { 0, 0 }; ALuint slots[2] = { 0, 0 }; ALuint direct_filter = 0; ALuint buffer = 0; ALuint source = 0; ALCint num_sends = 0; ALenum state = AL_INITIAL; ALfloat direct_gain = 1.0f; int basetime = 0; int loops = 0; /* Print out usage if no arguments were specified */ if(argc < 2) { fprintf(stderr, "Usage: %s [-device ] [options] \n\n" "Options:\n" "\t-nodirect\tSilence direct path output (easier to hear reverb)\n\n", argv[0]); return 1; } /* Initialize OpenAL, and check for EFX support with at least 2 auxiliary * sends (if multiple sends are supported, 2 are provided by default; if * you want more, you have to request it through alcCreateContext). */ argv++; argc--; if(InitAL(&argv, &argc) != 0) return 1; while(argc > 0) { if(strcmp(argv[0], "-nodirect") == 0) direct_gain = 0.0f; else break; argv++; argc--; } if(argc < 1) { fprintf(stderr, "No filename specified.\n"); CloseAL(); return 1; } context = alcGetCurrentContext(); device = alcGetContextsDevice(context); if(!alcIsExtensionPresent(device, "ALC_EXT_EFX")) { fprintf(stderr, "Error: EFX not supported\n"); CloseAL(); return 1; } num_sends = 0; alcGetIntegerv(device, ALC_MAX_AUXILIARY_SENDS, 1, &num_sends); if(alcGetError(device) != ALC_NO_ERROR || num_sends < 2) { fprintf(stderr, "Error: Device does not support multiple sends (got %d, need 2)\n", num_sends); CloseAL(); return 1; } /* Define a macro to help load the function pointers. */ #define LOAD_PROC(T, x) ((x) = FUNCTION_CAST(T, alGetProcAddress(#x))) LOAD_PROC(LPALGENFILTERS, alGenFilters); LOAD_PROC(LPALDELETEFILTERS, alDeleteFilters); LOAD_PROC(LPALISFILTER, alIsFilter); LOAD_PROC(LPALFILTERI, alFilteri); LOAD_PROC(LPALFILTERIV, alFilteriv); LOAD_PROC(LPALFILTERF, alFilterf); LOAD_PROC(LPALFILTERFV, alFilterfv); LOAD_PROC(LPALGETFILTERI, alGetFilteri); LOAD_PROC(LPALGETFILTERIV, alGetFilteriv); LOAD_PROC(LPALGETFILTERF, alGetFilterf); LOAD_PROC(LPALGETFILTERFV, alGetFilterfv); LOAD_PROC(LPALGENEFFECTS, alGenEffects); LOAD_PROC(LPALDELETEEFFECTS, alDeleteEffects); LOAD_PROC(LPALISEFFECT, alIsEffect); LOAD_PROC(LPALEFFECTI, alEffecti); LOAD_PROC(LPALEFFECTIV, alEffectiv); LOAD_PROC(LPALEFFECTF, alEffectf); LOAD_PROC(LPALEFFECTFV, alEffectfv); LOAD_PROC(LPALGETEFFECTI, alGetEffecti); LOAD_PROC(LPALGETEFFECTIV, alGetEffectiv); LOAD_PROC(LPALGETEFFECTF, alGetEffectf); LOAD_PROC(LPALGETEFFECTFV, alGetEffectfv); LOAD_PROC(LPALGENAUXILIARYEFFECTSLOTS, alGenAuxiliaryEffectSlots); LOAD_PROC(LPALDELETEAUXILIARYEFFECTSLOTS, alDeleteAuxiliaryEffectSlots); LOAD_PROC(LPALISAUXILIARYEFFECTSLOT, alIsAuxiliaryEffectSlot); LOAD_PROC(LPALAUXILIARYEFFECTSLOTI, alAuxiliaryEffectSloti); LOAD_PROC(LPALAUXILIARYEFFECTSLOTIV, alAuxiliaryEffectSlotiv); LOAD_PROC(LPALAUXILIARYEFFECTSLOTF, alAuxiliaryEffectSlotf); LOAD_PROC(LPALAUXILIARYEFFECTSLOTFV, alAuxiliaryEffectSlotfv); LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTI, alGetAuxiliaryEffectSloti); LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTIV, alGetAuxiliaryEffectSlotiv); LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTF, alGetAuxiliaryEffectSlotf); LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTFV, alGetAuxiliaryEffectSlotfv); #undef LOAD_PROC /* Load the sound into a buffer. */ buffer = LoadSound(argv[0]); if(!buffer) { CloseAL(); return 1; } /* Generate two effects for two "zones", and load a reverb into each one. * Note that unlike single-zone reverb, where you can store one effect per * preset, for multi-zone reverb you should have one effect per environment * instance, or one per audible zone. This is because we'll be changing the * effects' properties in real-time based on the environment instance * relative to the listener. */ alGenEffects(2, effects); if(!LoadEffect(effects[0], &reverbs[0]) || !LoadEffect(effects[1], &reverbs[1])) { alDeleteEffects(2, effects); alDeleteBuffers(1, &buffer); CloseAL(); return 1; } /* Create the effect slot objects, one for each "active" effect. */ alGenAuxiliaryEffectSlots(2, slots); /* Tell the effect slots to use the loaded effect objects, with slot 0 for * Zone 0 and slot 1 for Zone 1. Note that this effectively copies the * effect properties. Modifying or deleting the effect object afterward * won't directly affect the effect slot until they're reapplied like this. */ alAuxiliaryEffectSloti(slots[0], AL_EFFECTSLOT_EFFECT, (ALint)effects[0]); alAuxiliaryEffectSloti(slots[1], AL_EFFECTSLOT_EFFECT, (ALint)effects[1]); assert(alGetError()==AL_NO_ERROR && "Failed to set effect slot"); /* For the purposes of this example, prepare a filter that optionally * silences the direct path which allows us to hear just the reverberation. * A filter like this is normally used for obstruction, where the path * directly between the listener and source is blocked (the exact * properties depending on the type and thickness of the obstructing * material). */ alGenFilters(1, &direct_filter); alFilteri(direct_filter, AL_FILTER_TYPE, AL_FILTER_LOWPASS); alFilterf(direct_filter, AL_LOWPASS_GAIN, direct_gain); assert(alGetError()==AL_NO_ERROR && "Failed to set direct filter"); /* Create the source to play the sound with, place it in front of the * listener's path in the left zone. */ source = 0; alGenSources(1, &source); alSourcei(source, AL_LOOPING, AL_TRUE); alSource3f(source, AL_POSITION, -5.0f, 0.0f, -2.0f); alSourcei(source, AL_DIRECT_FILTER, (ALint)direct_filter); alSourcei(source, AL_BUFFER, (ALint)buffer); /* Connect the source to the effect slots. Here, we connect source send 0 * to Zone 0's slot, and send 1 to Zone 1's slot. Filters can be specified * to occlude the source from each zone by varying amounts; for example, a * source within a particular zone would be unfiltered, while a source that * can only see a zone through a window or thin wall may be attenuated for * that zone. */ alSource3i(source, AL_AUXILIARY_SEND_FILTER, (ALint)slots[0], 0, AL_FILTER_NULL); alSource3i(source, AL_AUXILIARY_SEND_FILTER, (ALint)slots[1], 1, AL_FILTER_NULL); assert(alGetError()==AL_NO_ERROR && "Failed to setup sound source"); /* Get the current time as the base for timing in the main loop. */ basetime = altime_get(); loops = 0; printf("Transition %d of %d...\n", loops+1, MaxTransitions); /* Play the sound for a while. */ alSourcePlay(source); do { int curtime; ALfloat timediff; /* Start a batch update, to ensure all changes apply simultaneously. */ alcSuspendContext(context); /* Get the current time to track the amount of time that passed. * Convert the difference to seconds. */ curtime = altime_get(); timediff = (float)(curtime - basetime) / 1000.0f; /* Avoid negative time deltas, in case of non-monotonic clocks. */ if(timediff < 0.0f) timediff = 0.0f; else while(timediff >= 4.0f*(float)((loops&1)+1)) { /* For this example, each transition occurs over 4 seconds, and * there's 2 transitions per cycle. */ if(++loops < MaxTransitions) printf("Transition %d of %d...\n", loops+1, MaxTransitions); if(!(loops&1)) { /* Cycle completed. Decrease the delta and increase the base * time to start a new cycle. */ timediff -= 8.0f; basetime += 8000; } } /* Update the listener and effects, and finish the batch. */ UpdateListenerAndEffects(timediff, slots, effects, reverbs); alcProcessContext(context); al_nssleep(10000000); alGetSourcei(source, AL_SOURCE_STATE, &state); } while(alGetError() == AL_NO_ERROR && state == AL_PLAYING && loops < MaxTransitions); /* All done. Delete resources, and close down OpenAL. */ alDeleteSources(1, &source); alDeleteAuxiliaryEffectSlots(2, slots); alDeleteEffects(2, effects); alDeleteFilters(1, &direct_filter); alDeleteBuffers(1, &buffer); CloseAL(); return 0; } openal-soft-1.24.2/examples/alplay.c000066400000000000000000000252511474041540300173020ustar00rootroot00000000000000/* * OpenAL Source Play Example * * Copyright (c) 2017 by Chris Robinson * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ /* This file contains an example for playing a sound buffer. */ #include #include #include #include #include #include "sndfile.h" #include "AL/al.h" #include "AL/alext.h" #include "common/alhelpers.h" #include "win_main_utf8.h" enum FormatType { Int16, Float, IMA4, MSADPCM }; /* LoadBuffer loads the named audio file into an OpenAL buffer object, and * returns the new buffer ID. */ static ALuint LoadSound(const char *filename) { enum FormatType sample_format = Int16; ALint byteblockalign = 0; ALint splblockalign = 0; sf_count_t num_frames; ALenum err, format; ALsizei num_bytes; SNDFILE *sndfile; SF_INFO sfinfo; ALuint buffer; void *membuf; /* Open the audio file and check that it's usable. */ sndfile = sf_open(filename, SFM_READ, &sfinfo); if(!sndfile) { fprintf(stderr, "Could not open audio in %s: %s\n", filename, sf_strerror(sndfile)); return 0; } if(sfinfo.frames < 1) { fprintf(stderr, "Bad sample count in %s (%" PRId64 ")\n", filename, sfinfo.frames); sf_close(sndfile); return 0; } /* Detect a suitable format to load. Formats like Vorbis and Opus use float * natively, so load as float to avoid clipping when possible. Formats * larger than 16-bit can also use float to preserve a bit more precision. */ switch((sfinfo.format&SF_FORMAT_SUBMASK)) { case SF_FORMAT_PCM_24: case SF_FORMAT_PCM_32: case SF_FORMAT_FLOAT: case SF_FORMAT_DOUBLE: case SF_FORMAT_VORBIS: case SF_FORMAT_OPUS: case SF_FORMAT_ALAC_20: case SF_FORMAT_ALAC_24: case SF_FORMAT_ALAC_32: case 0x0080/*SF_FORMAT_MPEG_LAYER_I*/: case 0x0081/*SF_FORMAT_MPEG_LAYER_II*/: case 0x0082/*SF_FORMAT_MPEG_LAYER_III*/: if(alIsExtensionPresent("AL_EXT_FLOAT32")) sample_format = Float; break; case SF_FORMAT_IMA_ADPCM: /* ADPCM formats require setting a block alignment as specified in the * file, which needs to be read from the wave 'fmt ' chunk manually * since libsndfile doesn't provide it in a format-agnostic way. */ if(sfinfo.channels <= 2 && (sfinfo.format&SF_FORMAT_TYPEMASK) == SF_FORMAT_WAV && alIsExtensionPresent("AL_EXT_IMA4") && alIsExtensionPresent("AL_SOFT_block_alignment")) sample_format = IMA4; break; case SF_FORMAT_MS_ADPCM: if(sfinfo.channels <= 2 && (sfinfo.format&SF_FORMAT_TYPEMASK) == SF_FORMAT_WAV && alIsExtensionPresent("AL_SOFT_MSADPCM") && alIsExtensionPresent("AL_SOFT_block_alignment")) sample_format = MSADPCM; break; } if(sample_format == IMA4 || sample_format == MSADPCM) { /* For ADPCM, lookup the wave file's "fmt " chunk, which is a * WAVEFORMATEX-based structure for the audio format. */ SF_CHUNK_INFO inf = { "fmt ", 4, 0, NULL }; SF_CHUNK_ITERATOR *iter = sf_get_chunk_iterator(sndfile, &inf); /* If there's an issue getting the chunk or block alignment, load as * 16-bit and have libsndfile do the conversion. */ if(!iter || sf_get_chunk_size(iter, &inf) != SF_ERR_NO_ERROR || inf.datalen < 14) sample_format = Int16; else { ALubyte *fmtbuf = calloc(inf.datalen, 1); inf.data = fmtbuf; if(sf_get_chunk_data(iter, &inf) != SF_ERR_NO_ERROR) sample_format = Int16; else { /* Read the nBlockAlign field, and convert from bytes- to * samples-per-block (verifying it's valid by converting back * and comparing to the original value). */ byteblockalign = fmtbuf[12] | (fmtbuf[13]<<8); if(sample_format == IMA4) { splblockalign = (byteblockalign/sfinfo.channels - 4)/4*8 + 1; if(splblockalign < 1 || ((splblockalign-1)/2 + 4)*sfinfo.channels != byteblockalign) sample_format = Int16; } else { splblockalign = (byteblockalign/sfinfo.channels - 7)*2 + 2; if(splblockalign < 2 || ((splblockalign-2)/2 + 7)*sfinfo.channels != byteblockalign) sample_format = Int16; } } free(fmtbuf); } } if(sample_format == Int16) { splblockalign = 1; byteblockalign = sfinfo.channels * 2; } else if(sample_format == Float) { splblockalign = 1; byteblockalign = sfinfo.channels * 4; } /* Figure out the OpenAL format from the file and desired sample type. */ format = AL_NONE; if(sfinfo.channels == 1) { if(sample_format == Int16) format = AL_FORMAT_MONO16; else if(sample_format == Float) format = AL_FORMAT_MONO_FLOAT32; else if(sample_format == IMA4) format = AL_FORMAT_MONO_IMA4; else if(sample_format == MSADPCM) format = AL_FORMAT_MONO_MSADPCM_SOFT; } else if(sfinfo.channels == 2) { if(sample_format == Int16) format = AL_FORMAT_STEREO16; else if(sample_format == Float) format = AL_FORMAT_STEREO_FLOAT32; else if(sample_format == IMA4) format = AL_FORMAT_STEREO_IMA4; else if(sample_format == MSADPCM) format = AL_FORMAT_STEREO_MSADPCM_SOFT; } else if(sfinfo.channels == 3) { if(sf_command(sndfile, SFC_WAVEX_GET_AMBISONIC, NULL, 0) == SF_AMBISONIC_B_FORMAT) { if(sample_format == Int16) format = AL_FORMAT_BFORMAT2D_16; else if(sample_format == Float) format = AL_FORMAT_BFORMAT2D_FLOAT32; } } else if(sfinfo.channels == 4) { if(sf_command(sndfile, SFC_WAVEX_GET_AMBISONIC, NULL, 0) == SF_AMBISONIC_B_FORMAT) { if(sample_format == Int16) format = AL_FORMAT_BFORMAT3D_16; else if(sample_format == Float) format = AL_FORMAT_BFORMAT3D_FLOAT32; } } if(!format) { fprintf(stderr, "Unsupported channel count: %d\n", sfinfo.channels); sf_close(sndfile); return 0; } if(sfinfo.frames/splblockalign > (sf_count_t)(INT_MAX/byteblockalign)) { fprintf(stderr, "Too many samples in %s (%" PRId64 ")\n", filename, sfinfo.frames); sf_close(sndfile); return 0; } /* Decode the whole audio file to a buffer. */ membuf = malloc((size_t)(sfinfo.frames / splblockalign * byteblockalign)); if(sample_format == Int16) num_frames = sf_readf_short(sndfile, membuf, sfinfo.frames); else if(sample_format == Float) num_frames = sf_readf_float(sndfile, membuf, sfinfo.frames); else { sf_count_t count = sfinfo.frames / splblockalign * byteblockalign; num_frames = sf_read_raw(sndfile, membuf, count); if(num_frames > 0) num_frames = num_frames / byteblockalign * splblockalign; } if(num_frames < 1) { free(membuf); sf_close(sndfile); fprintf(stderr, "Failed to read samples in %s (%" PRId64 ")\n", filename, num_frames); return 0; } num_bytes = (ALsizei)(num_frames / splblockalign * byteblockalign); printf("Loading: %s (%s, %dhz)\n", filename, FormatName(format), sfinfo.samplerate); fflush(stdout); /* Buffer the audio data into a new buffer object, then free the data and * close the file. */ buffer = 0; alGenBuffers(1, &buffer); if(splblockalign > 1) alBufferi(buffer, AL_UNPACK_BLOCK_ALIGNMENT_SOFT, splblockalign); alBufferData(buffer, format, membuf, num_bytes, sfinfo.samplerate); free(membuf); sf_close(sndfile); /* Check if an error occurred, and clean up if so. */ err = alGetError(); if(err != AL_NO_ERROR) { fprintf(stderr, "OpenAL Error: %s\n", alGetString(err)); if(buffer && alIsBuffer(buffer)) alDeleteBuffers(1, &buffer); return 0; } return buffer; } int main(int argc, char **argv) { ALuint source, buffer; ALfloat offset; ALenum state; /* Print out usage if no arguments were specified */ if(argc < 2) { fprintf(stderr, "Usage: %s [-device ] \n", argv[0]); return 1; } /* Initialize OpenAL. */ argv++; argc--; if(InitAL(&argv, &argc) != 0) return 1; /* Load the sound into a buffer. */ buffer = LoadSound(argv[0]); if(!buffer) { CloseAL(); return 1; } /* Create the source to play the sound with. */ source = 0; alGenSources(1, &source); alSourcei(source, AL_BUFFER, (ALint)buffer); assert(alGetError()==AL_NO_ERROR && "Failed to setup sound source"); /* Play the sound until it finishes. */ alSourcePlay(source); do { al_nssleep(10000000); alGetSourcei(source, AL_SOURCE_STATE, &state); /* Get the source offset. */ alGetSourcef(source, AL_SEC_OFFSET, &offset); printf("\rOffset: %f ", offset); fflush(stdout); } while(alGetError() == AL_NO_ERROR && state == AL_PLAYING); printf("\n"); /* All done. Delete resources, and close down OpenAL. */ alDeleteSources(1, &source); alDeleteBuffers(1, &buffer); CloseAL(); return 0; } openal-soft-1.24.2/examples/alrecord.c000066400000000000000000000316711474041540300176160ustar00rootroot00000000000000/* * OpenAL Recording Example * * Copyright (c) 2017 by Chris Robinson * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ /* This file contains a relatively simple recorder. */ #include #include #include #include #include "AL/al.h" #include "AL/alc.h" #include "AL/alext.h" #include "common/alhelpers.h" #include "win_main_utf8.h" #if defined(_WIN64) #define SZFMT "%I64u" #elif defined(_WIN32) #define SZFMT "%u" #else #define SZFMT "%zu" #endif #if defined(_MSC_VER) && (_MSC_VER < 1900) static float msvc_strtof(const char *str, char **end) { return (float)strtod(str, end); } #define strtof msvc_strtof #endif static void fwrite16le(ALushort val, FILE *f) { ALubyte data[2]; data[0] = (ALubyte)(val&0xff); data[1] = (ALubyte)(val>>8); fwrite(data, 1, 2, f); } static void fwrite32le(ALuint val, FILE *f) { ALubyte data[4]; data[0] = (ALubyte)(val&0xff); data[1] = (ALubyte)((val>>8)&0xff); data[2] = (ALubyte)((val>>16)&0xff); data[3] = (ALubyte)(val>>24); fwrite(data, 1, 4, f); } typedef struct Recorder { ALCdevice *mDevice; FILE *mFile; long mDataSizeOffset; ALuint mDataSize; float mRecTime; ALuint mChannels; ALuint mBits; ALuint mSampleRate; ALuint mFrameSize; ALbyte *mBuffer; ALsizei mBufferSize; } Recorder; int main(int argc, char **argv) { static const char optlist[] = " --channels/-c Set channel count (1 or 2)\n" " --bits/-b Set channel count (8, 16, or 32)\n" " --rate/-r Set sample rate (8000 to 96000)\n" " --time/-t ### Formatting User-Defined Types The {fmt} library provides formatters for many standard C++ types. See [`fmt/ranges.h`](#ranges-api) for ranges and tuples including standard containers such as `std::vector`, [`fmt/chrono.h`](#chrono-api) for date and time formatting and [`fmt/std.h`](#std-api) for other standard library types. There are two ways to make a user-defined type formattable: providing a `format_as` function or specializing the `formatter` struct template. Use `format_as` if you want to make your type formattable as some other type with the same format specifiers. The `format_as` function should take an object of your type and return an object of a formattable type. It should be defined in the same namespace as your type. Example ([run](https://godbolt.org/z/nvME4arz8)): #include namespace kevin_namespacy { enum class film { house_of_cards, american_beauty, se7en = 7 }; auto format_as(film f) { return fmt::underlying(f); } } int main() { fmt::print("{}\n", kevin_namespacy::film::se7en); // Output: 7 } Using specialization is more complex but gives you full control over parsing and formatting. To use this method specialize the `formatter` struct template for your type and implement `parse` and `format` methods. The recommended way of defining a formatter is by reusing an existing one via inheritance or composition. This way you can support standard format specifiers without implementing them yourself. For example: ```c++ // color.h: #include enum class color {red, green, blue}; template <> struct fmt::formatter: formatter { // parse is inherited from formatter. auto format(color c, format_context& ctx) const -> format_context::iterator; }; ``` ```c++ // color.cc: #include "color.h" #include auto fmt::formatter::format(color c, format_context& ctx) const -> format_context::iterator { string_view name = "unknown"; switch (c) { case color::red: name = "red"; break; case color::green: name = "green"; break; case color::blue: name = "blue"; break; } return formatter::format(name, ctx); } ``` Note that `formatter::format` is defined in `fmt/format.h` so it has to be included in the source file. Since `parse` is inherited from `formatter` it will recognize all string format specifications, for example ```c++ fmt::format("{:>10}", color::blue) ``` will return `" blue"`. In general the formatter has the following form: template <> struct fmt::formatter { // Parses format specifiers and stores them in the formatter. // // [ctx.begin(), ctx.end()) is a, possibly empty, character range that // contains a part of the format string starting from the format // specifications to be parsed, e.g. in // // fmt::format("{:f} continued", ...); // // the range will contain "f} continued". The formatter should parse // specifiers until '}' or the end of the range. In this example the // formatter should parse the 'f' specifier and return an iterator // pointing to '}'. constexpr auto parse(format_parse_context& ctx) -> format_parse_context::iterator; // Formats value using the parsed format specification stored in this // formatter and writes the output to ctx.out(). auto format(const T& value, format_context& ctx) const -> format_context::iterator; }; It is recommended to at least support fill, align and width that apply to the whole object and have the same semantics as in standard formatters. You can also write a formatter for a hierarchy of classes: ```c++ // demo.h: #include #include struct A { virtual ~A() {} virtual std::string name() const { return "A"; } }; struct B : A { virtual std::string name() const { return "B"; } }; template struct fmt::formatter, char>> : fmt::formatter { auto format(const A& a, format_context& ctx) const { return formatter::format(a.name(), ctx); } }; ``` ```c++ // demo.cc: #include "demo.h" #include int main() { B b; A& a = b; fmt::print("{}", a); // Output: B } ``` Providing both a `formatter` specialization and a `format_as` overload is disallowed. ::: basic_format_parse_context ::: context ::: format_context ### Compile-Time Checks Compile-time format string checks are enabled by default on compilers that support C++20 `consteval`. On older compilers you can use the [FMT_STRING](#legacy-checks) macro defined in `fmt/format.h` instead. Unused arguments are allowed as in Python's `str.format` and ordinary functions. See [Type Erasure](#type-erasure) for an example of how to enable compile-time checks in your own functions with `fmt::format_string` while avoiding template bloat. ::: fstring ::: format_string ::: runtime(string_view) ### Type Erasure You can create your own formatting function with compile-time checks and small binary footprint, for example ([run](https://godbolt.org/z/b9Pbasvzc)): ```c++ #include void vlog(const char* file, int line, fmt::string_view fmt, fmt::format_args args) { fmt::print("{}: {}: {}", file, line, fmt::vformat(fmt, args)); } template void log(const char* file, int line, fmt::format_string fmt, T&&... args) { vlog(file, line, fmt, fmt::make_format_args(args...)); } #define MY_LOG(fmt, ...) log(__FILE__, __LINE__, fmt, __VA_ARGS__) MY_LOG("invalid squishiness: {}", 42); ``` Note that `vlog` is not parameterized on argument types which improves compile times and reduces binary code size compared to a fully parameterized version. ::: make_format_args(T&...) ::: basic_format_args ::: format_args ::: basic_format_arg ### Named Arguments ::: arg(const Char*, const T&) Named arguments are not supported in compile-time checks at the moment. ### Compatibility ::: basic_string_view ::: string_view ## Format API `fmt/format.h` defines the full format API providing additional formatting functions and locale support. ::: format(format_string, T&&...) ::: vformat(string_view, format_args) ::: operator""_a() ### Utilities ::: ptr(T) ::: underlying(Enum) ::: to_string(const T&) ::: group_digits(T) ::: detail::buffer ::: basic_memory_buffer ### System Errors {fmt} does not use `errno` to communicate errors to the user, but it may call system functions which set `errno`. Users should not make any assumptions about the value of `errno` being preserved by library functions. ::: system_error ::: format_system_error ### Custom Allocators The {fmt} library supports custom dynamic memory allocators. A custom allocator class can be specified as a template argument to [`fmt::basic_memory_buffer`](#basic_memory_buffer): using custom_memory_buffer = fmt::basic_memory_buffer; It is also possible to write a formatting function that uses a custom allocator: using custom_string = std::basic_string, custom_allocator>; auto vformat(custom_allocator alloc, fmt::string_view fmt, fmt::format_args args) -> custom_string { auto buf = custom_memory_buffer(alloc); fmt::vformat_to(std::back_inserter(buf), fmt, args); return custom_string(buf.data(), buf.size(), alloc); } template auto format(custom_allocator alloc, fmt::string_view fmt, const Args& ... args) -> custom_string { return vformat(alloc, fmt, fmt::make_format_args(args...)); } The allocator will be used for the output container only. Formatting functions normally don't do any allocations for built-in and string types except for non-default floating-point formatting that occasionally falls back on `sprintf`. ### Locale All formatting is locale-independent by default. Use the `'L'` format specifier to insert the appropriate number separator characters from the locale: #include #include std::locale::global(std::locale("en_US.UTF-8")); auto s = fmt::format("{:L}", 1000000); // s == "1,000,000" `fmt/format.h` provides the following overloads of formatting functions that take `std::locale` as a parameter. The locale type is a template parameter to avoid the expensive `` include. ::: format(detail::locale_ref, format_string, T&&...) ::: format_to(OutputIt, detail::locale_ref, format_string, T&&...) ::: formatted_size(detail::locale_ref, format_string, T&&...) ### Legacy Compile-Time Checks `FMT_STRING` enables compile-time checks on older compilers. It requires C++14 or later and is a no-op in C++11. ::: FMT_STRING To force the use of legacy compile-time checks, define the preprocessor variable `FMT_ENFORCE_COMPILE_STRING`. When set, functions accepting `FMT_STRING` will fail to compile with regular strings. ## Range and Tuple Formatting `fmt/ranges.h` provides formatting support for ranges and tuples: #include fmt::print("{}", std::tuple{'a', 42}); // Output: ('a', 42) Using `fmt::join`, you can separate tuple elements with a custom separator: #include auto t = std::tuple{1, 'a'}; fmt::print("{}", fmt::join(t, ", ")); // Output: 1, a ::: join(Range&&, string_view) ::: join(It, Sentinel, string_view) ::: join(std::initializer_list, string_view) ## Date and Time Formatting `fmt/chrono.h` provides formatters for - [`std::chrono::duration`](https://en.cppreference.com/w/cpp/chrono/duration) - [`std::chrono::time_point`]( https://en.cppreference.com/w/cpp/chrono/time_point) - [`std::tm`](https://en.cppreference.com/w/cpp/chrono/c/tm) The format syntax is described in [Chrono Format Specifications](syntax.md# chrono-format-specifications). **Example**: #include int main() { std::time_t t = std::time(nullptr); fmt::print("The date is {:%Y-%m-%d}.", fmt::localtime(t)); // Output: The date is 2020-11-07. // (with 2020-11-07 replaced by the current date) using namespace std::literals::chrono_literals; fmt::print("Default format: {} {}\n", 42s, 100ms); // Output: Default format: 42s 100ms fmt::print("strftime-like format: {:%H:%M:%S}\n", 3h + 15min + 30s); // Output: strftime-like format: 03:15:30 } ::: localtime(std::time_t) ::: gmtime(std::time_t) ## Standard Library Types Formatting `fmt/std.h` provides formatters for: - [`std::atomic`](https://en.cppreference.com/w/cpp/atomic/atomic) - [`std::atomic_flag`](https://en.cppreference.com/w/cpp/atomic/atomic_flag) - [`std::bitset`](https://en.cppreference.com/w/cpp/utility/bitset) - [`std::error_code`](https://en.cppreference.com/w/cpp/error/error_code) - [`std::exception`](https://en.cppreference.com/w/cpp/error/exception) - [`std::filesystem::path`](https://en.cppreference.com/w/cpp/filesystem/path) - [`std::monostate`]( https://en.cppreference.com/w/cpp/utility/variant/monostate) - [`std::optional`](https://en.cppreference.com/w/cpp/utility/optional) - [`std::source_location`]( https://en.cppreference.com/w/cpp/utility/source_location) - [`std::thread::id`](https://en.cppreference.com/w/cpp/thread/thread/id) - [`std::variant`](https://en.cppreference.com/w/cpp/utility/variant/variant) ::: ptr(const std::unique_ptr&) ::: ptr(const std::shared_ptr&) ### Variants A `std::variant` is only formattable if every variant alternative is formattable, and requires the `__cpp_lib_variant` [library feature](https://en.cppreference.com/w/cpp/feature_test). **Example**: #include fmt::print("{}", std::variant('x')); // Output: variant('x') fmt::print("{}", std::variant()); // Output: variant(monostate) ## Bit-Fields and Packed Structs To format a bit-field or a field of a struct with `__attribute__((packed))` applied to it, you need to convert it to the underlying or compatible type via a cast or a unary `+` ([godbolt](https://www.godbolt.org/z/3qKKs6T5Y)): ```c++ struct smol { int bit : 1; }; auto s = smol(); fmt::print("{}", +s.bit); ``` This is a known limitation of "perfect" forwarding in C++. ## Format String Compilation `fmt/compile.h` provides format string compilation and compile-time (`constexpr`) formatting enabled via the `FMT_COMPILE` macro or the `_cf` user-defined literal defined in namespace `fmt::literals`. Format strings marked with `FMT_COMPILE` or `_cf` are parsed, checked and converted into efficient formatting code at compile-time. This supports arguments of built-in and string types as well as user-defined types with `format` functions taking the format context type as a template parameter in their `formatter` specializations. For example: template <> struct fmt::formatter { constexpr auto parse(format_parse_context& ctx); template auto format(const point& p, FormatContext& ctx) const; }; Format string compilation can generate more binary code compared to the default API and is only recommended in places where formatting is a performance bottleneck. ::: FMT_COMPILE ::: operator""_cf ## Terminal Colors and Text Styles `fmt/color.h` provides support for terminal color and text style output. ::: print(const text_style&, format_string, T&&...) ::: fg(detail::color_type) ::: bg(detail::color_type) ::: styled(const T&, text_style) ## System APIs ::: ostream ::: windows_error ## `std::ostream` Support `fmt/ostream.h` provides `std::ostream` support including formatting of user-defined types that have an overloaded insertion operator (`operator<<`). In order to make a type formattable via `std::ostream` you should provide a `formatter` specialization inherited from `ostream_formatter`: #include struct date { int year, month, day; friend std::ostream& operator<<(std::ostream& os, const date& d) { return os << d.year << '-' << d.month << '-' << d.day; } }; template <> struct fmt::formatter : ostream_formatter {}; std::string s = fmt::format("The date is {}", date{2012, 12, 9}); // s == "The date is 2012-12-9" ::: streamed(const T&) ::: print(std::ostream&, format_string, T&&...) ## Dynamic Argument Lists The header `fmt/args.h` provides `dynamic_format_arg_store`, a builder-like API that can be used to construct format argument lists dynamically. ::: dynamic_format_arg_store ## Safe `printf` The header `fmt/printf.h` provides `printf`-like formatting functionality. The following functions use [printf format string syntax](https://pubs.opengroup.org/onlinepubs/009695399/functions/fprintf.html) with the POSIX extension for positional arguments. Unlike their standard counterparts, the `fmt` functions are type-safe and throw an exception if an argument type doesn't match its format specification. ::: printf(string_view, const T&...) ::: fprintf(std::FILE*, const S&, const T&...) ::: sprintf(const S&, const T&...) ## Wide Strings The optional header `fmt/xchar.h` provides support for `wchar_t` and exotic character types. ::: is_char ::: wstring_view ::: wformat_context ::: to_wstring(const T&) ## Compatibility with C++20 `std::format` {fmt} implements nearly all of the [C++20 formatting library](https://en.cppreference.com/w/cpp/utility/format) with the following differences: - Names are defined in the `fmt` namespace instead of `std` to avoid collisions with standard library implementations. - Width calculation doesn't use grapheme clusterization. The latter has been implemented in a separate branch but hasn't been integrated yet. openal-soft-1.24.2/fmt-11.1.1/doc/fmt.css000066400000000000000000000023631474041540300174050ustar00rootroot00000000000000:root { --md-primary-fg-color: #0050D0; } .md-grid { max-width: 960px; } @media (min-width: 400px) { .md-tabs { display: block; } } .docblock { border-left: .05rem solid var(--md-primary-fg-color); } .docblock-desc { margin-left: 1em; } pre > code.decl { white-space: pre-wrap; } code.decl > div { text-indent: -2ch; /* Negative indent to counteract the indent on the first line */ padding-left: 2ch; /* Add padding to the left to create an indent */ } .features-container { display: flex; flex-wrap: wrap; gap: 20px; justify-content: center; /* Center the items horizontally */ } .feature { flex: 1 1 calc(50% - 20px); /* Two columns with space between */ max-width: 600px; /* Set the maximum width for the feature boxes */ box-sizing: border-box; padding: 10px; overflow: hidden; /* Hide overflow content */ text-overflow: ellipsis; /* Handle text overflow */ white-space: normal; /* Allow text wrapping */ } .feature h2 { margin-top: 0px; font-weight: bold; } @media (max-width: 768px) { .feature { flex: 1 1 100%; /* Stack columns on smaller screens */ max-width: 100%; /* Allow full width on smaller screens */ white-space: normal; /* Allow text wrapping on smaller screens */ } } openal-soft-1.24.2/fmt-11.1.1/doc/fmt.js000066400000000000000000000001141474041540300172210ustar00rootroot00000000000000document$.subscribe(() => { hljs.highlightAll(), { language: 'c++' } }) openal-soft-1.24.2/fmt-11.1.1/doc/get-started.md000066400000000000000000000152461474041540300206560ustar00rootroot00000000000000# Get Started Compile and run {fmt} examples online with [Compiler Explorer]( https://godbolt.org/z/P7h6cd6o3). {fmt} is compatible with any build system. The next section describes its usage with CMake, while the [Build Systems](#build-systems) section covers the rest. ## CMake {fmt} provides two CMake targets: `fmt::fmt` for the compiled library and `fmt::fmt-header-only` for the header-only library. It is recommended to use the compiled library for improved build times. There are three primary ways to use {fmt} with CMake: * **FetchContent**: Starting from CMake 3.11, you can use [`FetchContent`]( https://cmake.org/cmake/help/v3.30/module/FetchContent.html) to automatically download {fmt} as a dependency at configure time: include(FetchContent) FetchContent_Declare( fmt GIT_REPOSITORY https://github.com/fmtlib/fmt GIT_TAG e69e5f977d458f2650bb346dadf2ad30c5320281) # 10.2.1 FetchContent_MakeAvailable(fmt) target_link_libraries( fmt::fmt) * **Installed**: You can find and use an [installed](#installation) version of {fmt} in your `CMakeLists.txt` file as follows: find_package(fmt) target_link_libraries( fmt::fmt) * **Embedded**: You can add the {fmt} source tree to your project and include it in your `CMakeLists.txt` file: add_subdirectory(fmt) target_link_libraries( fmt::fmt) ## Installation ### Debian/Ubuntu To install {fmt} on Debian, Ubuntu, or any other Debian-based Linux distribution, use the following command: apt install libfmt-dev ### Homebrew Install {fmt} on macOS using [Homebrew](https://brew.sh/): brew install fmt ### Conda Install {fmt} on Linux, macOS, and Windows with [Conda]( https://docs.conda.io/en/latest/), using its [conda-forge package]( https://github.com/conda-forge/fmt-feedstock): conda install -c conda-forge fmt ### vcpkg Download and install {fmt} using the vcpkg package manager: git clone https://github.com/Microsoft/vcpkg.git cd vcpkg ./bootstrap-vcpkg.sh ./vcpkg integrate install ./vcpkg install fmt ## Building from Source CMake works by generating native makefiles or project files that can be used in the compiler environment of your choice. The typical workflow starts with: mkdir build # Create a directory to hold the build output. cd build cmake .. # Generate native build scripts. run in the `fmt` repository. If you are on a Unix-like system, you should now see a Makefile in the current directory. Now you can build the library by running `make`. Once the library has been built you can invoke `make test` to run the tests. You can control generation of the make `test` target with the `FMT_TEST` CMake option. This can be useful if you include fmt as a subdirectory in your project but don't want to add fmt's tests to your `test` target. To build a shared library set the `BUILD_SHARED_LIBS` CMake variable to `TRUE`: cmake -DBUILD_SHARED_LIBS=TRUE .. To build a static library with position-independent code (e.g. for linking it into another shared library such as a Python extension), set the `CMAKE_POSITION_INDEPENDENT_CODE` CMake variable to `TRUE`: cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. After building the library you can install it on a Unix-like system by running `sudo make install`. ### Building the Docs To build the documentation you need the following software installed on your system: - [Python](https://www.python.org/) - [Doxygen](http://www.stack.nl/~dimitri/doxygen/) - [MkDocs](https://www.mkdocs.org/) with `mkdocs-material`, `mkdocstrings`, `pymdown-extensions` and `mike` First generate makefiles or project files using CMake as described in the previous section. Then compile the `doc` target/project, for example: make doc This will generate the HTML documentation in `doc/html`. ## Build Systems ### build2 You can use [build2](https://build2.org), a dependency manager and a build system, to use {fmt}. Currently this package is available in these package repositories: - for released and published versions. - for unreleased or custom versions. **Usage:** - `build2` package name: `fmt` - Library target name: `lib{fmt}` To make your `build2` project depend on `fmt`: - Add one of the repositories to your configurations, or in your `repositories.manifest`, if not already there: : role: prerequisite location: https://pkg.cppget.org/1/stable - Add this package as a dependency to your `manifest` file (example for version 10): depends: fmt ~10.0.0 - Import the target and use it as a prerequisite to your own target using `fmt` in the appropriate `buildfile`: import fmt = fmt%lib{fmt} lib{mylib} : cxx{**} ... $fmt Then build your project as usual with `b` or `bdep update`. ### Meson [Meson WrapDB](https://mesonbuild.com/Wrapdb-projects.html) includes an `fmt` package. **Usage:** - Install the `fmt` subproject from the WrapDB by running: meson wrap install fmt from the root of your project. - In your project's `meson.build` file, add an entry for the new subproject: fmt = subproject('fmt') fmt_dep = fmt.get_variable('fmt_dep') - Include the new dependency object to link with fmt: my_build_target = executable( 'name', 'src/main.cc', dependencies: [fmt_dep]) **Options:** If desired, {fmt} can be built as a static library, or as a header-only library. For a static build, use the following subproject definition: fmt = subproject('fmt', default_options: 'default_library=static') fmt_dep = fmt.get_variable('fmt_dep') For the header-only version, use: fmt = subproject('fmt') fmt_dep = fmt.get_variable('fmt_header_only_dep') ### Android NDK {fmt} provides [Android.mk file]( https://github.com/fmtlib/fmt/blob/master/support/Android.mk) that can be used to build the library with [Android NDK]( https://developer.android.com/tools/sdk/ndk/index.html). ### Other To use the {fmt} library with any other build system, add `include/fmt/base.h`, `include/fmt/format.h`, `include/fmt/format-inl.h`, `src/format.cc` and optionally other headers from a [release archive]( https://github.com/fmtlib/fmt/releases) or the [git repository]( https://github.com/fmtlib/fmt) to your project, add `include` to include directories and make sure `src/format.cc` is compiled and linked with your code. openal-soft-1.24.2/fmt-11.1.1/doc/index.md000066400000000000000000000111311474041540300175270ustar00rootroot00000000000000--- hide: - navigation - toc --- # A modern formatting library

Safety

Inspired by Python's formatting facility, {fmt} provides a safe replacement for the printf family of functions. Errors in format strings, which are a common source of vulnerabilities in C, are reported at compile time. For example:

fmt::format("{:d}", "I am not a number");
will give a compile-time error because d is not a valid format specifier for strings. APIs like fmt::format prevent buffer overflow errors via automatic memory management.

→ Learn more

Extensibility

Formatting of most standard types, including all containers, dates, and times is supported out-of-the-box. For example:

fmt::print("{}", std::vector{1, 2, 3});
prints the vector in a JSON-like format:
[1, 2, 3]
You can make your own types formattable and even make compile-time checks work for them.

→ Learn more

Performance

{fmt} can be anywhere from tens of percent to 20-30 times faster than iostreams and sprintf, especially for numeric formatting. The library minimizes dynamic memory allocations and can optionally compile format strings to optimal code.

Unicode support

{fmt} provides portable Unicode support on major operating systems with UTF-8 and char strings. For example:

fmt::print("Слава Україні!");
will be printed correctly on Linux, macOS, and even Windows console, irrespective of the codepages.

The default is locale-independent, but you can opt into localized formatting and {fmt} makes it work with Unicode, addressing issues in the standard libary.

Fast compilation

The library makes extensive use of type erasure to achieve fast compilation. fmt/base.h provides a subset of the API with minimal include dependencies and enough functionality to replace all uses of *printf.

Code using {fmt} is usually several times faster to compile than the equivalent iostreams code, and while printf compiles faster still, the gap is narrowing.

→ Learn more

Small binary footprint

Type erasure is also used to prevent template bloat, resulting in compact per-call binary code. For example, a call to fmt::print with a single argument is just a few instructions, comparable to printf despite adding runtime safety, and much smaller than the equivalent iostreams code.

The library itself has small binary footprint and some components such as floating-point formatting can be disabled to make it even smaller for resource-constrained devices.

Portability

{fmt} has a small self-contained codebase with the core consisting of just three headers and no external dependencies.

The library is highly portable and requires only a minimal subset of C++11 features which are available in GCC 4.9, Clang 3.4, MSVC 19.10 (2017) and later. Newer compiler and standard library features are used if available, and enable additional functionality.

Where possible, the output of formatting functions is consistent across platforms.

Open source

{fmt} is in the top hundred open-source C++ libraries on GitHub and has hundreds of all-time contributors.

The library is distributed under a permissive MIT license and is relied upon by many open-source projects, including Blender, PyTorch, Apple's FoundationDB, Windows Terminal, MongoDB, and others.

openal-soft-1.24.2/fmt-11.1.1/doc/perf.svg000066400000000000000000000104011474041540300175520ustar00rootroot00000000000000double to string02505007501,0001,2501,500ostringstreamostrstreamsprintfdoubleconvfmtTime (ns), smaller is betteropenal-soft-1.24.2/fmt-11.1.1/doc/python-license.txt000066400000000000000000000350671474041540300216160ustar00rootroot00000000000000A. HISTORY OF THE SOFTWARE ========================== Python was created in the early 1990s by Guido van Rossum at Stichting Mathematisch Centrum (CWI, see http://www.cwi.nl) in the Netherlands as a successor of a language called ABC. Guido remains Python's principal author, although it includes many contributions from others. In 1995, Guido continued his work on Python at the Corporation for National Research Initiatives (CNRI, see http://www.cnri.reston.va.us) in Reston, Virginia where he released several versions of the software. In May 2000, Guido and the Python core development team moved to BeOpen.com to form the BeOpen PythonLabs team. In October of the same year, the PythonLabs team moved to Digital Creations (now Zope Corporation, see http://www.zope.com). In 2001, the Python Software Foundation (PSF, see http://www.python.org/psf/) was formed, a non-profit organization created specifically to own Python-related Intellectual Property. Zope Corporation is a sponsoring member of the PSF. All Python releases are Open Source (see http://www.opensource.org for the Open Source Definition). Historically, most, but not all, Python releases have also been GPL-compatible; the table below summarizes the various releases. Release Derived Year Owner GPL- from compatible? (1) 0.9.0 thru 1.2 1991-1995 CWI yes 1.3 thru 1.5.2 1.2 1995-1999 CNRI yes 1.6 1.5.2 2000 CNRI no 2.0 1.6 2000 BeOpen.com no 1.6.1 1.6 2001 CNRI yes (2) 2.1 2.0+1.6.1 2001 PSF no 2.0.1 2.0+1.6.1 2001 PSF yes 2.1.1 2.1+2.0.1 2001 PSF yes 2.2 2.1.1 2001 PSF yes 2.1.2 2.1.1 2002 PSF yes 2.1.3 2.1.2 2002 PSF yes 2.2.1 2.2 2002 PSF yes 2.2.2 2.2.1 2002 PSF yes 2.2.3 2.2.2 2003 PSF yes 2.3 2.2.2 2002-2003 PSF yes 2.3.1 2.3 2002-2003 PSF yes 2.3.2 2.3.1 2002-2003 PSF yes 2.3.3 2.3.2 2002-2003 PSF yes 2.3.4 2.3.3 2004 PSF yes 2.3.5 2.3.4 2005 PSF yes 2.4 2.3 2004 PSF yes 2.4.1 2.4 2005 PSF yes 2.4.2 2.4.1 2005 PSF yes 2.4.3 2.4.2 2006 PSF yes 2.4.4 2.4.3 2006 PSF yes 2.5 2.4 2006 PSF yes 2.5.1 2.5 2007 PSF yes 2.5.2 2.5.1 2008 PSF yes 2.5.3 2.5.2 2008 PSF yes 2.6 2.5 2008 PSF yes 2.6.1 2.6 2008 PSF yes 2.6.2 2.6.1 2009 PSF yes 2.6.3 2.6.2 2009 PSF yes 2.6.4 2.6.3 2009 PSF yes 2.6.5 2.6.4 2010 PSF yes 3.0 2.6 2008 PSF yes 3.0.1 3.0 2009 PSF yes 3.1 3.0.1 2009 PSF yes 3.1.1 3.1 2009 PSF yes 3.1.2 3.1.1 2010 PSF yes 3.1.3 3.1.2 2010 PSF yes 3.1.4 3.1.3 2011 PSF yes 3.2 3.1 2011 PSF yes 3.2.1 3.2 2011 PSF yes 3.2.2 3.2.1 2011 PSF yes 3.2.3 3.2.2 2012 PSF yes 3.3.0 3.2 2012 PSF yes Footnotes: (1) GPL-compatible doesn't mean that we're distributing Python under the GPL. All Python licenses, unlike the GPL, let you distribute a modified version without making your changes open source. The GPL-compatible licenses make it possible to combine Python with other software that is released under the GPL; the others don't. (2) According to Richard Stallman, 1.6.1 is not GPL-compatible, because its license has a choice of law clause. According to CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1 is "not incompatible" with the GPL. Thanks to the many outside volunteers who have worked under Guido's direction to make these releases possible. B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON =============================================================== PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 -------------------------------------------- 1. This LICENSE AGREEMENT is between the Python Software Foundation ("PSF"), and the Individual or Organization ("Licensee") accessing and otherwise using this software ("Python") in source or binary form and its associated documentation. 2. Subject to the terms and conditions of this License Agreement, PSF hereby grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, analyze, test, perform and/or display publicly, prepare derivative works, distribute, and otherwise use Python alone or in any derivative version, provided, however, that PSF's License Agreement and PSF's notice of copyright, i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 Python Software Foundation; All Rights Reserved" are retained in Python alone or in any derivative version prepared by Licensee. 3. In the event Licensee prepares a derivative work that is based on or incorporates Python or any part thereof, and wants to make the derivative work available to others as provided herein, then Licensee hereby agrees to include in any such work a brief summary of the changes made to Python. 4. PSF is making Python available to Licensee on an "AS IS" basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT INFRINGE ANY THIRD PARTY RIGHTS. 5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. 6. This License Agreement will automatically terminate upon a material breach of its terms and conditions. 7. Nothing in this License Agreement shall be deemed to create any relationship of agency, partnership, or joint venture between PSF and Licensee. This License Agreement does not grant permission to use PSF trademarks or trade name in a trademark sense to endorse or promote products or services of Licensee, or any third party. 8. By copying, installing or otherwise using Python, Licensee agrees to be bound by the terms and conditions of this License Agreement. BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0 ------------------------------------------- BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1 1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the Individual or Organization ("Licensee") accessing and otherwise using this software in source or binary form and its associated documentation ("the Software"). 2. Subject to the terms and conditions of this BeOpen Python License Agreement, BeOpen hereby grants Licensee a non-exclusive, royalty-free, world-wide license to reproduce, analyze, test, perform and/or display publicly, prepare derivative works, distribute, and otherwise use the Software alone or in any derivative version, provided, however, that the BeOpen Python License is retained in the Software, alone or in any derivative version prepared by Licensee. 3. BeOpen is making the Software available to Licensee on an "AS IS" basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT INFRINGE ANY THIRD PARTY RIGHTS. 4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. 5. This License Agreement will automatically terminate upon a material breach of its terms and conditions. 6. This License Agreement shall be governed by and interpreted in all respects by the law of the State of California, excluding conflict of law provisions. Nothing in this License Agreement shall be deemed to create any relationship of agency, partnership, or joint venture between BeOpen and Licensee. This License Agreement does not grant permission to use BeOpen trademarks or trade names in a trademark sense to endorse or promote products or services of Licensee, or any third party. As an exception, the "BeOpen Python" logos available at http://www.pythonlabs.com/logos.html may be used according to the permissions granted on that web page. 7. By copying, installing or otherwise using the software, Licensee agrees to be bound by the terms and conditions of this License Agreement. CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1 --------------------------------------- 1. This LICENSE AGREEMENT is between the Corporation for National Research Initiatives, having an office at 1895 Preston White Drive, Reston, VA 20191 ("CNRI"), and the Individual or Organization ("Licensee") accessing and otherwise using Python 1.6.1 software in source or binary form and its associated documentation. 2. Subject to the terms and conditions of this License Agreement, CNRI hereby grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, analyze, test, perform and/or display publicly, prepare derivative works, distribute, and otherwise use Python 1.6.1 alone or in any derivative version, provided, however, that CNRI's License Agreement and CNRI's notice of copyright, i.e., "Copyright (c) 1995-2001 Corporation for National Research Initiatives; All Rights Reserved" are retained in Python 1.6.1 alone or in any derivative version prepared by Licensee. Alternately, in lieu of CNRI's License Agreement, Licensee may substitute the following text (omitting the quotes): "Python 1.6.1 is made available subject to the terms and conditions in CNRI's License Agreement. This Agreement together with Python 1.6.1 may be located on the Internet using the following unique, persistent identifier (known as a handle): 1895.22/1013. This Agreement may also be obtained from a proxy server on the Internet using the following URL: http://hdl.handle.net/1895.22/1013". 3. In the event Licensee prepares a derivative work that is based on or incorporates Python 1.6.1 or any part thereof, and wants to make the derivative work available to others as provided herein, then Licensee hereby agrees to include in any such work a brief summary of the changes made to Python 1.6.1. 4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS" basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT INFRINGE ANY THIRD PARTY RIGHTS. 5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON 1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1, OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. 6. This License Agreement will automatically terminate upon a material breach of its terms and conditions. 7. This License Agreement shall be governed by the federal intellectual property law of the United States, including without limitation the federal copyright law, and, to the extent such U.S. federal law does not apply, by the law of the Commonwealth of Virginia, excluding Virginia's conflict of law provisions. Notwithstanding the foregoing, with regard to derivative works based on Python 1.6.1 that incorporate non-separable material that was previously distributed under the GNU General Public License (GPL), the law of the Commonwealth of Virginia shall govern this License Agreement only as to issues arising under or with respect to Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this License Agreement shall be deemed to create any relationship of agency, partnership, or joint venture between CNRI and Licensee. This License Agreement does not grant permission to use CNRI trademarks or trade name in a trademark sense to endorse or promote products or services of Licensee, or any third party. 8. By clicking on the "ACCEPT" button where indicated, or by copying, installing or otherwise using Python 1.6.1, Licensee agrees to be bound by the terms and conditions of this License Agreement. ACCEPT CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2 -------------------------------------------------- Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam, The Netherlands. All rights reserved. Permission to use, copy, modify, and distribute this software and its documentation for any purpose and without fee is hereby granted, provided that the above copyright notice appear in all copies and that both that copyright notice and this permission notice appear in supporting documentation, and that the name of Stichting Mathematisch Centrum or CWI not be used in advertising or publicity pertaining to distribution of the software without specific, written prior permission. STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. openal-soft-1.24.2/fmt-11.1.1/doc/syntax.md000066400000000000000000000665041474041540300177640ustar00rootroot00000000000000# Format String Syntax Formatting functions such as [`fmt::format`](api.md#format) and [`fmt::print`]( api.md#print) use the same format string syntax described in this section. Format strings contain "replacement fields" surrounded by curly braces `{}`. Anything that is not contained in braces is considered literal text, which is copied unchanged to the output. If you need to include a brace character in the literal text, it can be escaped by doubling: `{{` and `}}`. The grammar for a replacement field is as follows:
replacement_field ::= "{" [arg_id] [":" (format_spec | chrono_format_spec)] "}"
arg_id            ::= integer | identifier
integer           ::= digit+
digit             ::= "0"..."9"
identifier        ::= id_start id_continue*
id_start          ::= "a"..."z" | "A"..."Z" | "_"
id_continue       ::= id_start | digit
In less formal terms, the replacement field can start with an *arg_id* that specifies the argument whose value is to be formatted and inserted into the output instead of the replacement field. The *arg_id* is optionally followed by a *format_spec*, which is preceded by a colon `':'`. These specify a non-default format for the replacement value. See also the [Format Specification Mini-Language](#format-specification-mini-language) section. If the numerical arg_ids in a format string are 0, 1, 2, ... in sequence, they can all be omitted (not just some) and the numbers 0, 1, 2, ... will be automatically inserted in that order. Named arguments can be referred to by their names or indices. Some simple format string examples: ```c++ "First, thou shalt count to {0}" // References the first argument "Bring me a {}" // Implicitly references the first argument "From {} to {}" // Same as "From {0} to {1}" ``` The *format_spec* field contains a specification of how the value should be presented, including such details as field width, alignment, padding, decimal precision and so on. Each value type can define its own "formatting mini-language" or interpretation of the *format_spec*. Most built-in types support a common formatting mini-language, which is described in the next section. A *format_spec* field can also include nested replacement fields in certain positions within it. These nested replacement fields can contain only an argument id; format specifications are not allowed. This allows the formatting of a value to be dynamically specified. See the [Format Examples](#format-examples) section for some examples. ## Format Specification Mini-Language "Format specifications" are used within replacement fields contained within a format string to define how individual values are presented. Each formattable type may define how the format specification is to be interpreted. Most built-in types implement the following options for format specifications, although some of the formatting options are only supported by the numeric types. The general form of a *standard format specifier* is:
format_spec ::= [[fill]align][sign]["#"]["0"][width]["." precision]["L"][type]
fill        ::= <a character other than '{' or '}'>
align       ::= "<" | ">" | "^"
sign        ::= "+" | "-" | " "
width       ::= integer | "{" [arg_id] "}"
precision   ::= integer | "{" [arg_id] "}"
type        ::= "a" | "A" | "b" | "B" | "c" | "d" | "e" | "E" | "f" | "F" |
                "g" | "G" | "o" | "p" | "s" | "x" | "X" | "?"
The *fill* character can be any Unicode code point other than `'{'` or `'}'`. The presence of a fill character is signaled by the character following it, which must be one of the alignment options. If the second character of *format_spec* is not a valid alignment option, then it is assumed that both the fill character and the alignment option are absent. The meaning of the various alignment options is as follows:
Option Meaning
'<' Forces the field to be left-aligned within the available space (this is the default for most objects).
'>' Forces the field to be right-aligned within the available space (this is the default for numbers).
'^' Forces the field to be centered within the available space.
Note that unless a minimum field width is defined, the field width will always be the same size as the data to fill it, so that the alignment option has no meaning in this case. The *sign* option is only valid for floating point and signed integer types, and can be one of the following:
Option Meaning
'+' Indicates that a sign should be used for both nonnegative as well as negative numbers.
'-' Indicates that a sign should be used only for negative numbers (this is the default behavior).
space Indicates that a leading space should be used on nonnegative numbers, and a minus sign on negative numbers.
The `'#'` option causes the "alternate form" to be used for the conversion. The alternate form is defined differently for different types. This option is only valid for integer and floating-point types. For integers, when binary, octal, or hexadecimal output is used, this option adds the prefix respective `"0b"` (`"0B"`), `"0"`, or `"0x"` (`"0X"`) to the output value. Whether the prefix is lower-case or upper-case is determined by the case of the type specifier, for example, the prefix `"0x"` is used for the type `'x'` and `"0X"` is used for `'X'`. For floating-point numbers the alternate form causes the result of the conversion to always contain a decimal-point character, even if no digits follow it. Normally, a decimal-point character appears in the result of these conversions only if a digit follows it. In addition, for `'g'` and `'G'` conversions, trailing zeros are not removed from the result. *width* is a decimal integer defining the minimum field width. If not specified, then the field width will be determined by the content. Preceding the *width* field by a zero (`'0'`) character enables sign-aware zero-padding for numeric types. It forces the padding to be placed after the sign or base (if any) but before the digits. This is used for printing fields in the form "+000000120". This option is only valid for numeric types and it has no effect on formatting of infinity and NaN. This option is ignored when any alignment specifier is present. The *precision* is a decimal number indicating how many digits should be displayed after the decimal point for a floating-point value formatted with `'f'` and `'F'`, or before and after the decimal point for a floating-point value formatted with `'g'` or `'G'`. For non-number types the field indicates the maximum field size - in other words, how many characters will be used from the field content. The *precision* is not allowed for integer, character, Boolean, and pointer values. Note that a C string must be null-terminated even if precision is specified. The `'L'` option uses the current locale setting to insert the appropriate number separator characters. This option is only valid for numeric types. Finally, the *type* determines how the data should be presented. The available string presentation types are:
Type Meaning
's' String format. This is the default type for strings and may be omitted.
'?' Debug format. The string is quoted and special characters escaped.
none The same as 's'.
The available character presentation types are:
Type Meaning
'c' Character format. This is the default type for characters and may be omitted.
'?' Debug format. The character is quoted and special characters escaped.
none The same as 'c'.
The available integer presentation types are:
Type Meaning
'b' Binary format. Outputs the number in base 2. Using the '#' option with this type adds the prefix "0b" to the output value.
'B' Binary format. Outputs the number in base 2. Using the '#' option with this type adds the prefix "0B" to the output value.
'c' Character format. Outputs the number as a character.
'd' Decimal integer. Outputs the number in base 10.
'o' Octal format. Outputs the number in base 8.
'x' Hex format. Outputs the number in base 16, using lower-case letters for the digits above 9. Using the '#' option with this type adds the prefix "0x" to the output value.
'X' Hex format. Outputs the number in base 16, using upper-case letters for the digits above 9. Using the '#' option with this type adds the prefix "0X" to the output value.
none The same as 'd'.
Integer presentation types can also be used with character and Boolean values with the only exception that `'c'` cannot be used with `bool`. Boolean values are formatted using textual representation, either `true` or `false`, if the presentation type is not specified. The available presentation types for floating-point values are:
Type Meaning
'a' Hexadecimal floating point format. Prints the number in base 16 with prefix "0x" and lower-case letters for digits above 9. Uses 'p' to indicate the exponent.
'A' Same as 'a' except it uses upper-case letters for the prefix, digits above 9 and to indicate the exponent.
'e' Exponent notation. Prints the number in scientific notation using the letter 'e' to indicate the exponent.
'E' Exponent notation. Same as 'e' except it uses an upper-case 'E' as the separator character.
'f' Fixed point. Displays the number as a fixed-point number.
'F' Fixed point. Same as 'f', but converts nan to NAN and inf to INF.
'g'

General format. For a given precision p >= 1, this rounds the number to p significant digits and then formats the result in either fixed-point format or in scientific notation, depending on its magnitude.

A precision of 0 is treated as equivalent to a precision of 1.

'G' General format. Same as 'g' except switches to 'E' if the number gets too large. The representations of infinity and NaN are uppercased, too.
none Similar to 'g', except that the default precision is as high as needed to represent the particular value.
The available presentation types for pointers are:
Type Meaning
'p' Pointer format. This is the default type for pointers and may be omitted.
none The same as 'p'.
## Chrono Format Specifications Format specifications for chrono duration and time point types as well as `std::tm` have the following syntax:
chrono_format_spec ::= [[fill]align][width]["." precision][chrono_specs]
chrono_specs       ::= conversion_spec |
                       chrono_specs (conversion_spec | literal_char)
conversion_spec    ::= "%" [padding_modifier] [locale_modifier] chrono_type
literal_char       ::= <a character other than '{', '}' or '%'>
padding_modifier   ::= "-" | "_"  | "0"
locale_modifier    ::= "E" | "O"
chrono_type        ::= "a" | "A" | "b" | "B" | "c" | "C" | "d" | "D" | "e" |
                       "F" | "g" | "G" | "h" | "H" | "I" | "j" | "m" | "M" |
                       "n" | "p" | "q" | "Q" | "r" | "R" | "S" | "t" | "T" |
                       "u" | "U" | "V" | "w" | "W" | "x" | "X" | "y" | "Y" |
                       "z" | "Z" | "%"
Literal chars are copied unchanged to the output. Precision is valid only for `std::chrono::duration` types with a floating-point representation type. The available presentation types (*chrono_type*) are:
Type Meaning
'a' The abbreviated weekday name, e.g. "Sat". If the value does not contain a valid weekday, an exception of type format_error is thrown.
'A' The full weekday name, e.g. "Saturday". If the value does not contain a valid weekday, an exception of type format_error is thrown.
'b' The abbreviated month name, e.g. "Nov". If the value does not contain a valid month, an exception of type format_error is thrown.
'B' The full month name, e.g. "November". If the value does not contain a valid month, an exception of type format_error is thrown.
'c' The date and time representation, e.g. "Sat Nov 12 22:04:00 1955". The modified command %Ec produces the locale's alternate date and time representation.
'C' The year divided by 100 using floored division, e.g. "19". If the result is a single decimal digit, it is prefixed with 0. The modified command %EC produces the locale's alternative representation of the century.
'd' The day of month as a decimal number. If the result is a single decimal digit, it is prefixed with 0. The modified command %Od produces the locale's alternative representation.
'D' Equivalent to %m/%d/%y, e.g. "11/12/55".
'e' The day of month as a decimal number. If the result is a single decimal digit, it is prefixed with a space. The modified command %Oe produces the locale's alternative representation.
'F' Equivalent to %Y-%m-%d, e.g. "1955-11-12".
'g' The last two decimal digits of the ISO week-based year. If the result is a single digit it is prefixed by 0.
'G' The ISO week-based year as a decimal number. If the result is less than four digits it is left-padded with 0 to four digits.
'h' Equivalent to %b, e.g. "Nov".
'H' The hour (24-hour clock) as a decimal number. If the result is a single digit, it is prefixed with 0. The modified command %OH produces the locale's alternative representation.
'I' The hour (12-hour clock) as a decimal number. If the result is a single digit, it is prefixed with 0. The modified command %OI produces the locale's alternative representation.
'j' If the type being formatted is a specialization of duration, the decimal number of days without padding. Otherwise, the day of the year as a decimal number. Jan 1 is 001. If the result is less than three digits, it is left-padded with 0 to three digits.
'm' The month as a decimal number. Jan is 01. If the result is a single digit, it is prefixed with 0. The modified command %Om produces the locale's alternative representation.
'M' The minute as a decimal number. If the result is a single digit, it is prefixed with 0. The modified command %OM produces the locale's alternative representation.
'n' A new-line character.
'p' The AM/PM designations associated with a 12-hour clock.
'q' The duration's unit suffix.
'Q' The duration's numeric value (as if extracted via .count()).
'r' The 12-hour clock time, e.g. "10:04:00 PM".
'R' Equivalent to %H:%M, e.g. "22:04".
'S' Seconds as a decimal number. If the number of seconds is less than 10, the result is prefixed with 0. If the precision of the input cannot be exactly represented with seconds, then the format is a decimal floating-point number with a fixed format and a precision matching that of the precision of the input (or to a microseconds precision if the conversion to floating-point decimal seconds cannot be made within 18 fractional digits). The modified command %OS produces the locale's alternative representation.
't' A horizontal-tab character.
'T' Equivalent to %H:%M:%S.
'u' The ISO weekday as a decimal number (1-7), where Monday is 1. The modified command %Ou produces the locale's alternative representation.
'U' The week number of the year as a decimal number. The first Sunday of the year is the first day of week 01. Days of the same year prior to that are in week 00. If the result is a single digit, it is prefixed with 0. The modified command %OU produces the locale's alternative representation.
'V' The ISO week-based week number as a decimal number. If the result is a single digit, it is prefixed with 0. The modified command %OV produces the locale's alternative representation.
'w' The weekday as a decimal number (0-6), where Sunday is 0. The modified command %Ow produces the locale's alternative representation.
'W' The week number of the year as a decimal number. The first Monday of the year is the first day of week 01. Days of the same year prior to that are in week 00. If the result is a single digit, it is prefixed with 0. The modified command %OW produces the locale's alternative representation.
'x' The date representation, e.g. "11/12/55". The modified command %Ex produces the locale's alternate date representation.
'X' The time representation, e.g. "10:04:00". The modified command %EX produces the locale's alternate time representation.
'y' The last two decimal digits of the year. If the result is a single digit it is prefixed by 0. The modified command %Oy produces the locale's alternative representation. The modified command %Ey produces the locale's alternative representation of offset from %EC (year only).
'Y' The year as a decimal number. If the result is less than four digits it is left-padded with 0 to four digits. The modified command %EY produces the locale's alternative full year representation.
'z' The offset from UTC in the ISO 8601:2004 format. For example -0430 refers to 4 hours 30 minutes behind UTC. If the offset is zero, +0000 is used. The modified commands %Ez and %Oz insert a : between the hours and minutes: -04:30. If the offset information is not available, an exception of type format_error is thrown.
'Z' The time zone abbreviation. If the time zone abbreviation is not available, an exception of type format_error is thrown.
'%' A % character.
Specifiers that have a calendaric component such as `'d'` (the day of month) are valid only for `std::tm` and time points but not durations. The available padding modifiers (*padding_modifier*) are: | Type | Meaning | |-------|-----------------------------------------| | `'_'` | Pad a numeric result with spaces. | | `'-'` | Do not pad a numeric result string. | | `'0'` | Pad a numeric result string with zeros. | These modifiers are only supported for the `'H'`, `'I'`, `'M'`, `'S'`, `'U'`, `'V'`, `'W'`, `'Y'`, `'d'`, `'j'` and `'m'` presentation types. ## Range Format Specifications Format specifications for range types have the following syntax:
range_format_spec ::= ["n"][range_type][range_underlying_spec]
The `'n'` option formats the range without the opening and closing brackets. The available presentation types for `range_type` are: | Type | Meaning | |--------|------------------------------------------------------------| | none | Default format. | | `'s'` | String format. The range is formatted as a string. | | `'?â s'` | Debug format. The range is formatted as an escaped string. | If `range_type` is `'s'` or `'?s'`, the range element type must be a character type. The `'n'` option and `range_underlying_spec` are mutually exclusive with `'s'` and `'?s'`. The `range_underlying_spec` is parsed based on the formatter of the range's element type. By default, a range of characters or strings is printed escaped and quoted. But if any `range_underlying_spec` is provided (even if it is empty), then the characters or strings are printed according to the provided specification. Examples: ```c++ fmt::print("{}", std::vector{10, 20, 30}); // Output: [10, 20, 30] fmt::print("{::#x}", std::vector{10, 20, 30}); // Output: [0xa, 0x14, 0x1e] fmt::print("{}", std::vector{'h', 'e', 'l', 'l', 'o'}); // Output: ['h', 'e', 'l', 'l', 'o'] fmt::print("{:n}", std::vector{'h', 'e', 'l', 'l', 'o'}); // Output: 'h', 'e', 'l', 'l', 'o' fmt::print("{:s}", std::vector{'h', 'e', 'l', 'l', 'o'}); // Output: "hello" fmt::print("{:?s}", std::vector{'h', 'e', 'l', 'l', 'o', '\n'}); // Output: "hello\n" fmt::print("{::}", std::vector{'h', 'e', 'l', 'l', 'o'}); // Output: [h, e, l, l, o] fmt::print("{::d}", std::vector{'h', 'e', 'l', 'l', 'o'}); // Output: [104, 101, 108, 108, 111] ``` ## Format Examples This section contains examples of the format syntax and comparison with the printf formatting. In most of the cases the syntax is similar to the printf formatting, with the addition of the `{}` and with `:` used instead of `%`. For example, `"%03.2f"` can be translated to `"{:03.2f}"`. The new format syntax also supports new and different options, shown in the following examples. Accessing arguments by position: ```c++ fmt::format("{0}, {1}, {2}", 'a', 'b', 'c'); // Result: "a, b, c" fmt::format("{}, {}, {}", 'a', 'b', 'c'); // Result: "a, b, c" fmt::format("{2}, {1}, {0}", 'a', 'b', 'c'); // Result: "c, b, a" fmt::format("{0}{1}{0}", "abra", "cad"); // arguments' indices can be repeated // Result: "abracadabra" ``` Aligning the text and specifying a width: ```c++ fmt::format("{:<30}", "left aligned"); // Result: "left aligned " fmt::format("{:>30}", "right aligned"); // Result: " right aligned" fmt::format("{:^30}", "centered"); // Result: " centered " fmt::format("{:*^30}", "centered"); // use '*' as a fill char // Result: "***********centered***********" ``` Dynamic width: ```c++ fmt::format("{:<{}}", "left aligned", 30); // Result: "left aligned " ``` Dynamic precision: ```c++ fmt::format("{:.{}f}", 3.14, 1); // Result: "3.1" ``` Replacing `%+f`, `%-f`, and `% f` and specifying a sign: ```c++ fmt::format("{:+f}; {:+f}", 3.14, -3.14); // show it always // Result: "+3.140000; -3.140000" fmt::format("{: f}; {: f}", 3.14, -3.14); // show a space for positive numbers // Result: " 3.140000; -3.140000" fmt::format("{:-f}; {:-f}", 3.14, -3.14); // show only the minus -- same as '{:f}; {:f}' // Result: "3.140000; -3.140000" ``` Replacing `%x` and `%o` and converting the value to different bases: ```c++ fmt::format("int: {0:d}; hex: {0:x}; oct: {0:o}; bin: {0:b}", 42); // Result: "int: 42; hex: 2a; oct: 52; bin: 101010" // with 0x or 0 or 0b as prefix: fmt::format("int: {0:d}; hex: {0:#x}; oct: {0:#o}; bin: {0:#b}", 42); // Result: "int: 42; hex: 0x2a; oct: 052; bin: 0b101010" ``` Padded hex byte with prefix and always prints both hex characters: ```c++ fmt::format("{:#04x}", 0); // Result: "0x00" ``` Box drawing using Unicode fill: ```c++ fmt::print( "┌{0:─^{2}}â”\n" "│{1: ^{2}}│\n" "â””{0:─^{2}}┘\n", "", "Hello, world!", 20); ``` prints: ``` ┌────────────────────┠│ Hello, world! │ └────────────────────┘ ``` Using type-specific formatting: ```c++ #include auto t = tm(); t.tm_year = 2010 - 1900; t.tm_mon = 7; t.tm_mday = 4; t.tm_hour = 12; t.tm_min = 15; t.tm_sec = 58; fmt::print("{:%Y-%m-%d %H:%M:%S}", t); // Prints: 2010-08-04 12:15:58 ``` Using the comma as a thousands separator: ```c++ #include auto s = fmt::format(std::locale("en_US.UTF-8"), "{:L}", 1234567890); // s == "1,234,567,890" ``` openal-soft-1.24.2/fmt-11.1.1/include/000077500000000000000000000000001474041540300167575ustar00rootroot00000000000000openal-soft-1.24.2/fmt-11.1.1/include/fmt/000077500000000000000000000000001474041540300175455ustar00rootroot00000000000000openal-soft-1.24.2/fmt-11.1.1/include/fmt/args.h000066400000000000000000000160141474041540300206540ustar00rootroot00000000000000// Formatting library for C++ - dynamic argument lists // // Copyright (c) 2012 - present, Victor Zverovich // All rights reserved. // // For the license information refer to format.h. #ifndef FMT_ARGS_H_ #define FMT_ARGS_H_ #ifndef FMT_MODULE # include // std::reference_wrapper # include // std::unique_ptr # include #endif #include "format.h" // std_string_view FMT_BEGIN_NAMESPACE namespace detail { template struct is_reference_wrapper : std::false_type {}; template struct is_reference_wrapper> : std::true_type {}; template auto unwrap(const T& v) -> const T& { return v; } template auto unwrap(const std::reference_wrapper& v) -> const T& { return static_cast(v); } // node is defined outside dynamic_arg_list to workaround a C2504 bug in MSVC // 2022 (v17.10.0). // // Workaround for clang's -Wweak-vtables. Unlike for regular classes, for // templates it doesn't complain about inability to deduce single translation // unit for placing vtable. So node is made a fake template. template struct node { virtual ~node() = default; std::unique_ptr> next; }; class dynamic_arg_list { template struct typed_node : node<> { T value; template FMT_CONSTEXPR typed_node(const Arg& arg) : value(arg) {} template FMT_CONSTEXPR typed_node(const basic_string_view& arg) : value(arg.data(), arg.size()) {} }; std::unique_ptr> head_; public: template auto push(const Arg& arg) -> const T& { auto new_node = std::unique_ptr>(new typed_node(arg)); auto& value = new_node->value; new_node->next = std::move(head_); head_ = std::move(new_node); return value; } }; } // namespace detail /** * A dynamic list of formatting arguments with storage. * * It can be implicitly converted into `fmt::basic_format_args` for passing * into type-erased formatting functions such as `fmt::vformat`. */ template class dynamic_format_arg_store { private: using char_type = typename Context::char_type; template struct need_copy { static constexpr detail::type mapped_type = detail::mapped_type_constant::value; enum { value = !(detail::is_reference_wrapper::value || std::is_same>::value || std::is_same>::value || (mapped_type != detail::type::cstring_type && mapped_type != detail::type::string_type && mapped_type != detail::type::custom_type)) }; }; template using stored_t = conditional_t< std::is_convertible>::value && !detail::is_reference_wrapper::value, std::basic_string, T>; // Storage of basic_format_arg must be contiguous. std::vector> data_; std::vector> named_info_; // Storage of arguments not fitting into basic_format_arg must grow // without relocation because items in data_ refer to it. detail::dynamic_arg_list dynamic_args_; friend class basic_format_args; auto data() const -> const basic_format_arg* { return named_info_.empty() ? data_.data() : data_.data() + 1; } template void emplace_arg(const T& arg) { data_.emplace_back(arg); } template void emplace_arg(const detail::named_arg& arg) { if (named_info_.empty()) data_.insert(data_.begin(), basic_format_arg(nullptr, 0)); data_.emplace_back(detail::unwrap(arg.value)); auto pop_one = [](std::vector>* data) { data->pop_back(); }; std::unique_ptr>, decltype(pop_one)> guard{&data_, pop_one}; named_info_.push_back({arg.name, static_cast(data_.size() - 2u)}); data_[0] = {named_info_.data(), named_info_.size()}; guard.release(); } public: constexpr dynamic_format_arg_store() = default; operator basic_format_args() const { return basic_format_args(data(), static_cast(data_.size()), !named_info_.empty()); } /** * Adds an argument into the dynamic store for later passing to a formatting * function. * * Note that custom types and string types (but not string views) are copied * into the store dynamically allocating memory if necessary. * * **Example**: * * fmt::dynamic_format_arg_store store; * store.push_back(42); * store.push_back("abc"); * store.push_back(1.5f); * std::string result = fmt::vformat("{} and {} and {}", store); */ template void push_back(const T& arg) { if (detail::const_check(need_copy::value)) emplace_arg(dynamic_args_.push>(arg)); else emplace_arg(detail::unwrap(arg)); } /** * Adds a reference to the argument into the dynamic store for later passing * to a formatting function. * * **Example**: * * fmt::dynamic_format_arg_store store; * char band[] = "Rolling Stones"; * store.push_back(std::cref(band)); * band[9] = 'c'; // Changing str affects the output. * std::string result = fmt::vformat("{}", store); * // result == "Rolling Scones" */ template void push_back(std::reference_wrapper arg) { static_assert( need_copy::value, "objects of built-in types and string views are always copied"); emplace_arg(arg.get()); } /** * Adds named argument into the dynamic store for later passing to a * formatting function. `std::reference_wrapper` is supported to avoid * copying of the argument. The name is always copied into the store. */ template void push_back(const detail::named_arg& arg) { const char_type* arg_name = dynamic_args_.push>(arg.name).c_str(); if (detail::const_check(need_copy::value)) { emplace_arg( fmt::arg(arg_name, dynamic_args_.push>(arg.value))); } else { emplace_arg(fmt::arg(arg_name, arg.value)); } } /// Erase all elements from the store. void clear() { data_.clear(); named_info_.clear(); dynamic_args_ = {}; } /// Reserves space to store at least `new_cap` arguments including /// `new_cap_named` named arguments. void reserve(size_t new_cap, size_t new_cap_named) { FMT_ASSERT(new_cap >= new_cap_named, "set of arguments includes set of named arguments"); data_.reserve(new_cap); named_info_.reserve(new_cap_named); } /// Returns the number of elements in the store. size_t size() const noexcept { return data_.size(); } }; FMT_END_NAMESPACE #endif // FMT_ARGS_H_ openal-soft-1.24.2/fmt-11.1.1/include/fmt/base.h000066400000000000000000003077361474041540300206500ustar00rootroot00000000000000// Formatting library for C++ - the base API for char/UTF-8 // // Copyright (c) 2012 - present, Victor Zverovich // All rights reserved. // // For the license information refer to format.h. #ifndef FMT_BASE_H_ #define FMT_BASE_H_ #if defined(FMT_IMPORT_STD) && !defined(FMT_MODULE) # define FMT_MODULE #endif #ifndef FMT_MODULE # include // CHAR_BIT # include // FILE # include // memcmp # include // std::enable_if #endif // The fmt library version in the form major * 10000 + minor * 100 + patch. #define FMT_VERSION 110101 // Detect compiler versions. #if defined(__clang__) && !defined(__ibmxl__) # define FMT_CLANG_VERSION (__clang_major__ * 100 + __clang_minor__) #else # define FMT_CLANG_VERSION 0 #endif #if defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER) # define FMT_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__) #else # define FMT_GCC_VERSION 0 #endif #if defined(__ICL) # define FMT_ICC_VERSION __ICL #elif defined(__INTEL_COMPILER) # define FMT_ICC_VERSION __INTEL_COMPILER #else # define FMT_ICC_VERSION 0 #endif #if defined(_MSC_VER) # define FMT_MSC_VERSION _MSC_VER #else # define FMT_MSC_VERSION 0 #endif // Detect standard library versions. #ifdef _GLIBCXX_RELEASE # define FMT_GLIBCXX_RELEASE _GLIBCXX_RELEASE #else # define FMT_GLIBCXX_RELEASE 0 #endif #ifdef _LIBCPP_VERSION # define FMT_LIBCPP_VERSION _LIBCPP_VERSION #else # define FMT_LIBCPP_VERSION 0 #endif #ifdef _MSVC_LANG # define FMT_CPLUSPLUS _MSVC_LANG #else # define FMT_CPLUSPLUS __cplusplus #endif // Detect __has_*. #ifdef __has_feature # define FMT_HAS_FEATURE(x) __has_feature(x) #else # define FMT_HAS_FEATURE(x) 0 #endif #ifdef __has_include # define FMT_HAS_INCLUDE(x) __has_include(x) #else # define FMT_HAS_INCLUDE(x) 0 #endif #ifdef __has_builtin # define FMT_HAS_BUILTIN(x) __has_builtin(x) #else # define FMT_HAS_BUILTIN(x) 0 #endif #ifdef __has_cpp_attribute # define FMT_HAS_CPP_ATTRIBUTE(x) __has_cpp_attribute(x) #else # define FMT_HAS_CPP_ATTRIBUTE(x) 0 #endif #define FMT_HAS_CPP14_ATTRIBUTE(attribute) \ (FMT_CPLUSPLUS >= 201402L && FMT_HAS_CPP_ATTRIBUTE(attribute)) #define FMT_HAS_CPP17_ATTRIBUTE(attribute) \ (FMT_CPLUSPLUS >= 201703L && FMT_HAS_CPP_ATTRIBUTE(attribute)) // Detect C++14 relaxed constexpr. #ifdef FMT_USE_CONSTEXPR // Use the provided definition. #elif FMT_GCC_VERSION >= 600 && FMT_CPLUSPLUS >= 201402L // GCC only allows throw in constexpr since version 6: // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=67371. # define FMT_USE_CONSTEXPR 1 #elif FMT_ICC_VERSION # define FMT_USE_CONSTEXPR 0 // https://github.com/fmtlib/fmt/issues/1628 #elif FMT_HAS_FEATURE(cxx_relaxed_constexpr) || FMT_MSC_VERSION >= 1912 # define FMT_USE_CONSTEXPR 1 #else # define FMT_USE_CONSTEXPR 0 #endif #if FMT_USE_CONSTEXPR # define FMT_CONSTEXPR constexpr #else # define FMT_CONSTEXPR #endif // Detect consteval, C++20 constexpr extensions and std::is_constant_evaluated. #if !defined(__cpp_lib_is_constant_evaluated) # define FMT_USE_CONSTEVAL 0 #elif FMT_CPLUSPLUS < 201709L # define FMT_USE_CONSTEVAL 0 #elif FMT_GLIBCXX_RELEASE && FMT_GLIBCXX_RELEASE < 10 # define FMT_USE_CONSTEVAL 0 #elif FMT_LIBCPP_VERSION && FMT_LIBCPP_VERSION < 10000 # define FMT_USE_CONSTEVAL 0 #elif defined(__apple_build_version__) && __apple_build_version__ < 14000029L # define FMT_USE_CONSTEVAL 0 // consteval is broken in Apple clang < 14. #elif FMT_MSC_VERSION && FMT_MSC_VERSION < 1929 # define FMT_USE_CONSTEVAL 0 // consteval is broken in MSVC VS2019 < 16.10. #elif defined(__cpp_consteval) # define FMT_USE_CONSTEVAL 1 #elif FMT_GCC_VERSION >= 1002 || FMT_CLANG_VERSION >= 1101 # define FMT_USE_CONSTEVAL 1 #else # define FMT_USE_CONSTEVAL 0 #endif #if FMT_USE_CONSTEVAL # define FMT_CONSTEVAL consteval # define FMT_CONSTEXPR20 constexpr #else # define FMT_CONSTEVAL # define FMT_CONSTEXPR20 #endif // Check if exceptions are disabled. #ifdef FMT_USE_EXCEPTIONS // Use the provided definition. #elif defined(__GNUC__) && !defined(__EXCEPTIONS) # define FMT_USE_EXCEPTIONS 0 #elif defined(__clang__) && !defined(__cpp_exceptions) # define FMT_USE_EXCEPTIONS 0 #elif FMT_MSC_VERSION && !_HAS_EXCEPTIONS # define FMT_USE_EXCEPTIONS 0 #else # define FMT_USE_EXCEPTIONS 1 #endif #if FMT_USE_EXCEPTIONS # define FMT_TRY try # define FMT_CATCH(x) catch (x) #else # define FMT_TRY if (true) # define FMT_CATCH(x) if (false) #endif #ifdef FMT_NO_UNIQUE_ADDRESS // Use the provided definition. #elif FMT_CPLUSPLUS < 202002L // Not supported. #elif FMT_HAS_CPP_ATTRIBUTE(no_unique_address) # define FMT_NO_UNIQUE_ADDRESS [[no_unique_address]] // VS2019 v16.10 and later except clang-cl (https://reviews.llvm.org/D110485). #elif FMT_MSC_VERSION >= 1929 && !FMT_CLANG_VERSION # define FMT_NO_UNIQUE_ADDRESS [[msvc::no_unique_address]] #endif #ifndef FMT_NO_UNIQUE_ADDRESS # define FMT_NO_UNIQUE_ADDRESS #endif #if FMT_HAS_CPP17_ATTRIBUTE(fallthrough) # define FMT_FALLTHROUGH [[fallthrough]] #elif defined(__clang__) # define FMT_FALLTHROUGH [[clang::fallthrough]] #elif FMT_GCC_VERSION >= 700 && \ (!defined(__EDG_VERSION__) || __EDG_VERSION__ >= 520) # define FMT_FALLTHROUGH [[gnu::fallthrough]] #else # define FMT_FALLTHROUGH #endif // Disable [[noreturn]] on MSVC/NVCC because of bogus unreachable code warnings. #if FMT_HAS_CPP_ATTRIBUTE(noreturn) && !FMT_MSC_VERSION && !defined(__NVCC__) # define FMT_NORETURN [[noreturn]] #else # define FMT_NORETURN #endif #ifdef FMT_NODISCARD // Use the provided definition. #elif FMT_HAS_CPP17_ATTRIBUTE(nodiscard) # define FMT_NODISCARD [[nodiscard]] #else # define FMT_NODISCARD #endif #ifdef FMT_DEPRECATED // Use the provided definition. #elif FMT_HAS_CPP14_ATTRIBUTE(deprecated) # define FMT_DEPRECATED [[deprecated]] #else # define FMT_DEPRECATED /* deprecated */ #endif #ifdef FMT_ALWAYS_INLINE // Use the provided definition. #elif FMT_GCC_VERSION || FMT_CLANG_VERSION # define FMT_ALWAYS_INLINE inline __attribute__((always_inline)) #else # define FMT_ALWAYS_INLINE inline #endif // A version of FMT_ALWAYS_INLINE to prevent code bloat in debug mode. #ifdef NDEBUG # define FMT_INLINE FMT_ALWAYS_INLINE #else # define FMT_INLINE inline #endif #if FMT_GCC_VERSION || FMT_CLANG_VERSION # define FMT_VISIBILITY(value) __attribute__((visibility(value))) #else # define FMT_VISIBILITY(value) #endif // Detect pragmas. #define FMT_PRAGMA_IMPL(x) _Pragma(#x) #if FMT_GCC_VERSION >= 504 && !defined(__NVCOMPILER) // Workaround a _Pragma bug https://gcc.gnu.org/bugzilla/show_bug.cgi?id=59884 // and an nvhpc warning: https://github.com/fmtlib/fmt/pull/2582. # define FMT_PRAGMA_GCC(x) FMT_PRAGMA_IMPL(GCC x) #else # define FMT_PRAGMA_GCC(x) #endif #if FMT_CLANG_VERSION # define FMT_PRAGMA_CLANG(x) FMT_PRAGMA_IMPL(clang x) #else # define FMT_PRAGMA_CLANG(x) #endif #if FMT_MSC_VERSION # define FMT_MSC_WARNING(...) __pragma(warning(__VA_ARGS__)) #else # define FMT_MSC_WARNING(...) #endif #ifndef FMT_BEGIN_NAMESPACE # define FMT_BEGIN_NAMESPACE \ namespace fmt { \ inline namespace v11 { # define FMT_END_NAMESPACE \ } \ } #endif #ifndef FMT_EXPORT # define FMT_EXPORT # define FMT_BEGIN_EXPORT # define FMT_END_EXPORT #endif #ifdef _WIN32 # define FMT_WIN32 1 #else # define FMT_WIN32 0 #endif #if !defined(FMT_HEADER_ONLY) && FMT_WIN32 # if defined(FMT_LIB_EXPORT) # define FMT_API __declspec(dllexport) # elif defined(FMT_SHARED) # define FMT_API __declspec(dllimport) # endif #elif defined(FMT_LIB_EXPORT) || defined(FMT_SHARED) # define FMT_API FMT_VISIBILITY("default") #endif #ifndef FMT_API # define FMT_API #endif #ifndef FMT_OPTIMIZE_SIZE # define FMT_OPTIMIZE_SIZE 0 #endif // FMT_BUILTIN_TYPE=0 may result in smaller library size at the cost of higher // per-call binary size by passing built-in types through the extension API. #ifndef FMT_BUILTIN_TYPES # define FMT_BUILTIN_TYPES 1 #endif #define FMT_APPLY_VARIADIC(expr) \ using ignore = int[]; \ (void)ignore { 0, (expr, 0)... } // Enable minimal optimizations for more compact code in debug mode. FMT_PRAGMA_GCC(push_options) #if !defined(__OPTIMIZE__) && !defined(__CUDACC__) FMT_PRAGMA_GCC(optimize("Og")) #endif FMT_PRAGMA_CLANG(diagnostic push) FMT_BEGIN_NAMESPACE // Implementations of enable_if_t and other metafunctions for older systems. template using enable_if_t = typename std::enable_if::type; template using conditional_t = typename std::conditional::type; template using bool_constant = std::integral_constant; template using remove_reference_t = typename std::remove_reference::type; template using remove_const_t = typename std::remove_const::type; template using remove_cvref_t = typename std::remove_cv>::type; template using make_unsigned_t = typename std::make_unsigned::type; template using underlying_t = typename std::underlying_type::type; template using decay_t = typename std::decay::type; using nullptr_t = decltype(nullptr); #if FMT_GCC_VERSION && FMT_GCC_VERSION < 500 // A workaround for gcc 4.9 to make void_t work in a SFINAE context. template struct void_t_impl { using type = void; }; template using void_t = typename void_t_impl::type; #else template using void_t = void; #endif struct monostate { constexpr monostate() {} }; // An enable_if helper to be used in template parameters which results in much // shorter symbols: https://godbolt.org/z/sWw4vP. Extra parentheses are needed // to workaround a bug in MSVC 2019 (see #1140 and #1186). #ifdef FMT_DOC # define FMT_ENABLE_IF(...) #else # define FMT_ENABLE_IF(...) fmt::enable_if_t<(__VA_ARGS__), int> = 0 #endif template constexpr auto min_of(T a, T b) -> T { return a < b ? a : b; } template constexpr auto max_of(T a, T b) -> T { return a > b ? a : b; } namespace detail { // Suppresses "unused variable" warnings with the method described in // https://herbsutter.com/2009/10/18/mailbag-shutting-up-compiler-warnings/. // (void)var does not work on many Intel compilers. template FMT_CONSTEXPR void ignore_unused(const T&...) {} constexpr auto is_constant_evaluated(bool default_value = false) noexcept -> bool { // Workaround for incompatibility between clang 14 and libstdc++ consteval-based // std::is_constant_evaluated: https://github.com/fmtlib/fmt/issues/3247. #if FMT_CPLUSPLUS >= 202002L && FMT_GLIBCXX_RELEASE >= 12 && \ (FMT_CLANG_VERSION >= 1400 && FMT_CLANG_VERSION < 1500) ignore_unused(default_value); return __builtin_is_constant_evaluated(); #elif defined(__cpp_lib_is_constant_evaluated) ignore_unused(default_value); return std::is_constant_evaluated(); #else return default_value; #endif } // Suppresses "conditional expression is constant" warnings. template FMT_ALWAYS_INLINE constexpr auto const_check(T val) -> T { return val; } FMT_NORETURN FMT_API void assert_fail(const char* file, int line, const char* message); #if defined(FMT_ASSERT) // Use the provided definition. #elif defined(NDEBUG) // FMT_ASSERT is not empty to avoid -Wempty-body. # define FMT_ASSERT(condition, message) \ fmt::detail::ignore_unused((condition), (message)) #else # define FMT_ASSERT(condition, message) \ ((condition) /* void() fails with -Winvalid-constexpr on clang 4.0.1 */ \ ? (void)0 \ : fmt::detail::assert_fail(__FILE__, __LINE__, (message))) #endif #ifdef FMT_USE_INT128 // Use the provided definition. #elif defined(__SIZEOF_INT128__) && !defined(__NVCC__) && \ !(FMT_CLANG_VERSION && FMT_MSC_VERSION) # define FMT_USE_INT128 1 using int128_opt = __int128_t; // An optional native 128-bit integer. using uint128_opt = __uint128_t; inline auto map(int128_opt x) -> int128_opt { return x; } inline auto map(uint128_opt x) -> uint128_opt { return x; } #else # define FMT_USE_INT128 0 #endif #if !FMT_USE_INT128 enum class int128_opt {}; enum class uint128_opt {}; // Reduce template instantiations. inline auto map(int128_opt) -> monostate { return {}; } inline auto map(uint128_opt) -> monostate { return {}; } #endif #ifndef FMT_USE_BITINT # define FMT_USE_BITINT (FMT_CLANG_VERSION >= 1500) #endif #if FMT_USE_BITINT FMT_PRAGMA_CLANG(diagnostic ignored "-Wbit-int-extension") template using bitint = _BitInt(N); template using ubitint = unsigned _BitInt(N); #else template struct bitint {}; template struct ubitint {}; #endif // FMT_USE_BITINT // Casts a nonnegative integer to unsigned. template FMT_CONSTEXPR auto to_unsigned(Int value) -> make_unsigned_t { FMT_ASSERT(std::is_unsigned::value || value >= 0, "negative value"); return static_cast>(value); } template using unsigned_char = conditional_t; // A heuristic to detect std::string and std::[experimental::]string_view. // It is mainly used to avoid dependency on <[experimental/]string_view>. template struct is_std_string_like : std::false_type {}; template struct is_std_string_like().find_first_of( typename T::value_type(), 0))>> : std::is_convertible().data()), const typename T::value_type*> {}; // Check if the literal encoding is UTF-8. enum { is_utf8_enabled = "\u00A7"[1] == '\xA7' }; enum { use_utf8 = !FMT_WIN32 || is_utf8_enabled }; #ifndef FMT_UNICODE # define FMT_UNICODE 1 #endif static_assert(!FMT_UNICODE || use_utf8, "Unicode support requires compiling with /utf-8"); template constexpr const char* narrow(const T*) { return nullptr; } constexpr FMT_ALWAYS_INLINE const char* narrow(const char* s) { return s; } template FMT_CONSTEXPR auto compare(const Char* s1, const Char* s2, std::size_t n) -> int { if (!is_constant_evaluated() && sizeof(Char) == 1) return memcmp(s1, s2, n); for (; n != 0; ++s1, ++s2, --n) { if (*s1 < *s2) return -1; if (*s1 > *s2) return 1; } return 0; } namespace adl { using namespace std; template auto invoke_back_inserter() -> decltype(back_inserter(std::declval())); } // namespace adl template struct is_back_insert_iterator : std::false_type {}; template struct is_back_insert_iterator< It, bool_constant()), It>::value>> : std::true_type {}; // Extracts a reference to the container from *insert_iterator. template inline FMT_CONSTEXPR20 auto get_container(OutputIt it) -> typename OutputIt::container_type& { struct accessor : OutputIt { FMT_CONSTEXPR20 accessor(OutputIt base) : OutputIt(base) {} using OutputIt::container; }; return *accessor(it).container; } } // namespace detail // Parsing-related public API and forward declarations. FMT_BEGIN_EXPORT /** * An implementation of `std::basic_string_view` for pre-C++17. It provides a * subset of the API. `fmt::basic_string_view` is used for format strings even * if `std::basic_string_view` is available to prevent issues when a library is * compiled with a different `-std` option than the client code (which is not * recommended). */ template class basic_string_view { private: const Char* data_; size_t size_; public: using value_type = Char; using iterator = const Char*; constexpr basic_string_view() noexcept : data_(nullptr), size_(0) {} /// Constructs a string reference object from a C string and a size. constexpr basic_string_view(const Char* s, size_t count) noexcept : data_(s), size_(count) {} constexpr basic_string_view(nullptr_t) = delete; /// Constructs a string reference object from a C string. #if FMT_GCC_VERSION FMT_ALWAYS_INLINE #endif FMT_CONSTEXPR20 basic_string_view(const Char* s) : data_(s) { #if FMT_HAS_BUILTIN(__buitin_strlen) || FMT_GCC_VERSION || FMT_CLANG_VERSION if (std::is_same::value) { size_ = __builtin_strlen(detail::narrow(s)); return; } #endif size_t len = 0; while (*s++) ++len; size_ = len; } /// Constructs a string reference from a `std::basic_string` or a /// `std::basic_string_view` object. template ::value&& std::is_same< typename S::value_type, Char>::value)> FMT_CONSTEXPR basic_string_view(const S& s) noexcept : data_(s.data()), size_(s.size()) {} /// Returns a pointer to the string data. constexpr auto data() const noexcept -> const Char* { return data_; } /// Returns the string size. constexpr auto size() const noexcept -> size_t { return size_; } constexpr auto begin() const noexcept -> iterator { return data_; } constexpr auto end() const noexcept -> iterator { return data_ + size_; } constexpr auto operator[](size_t pos) const noexcept -> const Char& { return data_[pos]; } FMT_CONSTEXPR void remove_prefix(size_t n) noexcept { data_ += n; size_ -= n; } FMT_CONSTEXPR auto starts_with(basic_string_view sv) const noexcept -> bool { return size_ >= sv.size_ && detail::compare(data_, sv.data_, sv.size_) == 0; } FMT_CONSTEXPR auto starts_with(Char c) const noexcept -> bool { return size_ >= 1 && *data_ == c; } FMT_CONSTEXPR auto starts_with(const Char* s) const -> bool { return starts_with(basic_string_view(s)); } // Lexicographically compare this string reference to other. FMT_CONSTEXPR auto compare(basic_string_view other) const -> int { int result = detail::compare(data_, other.data_, min_of(size_, other.size_)); if (result != 0) return result; return size_ == other.size_ ? 0 : (size_ < other.size_ ? -1 : 1); } FMT_CONSTEXPR friend auto operator==(basic_string_view lhs, basic_string_view rhs) -> bool { return lhs.compare(rhs) == 0; } friend auto operator!=(basic_string_view lhs, basic_string_view rhs) -> bool { return lhs.compare(rhs) != 0; } friend auto operator<(basic_string_view lhs, basic_string_view rhs) -> bool { return lhs.compare(rhs) < 0; } friend auto operator<=(basic_string_view lhs, basic_string_view rhs) -> bool { return lhs.compare(rhs) <= 0; } friend auto operator>(basic_string_view lhs, basic_string_view rhs) -> bool { return lhs.compare(rhs) > 0; } friend auto operator>=(basic_string_view lhs, basic_string_view rhs) -> bool { return lhs.compare(rhs) >= 0; } }; using string_view = basic_string_view; /// Specifies if `T` is an extended character type. Can be specialized by users. template struct is_xchar : std::false_type {}; template <> struct is_xchar : std::true_type {}; template <> struct is_xchar : std::true_type {}; template <> struct is_xchar : std::true_type {}; #ifdef __cpp_char8_t template <> struct is_xchar : std::true_type {}; #endif // DEPRECATED! Will be replaced with an alias to prevent specializations. template struct is_char : is_xchar {}; template <> struct is_char : std::true_type {}; template class basic_appender; using appender = basic_appender; // Checks whether T is a container with contiguous storage. template struct is_contiguous : std::false_type {}; class context; template class generic_context; template class parse_context; // Longer aliases for C++20 compatibility. template using basic_format_parse_context = parse_context; using format_parse_context = parse_context; template using basic_format_context = conditional_t::value, context, generic_context>; using format_context = context; template using buffered_context = conditional_t::value, context, generic_context, Char>>; template class basic_format_arg; template class basic_format_args; // A separate type would result in shorter symbols but break ABI compatibility // between clang and gcc on ARM (#1919). using format_args = basic_format_args; // A formatter for objects of type T. template struct formatter { // A deleted default constructor indicates a disabled formatter. formatter() = delete; }; /// Reports a format error at compile time or, via a `format_error` exception, /// at runtime. // This function is intentionally not constexpr to give a compile-time error. FMT_NORETURN FMT_API void report_error(const char* message); enum class presentation_type : unsigned char { // Common specifiers: none = 0, debug = 1, // '?' string = 2, // 's' (string, bool) // Integral, bool and character specifiers: dec = 3, // 'd' hex, // 'x' or 'X' oct, // 'o' bin, // 'b' or 'B' chr, // 'c' // String and pointer specifiers: pointer = 3, // 'p' // Floating-point specifiers: exp = 1, // 'e' or 'E' (1 since there is no FP debug presentation) fixed, // 'f' or 'F' general, // 'g' or 'G' hexfloat // 'a' or 'A' }; enum class align { none, left, right, center, numeric }; enum class sign { none, minus, plus, space }; enum class arg_id_kind { none, index, name }; // Basic format specifiers for built-in and string types. class basic_specs { private: // Data is arranged as follows: // // 0 1 2 3 // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // |type |align| w | p | s |u|#|L| f | unused | // +-----+-----+---+---+---+-+-+-+-----+---------------------------+ // // w - dynamic width info // p - dynamic precision info // s - sign // u - uppercase (e.g. 'X' for 'x') // # - alternate form ('#') // L - localized // f - fill size // // Bitfields are not used because of compiler bugs such as gcc bug 61414. enum : unsigned { type_mask = 0x00007, align_mask = 0x00038, width_mask = 0x000C0, precision_mask = 0x00300, sign_mask = 0x00C00, uppercase_mask = 0x01000, alternate_mask = 0x02000, localized_mask = 0x04000, fill_size_mask = 0x38000, align_shift = 3, width_shift = 6, precision_shift = 8, sign_shift = 10, fill_size_shift = 15, max_fill_size = 4 }; size_t data_ = 1 << fill_size_shift; // Character (code unit) type is erased to prevent template bloat. char fill_data_[max_fill_size] = {' '}; FMT_CONSTEXPR void set_fill_size(size_t size) { data_ = (data_ & ~fill_size_mask) | (size << fill_size_shift); } public: constexpr auto type() const -> presentation_type { return static_cast(data_ & type_mask); } FMT_CONSTEXPR void set_type(presentation_type t) { data_ = (data_ & ~type_mask) | static_cast(t); } constexpr auto align() const -> align { return static_cast((data_ & align_mask) >> align_shift); } FMT_CONSTEXPR void set_align(fmt::align a) { data_ = (data_ & ~align_mask) | (static_cast(a) << align_shift); } constexpr auto dynamic_width() const -> arg_id_kind { return static_cast((data_ & width_mask) >> width_shift); } FMT_CONSTEXPR void set_dynamic_width(arg_id_kind w) { data_ = (data_ & ~width_mask) | (static_cast(w) << width_shift); } FMT_CONSTEXPR auto dynamic_precision() const -> arg_id_kind { return static_cast((data_ & precision_mask) >> precision_shift); } FMT_CONSTEXPR void set_dynamic_precision(arg_id_kind p) { data_ = (data_ & ~precision_mask) | (static_cast(p) << precision_shift); } constexpr bool dynamic() const { return (data_ & (width_mask | precision_mask)) != 0; } constexpr auto sign() const -> sign { return static_cast((data_ & sign_mask) >> sign_shift); } FMT_CONSTEXPR void set_sign(fmt::sign s) { data_ = (data_ & ~sign_mask) | (static_cast(s) << sign_shift); } constexpr auto upper() const -> bool { return (data_ & uppercase_mask) != 0; } FMT_CONSTEXPR void set_upper() { data_ |= uppercase_mask; } constexpr auto alt() const -> bool { return (data_ & alternate_mask) != 0; } FMT_CONSTEXPR void set_alt() { data_ |= alternate_mask; } FMT_CONSTEXPR void clear_alt() { data_ &= ~alternate_mask; } constexpr auto localized() const -> bool { return (data_ & localized_mask) != 0; } FMT_CONSTEXPR void set_localized() { data_ |= localized_mask; } constexpr auto fill_size() const -> size_t { return (data_ & fill_size_mask) >> fill_size_shift; } template ::value)> constexpr auto fill() const -> const Char* { return fill_data_; } template ::value)> constexpr auto fill() const -> const Char* { return nullptr; } template constexpr auto fill_unit() const -> Char { using uchar = unsigned char; return static_cast(static_cast(fill_data_[0]) | (static_cast(fill_data_[1]) << 8) | (static_cast(fill_data_[2]) << 16)); } FMT_CONSTEXPR void set_fill(char c) { fill_data_[0] = c; set_fill_size(1); } template FMT_CONSTEXPR void set_fill(basic_string_view s) { auto size = s.size(); set_fill_size(size); if (size == 1) { unsigned uchar = static_cast>(s[0]); fill_data_[0] = static_cast(uchar); fill_data_[1] = static_cast(uchar >> 8); fill_data_[2] = static_cast(uchar >> 16); return; } FMT_ASSERT(size <= max_fill_size, "invalid fill"); for (size_t i = 0; i < size; ++i) fill_data_[i & 3] = static_cast(s[i]); } }; // Format specifiers for built-in and string types. struct format_specs : basic_specs { int width; int precision; constexpr format_specs() : width(0), precision(-1) {} }; /** * Parsing context consisting of a format string range being parsed and an * argument counter for automatic indexing. */ template class parse_context { private: basic_string_view fmt_; int next_arg_id_; enum { use_constexpr_cast = !FMT_GCC_VERSION || FMT_GCC_VERSION >= 1200 }; FMT_CONSTEXPR void do_check_arg_id(int arg_id); public: using char_type = Char; using iterator = const Char*; constexpr explicit parse_context(basic_string_view fmt, int next_arg_id = 0) : fmt_(fmt), next_arg_id_(next_arg_id) {} /// Returns an iterator to the beginning of the format string range being /// parsed. constexpr auto begin() const noexcept -> iterator { return fmt_.begin(); } /// Returns an iterator past the end of the format string range being parsed. constexpr auto end() const noexcept -> iterator { return fmt_.end(); } /// Advances the begin iterator to `it`. FMT_CONSTEXPR void advance_to(iterator it) { fmt_.remove_prefix(detail::to_unsigned(it - begin())); } /// Reports an error if using the manual argument indexing; otherwise returns /// the next argument index and switches to the automatic indexing. FMT_CONSTEXPR auto next_arg_id() -> int { if (next_arg_id_ < 0) { report_error("cannot switch from manual to automatic argument indexing"); return 0; } int id = next_arg_id_++; do_check_arg_id(id); return id; } /// Reports an error if using the automatic argument indexing; otherwise /// switches to the manual indexing. FMT_CONSTEXPR void check_arg_id(int id) { if (next_arg_id_ > 0) { report_error("cannot switch from automatic to manual argument indexing"); return; } next_arg_id_ = -1; do_check_arg_id(id); } FMT_CONSTEXPR void check_arg_id(basic_string_view) { next_arg_id_ = -1; } FMT_CONSTEXPR void check_dynamic_spec(int arg_id); }; FMT_END_EXPORT namespace detail { // Constructs fmt::basic_string_view from types implicitly convertible // to it, deducing Char. Explicitly convertible types such as the ones returned // from FMT_STRING are intentionally excluded. template ::value)> constexpr auto to_string_view(const Char* s) -> basic_string_view { return s; } template ::value)> constexpr auto to_string_view(const T& s) -> basic_string_view { return s; } template constexpr auto to_string_view(basic_string_view s) -> basic_string_view { return s; } template struct has_to_string_view : std::false_type {}; // detail:: is intentional since to_string_view is not an extension point. template struct has_to_string_view< T, void_t()))>> : std::true_type {}; /// String's character (code unit) type. detail:: is intentional to prevent ADL. template ()))> using char_t = typename V::value_type; enum class type { none_type, // Integer types should go first, int_type, uint_type, long_long_type, ulong_long_type, int128_type, uint128_type, bool_type, char_type, last_integer_type = char_type, // followed by floating-point types. float_type, double_type, long_double_type, last_numeric_type = long_double_type, cstring_type, string_type, pointer_type, custom_type }; // Maps core type T to the corresponding type enum constant. template struct type_constant : std::integral_constant {}; #define FMT_TYPE_CONSTANT(Type, constant) \ template \ struct type_constant \ : std::integral_constant {} FMT_TYPE_CONSTANT(int, int_type); FMT_TYPE_CONSTANT(unsigned, uint_type); FMT_TYPE_CONSTANT(long long, long_long_type); FMT_TYPE_CONSTANT(unsigned long long, ulong_long_type); FMT_TYPE_CONSTANT(int128_opt, int128_type); FMT_TYPE_CONSTANT(uint128_opt, uint128_type); FMT_TYPE_CONSTANT(bool, bool_type); FMT_TYPE_CONSTANT(Char, char_type); FMT_TYPE_CONSTANT(float, float_type); FMT_TYPE_CONSTANT(double, double_type); FMT_TYPE_CONSTANT(long double, long_double_type); FMT_TYPE_CONSTANT(const Char*, cstring_type); FMT_TYPE_CONSTANT(basic_string_view, string_type); FMT_TYPE_CONSTANT(const void*, pointer_type); constexpr auto is_integral_type(type t) -> bool { return t > type::none_type && t <= type::last_integer_type; } constexpr auto is_arithmetic_type(type t) -> bool { return t > type::none_type && t <= type::last_numeric_type; } constexpr auto set(type rhs) -> int { return 1 << static_cast(rhs); } constexpr auto in(type t, int set) -> bool { return ((set >> static_cast(t)) & 1) != 0; } // Bitsets of types. enum { sint_set = set(type::int_type) | set(type::long_long_type) | set(type::int128_type), uint_set = set(type::uint_type) | set(type::ulong_long_type) | set(type::uint128_type), bool_set = set(type::bool_type), char_set = set(type::char_type), float_set = set(type::float_type) | set(type::double_type) | set(type::long_double_type), string_set = set(type::string_type), cstring_set = set(type::cstring_type), pointer_set = set(type::pointer_type) }; struct view {}; template struct named_arg; template struct is_named_arg : std::false_type {}; template struct is_static_named_arg : std::false_type {}; template struct is_named_arg> : std::true_type {}; template struct named_arg : view { const Char* name; const T& value; named_arg(const Char* n, const T& v) : name(n), value(v) {} static_assert(!is_named_arg::value, "nested named arguments"); }; template constexpr auto count() -> int { return B ? 1 : 0; } template constexpr auto count() -> int { return (B1 ? 1 : 0) + count(); } template constexpr auto count_named_args() -> int { return count::value...>(); } template constexpr auto count_static_named_args() -> int { return count::value...>(); } template struct named_arg_info { const Char* name; int id; }; template ::value)> void init_named_arg(named_arg_info*, int& arg_index, int&, const T&) { ++arg_index; } template ::value)> void init_named_arg(named_arg_info* named_args, int& arg_index, int& named_arg_index, const T& arg) { named_args[named_arg_index++] = {arg.name, arg_index++}; } template ::value)> FMT_CONSTEXPR void init_static_named_arg(named_arg_info*, int& arg_index, int&) { ++arg_index; } template ::value)> FMT_CONSTEXPR void init_static_named_arg(named_arg_info* named_args, int& arg_index, int& named_arg_index) { named_args[named_arg_index++] = {T::name, arg_index++}; } // To minimize the number of types we need to deal with, long is translated // either to int or to long long depending on its size. enum { long_short = sizeof(long) == sizeof(int) }; using long_type = conditional_t; using ulong_type = conditional_t; template using format_as_result = remove_cvref_t()))>; template using format_as_member_result = remove_cvref_t::format_as(std::declval()))>; template struct use_format_as : std::false_type {}; // format_as member is only used to avoid injection into the std namespace. template struct use_format_as_member : std::false_type {}; // Only map owning types because mapping views can be unsafe. template struct use_format_as< T, bool_constant>::value>> : std::true_type {}; template struct use_format_as_member< T, bool_constant>::value>> : std::true_type {}; template > using use_formatter = bool_constant<(std::is_class::value || std::is_enum::value || std::is_union::value || std::is_array::value) && !has_to_string_view::value && !is_named_arg::value && !use_format_as::value && !use_format_as_member::value>; template > auto has_formatter_impl(T* p, buffered_context* ctx = nullptr) -> decltype(formatter().format(*p, *ctx), std::true_type()); template auto has_formatter_impl(...) -> std::false_type; // T can be const-qualified to check if it is const-formattable. template constexpr auto has_formatter() -> bool { return decltype(has_formatter_impl(static_cast(nullptr)))::value; } // Maps formatting argument types to natively supported types or user-defined // types with formatters. Returns void on errors to be SFINAE-friendly. template struct type_mapper { static auto map(signed char) -> int; static auto map(unsigned char) -> unsigned; static auto map(short) -> int; static auto map(unsigned short) -> unsigned; static auto map(int) -> int; static auto map(unsigned) -> unsigned; static auto map(long) -> long_type; static auto map(unsigned long) -> ulong_type; static auto map(long long) -> long long; static auto map(unsigned long long) -> unsigned long long; static auto map(int128_opt) -> int128_opt; static auto map(uint128_opt) -> uint128_opt; static auto map(bool) -> bool; template static auto map(bitint) -> conditional_t; template static auto map(ubitint) -> conditional_t; template ::value)> static auto map(T) -> conditional_t< std::is_same::value || std::is_same::value, Char, void>; static auto map(float) -> float; static auto map(double) -> double; static auto map(long double) -> long double; static auto map(Char*) -> const Char*; static auto map(const Char*) -> const Char*; template , FMT_ENABLE_IF(!std::is_pointer::value)> static auto map(const T&) -> conditional_t::value, basic_string_view, void>; static auto map(void*) -> const void*; static auto map(const void*) -> const void*; static auto map(volatile void*) -> const void*; static auto map(const volatile void*) -> const void*; static auto map(nullptr_t) -> const void*; template ::value || std::is_member_pointer::value)> static auto map(const T&) -> void; template ::value)> static auto map(const T& x) -> decltype(map(format_as(x))); template ::value)> static auto map(const T& x) -> decltype(map(formatter::format_as(x))); template ::value)> static auto map(T&) -> conditional_t(), T&, void>; template ::value)> static auto map(const T& named_arg) -> decltype(map(named_arg.value)); }; // detail:: is used to workaround a bug in MSVC 2017. template using mapped_t = decltype(detail::type_mapper::map(std::declval())); // A type constant after applying type_mapper. template using mapped_type_constant = type_constant, Char>; template ::value> using stored_type_constant = std::integral_constant< type, Context::builtin_types || TYPE == type::int_type ? TYPE : type::custom_type>; // A parse context with extra data used only in compile-time checks. template class compile_parse_context : public parse_context { private: int num_args_; const type* types_; using base = parse_context; public: FMT_CONSTEXPR explicit compile_parse_context(basic_string_view fmt, int num_args, const type* types, int next_arg_id = 0) : base(fmt, next_arg_id), num_args_(num_args), types_(types) {} constexpr auto num_args() const -> int { return num_args_; } constexpr auto arg_type(int id) const -> type { return types_[id]; } FMT_CONSTEXPR auto next_arg_id() -> int { int id = base::next_arg_id(); if (id >= num_args_) report_error("argument not found"); return id; } FMT_CONSTEXPR void check_arg_id(int id) { base::check_arg_id(id); if (id >= num_args_) report_error("argument not found"); } using base::check_arg_id; FMT_CONSTEXPR void check_dynamic_spec(int arg_id) { ignore_unused(arg_id); if (arg_id < num_args_ && types_ && !is_integral_type(types_[arg_id])) report_error("width/precision is not integer"); } }; // An argument reference. template union arg_ref { FMT_CONSTEXPR arg_ref(int idx = 0) : index(idx) {} FMT_CONSTEXPR arg_ref(basic_string_view n) : name(n) {} int index; basic_string_view name; }; // Format specifiers with width and precision resolved at formatting rather // than parsing time to allow reusing the same parsed specifiers with // different sets of arguments (precompilation of format strings). template struct dynamic_format_specs : format_specs { arg_ref width_ref; arg_ref precision_ref; }; // Converts a character to ASCII. Returns '\0' on conversion failure. template ::value)> constexpr auto to_ascii(Char c) -> char { return c <= 0xff ? static_cast(c) : '\0'; } // Returns the number of code units in a code point or 1 on error. template FMT_CONSTEXPR auto code_point_length(const Char* begin) -> int { if (const_check(sizeof(Char) != 1)) return 1; auto c = static_cast(*begin); return static_cast((0x3a55000000000000ull >> (2 * (c >> 3))) & 3) + 1; } // Parses the range [begin, end) as an unsigned integer. This function assumes // that the range is non-empty and the first character is a digit. template FMT_CONSTEXPR auto parse_nonnegative_int(const Char*& begin, const Char* end, int error_value) noexcept -> int { FMT_ASSERT(begin != end && '0' <= *begin && *begin <= '9', ""); unsigned value = 0, prev = 0; auto p = begin; do { prev = value; value = value * 10 + unsigned(*p - '0'); ++p; } while (p != end && '0' <= *p && *p <= '9'); auto num_digits = p - begin; begin = p; int digits10 = static_cast(sizeof(int) * CHAR_BIT * 3 / 10); if (num_digits <= digits10) return static_cast(value); // Check for overflow. unsigned max = INT_MAX; return num_digits == digits10 + 1 && prev * 10ull + unsigned(p[-1] - '0') <= max ? static_cast(value) : error_value; } FMT_CONSTEXPR inline auto parse_align(char c) -> align { switch (c) { case '<': return align::left; case '>': return align::right; case '^': return align::center; } return align::none; } template constexpr auto is_name_start(Char c) -> bool { return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '_'; } template FMT_CONSTEXPR auto parse_arg_id(const Char* begin, const Char* end, Handler&& handler) -> const Char* { Char c = *begin; if (c >= '0' && c <= '9') { int index = 0; if (c != '0') index = parse_nonnegative_int(begin, end, INT_MAX); else ++begin; if (begin == end || (*begin != '}' && *begin != ':')) report_error("invalid format string"); else handler.on_index(index); return begin; } if (FMT_OPTIMIZE_SIZE > 1 || !is_name_start(c)) { report_error("invalid format string"); return begin; } auto it = begin; do { ++it; } while (it != end && (is_name_start(*it) || ('0' <= *it && *it <= '9'))); handler.on_name({begin, to_unsigned(it - begin)}); return it; } template struct dynamic_spec_handler { parse_context& ctx; arg_ref& ref; arg_id_kind& kind; FMT_CONSTEXPR void on_index(int id) { ref = id; kind = arg_id_kind::index; ctx.check_arg_id(id); ctx.check_dynamic_spec(id); } FMT_CONSTEXPR void on_name(basic_string_view id) { ref = id; kind = arg_id_kind::name; ctx.check_arg_id(id); } }; template struct parse_dynamic_spec_result { const Char* end; arg_id_kind kind; }; // Parses integer | "{" [arg_id] "}". template FMT_CONSTEXPR auto parse_dynamic_spec(const Char* begin, const Char* end, int& value, arg_ref& ref, parse_context& ctx) -> parse_dynamic_spec_result { FMT_ASSERT(begin != end, ""); auto kind = arg_id_kind::none; if ('0' <= *begin && *begin <= '9') { int val = parse_nonnegative_int(begin, end, -1); if (val == -1) report_error("number is too big"); value = val; } else { if (*begin == '{') { ++begin; if (begin != end) { Char c = *begin; if (c == '}' || c == ':') { int id = ctx.next_arg_id(); ref = id; kind = arg_id_kind::index; ctx.check_dynamic_spec(id); } else { begin = parse_arg_id(begin, end, dynamic_spec_handler{ctx, ref, kind}); } } if (begin != end && *begin == '}') return {++begin, kind}; } report_error("invalid format string"); } return {begin, kind}; } template FMT_CONSTEXPR auto parse_width(const Char* begin, const Char* end, format_specs& specs, arg_ref& width_ref, parse_context& ctx) -> const Char* { auto result = parse_dynamic_spec(begin, end, specs.width, width_ref, ctx); specs.set_dynamic_width(result.kind); return result.end; } template FMT_CONSTEXPR auto parse_precision(const Char* begin, const Char* end, format_specs& specs, arg_ref& precision_ref, parse_context& ctx) -> const Char* { ++begin; if (begin == end) { report_error("invalid precision"); return begin; } auto result = parse_dynamic_spec(begin, end, specs.precision, precision_ref, ctx); specs.set_dynamic_precision(result.kind); return result.end; } enum class state { start, align, sign, hash, zero, width, precision, locale }; // Parses standard format specifiers. template FMT_CONSTEXPR auto parse_format_specs(const Char* begin, const Char* end, dynamic_format_specs& specs, parse_context& ctx, type arg_type) -> const Char* { auto c = '\0'; if (end - begin > 1) { auto next = to_ascii(begin[1]); c = parse_align(next) == align::none ? to_ascii(*begin) : '\0'; } else { if (begin == end) return begin; c = to_ascii(*begin); } struct { state current_state = state::start; FMT_CONSTEXPR void operator()(state s, bool valid = true) { if (current_state >= s || !valid) report_error("invalid format specifier"); current_state = s; } } enter_state; using pres = presentation_type; constexpr auto integral_set = sint_set | uint_set | bool_set | char_set; struct { const Char*& begin; format_specs& specs; type arg_type; FMT_CONSTEXPR auto operator()(pres pres_type, int set) -> const Char* { if (!in(arg_type, set)) report_error("invalid format specifier"); specs.set_type(pres_type); return begin + 1; } } parse_presentation_type{begin, specs, arg_type}; for (;;) { switch (c) { case '<': case '>': case '^': enter_state(state::align); specs.set_align(parse_align(c)); ++begin; break; case '+': case ' ': specs.set_sign(c == ' ' ? sign::space : sign::plus); FMT_FALLTHROUGH; case '-': enter_state(state::sign, in(arg_type, sint_set | float_set)); ++begin; break; case '#': enter_state(state::hash, is_arithmetic_type(arg_type)); specs.set_alt(); ++begin; break; case '0': enter_state(state::zero); if (!is_arithmetic_type(arg_type)) report_error("format specifier requires numeric argument"); if (specs.align() == align::none) { // Ignore 0 if align is specified for compatibility with std::format. specs.set_align(align::numeric); specs.set_fill('0'); } ++begin; break; // clang-format off case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '{': // clang-format on enter_state(state::width); begin = parse_width(begin, end, specs, specs.width_ref, ctx); break; case '.': enter_state(state::precision, in(arg_type, float_set | string_set | cstring_set)); begin = parse_precision(begin, end, specs, specs.precision_ref, ctx); break; case 'L': enter_state(state::locale, is_arithmetic_type(arg_type)); specs.set_localized(); ++begin; break; case 'd': return parse_presentation_type(pres::dec, integral_set); case 'X': specs.set_upper(); FMT_FALLTHROUGH; case 'x': return parse_presentation_type(pres::hex, integral_set); case 'o': return parse_presentation_type(pres::oct, integral_set); case 'B': specs.set_upper(); FMT_FALLTHROUGH; case 'b': return parse_presentation_type(pres::bin, integral_set); case 'E': specs.set_upper(); FMT_FALLTHROUGH; case 'e': return parse_presentation_type(pres::exp, float_set); case 'F': specs.set_upper(); FMT_FALLTHROUGH; case 'f': return parse_presentation_type(pres::fixed, float_set); case 'G': specs.set_upper(); FMT_FALLTHROUGH; case 'g': return parse_presentation_type(pres::general, float_set); case 'A': specs.set_upper(); FMT_FALLTHROUGH; case 'a': return parse_presentation_type(pres::hexfloat, float_set); case 'c': if (arg_type == type::bool_type) report_error("invalid format specifier"); return parse_presentation_type(pres::chr, integral_set); case 's': return parse_presentation_type(pres::string, bool_set | string_set | cstring_set); case 'p': return parse_presentation_type(pres::pointer, pointer_set | cstring_set); case '?': return parse_presentation_type(pres::debug, char_set | string_set | cstring_set); case '}': return begin; default: { if (*begin == '}') return begin; // Parse fill and alignment. auto fill_end = begin + code_point_length(begin); if (end - fill_end <= 0) { report_error("invalid format specifier"); return begin; } if (*begin == '{') { report_error("invalid fill character '{'"); return begin; } auto alignment = parse_align(to_ascii(*fill_end)); enter_state(state::align, alignment != align::none); specs.set_fill( basic_string_view(begin, to_unsigned(fill_end - begin))); specs.set_align(alignment); begin = fill_end + 1; } } if (begin == end) return begin; c = to_ascii(*begin); } } template FMT_CONSTEXPR FMT_INLINE auto parse_replacement_field(const Char* begin, const Char* end, Handler&& handler) -> const Char* { ++begin; if (begin == end) { handler.on_error("invalid format string"); return end; } int arg_id = 0; switch (*begin) { case '}': handler.on_replacement_field(handler.on_arg_id(), begin); return begin + 1; case '{': handler.on_text(begin, begin + 1); return begin + 1; case ':': arg_id = handler.on_arg_id(); break; default: { struct id_adapter { Handler& handler; int arg_id; FMT_CONSTEXPR void on_index(int id) { arg_id = handler.on_arg_id(id); } FMT_CONSTEXPR void on_name(basic_string_view id) { arg_id = handler.on_arg_id(id); } } adapter = {handler, 0}; begin = parse_arg_id(begin, end, adapter); arg_id = adapter.arg_id; Char c = begin != end ? *begin : Char(); if (c == '}') { handler.on_replacement_field(arg_id, begin); return begin + 1; } if (c != ':') { handler.on_error("missing '}' in format string"); return end; } break; } } begin = handler.on_format_specs(arg_id, begin + 1, end); if (begin == end || *begin != '}') return handler.on_error("unknown format specifier"), end; return begin + 1; } template FMT_CONSTEXPR void parse_format_string(basic_string_view fmt, Handler&& handler) { auto begin = fmt.data(), end = begin + fmt.size(); auto p = begin; while (p != end) { auto c = *p++; if (c == '{') { handler.on_text(begin, p - 1); begin = p = parse_replacement_field(p - 1, end, handler); } else if (c == '}') { if (p == end || *p != '}') return handler.on_error("unmatched '}' in format string"); handler.on_text(begin, p); begin = ++p; } } handler.on_text(begin, end); } // Checks char specs and returns true iff the presentation type is char-like. FMT_CONSTEXPR inline auto check_char_specs(const format_specs& specs) -> bool { auto type = specs.type(); if (type != presentation_type::none && type != presentation_type::chr && type != presentation_type::debug) { return false; } if (specs.align() == align::numeric || specs.sign() != sign::none || specs.alt()) { report_error("invalid format specifier for char"); } return true; } // A base class for compile-time strings. struct compile_string {}; template FMT_VISIBILITY("hidden") // Suppress an ld warning on macOS (#3769). FMT_CONSTEXPR auto invoke_parse(parse_context& ctx) -> const Char* { using mapped_type = remove_cvref_t>; constexpr bool formattable = std::is_constructible>::value; if (!formattable) return ctx.begin(); // Error is reported in the value ctor. using formatted_type = conditional_t; return formatter().parse(ctx); } template struct arg_pack {}; template class format_string_checker { private: type types_[max_of(1, NUM_ARGS)]; named_arg_info named_args_[max_of(1, NUM_NAMED_ARGS)]; compile_parse_context context_; using parse_func = auto (*)(parse_context&) -> const Char*; parse_func parse_funcs_[max_of(1, NUM_ARGS)]; public: template FMT_CONSTEXPR explicit format_string_checker(basic_string_view fmt, arg_pack) : types_{mapped_type_constant::value...}, named_args_{}, context_(fmt, NUM_ARGS, types_), parse_funcs_{&invoke_parse...} { int arg_index = 0, named_arg_index = 0; FMT_APPLY_VARIADIC( init_static_named_arg(named_args_, arg_index, named_arg_index)); ignore_unused(arg_index, named_arg_index); } FMT_CONSTEXPR void on_text(const Char*, const Char*) {} FMT_CONSTEXPR auto on_arg_id() -> int { return context_.next_arg_id(); } FMT_CONSTEXPR auto on_arg_id(int id) -> int { context_.check_arg_id(id); return id; } FMT_CONSTEXPR auto on_arg_id(basic_string_view id) -> int { for (int i = 0; i < NUM_NAMED_ARGS; ++i) { if (named_args_[i].name == id) return named_args_[i].id; } if (!DYNAMIC_NAMES) on_error("argument not found"); return -1; } FMT_CONSTEXPR void on_replacement_field(int id, const Char* begin) { on_format_specs(id, begin, begin); // Call parse() on empty specs. } FMT_CONSTEXPR auto on_format_specs(int id, const Char* begin, const Char* end) -> const Char* { context_.advance_to(begin); if (id >= 0 && id < NUM_ARGS) return parse_funcs_[id](context_); while (begin != end && *begin != '}') ++begin; return begin; } FMT_NORETURN FMT_CONSTEXPR void on_error(const char* message) { report_error(message); } }; /// A contiguous memory buffer with an optional growing ability. It is an /// internal class and shouldn't be used directly, only via `memory_buffer`. template class buffer { private: T* ptr_; size_t size_; size_t capacity_; using grow_fun = void (*)(buffer& buf, size_t capacity); grow_fun grow_; protected: // Don't initialize ptr_ since it is not accessed to save a few cycles. FMT_MSC_WARNING(suppress : 26495) FMT_CONSTEXPR buffer(grow_fun grow, size_t sz) noexcept : size_(sz), capacity_(sz), grow_(grow) {} constexpr buffer(grow_fun grow, T* p = nullptr, size_t sz = 0, size_t cap = 0) noexcept : ptr_(p), size_(sz), capacity_(cap), grow_(grow) {} FMT_CONSTEXPR20 ~buffer() = default; buffer(buffer&&) = default; /// Sets the buffer data and capacity. FMT_CONSTEXPR void set(T* buf_data, size_t buf_capacity) noexcept { ptr_ = buf_data; capacity_ = buf_capacity; } public: using value_type = T; using const_reference = const T&; buffer(const buffer&) = delete; void operator=(const buffer&) = delete; auto begin() noexcept -> T* { return ptr_; } auto end() noexcept -> T* { return ptr_ + size_; } auto begin() const noexcept -> const T* { return ptr_; } auto end() const noexcept -> const T* { return ptr_ + size_; } /// Returns the size of this buffer. constexpr auto size() const noexcept -> size_t { return size_; } /// Returns the capacity of this buffer. constexpr auto capacity() const noexcept -> size_t { return capacity_; } /// Returns a pointer to the buffer data (not null-terminated). FMT_CONSTEXPR auto data() noexcept -> T* { return ptr_; } FMT_CONSTEXPR auto data() const noexcept -> const T* { return ptr_; } /// Clears this buffer. FMT_CONSTEXPR void clear() { size_ = 0; } // Tries resizing the buffer to contain `count` elements. If T is a POD type // the new elements may not be initialized. FMT_CONSTEXPR void try_resize(size_t count) { try_reserve(count); size_ = min_of(count, capacity_); } // Tries increasing the buffer capacity to `new_capacity`. It can increase the // capacity by a smaller amount than requested but guarantees there is space // for at least one additional element either by increasing the capacity or by // flushing the buffer if it is full. FMT_CONSTEXPR void try_reserve(size_t new_capacity) { if (new_capacity > capacity_) grow_(*this, new_capacity); } FMT_CONSTEXPR void push_back(const T& value) { try_reserve(size_ + 1); ptr_[size_++] = value; } /// Appends data to the end of the buffer. template // Workaround for MSVC2019 to fix error C2893: Failed to specialize function // template 'void fmt::v11::detail::buffer::append(const U *,const U *)'. #if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1940 FMT_CONSTEXPR20 #endif void append(const U* begin, const U* end) { while (begin != end) { auto count = to_unsigned(end - begin); try_reserve(size_ + count); auto free_cap = capacity_ - size_; if (free_cap < count) count = free_cap; // A loop is faster than memcpy on small sizes. T* out = ptr_ + size_; for (size_t i = 0; i < count; ++i) out[i] = begin[i]; size_ += count; begin += count; } } template FMT_CONSTEXPR auto operator[](Idx index) -> T& { return ptr_[index]; } template FMT_CONSTEXPR auto operator[](Idx index) const -> const T& { return ptr_[index]; } }; struct buffer_traits { constexpr explicit buffer_traits(size_t) {} constexpr auto count() const -> size_t { return 0; } constexpr auto limit(size_t size) const -> size_t { return size; } }; class fixed_buffer_traits { private: size_t count_ = 0; size_t limit_; public: constexpr explicit fixed_buffer_traits(size_t limit) : limit_(limit) {} constexpr auto count() const -> size_t { return count_; } FMT_CONSTEXPR auto limit(size_t size) -> size_t { size_t n = limit_ > count_ ? limit_ - count_ : 0; count_ += size; return min_of(size, n); } }; // A buffer that writes to an output iterator when flushed. template class iterator_buffer : public Traits, public buffer { private: OutputIt out_; enum { buffer_size = 256 }; T data_[buffer_size]; static FMT_CONSTEXPR void grow(buffer& buf, size_t) { if (buf.size() == buffer_size) static_cast(buf).flush(); } void flush() { auto size = this->size(); this->clear(); const T* begin = data_; const T* end = begin + this->limit(size); while (begin != end) *out_++ = *begin++; } public: explicit iterator_buffer(OutputIt out, size_t n = buffer_size) : Traits(n), buffer(grow, data_, 0, buffer_size), out_(out) {} iterator_buffer(iterator_buffer&& other) noexcept : Traits(other), buffer(grow, data_, 0, buffer_size), out_(other.out_) {} ~iterator_buffer() { // Don't crash if flush fails during unwinding. FMT_TRY { flush(); } FMT_CATCH(...) {} } auto out() -> OutputIt { flush(); return out_; } auto count() const -> size_t { return Traits::count() + this->size(); } }; template class iterator_buffer : public fixed_buffer_traits, public buffer { private: T* out_; enum { buffer_size = 256 }; T data_[buffer_size]; static FMT_CONSTEXPR void grow(buffer& buf, size_t) { if (buf.size() == buf.capacity()) static_cast(buf).flush(); } void flush() { size_t n = this->limit(this->size()); if (this->data() == out_) { out_ += n; this->set(data_, buffer_size); } this->clear(); } public: explicit iterator_buffer(T* out, size_t n = buffer_size) : fixed_buffer_traits(n), buffer(grow, out, 0, n), out_(out) {} iterator_buffer(iterator_buffer&& other) noexcept : fixed_buffer_traits(other), buffer(static_cast(other)), out_(other.out_) { if (this->data() != out_) { this->set(data_, buffer_size); this->clear(); } } ~iterator_buffer() { flush(); } auto out() -> T* { flush(); return out_; } auto count() const -> size_t { return fixed_buffer_traits::count() + this->size(); } }; template class iterator_buffer : public buffer { public: explicit iterator_buffer(T* out, size_t = 0) : buffer([](buffer&, size_t) {}, out, 0, ~size_t()) {} auto out() -> T* { return &*this->end(); } }; template class container_buffer : public buffer { private: using value_type = typename Container::value_type; static FMT_CONSTEXPR void grow(buffer& buf, size_t capacity) { auto& self = static_cast(buf); self.container.resize(capacity); self.set(&self.container[0], capacity); } public: Container& container; explicit container_buffer(Container& c) : buffer(grow, c.size()), container(c) {} }; // A buffer that writes to a container with the contiguous storage. template class iterator_buffer< OutputIt, enable_if_t::value && is_contiguous::value, typename OutputIt::container_type::value_type>> : public container_buffer { private: using base = container_buffer; public: explicit iterator_buffer(typename OutputIt::container_type& c) : base(c) {} explicit iterator_buffer(OutputIt out, size_t = 0) : base(get_container(out)) {} auto out() -> OutputIt { return OutputIt(this->container); } }; // A buffer that counts the number of code units written discarding the output. template class counting_buffer : public buffer { private: enum { buffer_size = 256 }; T data_[buffer_size]; size_t count_ = 0; static FMT_CONSTEXPR void grow(buffer& buf, size_t) { if (buf.size() != buffer_size) return; static_cast(buf).count_ += buf.size(); buf.clear(); } public: FMT_CONSTEXPR counting_buffer() : buffer(grow, data_, 0, buffer_size) {} constexpr auto count() const noexcept -> size_t { return count_ + this->size(); } }; template struct is_back_insert_iterator> : std::true_type {}; template struct has_back_insert_iterator_container_append : std::false_type {}; template struct has_back_insert_iterator_container_append< OutputIt, InputIt, void_t()) .append(std::declval(), std::declval()))>> : std::true_type {}; // An optimized version of std::copy with the output value type (T). template ::value&& has_back_insert_iterator_container_append< OutputIt, InputIt>::value)> FMT_CONSTEXPR20 auto copy(InputIt begin, InputIt end, OutputIt out) -> OutputIt { get_container(out).append(begin, end); return out; } template ::value && !has_back_insert_iterator_container_append< OutputIt, InputIt>::value)> FMT_CONSTEXPR20 auto copy(InputIt begin, InputIt end, OutputIt out) -> OutputIt { auto& c = get_container(out); c.insert(c.end(), begin, end); return out; } template ::value)> FMT_CONSTEXPR auto copy(InputIt begin, InputIt end, OutputIt out) -> OutputIt { while (begin != end) *out++ = static_cast(*begin++); return out; } template FMT_CONSTEXPR auto copy(basic_string_view s, OutputIt out) -> OutputIt { return copy(s.begin(), s.end(), out); } template struct is_buffer_appender : std::false_type {}; template struct is_buffer_appender< It, bool_constant< is_back_insert_iterator::value && std::is_base_of, typename It::container_type>::value>> : std::true_type {}; // Maps an output iterator to a buffer. template ::value)> auto get_buffer(OutputIt out) -> iterator_buffer { return iterator_buffer(out); } template ::value)> auto get_buffer(OutputIt out) -> buffer& { return get_container(out); } template auto get_iterator(Buf& buf, OutputIt) -> decltype(buf.out()) { return buf.out(); } template auto get_iterator(buffer&, OutputIt out) -> OutputIt { return out; } // This type is intentionally undefined, only used for errors. template struct type_is_unformattable_for; template struct string_value { const Char* data; size_t size; auto str() const -> basic_string_view { return {data, size}; } }; template struct custom_value { using char_type = typename Context::char_type; void* value; void (*format)(void* arg, parse_context& parse_ctx, Context& ctx); }; template struct named_arg_value { const named_arg_info* data; size_t size; }; struct custom_tag {}; #if !FMT_BUILTIN_TYPES # define FMT_BUILTIN , monostate #else # define FMT_BUILTIN #endif // A formatting argument value. template class value { public: using char_type = typename Context::char_type; union { monostate no_value; int int_value; unsigned uint_value; long long long_long_value; unsigned long long ulong_long_value; int128_opt int128_value; uint128_opt uint128_value; bool bool_value; char_type char_value; float float_value; double double_value; long double long_double_value; const void* pointer; string_value string; custom_value custom; named_arg_value named_args; }; constexpr FMT_INLINE value() : no_value() {} constexpr FMT_INLINE value(signed char x) : int_value(x) {} constexpr FMT_INLINE value(unsigned char x FMT_BUILTIN) : uint_value(x) {} constexpr FMT_INLINE value(signed short x) : int_value(x) {} constexpr FMT_INLINE value(unsigned short x FMT_BUILTIN) : uint_value(x) {} constexpr FMT_INLINE value(int x) : int_value(x) {} constexpr FMT_INLINE value(unsigned x FMT_BUILTIN) : uint_value(x) {} FMT_CONSTEXPR FMT_INLINE value(long x FMT_BUILTIN) : value(long_type(x)) {} FMT_CONSTEXPR FMT_INLINE value(unsigned long x FMT_BUILTIN) : value(ulong_type(x)) {} constexpr FMT_INLINE value(long long x FMT_BUILTIN) : long_long_value(x) {} constexpr FMT_INLINE value(unsigned long long x FMT_BUILTIN) : ulong_long_value(x) {} FMT_INLINE value(int128_opt x FMT_BUILTIN) : int128_value(x) {} FMT_INLINE value(uint128_opt x FMT_BUILTIN) : uint128_value(x) {} constexpr FMT_INLINE value(bool x FMT_BUILTIN) : bool_value(x) {} template constexpr FMT_INLINE value(bitint x FMT_BUILTIN) : long_long_value(x) { static_assert(N <= 64, "unsupported _BitInt"); } template constexpr FMT_INLINE value(ubitint x FMT_BUILTIN) : ulong_long_value(x) { static_assert(N <= 64, "unsupported _BitInt"); } template ::value)> constexpr FMT_INLINE value(T x FMT_BUILTIN) : char_value(x) { static_assert( std::is_same::value || std::is_same::value, "mixing character types is disallowed"); } constexpr FMT_INLINE value(float x FMT_BUILTIN) : float_value(x) {} constexpr FMT_INLINE value(double x FMT_BUILTIN) : double_value(x) {} FMT_INLINE value(long double x FMT_BUILTIN) : long_double_value(x) {} FMT_CONSTEXPR FMT_INLINE value(char_type* x FMT_BUILTIN) { string.data = x; if (is_constant_evaluated()) string.size = 0; } FMT_CONSTEXPR FMT_INLINE value(const char_type* x FMT_BUILTIN) { string.data = x; if (is_constant_evaluated()) string.size = 0; } template , FMT_ENABLE_IF(!std::is_pointer::value)> FMT_CONSTEXPR value(const T& x FMT_BUILTIN) { static_assert(std::is_same::value, "mixing character types is disallowed"); auto sv = to_string_view(x); string.data = sv.data(); string.size = sv.size(); } FMT_INLINE value(void* x FMT_BUILTIN) : pointer(x) {} FMT_INLINE value(const void* x FMT_BUILTIN) : pointer(x) {} FMT_INLINE value(volatile void* x FMT_BUILTIN) : pointer(const_cast(x)) {} FMT_INLINE value(const volatile void* x FMT_BUILTIN) : pointer(const_cast(x)) {} FMT_INLINE value(nullptr_t) : pointer(nullptr) {} template ::value || std::is_member_pointer::value)> value(const T&) { // Formatting of arbitrary pointers is disallowed. If you want to format a // pointer cast it to `void*` or `const void*`. In particular, this forbids // formatting of `[const] volatile char*` printed as bool by iostreams. static_assert(sizeof(T) == 0, "formatting of non-void pointers is disallowed"); } template ::value)> value(const T& x) : value(format_as(x)) {} template ::value)> value(const T& x) : value(formatter::format_as(x)) {} template ::value)> value(const T& named_arg) : value(named_arg.value) {} template ::value || !FMT_BUILTIN_TYPES)> FMT_CONSTEXPR20 FMT_INLINE value(T& x) : value(x, custom_tag()) {} FMT_ALWAYS_INLINE value(const named_arg_info* args, size_t size) : named_args{args, size} {} private: template ())> FMT_CONSTEXPR value(T& x, custom_tag) { using value_type = remove_const_t; // T may overload operator& e.g. std::vector::reference in libc++. if (!is_constant_evaluated()) { custom.value = const_cast(&reinterpret_cast(x)); } else { custom.value = nullptr; #if defined(__cpp_if_constexpr) if constexpr (std::is_same*>::value) custom.value = const_cast(&x); #endif } custom.format = format_custom>; } template ())> FMT_CONSTEXPR value(const T&, custom_tag) { // Cannot format an argument; to make type T formattable provide a // formatter specialization: https://fmt.dev/latest/api.html#udt. type_is_unformattable_for _; } // Formats an argument of a custom type, such as a user-defined class. template static void format_custom(void* arg, parse_context& parse_ctx, Context& ctx) { auto f = Formatter(); parse_ctx.advance_to(f.parse(parse_ctx)); using qualified_type = conditional_t(), const T, T>; // format must be const for compatibility with std::format and compilation. const auto& cf = f; ctx.advance_to(cf.format(*static_cast(arg), ctx)); } }; enum { packed_arg_bits = 4 }; // Maximum number of arguments with packed types. enum { max_packed_args = 62 / packed_arg_bits }; enum : unsigned long long { is_unpacked_bit = 1ULL << 63 }; enum : unsigned long long { has_named_args_bit = 1ULL << 62 }; template struct is_output_iterator : std::false_type {}; template <> struct is_output_iterator : std::true_type {}; template struct is_output_iterator< It, T, void_t&>()++ = std::declval())>> : std::true_type {}; #ifndef FMT_USE_LOCALE # define FMT_USE_LOCALE (FMT_OPTIMIZE_SIZE <= 1) #endif // A type-erased reference to an std::locale to avoid a heavy include. struct locale_ref { #if FMT_USE_LOCALE private: const void* locale_; // A type-erased pointer to std::locale. public: constexpr locale_ref() : locale_(nullptr) {} template locale_ref(const Locale& loc); inline explicit operator bool() const noexcept { return locale_ != nullptr; } #endif // FMT_USE_LOCALE template auto get() const -> Locale; }; template constexpr auto encode_types() -> unsigned long long { return 0; } template constexpr auto encode_types() -> unsigned long long { return static_cast(stored_type_constant::value) | (encode_types() << packed_arg_bits); } template constexpr auto make_descriptor() -> unsigned long long { return NUM_ARGS <= max_packed_args ? encode_types() : is_unpacked_bit | NUM_ARGS; } template using arg_t = conditional_t, basic_format_arg>; template struct named_arg_store { // args_[0].named_args points to named_args to avoid bloating format_args. arg_t args[1 + NUM_ARGS]; named_arg_info named_args[NUM_NAMED_ARGS]; template FMT_CONSTEXPR FMT_ALWAYS_INLINE named_arg_store(T&... values) : args{{named_args, NUM_NAMED_ARGS}, values...} { int arg_index = 0, named_arg_index = 0; FMT_APPLY_VARIADIC( init_named_arg(named_args, arg_index, named_arg_index, values)); } named_arg_store(named_arg_store&& rhs) { args[0] = {named_args, NUM_NAMED_ARGS}; for (size_t i = 1; i < sizeof(args) / sizeof(*args); ++i) args[i] = rhs.args[i]; for (size_t i = 0; i < NUM_NAMED_ARGS; ++i) named_args[i] = rhs.named_args[i]; } named_arg_store(const named_arg_store& rhs) = delete; named_arg_store& operator=(const named_arg_store& rhs) = delete; named_arg_store& operator=(named_arg_store&& rhs) = delete; operator const arg_t*() const { return args + 1; } }; // An array of references to arguments. It can be implicitly converted to // `basic_format_args` for passing into type-erased formatting functions // such as `vformat`. It is a plain struct to reduce binary size in debug mode. template struct format_arg_store { // +1 to workaround a bug in gcc 7.5 that causes duplicated-branches warning. using type = conditional_t[max_of(1, NUM_ARGS)], named_arg_store>; type args; }; // TYPE can be different from type_constant, e.g. for __float128. template struct native_formatter { private: dynamic_format_specs specs_; public: using nonlocking = void; FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { if (ctx.begin() == ctx.end() || *ctx.begin() == '}') return ctx.begin(); auto end = parse_format_specs(ctx.begin(), ctx.end(), specs_, ctx, TYPE); if (const_check(TYPE == type::char_type)) check_char_specs(specs_); return end; } template FMT_CONSTEXPR void set_debug_format(bool set = true) { specs_.set_type(set ? presentation_type::debug : presentation_type::none); } FMT_PRAGMA_CLANG(diagnostic ignored "-Wundefined-inline") template FMT_CONSTEXPR auto format(const T& val, FormatContext& ctx) const -> decltype(ctx.out()); }; template struct locking : bool_constant::value == type::custom_type> {}; template struct locking>::nonlocking>> : std::false_type {}; template FMT_CONSTEXPR inline auto is_locking() -> bool { return locking::value; } template FMT_CONSTEXPR inline auto is_locking() -> bool { return locking::value || is_locking(); } FMT_API void vformat_to(buffer& buf, string_view fmt, format_args args, locale_ref loc = {}); #if FMT_WIN32 FMT_API void vprint_mojibake(FILE*, string_view, format_args, bool); #else // format_args is passed by reference since it is defined later. inline void vprint_mojibake(FILE*, string_view, const format_args&, bool) {} #endif } // namespace detail // The main public API. template FMT_CONSTEXPR void parse_context::do_check_arg_id(int arg_id) { // Argument id is only checked at compile time during parsing because // formatting has its own validation. if (detail::is_constant_evaluated() && use_constexpr_cast) { auto ctx = static_cast*>(this); if (arg_id >= ctx->num_args()) report_error("argument not found"); } } template FMT_CONSTEXPR void parse_context::check_dynamic_spec(int arg_id) { using detail::compile_parse_context; if (detail::is_constant_evaluated() && use_constexpr_cast) static_cast*>(this)->check_dynamic_spec(arg_id); } FMT_BEGIN_EXPORT // An output iterator that appends to a buffer. It is used instead of // back_insert_iterator to reduce symbol sizes and avoid dependency. template class basic_appender { protected: detail::buffer* container; public: using container_type = detail::buffer; FMT_CONSTEXPR basic_appender(detail::buffer& buf) : container(&buf) {} FMT_CONSTEXPR20 auto operator=(T c) -> basic_appender& { container->push_back(c); return *this; } FMT_CONSTEXPR20 auto operator*() -> basic_appender& { return *this; } FMT_CONSTEXPR20 auto operator++() -> basic_appender& { return *this; } FMT_CONSTEXPR20 auto operator++(int) -> basic_appender { return *this; } }; // A formatting argument. Context is a template parameter for the compiled API // where output can be unbuffered. template class basic_format_arg { private: detail::value value_; detail::type type_; friend class basic_format_args; using char_type = typename Context::char_type; public: class handle { private: detail::custom_value custom_; public: explicit handle(detail::custom_value custom) : custom_(custom) {} void format(parse_context& parse_ctx, Context& ctx) const { custom_.format(custom_.value, parse_ctx, ctx); } }; constexpr basic_format_arg() : type_(detail::type::none_type) {} basic_format_arg(const detail::named_arg_info* args, size_t size) : value_(args, size) {} template basic_format_arg(T&& val) : value_(val), type_(detail::stored_type_constant::value) {} constexpr explicit operator bool() const noexcept { return type_ != detail::type::none_type; } auto type() const -> detail::type { return type_; } /** * Visits an argument dispatching to the appropriate visit method based on * the argument type. For example, if the argument type is `double` then * `vis(value)` will be called with the value of type `double`. */ template FMT_CONSTEXPR FMT_INLINE auto visit(Visitor&& vis) const -> decltype(vis(0)) { using detail::map; switch (type_) { case detail::type::none_type: break; case detail::type::int_type: return vis(value_.int_value); case detail::type::uint_type: return vis(value_.uint_value); case detail::type::long_long_type: return vis(value_.long_long_value); case detail::type::ulong_long_type: return vis(value_.ulong_long_value); case detail::type::int128_type: return vis(map(value_.int128_value)); case detail::type::uint128_type: return vis(map(value_.uint128_value)); case detail::type::bool_type: return vis(value_.bool_value); case detail::type::char_type: return vis(value_.char_value); case detail::type::float_type: return vis(value_.float_value); case detail::type::double_type: return vis(value_.double_value); case detail::type::long_double_type: return vis(value_.long_double_value); case detail::type::cstring_type: return vis(value_.string.data); case detail::type::string_type: return vis(value_.string.str()); case detail::type::pointer_type: return vis(value_.pointer); case detail::type::custom_type: return vis(handle(value_.custom)); } return vis(monostate()); } auto format_custom(const char_type* parse_begin, parse_context& parse_ctx, Context& ctx) -> bool { if (type_ != detail::type::custom_type) return false; parse_ctx.advance_to(parse_begin); value_.custom.format(value_.custom.value, parse_ctx, ctx); return true; } }; /** * A view of a collection of formatting arguments. To avoid lifetime issues it * should only be used as a parameter type in type-erased functions such as * `vformat`: * * void vlog(fmt::string_view fmt, fmt::format_args args); // OK * fmt::format_args args = fmt::make_format_args(); // Dangling reference */ template class basic_format_args { private: // A descriptor that contains information about formatting arguments. // If the number of arguments is less or equal to max_packed_args then // argument types are passed in the descriptor. This reduces binary code size // per formatting function call. unsigned long long desc_; union { // If is_packed() returns true then argument values are stored in values_; // otherwise they are stored in args_. This is done to improve cache // locality and reduce compiled code size since storing larger objects // may require more code (at least on x86-64) even if the same amount of // data is actually copied to stack. It saves ~10% on the bloat test. const detail::value* values_; const basic_format_arg* args_; }; constexpr auto is_packed() const -> bool { return (desc_ & detail::is_unpacked_bit) == 0; } constexpr auto has_named_args() const -> bool { return (desc_ & detail::has_named_args_bit) != 0; } FMT_CONSTEXPR auto type(int index) const -> detail::type { int shift = index * detail::packed_arg_bits; unsigned mask = (1 << detail::packed_arg_bits) - 1; return static_cast((desc_ >> shift) & mask); } template using store = detail::format_arg_store; public: using format_arg = basic_format_arg; constexpr basic_format_args() : desc_(0), args_(nullptr) {} /// Constructs a `basic_format_args` object from `format_arg_store`. template constexpr FMT_ALWAYS_INLINE basic_format_args( const store& s) : desc_(DESC | (NUM_NAMED_ARGS != 0 ? +detail::has_named_args_bit : 0)), values_(s.args) {} template detail::max_packed_args)> constexpr basic_format_args(const store& s) : desc_(DESC | (NUM_NAMED_ARGS != 0 ? +detail::has_named_args_bit : 0)), args_(s.args) {} /// Constructs a `basic_format_args` object from a dynamic list of arguments. constexpr basic_format_args(const format_arg* args, int count, bool has_named = false) : desc_(detail::is_unpacked_bit | detail::to_unsigned(count) | (has_named ? +detail::has_named_args_bit : 0)), args_(args) {} /// Returns the argument with the specified id. FMT_CONSTEXPR auto get(int id) const -> format_arg { auto arg = format_arg(); if (!is_packed()) { if (id < max_size()) arg = args_[id]; return arg; } if (static_cast(id) >= detail::max_packed_args) return arg; arg.type_ = type(id); if (arg.type_ != detail::type::none_type) arg.value_ = values_[id]; return arg; } template auto get(basic_string_view name) const -> format_arg { int id = get_id(name); return id >= 0 ? get(id) : format_arg(); } template FMT_CONSTEXPR auto get_id(basic_string_view name) const -> int { if (!has_named_args()) return -1; const auto& named_args = (is_packed() ? values_[-1] : args_[-1].value_).named_args; for (size_t i = 0; i < named_args.size; ++i) { if (named_args.data[i].name == name) return named_args.data[i].id; } return -1; } auto max_size() const -> int { unsigned long long max_packed = detail::max_packed_args; return static_cast(is_packed() ? max_packed : desc_ & ~detail::is_unpacked_bit); } }; // A formatting context. class context { private: appender out_; format_args args_; FMT_NO_UNIQUE_ADDRESS detail::locale_ref loc_; public: /// The character type for the output. using char_type = char; using iterator = appender; using format_arg = basic_format_arg; using parse_context_type FMT_DEPRECATED = parse_context<>; template using formatter_type FMT_DEPRECATED = formatter; enum { builtin_types = FMT_BUILTIN_TYPES }; /// Constructs a `context` object. References to the arguments are stored /// in the object so make sure they have appropriate lifetimes. FMT_CONSTEXPR context(iterator out, format_args args, detail::locale_ref loc = {}) : out_(out), args_(args), loc_(loc) {} context(context&&) = default; context(const context&) = delete; void operator=(const context&) = delete; FMT_CONSTEXPR auto arg(int id) const -> format_arg { return args_.get(id); } inline auto arg(string_view name) const -> format_arg { return args_.get(name); } FMT_CONSTEXPR auto arg_id(string_view name) const -> int { return args_.get_id(name); } // Returns an iterator to the beginning of the output range. FMT_CONSTEXPR auto out() const -> iterator { return out_; } // Advances the begin iterator to `it`. FMT_CONSTEXPR void advance_to(iterator) {} FMT_CONSTEXPR auto locale() const -> detail::locale_ref { return loc_; } }; template struct runtime_format_string { basic_string_view str; }; /** * Creates a runtime format string. * * **Example**: * * // Check format string at runtime instead of compile-time. * fmt::print(fmt::runtime("{:d}"), "I am not a number"); */ inline auto runtime(string_view s) -> runtime_format_string<> { return {{s}}; } /// A compile-time format string. Use `format_string` in the public API to /// prevent type deduction. template struct fstring { private: static constexpr int num_static_named_args = detail::count_static_named_args(); using checker = detail::format_string_checker< char, static_cast(sizeof...(T)), num_static_named_args, num_static_named_args != detail::count_named_args()>; using arg_pack = detail::arg_pack; public: string_view str; using t = fstring; // Reports a compile-time error if S is not a valid format string for T. template FMT_CONSTEVAL FMT_ALWAYS_INLINE fstring(const char (&s)[N]) : str(s, N - 1) { using namespace detail; static_assert(count<(std::is_base_of>::value && std::is_reference::value)...>() == 0, "passing views as lvalues is disallowed"); if (FMT_USE_CONSTEVAL) parse_format_string(s, checker(s, arg_pack())); #ifdef FMT_ENFORCE_COMPILE_STRING static_assert( FMT_USE_CONSTEVAL && sizeof(s) != 0, "FMT_ENFORCE_COMPILE_STRING requires format strings to use FMT_STRING"); #endif } template ::value)> FMT_CONSTEVAL FMT_ALWAYS_INLINE fstring(const S& s) : str(s) { auto sv = string_view(str); if (FMT_USE_CONSTEVAL) detail::parse_format_string(sv, checker(sv, arg_pack())); #ifdef FMT_ENFORCE_COMPILE_STRING static_assert( FMT_USE_CONSTEVAL && sizeof(s) != 0, "FMT_ENFORCE_COMPILE_STRING requires format strings to use FMT_STRING"); #endif } template ::value&& std::is_same::value)> FMT_ALWAYS_INLINE fstring(const S&) : str(S()) { FMT_CONSTEXPR auto sv = string_view(S()); FMT_CONSTEXPR int ignore = (parse_format_string(sv, checker(sv, arg_pack())), 0); detail::ignore_unused(ignore); } fstring(runtime_format_string<> fmt) : str(fmt.str) {} // Returning by reference generates better code in debug mode. FMT_ALWAYS_INLINE operator const string_view&() const { return str; } auto get() const -> string_view { return str; } }; template using format_string = typename fstring::t; template using is_formattable = bool_constant::value, int*, T>, Char>, void>::value>; #ifdef __cpp_concepts template concept formattable = is_formattable, Char>::value; #endif template using has_formatter FMT_DEPRECATED = std::is_constructible>; // A formatter specialization for natively supported types. template struct formatter::value != detail::type::custom_type>> : detail::native_formatter::value> { }; /** * Constructs an object that stores references to arguments and can be * implicitly converted to `format_args`. `Context` can be omitted in which case * it defaults to `context`. See `arg` for lifetime considerations. */ // Take arguments by lvalue references to avoid some lifetime issues, e.g. // auto args = make_format_args(std::string()); template (), unsigned long long DESC = detail::make_descriptor()> constexpr FMT_ALWAYS_INLINE auto make_format_args(T&... args) -> detail::format_arg_store { // Suppress warnings for pathological types convertible to detail::value. FMT_PRAGMA_GCC(diagnostic ignored "-Wconversion") return {{args...}}; } template using vargs = detail::format_arg_store(), detail::make_descriptor()>; /** * Returns a named argument to be used in a formatting function. * It should only be used in a call to a formatting function. * * **Example**: * * fmt::print("The answer is {answer}.", fmt::arg("answer", 42)); */ template inline auto arg(const Char* name, const T& arg) -> detail::named_arg { return {name, arg}; } /// Formats a string and writes the output to `out`. template , char>::value)> auto vformat_to(OutputIt&& out, string_view fmt, format_args args) -> remove_cvref_t { auto&& buf = detail::get_buffer(out); detail::vformat_to(buf, fmt, args, {}); return detail::get_iterator(buf, out); } /** * Formats `args` according to specifications in `fmt`, writes the result to * the output iterator `out` and returns the iterator past the end of the output * range. `format_to` does not append a terminating null character. * * **Example**: * * auto out = std::vector(); * fmt::format_to(std::back_inserter(out), "{}", 42); */ template , char>::value)> FMT_INLINE auto format_to(OutputIt&& out, format_string fmt, T&&... args) -> remove_cvref_t { return vformat_to(out, fmt.str, vargs{{args...}}); } template struct format_to_n_result { /// Iterator past the end of the output range. OutputIt out; /// Total (not truncated) output size. size_t size; }; template ::value)> auto vformat_to_n(OutputIt out, size_t n, string_view fmt, format_args args) -> format_to_n_result { using traits = detail::fixed_buffer_traits; auto buf = detail::iterator_buffer(out, n); detail::vformat_to(buf, fmt, args, {}); return {buf.out(), buf.count()}; } /** * Formats `args` according to specifications in `fmt`, writes up to `n` * characters of the result to the output iterator `out` and returns the total * (not truncated) output size and the iterator past the end of the output * range. `format_to_n` does not append a terminating null character. */ template ::value)> FMT_INLINE auto format_to_n(OutputIt out, size_t n, format_string fmt, T&&... args) -> format_to_n_result { return vformat_to_n(out, n, fmt.str, vargs{{args...}}); } struct format_to_result { /// Pointer to just after the last successful write in the array. char* out; /// Specifies if the output was truncated. bool truncated; FMT_CONSTEXPR operator char*() const { // Report truncation to prevent silent data loss. if (truncated) report_error("output is truncated"); return out; } }; template auto vformat_to(char (&out)[N], string_view fmt, format_args args) -> format_to_result { auto result = vformat_to_n(out, N, fmt, args); return {result.out, result.size > N}; } template FMT_INLINE auto format_to(char (&out)[N], format_string fmt, T&&... args) -> format_to_result { auto result = vformat_to_n(out, N, fmt.str, vargs{{args...}}); return {result.out, result.size > N}; } /// Returns the number of chars in the output of `format(fmt, args...)`. template FMT_NODISCARD FMT_INLINE auto formatted_size(format_string fmt, T&&... args) -> size_t { auto buf = detail::counting_buffer<>(); detail::vformat_to(buf, fmt.str, vargs{{args...}}, {}); return buf.count(); } FMT_API void vprint(string_view fmt, format_args args); FMT_API void vprint(FILE* f, string_view fmt, format_args args); FMT_API void vprintln(FILE* f, string_view fmt, format_args args); FMT_API void vprint_buffered(FILE* f, string_view fmt, format_args args); /** * Formats `args` according to specifications in `fmt` and writes the output * to `stdout`. * * **Example**: * * fmt::print("The answer is {}.", 42); */ template FMT_INLINE void print(format_string fmt, T&&... args) { vargs va = {{args...}}; if (detail::const_check(!detail::use_utf8)) return detail::vprint_mojibake(stdout, fmt.str, va, false); return detail::is_locking() ? vprint_buffered(stdout, fmt.str, va) : vprint(fmt.str, va); } /** * Formats `args` according to specifications in `fmt` and writes the * output to the file `f`. * * **Example**: * * fmt::print(stderr, "Don't {}!", "panic"); */ template FMT_INLINE void print(FILE* f, format_string fmt, T&&... args) { vargs va = {{args...}}; if (detail::const_check(!detail::use_utf8)) return detail::vprint_mojibake(f, fmt.str, va, false); return detail::is_locking() ? vprint_buffered(f, fmt.str, va) : vprint(f, fmt.str, va); } /// Formats `args` according to specifications in `fmt` and writes the output /// to the file `f` followed by a newline. template FMT_INLINE void println(FILE* f, format_string fmt, T&&... args) { vargs va = {{args...}}; return detail::const_check(detail::use_utf8) ? vprintln(f, fmt.str, va) : detail::vprint_mojibake(f, fmt.str, va, true); } /// Formats `args` according to specifications in `fmt` and writes the output /// to `stdout` followed by a newline. template FMT_INLINE void println(format_string fmt, T&&... args) { return fmt::println(stdout, fmt, static_cast(args)...); } FMT_END_EXPORT FMT_PRAGMA_CLANG(diagnostic pop) FMT_PRAGMA_GCC(pop_options) FMT_END_NAMESPACE #ifdef FMT_HEADER_ONLY # include "format.h" #endif #endif // FMT_BASE_H_ openal-soft-1.24.2/fmt-11.1.1/include/fmt/chrono.h000066400000000000000000002342021474041540300212110ustar00rootroot00000000000000// Formatting library for C++ - chrono support // // Copyright (c) 2012 - present, Victor Zverovich // All rights reserved. // // For the license information refer to format.h. #ifndef FMT_CHRONO_H_ #define FMT_CHRONO_H_ #ifndef FMT_MODULE # include # include # include // std::isfinite # include // std::memcpy # include # include # include # include # include #endif #include "format.h" namespace fmt_detail { struct time_zone { template auto to_sys(T) -> std::chrono::time_point { return {}; } }; template inline auto current_zone(T...) -> time_zone* { return nullptr; } template inline void _tzset(T...) {} } // namespace fmt_detail FMT_BEGIN_NAMESPACE // Enable safe chrono durations, unless explicitly disabled. #ifndef FMT_SAFE_DURATION_CAST # define FMT_SAFE_DURATION_CAST 1 #endif #if FMT_SAFE_DURATION_CAST // For conversion between std::chrono::durations without undefined // behaviour or erroneous results. // This is a stripped down version of duration_cast, for inclusion in fmt. // See https://github.com/pauldreik/safe_duration_cast // // Copyright Paul Dreik 2019 namespace safe_duration_cast { template ::value && std::numeric_limits::is_signed == std::numeric_limits::is_signed)> FMT_CONSTEXPR auto lossless_integral_conversion(const From from, int& ec) -> To { ec = 0; using F = std::numeric_limits; using T = std::numeric_limits; static_assert(F::is_integer, "From must be integral"); static_assert(T::is_integer, "To must be integral"); // A and B are both signed, or both unsigned. if (detail::const_check(F::digits <= T::digits)) { // From fits in To without any problem. } else { // From does not always fit in To, resort to a dynamic check. if (from < (T::min)() || from > (T::max)()) { // outside range. ec = 1; return {}; } } return static_cast(from); } /// Converts From to To, without loss. If the dynamic value of from /// can't be converted to To without loss, ec is set. template ::value && std::numeric_limits::is_signed != std::numeric_limits::is_signed)> FMT_CONSTEXPR auto lossless_integral_conversion(const From from, int& ec) -> To { ec = 0; using F = std::numeric_limits; using T = std::numeric_limits; static_assert(F::is_integer, "From must be integral"); static_assert(T::is_integer, "To must be integral"); if (detail::const_check(F::is_signed && !T::is_signed)) { // From may be negative, not allowed! if (fmt::detail::is_negative(from)) { ec = 1; return {}; } // From is positive. Can it always fit in To? if (detail::const_check(F::digits > T::digits) && from > static_cast(detail::max_value())) { ec = 1; return {}; } } if (detail::const_check(!F::is_signed && T::is_signed && F::digits >= T::digits) && from > static_cast(detail::max_value())) { ec = 1; return {}; } return static_cast(from); // Lossless conversion. } template ::value)> FMT_CONSTEXPR auto lossless_integral_conversion(const From from, int& ec) -> To { ec = 0; return from; } // function // clang-format off /** * converts From to To if possible, otherwise ec is set. * * input | output * ---------------------------------|--------------- * NaN | NaN * Inf | Inf * normal, fits in output | converted (possibly lossy) * normal, does not fit in output | ec is set * subnormal | best effort * -Inf | -Inf */ // clang-format on template ::value)> FMT_CONSTEXPR auto safe_float_conversion(const From from, int& ec) -> To { ec = 0; using T = std::numeric_limits; static_assert(std::is_floating_point::value, "From must be floating"); static_assert(std::is_floating_point::value, "To must be floating"); // catch the only happy case if (std::isfinite(from)) { if (from >= T::lowest() && from <= (T::max)()) { return static_cast(from); } // not within range. ec = 1; return {}; } // nan and inf will be preserved return static_cast(from); } // function template ::value)> FMT_CONSTEXPR auto safe_float_conversion(const From from, int& ec) -> To { ec = 0; static_assert(std::is_floating_point::value, "From must be floating"); return from; } /// Safe duration_cast between floating point durations template ::value), FMT_ENABLE_IF(std::is_floating_point::value)> auto safe_duration_cast(std::chrono::duration from, int& ec) -> To { using From = std::chrono::duration; ec = 0; if (std::isnan(from.count())) { // nan in, gives nan out. easy. return To{std::numeric_limits::quiet_NaN()}; } // maybe we should also check if from is denormal, and decide what to do about // it. // +-inf should be preserved. if (std::isinf(from.count())) { return To{from.count()}; } // the basic idea is that we need to convert from count() in the from type // to count() in the To type, by multiplying it with this: struct Factor : std::ratio_divide {}; static_assert(Factor::num > 0, "num must be positive"); static_assert(Factor::den > 0, "den must be positive"); // the conversion is like this: multiply from.count() with Factor::num // /Factor::den and convert it to To::rep, all this without // overflow/underflow. let's start by finding a suitable type that can hold // both To, From and Factor::num using IntermediateRep = typename std::common_type::type; // force conversion of From::rep -> IntermediateRep to be safe, // even if it will never happen be narrowing in this context. IntermediateRep count = safe_float_conversion(from.count(), ec); if (ec) { return {}; } // multiply with Factor::num without overflow or underflow if (detail::const_check(Factor::num != 1)) { constexpr auto max1 = detail::max_value() / static_cast(Factor::num); if (count > max1) { ec = 1; return {}; } constexpr auto min1 = std::numeric_limits::lowest() / static_cast(Factor::num); if (count < min1) { ec = 1; return {}; } count *= static_cast(Factor::num); } // this can't go wrong, right? den>0 is checked earlier. if (detail::const_check(Factor::den != 1)) { using common_t = typename std::common_type::type; count /= static_cast(Factor::den); } // convert to the to type, safely using ToRep = typename To::rep; const ToRep tocount = safe_float_conversion(count, ec); if (ec) { return {}; } return To{tocount}; } } // namespace safe_duration_cast #endif namespace detail { // Check if std::chrono::utc_time is available. #ifdef FMT_USE_UTC_TIME // Use the provided definition. #elif defined(__cpp_lib_chrono) # define FMT_USE_UTC_TIME (__cpp_lib_chrono >= 201907L) #else # define FMT_USE_UTC_TIME 0 #endif #if FMT_USE_UTC_TIME using utc_clock = std::chrono::utc_clock; #else struct utc_clock { void to_sys(); }; #endif // Check if std::chrono::local_time is available. #ifdef FMT_USE_LOCAL_TIME // Use the provided definition. #elif defined(__cpp_lib_chrono) # define FMT_USE_LOCAL_TIME (__cpp_lib_chrono >= 201907L) #else # define FMT_USE_LOCAL_TIME 0 #endif #if FMT_USE_LOCAL_TIME using local_t = std::chrono::local_t; #else struct local_t {}; #endif } // namespace detail template using sys_time = std::chrono::time_point; template using utc_time = std::chrono::time_point; template using local_time = std::chrono::time_point; namespace detail { // Prevents expansion of a preceding token as a function-style macro. // Usage: f FMT_NOMACRO() #define FMT_NOMACRO template struct null {}; inline auto localtime_r FMT_NOMACRO(...) -> null<> { return null<>(); } inline auto localtime_s(...) -> null<> { return null<>(); } inline auto gmtime_r(...) -> null<> { return null<>(); } inline auto gmtime_s(...) -> null<> { return null<>(); } // It is defined here and not in ostream.h because the latter has expensive // includes. template class formatbuf : public StreamBuf { private: using char_type = typename StreamBuf::char_type; using streamsize = decltype(std::declval().sputn(nullptr, 0)); using int_type = typename StreamBuf::int_type; using traits_type = typename StreamBuf::traits_type; buffer& buffer_; public: explicit formatbuf(buffer& buf) : buffer_(buf) {} protected: // The put area is always empty. This makes the implementation simpler and has // the advantage that the streambuf and the buffer are always in sync and // sputc never writes into uninitialized memory. A disadvantage is that each // call to sputc always results in a (virtual) call to overflow. There is no // disadvantage here for sputn since this always results in a call to xsputn. auto overflow(int_type ch) -> int_type override { if (!traits_type::eq_int_type(ch, traits_type::eof())) buffer_.push_back(static_cast(ch)); return ch; } auto xsputn(const char_type* s, streamsize count) -> streamsize override { buffer_.append(s, s + count); return count; } }; inline auto get_classic_locale() -> const std::locale& { static const auto& locale = std::locale::classic(); return locale; } template struct codecvt_result { static constexpr const size_t max_size = 32; CodeUnit buf[max_size]; CodeUnit* end; }; template void write_codecvt(codecvt_result& out, string_view in, const std::locale& loc) { FMT_PRAGMA_CLANG(diagnostic push) FMT_PRAGMA_CLANG(diagnostic ignored "-Wdeprecated") auto& f = std::use_facet>(loc); FMT_PRAGMA_CLANG(diagnostic pop) auto mb = std::mbstate_t(); const char* from_next = nullptr; auto result = f.in(mb, in.begin(), in.end(), from_next, std::begin(out.buf), std::end(out.buf), out.end); if (result != std::codecvt_base::ok) FMT_THROW(format_error("failed to format time")); } template auto write_encoded_tm_str(OutputIt out, string_view in, const std::locale& loc) -> OutputIt { if (detail::use_utf8 && loc != get_classic_locale()) { // char16_t and char32_t codecvts are broken in MSVC (linkage errors) and // gcc-4. #if FMT_MSC_VERSION != 0 || \ (defined(__GLIBCXX__) && \ (!defined(_GLIBCXX_USE_DUAL_ABI) || _GLIBCXX_USE_DUAL_ABI == 0)) // The _GLIBCXX_USE_DUAL_ABI macro is always defined in libstdc++ from gcc-5 // and newer. using code_unit = wchar_t; #else using code_unit = char32_t; #endif using unit_t = codecvt_result; unit_t unit; write_codecvt(unit, in, loc); // In UTF-8 is used one to four one-byte code units. auto u = to_utf8>(); if (!u.convert({unit.buf, to_unsigned(unit.end - unit.buf)})) FMT_THROW(format_error("failed to format time")); return copy(u.c_str(), u.c_str() + u.size(), out); } return copy(in.data(), in.data() + in.size(), out); } template ::value)> auto write_tm_str(OutputIt out, string_view sv, const std::locale& loc) -> OutputIt { codecvt_result unit; write_codecvt(unit, sv, loc); return copy(unit.buf, unit.end, out); } template ::value)> auto write_tm_str(OutputIt out, string_view sv, const std::locale& loc) -> OutputIt { return write_encoded_tm_str(out, sv, loc); } template inline void do_write(buffer& buf, const std::tm& time, const std::locale& loc, char format, char modifier) { auto&& format_buf = formatbuf>(buf); auto&& os = std::basic_ostream(&format_buf); os.imbue(loc); const auto& facet = std::use_facet>(loc); auto end = facet.put(os, os, Char(' '), &time, format, modifier); if (end.failed()) FMT_THROW(format_error("failed to format time")); } template ::value)> auto write(OutputIt out, const std::tm& time, const std::locale& loc, char format, char modifier = 0) -> OutputIt { auto&& buf = get_buffer(out); do_write(buf, time, loc, format, modifier); return get_iterator(buf, out); } template ::value)> auto write(OutputIt out, const std::tm& time, const std::locale& loc, char format, char modifier = 0) -> OutputIt { auto&& buf = basic_memory_buffer(); do_write(buf, time, loc, format, modifier); return write_encoded_tm_str(out, string_view(buf.data(), buf.size()), loc); } template struct is_same_arithmetic_type : public std::integral_constant::value && std::is_integral::value) || (std::is_floating_point::value && std::is_floating_point::value)> { }; FMT_NORETURN inline void throw_duration_error() { FMT_THROW(format_error("cannot format duration")); } // Cast one integral duration to another with an overflow check. template ::value&& std::is_integral::value)> auto duration_cast(std::chrono::duration from) -> To { #if !FMT_SAFE_DURATION_CAST return std::chrono::duration_cast(from); #else // The conversion factor: to.count() == factor * from.count(). using factor = std::ratio_divide; using common_rep = typename std::common_type::type; int ec = 0; auto count = safe_duration_cast::lossless_integral_conversion( from.count(), ec); if (ec) throw_duration_error(); // Multiply from.count() by factor and check for overflow. if (const_check(factor::num != 1)) { if (count > max_value() / factor::num) throw_duration_error(); const auto min = (std::numeric_limits::min)() / factor::num; if (const_check(!std::is_unsigned::value) && count < min) throw_duration_error(); count *= factor::num; } if (const_check(factor::den != 1)) count /= factor::den; auto to = To(safe_duration_cast::lossless_integral_conversion( count, ec)); if (ec) throw_duration_error(); return to; #endif } template ::value&& std::is_floating_point::value)> auto duration_cast(std::chrono::duration from) -> To { #if FMT_SAFE_DURATION_CAST // Throwing version of safe_duration_cast is only available for // integer to integer or float to float casts. int ec; To to = safe_duration_cast::safe_duration_cast(from, ec); if (ec) throw_duration_error(); return to; #else // Standard duration cast, may overflow. return std::chrono::duration_cast(from); #endif } template < typename To, typename FromRep, typename FromPeriod, FMT_ENABLE_IF(!is_same_arithmetic_type::value)> auto duration_cast(std::chrono::duration from) -> To { // Mixed integer <-> float cast is not supported by safe_duration_cast. return std::chrono::duration_cast(from); } template auto to_time_t(sys_time time_point) -> std::time_t { // Cannot use std::chrono::system_clock::to_time_t since this would first // require a cast to std::chrono::system_clock::time_point, which could // overflow. return detail::duration_cast>( time_point.time_since_epoch()) .count(); } // Workaround a bug in libstdc++ which sets __cpp_lib_chrono to 201907 without // providing current_zone(): https://github.com/fmtlib/fmt/issues/4160. template FMT_CONSTEXPR auto has_current_zone() -> bool { using namespace std::chrono; using namespace fmt_detail; return !std::is_same::value; } } // namespace detail FMT_BEGIN_EXPORT /** * Converts given time since epoch as `std::time_t` value into calendar time, * expressed in local time. Unlike `std::localtime`, this function is * thread-safe on most platforms. */ inline auto localtime(std::time_t time) -> std::tm { struct dispatcher { std::time_t time_; std::tm tm_; inline dispatcher(std::time_t t) : time_(t) {} inline auto run() -> bool { using namespace fmt::detail; return handle(localtime_r(&time_, &tm_)); } inline auto handle(std::tm* tm) -> bool { return tm != nullptr; } inline auto handle(detail::null<>) -> bool { using namespace fmt::detail; return fallback(localtime_s(&tm_, &time_)); } inline auto fallback(int res) -> bool { return res == 0; } #if !FMT_MSC_VERSION inline auto fallback(detail::null<>) -> bool { using namespace fmt::detail; std::tm* tm = std::localtime(&time_); if (tm) tm_ = *tm; return tm != nullptr; } #endif }; dispatcher lt(time); // Too big time values may be unsupported. if (!lt.run()) FMT_THROW(format_error("time_t value out of range")); return lt.tm_; } #if FMT_USE_LOCAL_TIME template ())> inline auto localtime(std::chrono::local_time time) -> std::tm { using namespace std::chrono; using namespace fmt_detail; return localtime(detail::to_time_t(current_zone()->to_sys(time))); } #endif /** * Converts given time since epoch as `std::time_t` value into calendar time, * expressed in Coordinated Universal Time (UTC). Unlike `std::gmtime`, this * function is thread-safe on most platforms. */ inline auto gmtime(std::time_t time) -> std::tm { struct dispatcher { std::time_t time_; std::tm tm_; inline dispatcher(std::time_t t) : time_(t) {} inline auto run() -> bool { using namespace fmt::detail; return handle(gmtime_r(&time_, &tm_)); } inline auto handle(std::tm* tm) -> bool { return tm != nullptr; } inline auto handle(detail::null<>) -> bool { using namespace fmt::detail; return fallback(gmtime_s(&tm_, &time_)); } inline auto fallback(int res) -> bool { return res == 0; } #if !FMT_MSC_VERSION inline auto fallback(detail::null<>) -> bool { std::tm* tm = std::gmtime(&time_); if (tm) tm_ = *tm; return tm != nullptr; } #endif }; auto gt = dispatcher(time); // Too big time values may be unsupported. if (!gt.run()) FMT_THROW(format_error("time_t value out of range")); return gt.tm_; } template inline auto gmtime(sys_time time_point) -> std::tm { return gmtime(detail::to_time_t(time_point)); } namespace detail { // Writes two-digit numbers a, b and c separated by sep to buf. // The method by Pavel Novikov based on // https://johnnylee-sde.github.io/Fast-unsigned-integer-to-time-string/. inline void write_digit2_separated(char* buf, unsigned a, unsigned b, unsigned c, char sep) { unsigned long long digits = a | (b << 24) | (static_cast(c) << 48); // Convert each value to BCD. // We have x = a * 10 + b and we want to convert it to BCD y = a * 16 + b. // The difference is // y - x = a * 6 // a can be found from x: // a = floor(x / 10) // then // y = x + a * 6 = x + floor(x / 10) * 6 // floor(x / 10) is (x * 205) >> 11 (needs 16 bits). digits += (((digits * 205) >> 11) & 0x000f00000f00000f) * 6; // Put low nibbles to high bytes and high nibbles to low bytes. digits = ((digits & 0x00f00000f00000f0) >> 4) | ((digits & 0x000f00000f00000f) << 8); auto usep = static_cast(sep); // Add ASCII '0' to each digit byte and insert separators. digits |= 0x3030003030003030 | (usep << 16) | (usep << 40); constexpr const size_t len = 8; if (const_check(is_big_endian())) { char tmp[len]; std::memcpy(tmp, &digits, len); std::reverse_copy(tmp, tmp + len, buf); } else { std::memcpy(buf, &digits, len); } } template FMT_CONSTEXPR inline auto get_units() -> const char* { if (std::is_same::value) return "as"; if (std::is_same::value) return "fs"; if (std::is_same::value) return "ps"; if (std::is_same::value) return "ns"; if (std::is_same::value) return detail::use_utf8 ? "µs" : "us"; if (std::is_same::value) return "ms"; if (std::is_same::value) return "cs"; if (std::is_same::value) return "ds"; if (std::is_same>::value) return "s"; if (std::is_same::value) return "das"; if (std::is_same::value) return "hs"; if (std::is_same::value) return "ks"; if (std::is_same::value) return "Ms"; if (std::is_same::value) return "Gs"; if (std::is_same::value) return "Ts"; if (std::is_same::value) return "Ps"; if (std::is_same::value) return "Es"; if (std::is_same>::value) return "min"; if (std::is_same>::value) return "h"; if (std::is_same>::value) return "d"; return nullptr; } enum class numeric_system { standard, // Alternative numeric system, e.g. å二 instead of 12 in ja_JP locale. alternative }; // Glibc extensions for formatting numeric values. enum class pad_type { // Pad a numeric result string with zeros (the default). zero, // Do not pad a numeric result string. none, // Pad a numeric result string with spaces. space, }; template auto write_padding(OutputIt out, pad_type pad, int width) -> OutputIt { if (pad == pad_type::none) return out; return detail::fill_n(out, width, pad == pad_type::space ? ' ' : '0'); } template auto write_padding(OutputIt out, pad_type pad) -> OutputIt { if (pad != pad_type::none) *out++ = pad == pad_type::space ? ' ' : '0'; return out; } // Parses a put_time-like format string and invokes handler actions. template FMT_CONSTEXPR auto parse_chrono_format(const Char* begin, const Char* end, Handler&& handler) -> const Char* { if (begin == end || *begin == '}') return begin; if (*begin != '%') FMT_THROW(format_error("invalid format")); auto ptr = begin; while (ptr != end) { pad_type pad = pad_type::zero; auto c = *ptr; if (c == '}') break; if (c != '%') { ++ptr; continue; } if (begin != ptr) handler.on_text(begin, ptr); ++ptr; // consume '%' if (ptr == end) FMT_THROW(format_error("invalid format")); c = *ptr; switch (c) { case '_': pad = pad_type::space; ++ptr; break; case '-': pad = pad_type::none; ++ptr; break; } if (ptr == end) FMT_THROW(format_error("invalid format")); c = *ptr++; switch (c) { case '%': handler.on_text(ptr - 1, ptr); break; case 'n': { const Char newline[] = {'\n'}; handler.on_text(newline, newline + 1); break; } case 't': { const Char tab[] = {'\t'}; handler.on_text(tab, tab + 1); break; } // Year: case 'Y': handler.on_year(numeric_system::standard, pad); break; case 'y': handler.on_short_year(numeric_system::standard); break; case 'C': handler.on_century(numeric_system::standard); break; case 'G': handler.on_iso_week_based_year(); break; case 'g': handler.on_iso_week_based_short_year(); break; // Day of the week: case 'a': handler.on_abbr_weekday(); break; case 'A': handler.on_full_weekday(); break; case 'w': handler.on_dec0_weekday(numeric_system::standard); break; case 'u': handler.on_dec1_weekday(numeric_system::standard); break; // Month: case 'b': case 'h': handler.on_abbr_month(); break; case 'B': handler.on_full_month(); break; case 'm': handler.on_dec_month(numeric_system::standard, pad); break; // Day of the year/month: case 'U': handler.on_dec0_week_of_year(numeric_system::standard, pad); break; case 'W': handler.on_dec1_week_of_year(numeric_system::standard, pad); break; case 'V': handler.on_iso_week_of_year(numeric_system::standard, pad); break; case 'j': handler.on_day_of_year(pad); break; case 'd': handler.on_day_of_month(numeric_system::standard, pad); break; case 'e': handler.on_day_of_month(numeric_system::standard, pad_type::space); break; // Hour, minute, second: case 'H': handler.on_24_hour(numeric_system::standard, pad); break; case 'I': handler.on_12_hour(numeric_system::standard, pad); break; case 'M': handler.on_minute(numeric_system::standard, pad); break; case 'S': handler.on_second(numeric_system::standard, pad); break; // Other: case 'c': handler.on_datetime(numeric_system::standard); break; case 'x': handler.on_loc_date(numeric_system::standard); break; case 'X': handler.on_loc_time(numeric_system::standard); break; case 'D': handler.on_us_date(); break; case 'F': handler.on_iso_date(); break; case 'r': handler.on_12_hour_time(); break; case 'R': handler.on_24_hour_time(); break; case 'T': handler.on_iso_time(); break; case 'p': handler.on_am_pm(); break; case 'Q': handler.on_duration_value(); break; case 'q': handler.on_duration_unit(); break; case 'z': handler.on_utc_offset(numeric_system::standard); break; case 'Z': handler.on_tz_name(); break; // Alternative representation: case 'E': { if (ptr == end) FMT_THROW(format_error("invalid format")); c = *ptr++; switch (c) { case 'Y': handler.on_year(numeric_system::alternative, pad); break; case 'y': handler.on_offset_year(); break; case 'C': handler.on_century(numeric_system::alternative); break; case 'c': handler.on_datetime(numeric_system::alternative); break; case 'x': handler.on_loc_date(numeric_system::alternative); break; case 'X': handler.on_loc_time(numeric_system::alternative); break; case 'z': handler.on_utc_offset(numeric_system::alternative); break; default: FMT_THROW(format_error("invalid format")); } break; } case 'O': if (ptr == end) FMT_THROW(format_error("invalid format")); c = *ptr++; switch (c) { case 'y': handler.on_short_year(numeric_system::alternative); break; case 'm': handler.on_dec_month(numeric_system::alternative, pad); break; case 'U': handler.on_dec0_week_of_year(numeric_system::alternative, pad); break; case 'W': handler.on_dec1_week_of_year(numeric_system::alternative, pad); break; case 'V': handler.on_iso_week_of_year(numeric_system::alternative, pad); break; case 'd': handler.on_day_of_month(numeric_system::alternative, pad); break; case 'e': handler.on_day_of_month(numeric_system::alternative, pad_type::space); break; case 'w': handler.on_dec0_weekday(numeric_system::alternative); break; case 'u': handler.on_dec1_weekday(numeric_system::alternative); break; case 'H': handler.on_24_hour(numeric_system::alternative, pad); break; case 'I': handler.on_12_hour(numeric_system::alternative, pad); break; case 'M': handler.on_minute(numeric_system::alternative, pad); break; case 'S': handler.on_second(numeric_system::alternative, pad); break; case 'z': handler.on_utc_offset(numeric_system::alternative); break; default: FMT_THROW(format_error("invalid format")); } break; default: FMT_THROW(format_error("invalid format")); } begin = ptr; } if (begin != ptr) handler.on_text(begin, ptr); return ptr; } template struct null_chrono_spec_handler { FMT_CONSTEXPR void unsupported() { static_cast(this)->unsupported(); } FMT_CONSTEXPR void on_year(numeric_system, pad_type) { unsupported(); } FMT_CONSTEXPR void on_short_year(numeric_system) { unsupported(); } FMT_CONSTEXPR void on_offset_year() { unsupported(); } FMT_CONSTEXPR void on_century(numeric_system) { unsupported(); } FMT_CONSTEXPR void on_iso_week_based_year() { unsupported(); } FMT_CONSTEXPR void on_iso_week_based_short_year() { unsupported(); } FMT_CONSTEXPR void on_abbr_weekday() { unsupported(); } FMT_CONSTEXPR void on_full_weekday() { unsupported(); } FMT_CONSTEXPR void on_dec0_weekday(numeric_system) { unsupported(); } FMT_CONSTEXPR void on_dec1_weekday(numeric_system) { unsupported(); } FMT_CONSTEXPR void on_abbr_month() { unsupported(); } FMT_CONSTEXPR void on_full_month() { unsupported(); } FMT_CONSTEXPR void on_dec_month(numeric_system, pad_type) { unsupported(); } FMT_CONSTEXPR void on_dec0_week_of_year(numeric_system, pad_type) { unsupported(); } FMT_CONSTEXPR void on_dec1_week_of_year(numeric_system, pad_type) { unsupported(); } FMT_CONSTEXPR void on_iso_week_of_year(numeric_system, pad_type) { unsupported(); } FMT_CONSTEXPR void on_day_of_year(pad_type) { unsupported(); } FMT_CONSTEXPR void on_day_of_month(numeric_system, pad_type) { unsupported(); } FMT_CONSTEXPR void on_24_hour(numeric_system) { unsupported(); } FMT_CONSTEXPR void on_12_hour(numeric_system) { unsupported(); } FMT_CONSTEXPR void on_minute(numeric_system) { unsupported(); } FMT_CONSTEXPR void on_second(numeric_system) { unsupported(); } FMT_CONSTEXPR void on_datetime(numeric_system) { unsupported(); } FMT_CONSTEXPR void on_loc_date(numeric_system) { unsupported(); } FMT_CONSTEXPR void on_loc_time(numeric_system) { unsupported(); } FMT_CONSTEXPR void on_us_date() { unsupported(); } FMT_CONSTEXPR void on_iso_date() { unsupported(); } FMT_CONSTEXPR void on_12_hour_time() { unsupported(); } FMT_CONSTEXPR void on_24_hour_time() { unsupported(); } FMT_CONSTEXPR void on_iso_time() { unsupported(); } FMT_CONSTEXPR void on_am_pm() { unsupported(); } FMT_CONSTEXPR void on_duration_value() { unsupported(); } FMT_CONSTEXPR void on_duration_unit() { unsupported(); } FMT_CONSTEXPR void on_utc_offset(numeric_system) { unsupported(); } FMT_CONSTEXPR void on_tz_name() { unsupported(); } }; struct tm_format_checker : null_chrono_spec_handler { FMT_NORETURN inline void unsupported() { FMT_THROW(format_error("no format")); } template FMT_CONSTEXPR void on_text(const Char*, const Char*) {} FMT_CONSTEXPR void on_year(numeric_system, pad_type) {} FMT_CONSTEXPR void on_short_year(numeric_system) {} FMT_CONSTEXPR void on_offset_year() {} FMT_CONSTEXPR void on_century(numeric_system) {} FMT_CONSTEXPR void on_iso_week_based_year() {} FMT_CONSTEXPR void on_iso_week_based_short_year() {} FMT_CONSTEXPR void on_abbr_weekday() {} FMT_CONSTEXPR void on_full_weekday() {} FMT_CONSTEXPR void on_dec0_weekday(numeric_system) {} FMT_CONSTEXPR void on_dec1_weekday(numeric_system) {} FMT_CONSTEXPR void on_abbr_month() {} FMT_CONSTEXPR void on_full_month() {} FMT_CONSTEXPR void on_dec_month(numeric_system, pad_type) {} FMT_CONSTEXPR void on_dec0_week_of_year(numeric_system, pad_type) {} FMT_CONSTEXPR void on_dec1_week_of_year(numeric_system, pad_type) {} FMT_CONSTEXPR void on_iso_week_of_year(numeric_system, pad_type) {} FMT_CONSTEXPR void on_day_of_year(pad_type) {} FMT_CONSTEXPR void on_day_of_month(numeric_system, pad_type) {} FMT_CONSTEXPR void on_24_hour(numeric_system, pad_type) {} FMT_CONSTEXPR void on_12_hour(numeric_system, pad_type) {} FMT_CONSTEXPR void on_minute(numeric_system, pad_type) {} FMT_CONSTEXPR void on_second(numeric_system, pad_type) {} FMT_CONSTEXPR void on_datetime(numeric_system) {} FMT_CONSTEXPR void on_loc_date(numeric_system) {} FMT_CONSTEXPR void on_loc_time(numeric_system) {} FMT_CONSTEXPR void on_us_date() {} FMT_CONSTEXPR void on_iso_date() {} FMT_CONSTEXPR void on_12_hour_time() {} FMT_CONSTEXPR void on_24_hour_time() {} FMT_CONSTEXPR void on_iso_time() {} FMT_CONSTEXPR void on_am_pm() {} FMT_CONSTEXPR void on_utc_offset(numeric_system) {} FMT_CONSTEXPR void on_tz_name() {} }; inline auto tm_wday_full_name(int wday) -> const char* { static constexpr const char* full_name_list[] = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}; return wday >= 0 && wday <= 6 ? full_name_list[wday] : "?"; } inline auto tm_wday_short_name(int wday) -> const char* { static constexpr const char* short_name_list[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; return wday >= 0 && wday <= 6 ? short_name_list[wday] : "???"; } inline auto tm_mon_full_name(int mon) -> const char* { static constexpr const char* full_name_list[] = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"}; return mon >= 0 && mon <= 11 ? full_name_list[mon] : "?"; } inline auto tm_mon_short_name(int mon) -> const char* { static constexpr const char* short_name_list[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", }; return mon >= 0 && mon <= 11 ? short_name_list[mon] : "???"; } template struct has_member_data_tm_gmtoff : std::false_type {}; template struct has_member_data_tm_gmtoff> : std::true_type {}; template struct has_member_data_tm_zone : std::false_type {}; template struct has_member_data_tm_zone> : std::true_type {}; inline void tzset_once() { static bool init = []() { using namespace fmt_detail; _tzset(); return false; }(); ignore_unused(init); } // Converts value to Int and checks that it's in the range [0, upper). template ::value)> inline auto to_nonnegative_int(T value, Int upper) -> Int { if (!std::is_unsigned::value && (value < 0 || to_unsigned(value) > to_unsigned(upper))) { FMT_THROW(fmt::format_error("chrono value is out of range")); } return static_cast(value); } template ::value)> inline auto to_nonnegative_int(T value, Int upper) -> Int { auto int_value = static_cast(value); if (int_value < 0 || value > static_cast(upper)) FMT_THROW(format_error("invalid value")); return int_value; } constexpr auto pow10(std::uint32_t n) -> long long { return n == 0 ? 1 : 10 * pow10(n - 1); } // Counts the number of fractional digits in the range [0, 18] according to the // C++20 spec. If more than 18 fractional digits are required then returns 6 for // microseconds precision. template () / 10)> struct count_fractional_digits { static constexpr int value = Num % Den == 0 ? N : count_fractional_digits::value; }; // Base case that doesn't instantiate any more templates // in order to avoid overflow. template struct count_fractional_digits { static constexpr int value = (Num % Den == 0) ? N : 6; }; // Format subseconds which are given as an integer type with an appropriate // number of digits. template void write_fractional_seconds(OutputIt& out, Duration d, int precision = -1) { constexpr auto num_fractional_digits = count_fractional_digits::value; using subsecond_precision = std::chrono::duration< typename std::common_type::type, std::ratio<1, pow10(num_fractional_digits)>>; const auto fractional = d - detail::duration_cast(d); const auto subseconds = std::chrono::treat_as_floating_point< typename subsecond_precision::rep>::value ? fractional.count() : detail::duration_cast(fractional).count(); auto n = static_cast>(subseconds); const int num_digits = count_digits(n); int leading_zeroes = (std::max)(0, num_fractional_digits - num_digits); if (precision < 0) { FMT_ASSERT(!std::is_floating_point::value, ""); if (std::ratio_less::value) { *out++ = '.'; out = detail::fill_n(out, leading_zeroes, '0'); out = format_decimal(out, n, num_digits); } } else if (precision > 0) { *out++ = '.'; leading_zeroes = min_of(leading_zeroes, precision); int remaining = precision - leading_zeroes; out = detail::fill_n(out, leading_zeroes, '0'); if (remaining < num_digits) { int num_truncated_digits = num_digits - remaining; n /= to_unsigned(pow10(to_unsigned(num_truncated_digits))); if (n != 0) out = format_decimal(out, n, remaining); return; } if (n != 0) { out = format_decimal(out, n, num_digits); remaining -= num_digits; } out = detail::fill_n(out, remaining, '0'); } } // Format subseconds which are given as a floating point type with an // appropriate number of digits. We cannot pass the Duration here, as we // explicitly need to pass the Rep value in the chrono_formatter. template void write_floating_seconds(memory_buffer& buf, Duration duration, int num_fractional_digits = -1) { using rep = typename Duration::rep; FMT_ASSERT(std::is_floating_point::value, ""); auto val = duration.count(); if (num_fractional_digits < 0) { // For `std::round` with fallback to `round`: // On some toolchains `std::round` is not available (e.g. GCC 6). using namespace std; num_fractional_digits = count_fractional_digits::value; if (num_fractional_digits < 6 && static_cast(round(val)) != val) num_fractional_digits = 6; } fmt::format_to(std::back_inserter(buf), FMT_STRING("{:.{}f}"), std::fmod(val * static_cast(Duration::period::num) / static_cast(Duration::period::den), static_cast(60)), num_fractional_digits); } template class tm_writer { private: static constexpr int days_per_week = 7; const std::locale& loc_; const bool is_classic_; OutputIt out_; const Duration* subsecs_; const std::tm& tm_; auto tm_sec() const noexcept -> int { FMT_ASSERT(tm_.tm_sec >= 0 && tm_.tm_sec <= 61, ""); return tm_.tm_sec; } auto tm_min() const noexcept -> int { FMT_ASSERT(tm_.tm_min >= 0 && tm_.tm_min <= 59, ""); return tm_.tm_min; } auto tm_hour() const noexcept -> int { FMT_ASSERT(tm_.tm_hour >= 0 && tm_.tm_hour <= 23, ""); return tm_.tm_hour; } auto tm_mday() const noexcept -> int { FMT_ASSERT(tm_.tm_mday >= 1 && tm_.tm_mday <= 31, ""); return tm_.tm_mday; } auto tm_mon() const noexcept -> int { FMT_ASSERT(tm_.tm_mon >= 0 && tm_.tm_mon <= 11, ""); return tm_.tm_mon; } auto tm_year() const noexcept -> long long { return 1900ll + tm_.tm_year; } auto tm_wday() const noexcept -> int { FMT_ASSERT(tm_.tm_wday >= 0 && tm_.tm_wday <= 6, ""); return tm_.tm_wday; } auto tm_yday() const noexcept -> int { FMT_ASSERT(tm_.tm_yday >= 0 && tm_.tm_yday <= 365, ""); return tm_.tm_yday; } auto tm_hour12() const noexcept -> int { const auto h = tm_hour(); const auto z = h < 12 ? h : h - 12; return z == 0 ? 12 : z; } // POSIX and the C Standard are unclear or inconsistent about what %C and %y // do if the year is negative or exceeds 9999. Use the convention that %C // concatenated with %y yields the same output as %Y, and that %Y contains at // least 4 characters, with more only if necessary. auto split_year_lower(long long year) const noexcept -> int { auto l = year % 100; if (l < 0) l = -l; // l in [0, 99] return static_cast(l); } // Algorithm: https://en.wikipedia.org/wiki/ISO_week_date. auto iso_year_weeks(long long curr_year) const noexcept -> int { const auto prev_year = curr_year - 1; const auto curr_p = (curr_year + curr_year / 4 - curr_year / 100 + curr_year / 400) % days_per_week; const auto prev_p = (prev_year + prev_year / 4 - prev_year / 100 + prev_year / 400) % days_per_week; return 52 + ((curr_p == 4 || prev_p == 3) ? 1 : 0); } auto iso_week_num(int tm_yday, int tm_wday) const noexcept -> int { return (tm_yday + 11 - (tm_wday == 0 ? days_per_week : tm_wday)) / days_per_week; } auto tm_iso_week_year() const noexcept -> long long { const auto year = tm_year(); const auto w = iso_week_num(tm_yday(), tm_wday()); if (w < 1) return year - 1; if (w > iso_year_weeks(year)) return year + 1; return year; } auto tm_iso_week_of_year() const noexcept -> int { const auto year = tm_year(); const auto w = iso_week_num(tm_yday(), tm_wday()); if (w < 1) return iso_year_weeks(year - 1); if (w > iso_year_weeks(year)) return 1; return w; } void write1(int value) { *out_++ = static_cast('0' + to_unsigned(value) % 10); } void write2(int value) { const char* d = digits2(to_unsigned(value) % 100); *out_++ = *d++; *out_++ = *d; } void write2(int value, pad_type pad) { unsigned int v = to_unsigned(value) % 100; if (v >= 10) { const char* d = digits2(v); *out_++ = *d++; *out_++ = *d; } else { out_ = detail::write_padding(out_, pad); *out_++ = static_cast('0' + v); } } void write_year_extended(long long year, pad_type pad) { // At least 4 characters. int width = 4; bool negative = year < 0; if (negative) { year = 0 - year; --width; } uint32_or_64_or_128_t n = to_unsigned(year); const int num_digits = count_digits(n); if (negative && pad == pad_type::zero) *out_++ = '-'; if (width > num_digits) { out_ = detail::write_padding(out_, pad, width - num_digits); } if (negative && pad != pad_type::zero) *out_++ = '-'; out_ = format_decimal(out_, n, num_digits); } void write_year(long long year, pad_type pad) { write_year_extended(year, pad); } void write_utc_offset(long long offset, numeric_system ns) { if (offset < 0) { *out_++ = '-'; offset = -offset; } else { *out_++ = '+'; } offset /= 60; write2(static_cast(offset / 60)); if (ns != numeric_system::standard) *out_++ = ':'; write2(static_cast(offset % 60)); } template ::value)> void format_utc_offset_impl(const T& tm, numeric_system ns) { write_utc_offset(tm.tm_gmtoff, ns); } template ::value)> void format_utc_offset_impl(const T& tm, numeric_system ns) { #if defined(_WIN32) && defined(_UCRT) tzset_once(); long offset = 0; _get_timezone(&offset); if (tm.tm_isdst) { long dstbias = 0; _get_dstbias(&dstbias); offset += dstbias; } write_utc_offset(-offset, ns); #else if (ns == numeric_system::standard) return format_localized('z'); // Extract timezone offset from timezone conversion functions. std::tm gtm = tm; std::time_t gt = std::mktime(>m); std::tm ltm = gmtime(gt); std::time_t lt = std::mktime(<m); long long offset = gt - lt; write_utc_offset(offset, ns); #endif } template ::value)> void format_tz_name_impl(const T& tm) { if (is_classic_) out_ = write_tm_str(out_, tm.tm_zone, loc_); else format_localized('Z'); } template ::value)> void format_tz_name_impl(const T&) { format_localized('Z'); } void format_localized(char format, char modifier = 0) { out_ = write(out_, tm_, loc_, format, modifier); } public: tm_writer(const std::locale& loc, OutputIt out, const std::tm& tm, const Duration* subsecs = nullptr) : loc_(loc), is_classic_(loc_ == get_classic_locale()), out_(out), subsecs_(subsecs), tm_(tm) {} auto out() const -> OutputIt { return out_; } FMT_CONSTEXPR void on_text(const Char* begin, const Char* end) { out_ = copy(begin, end, out_); } void on_abbr_weekday() { if (is_classic_) out_ = write(out_, tm_wday_short_name(tm_wday())); else format_localized('a'); } void on_full_weekday() { if (is_classic_) out_ = write(out_, tm_wday_full_name(tm_wday())); else format_localized('A'); } void on_dec0_weekday(numeric_system ns) { if (is_classic_ || ns == numeric_system::standard) return write1(tm_wday()); format_localized('w', 'O'); } void on_dec1_weekday(numeric_system ns) { if (is_classic_ || ns == numeric_system::standard) { auto wday = tm_wday(); write1(wday == 0 ? days_per_week : wday); } else { format_localized('u', 'O'); } } void on_abbr_month() { if (is_classic_) out_ = write(out_, tm_mon_short_name(tm_mon())); else format_localized('b'); } void on_full_month() { if (is_classic_) out_ = write(out_, tm_mon_full_name(tm_mon())); else format_localized('B'); } void on_datetime(numeric_system ns) { if (is_classic_) { on_abbr_weekday(); *out_++ = ' '; on_abbr_month(); *out_++ = ' '; on_day_of_month(numeric_system::standard, pad_type::space); *out_++ = ' '; on_iso_time(); *out_++ = ' '; on_year(numeric_system::standard, pad_type::space); } else { format_localized('c', ns == numeric_system::standard ? '\0' : 'E'); } } void on_loc_date(numeric_system ns) { if (is_classic_) on_us_date(); else format_localized('x', ns == numeric_system::standard ? '\0' : 'E'); } void on_loc_time(numeric_system ns) { if (is_classic_) on_iso_time(); else format_localized('X', ns == numeric_system::standard ? '\0' : 'E'); } void on_us_date() { char buf[8]; write_digit2_separated(buf, to_unsigned(tm_mon() + 1), to_unsigned(tm_mday()), to_unsigned(split_year_lower(tm_year())), '/'); out_ = copy(std::begin(buf), std::end(buf), out_); } void on_iso_date() { auto year = tm_year(); char buf[10]; size_t offset = 0; if (year >= 0 && year < 10000) { write2digits(buf, static_cast(year / 100)); } else { offset = 4; write_year_extended(year, pad_type::zero); year = 0; } write_digit2_separated(buf + 2, static_cast(year % 100), to_unsigned(tm_mon() + 1), to_unsigned(tm_mday()), '-'); out_ = copy(std::begin(buf) + offset, std::end(buf), out_); } void on_utc_offset(numeric_system ns) { format_utc_offset_impl(tm_, ns); } void on_tz_name() { format_tz_name_impl(tm_); } void on_year(numeric_system ns, pad_type pad) { if (is_classic_ || ns == numeric_system::standard) return write_year(tm_year(), pad); format_localized('Y', 'E'); } void on_short_year(numeric_system ns) { if (is_classic_ || ns == numeric_system::standard) return write2(split_year_lower(tm_year())); format_localized('y', 'O'); } void on_offset_year() { if (is_classic_) return write2(split_year_lower(tm_year())); format_localized('y', 'E'); } void on_century(numeric_system ns) { if (is_classic_ || ns == numeric_system::standard) { auto year = tm_year(); auto upper = year / 100; if (year >= -99 && year < 0) { // Zero upper on negative year. *out_++ = '-'; *out_++ = '0'; } else if (upper >= 0 && upper < 100) { write2(static_cast(upper)); } else { out_ = write(out_, upper); } } else { format_localized('C', 'E'); } } void on_dec_month(numeric_system ns, pad_type pad) { if (is_classic_ || ns == numeric_system::standard) return write2(tm_mon() + 1, pad); format_localized('m', 'O'); } void on_dec0_week_of_year(numeric_system ns, pad_type pad) { if (is_classic_ || ns == numeric_system::standard) return write2((tm_yday() + days_per_week - tm_wday()) / days_per_week, pad); format_localized('U', 'O'); } void on_dec1_week_of_year(numeric_system ns, pad_type pad) { if (is_classic_ || ns == numeric_system::standard) { auto wday = tm_wday(); write2((tm_yday() + days_per_week - (wday == 0 ? (days_per_week - 1) : (wday - 1))) / days_per_week, pad); } else { format_localized('W', 'O'); } } void on_iso_week_of_year(numeric_system ns, pad_type pad) { if (is_classic_ || ns == numeric_system::standard) return write2(tm_iso_week_of_year(), pad); format_localized('V', 'O'); } void on_iso_week_based_year() { write_year(tm_iso_week_year(), pad_type::zero); } void on_iso_week_based_short_year() { write2(split_year_lower(tm_iso_week_year())); } void on_day_of_year(pad_type pad) { auto yday = tm_yday() + 1; auto digit1 = yday / 100; if (digit1 != 0) { write1(digit1); } else { out_ = detail::write_padding(out_, pad); } write2(yday % 100, pad); } void on_day_of_month(numeric_system ns, pad_type pad) { if (is_classic_ || ns == numeric_system::standard) return write2(tm_mday(), pad); format_localized('d', 'O'); } void on_24_hour(numeric_system ns, pad_type pad) { if (is_classic_ || ns == numeric_system::standard) return write2(tm_hour(), pad); format_localized('H', 'O'); } void on_12_hour(numeric_system ns, pad_type pad) { if (is_classic_ || ns == numeric_system::standard) return write2(tm_hour12(), pad); format_localized('I', 'O'); } void on_minute(numeric_system ns, pad_type pad) { if (is_classic_ || ns == numeric_system::standard) return write2(tm_min(), pad); format_localized('M', 'O'); } void on_second(numeric_system ns, pad_type pad) { if (is_classic_ || ns == numeric_system::standard) { write2(tm_sec(), pad); if (subsecs_) { if (std::is_floating_point::value) { auto buf = memory_buffer(); write_floating_seconds(buf, *subsecs_); if (buf.size() > 1) { // Remove the leading "0", write something like ".123". out_ = copy(buf.begin() + 1, buf.end(), out_); } } else { write_fractional_seconds(out_, *subsecs_); } } } else { // Currently no formatting of subseconds when a locale is set. format_localized('S', 'O'); } } void on_12_hour_time() { if (is_classic_) { char buf[8]; write_digit2_separated(buf, to_unsigned(tm_hour12()), to_unsigned(tm_min()), to_unsigned(tm_sec()), ':'); out_ = copy(std::begin(buf), std::end(buf), out_); *out_++ = ' '; on_am_pm(); } else { format_localized('r'); } } void on_24_hour_time() { write2(tm_hour()); *out_++ = ':'; write2(tm_min()); } void on_iso_time() { on_24_hour_time(); *out_++ = ':'; on_second(numeric_system::standard, pad_type::zero); } void on_am_pm() { if (is_classic_) { *out_++ = tm_hour() < 12 ? 'A' : 'P'; *out_++ = 'M'; } else { format_localized('p'); } } // These apply to chrono durations but not tm. void on_duration_value() {} void on_duration_unit() {} }; struct chrono_format_checker : null_chrono_spec_handler { bool has_precision_integral = false; FMT_NORETURN inline void unsupported() { FMT_THROW(format_error("no date")); } template FMT_CONSTEXPR void on_text(const Char*, const Char*) {} FMT_CONSTEXPR void on_day_of_year(pad_type) {} FMT_CONSTEXPR void on_24_hour(numeric_system, pad_type) {} FMT_CONSTEXPR void on_12_hour(numeric_system, pad_type) {} FMT_CONSTEXPR void on_minute(numeric_system, pad_type) {} FMT_CONSTEXPR void on_second(numeric_system, pad_type) {} FMT_CONSTEXPR void on_12_hour_time() {} FMT_CONSTEXPR void on_24_hour_time() {} FMT_CONSTEXPR void on_iso_time() {} FMT_CONSTEXPR void on_am_pm() {} FMT_CONSTEXPR void on_duration_value() const { if (has_precision_integral) FMT_THROW(format_error("precision not allowed for this argument type")); } FMT_CONSTEXPR void on_duration_unit() {} }; template ::value&& has_isfinite::value)> inline auto isfinite(T) -> bool { return true; } template ::value)> inline auto mod(T x, int y) -> T { return x % static_cast(y); } template ::value)> inline auto mod(T x, int y) -> T { return std::fmod(x, static_cast(y)); } // If T is an integral type, maps T to its unsigned counterpart, otherwise // leaves it unchanged (unlike std::make_unsigned). template ::value> struct make_unsigned_or_unchanged { using type = T; }; template struct make_unsigned_or_unchanged { using type = typename std::make_unsigned::type; }; template ::value)> inline auto get_milliseconds(std::chrono::duration d) -> std::chrono::duration { // this may overflow and/or the result may not fit in the // target type. #if FMT_SAFE_DURATION_CAST using CommonSecondsType = typename std::common_type::type; const auto d_as_common = detail::duration_cast(d); const auto d_as_whole_seconds = detail::duration_cast(d_as_common); // this conversion should be nonproblematic const auto diff = d_as_common - d_as_whole_seconds; const auto ms = detail::duration_cast>(diff); return ms; #else auto s = detail::duration_cast(d); return detail::duration_cast(d - s); #endif } template ::value)> auto format_duration_value(OutputIt out, Rep val, int) -> OutputIt { return write(out, val); } template ::value)> auto format_duration_value(OutputIt out, Rep val, int precision) -> OutputIt { auto specs = format_specs(); specs.precision = precision; specs.set_type(precision >= 0 ? presentation_type::fixed : presentation_type::general); return write(out, val, specs); } template auto copy_unit(string_view unit, OutputIt out, Char) -> OutputIt { return copy(unit.begin(), unit.end(), out); } template auto copy_unit(string_view unit, OutputIt out, wchar_t) -> OutputIt { // This works when wchar_t is UTF-32 because units only contain characters // that have the same representation in UTF-16 and UTF-32. utf8_to_utf16 u(unit); return copy(u.c_str(), u.c_str() + u.size(), out); } template auto format_duration_unit(OutputIt out) -> OutputIt { if (const char* unit = get_units()) return copy_unit(string_view(unit), out, Char()); *out++ = '['; out = write(out, Period::num); if (const_check(Period::den != 1)) { *out++ = '/'; out = write(out, Period::den); } *out++ = ']'; *out++ = 's'; return out; } class get_locale { private: union { std::locale locale_; }; bool has_locale_ = false; public: inline get_locale(bool localized, locale_ref loc) : has_locale_(localized) { if (localized) ::new (&locale_) std::locale(loc.template get()); } inline ~get_locale() { if (has_locale_) locale_.~locale(); } inline operator const std::locale&() const { return has_locale_ ? locale_ : get_classic_locale(); } }; template struct chrono_formatter { FormatContext& context; OutputIt out; int precision; bool localized = false; // rep is unsigned to avoid overflow. using rep = conditional_t::value && sizeof(Rep) < sizeof(int), unsigned, typename make_unsigned_or_unchanged::type>; rep val; using seconds = std::chrono::duration; seconds s; using milliseconds = std::chrono::duration; bool negative; using char_type = typename FormatContext::char_type; using tm_writer_type = tm_writer; chrono_formatter(FormatContext& ctx, OutputIt o, std::chrono::duration d) : context(ctx), out(o), val(static_cast(d.count())), negative(false) { if (d.count() < 0) { val = 0 - val; negative = true; } // this may overflow and/or the result may not fit in the // target type. // might need checked conversion (rep!=Rep) s = detail::duration_cast(std::chrono::duration(val)); } // returns true if nan or inf, writes to out. auto handle_nan_inf() -> bool { if (isfinite(val)) { return false; } if (isnan(val)) { write_nan(); return true; } // must be +-inf if (val > 0) { write_pinf(); } else { write_ninf(); } return true; } auto days() const -> Rep { return static_cast(s.count() / 86400); } auto hour() const -> Rep { return static_cast(mod((s.count() / 3600), 24)); } auto hour12() const -> Rep { Rep hour = static_cast(mod((s.count() / 3600), 12)); return hour <= 0 ? 12 : hour; } auto minute() const -> Rep { return static_cast(mod((s.count() / 60), 60)); } auto second() const -> Rep { return static_cast(mod(s.count(), 60)); } auto time() const -> std::tm { auto time = std::tm(); time.tm_hour = to_nonnegative_int(hour(), 24); time.tm_min = to_nonnegative_int(minute(), 60); time.tm_sec = to_nonnegative_int(second(), 60); return time; } void write_sign() { if (negative) { *out++ = '-'; negative = false; } } void write(Rep value, int width, pad_type pad = pad_type::zero) { write_sign(); if (isnan(value)) return write_nan(); uint32_or_64_or_128_t n = to_unsigned(to_nonnegative_int(value, max_value())); int num_digits = detail::count_digits(n); if (width > num_digits) { out = detail::write_padding(out, pad, width - num_digits); } out = format_decimal(out, n, num_digits); } void write_nan() { std::copy_n("nan", 3, out); } void write_pinf() { std::copy_n("inf", 3, out); } void write_ninf() { std::copy_n("-inf", 4, out); } template void format_tm(const tm& time, Callback cb, Args... args) { if (isnan(val)) return write_nan(); get_locale loc(localized, context.locale()); auto w = tm_writer_type(loc, out, time); (w.*cb)(args...); out = w.out(); } void on_text(const char_type* begin, const char_type* end) { copy(begin, end, out); } // These are not implemented because durations don't have date information. void on_abbr_weekday() {} void on_full_weekday() {} void on_dec0_weekday(numeric_system) {} void on_dec1_weekday(numeric_system) {} void on_abbr_month() {} void on_full_month() {} void on_datetime(numeric_system) {} void on_loc_date(numeric_system) {} void on_loc_time(numeric_system) {} void on_us_date() {} void on_iso_date() {} void on_utc_offset(numeric_system) {} void on_tz_name() {} void on_year(numeric_system, pad_type) {} void on_short_year(numeric_system) {} void on_offset_year() {} void on_century(numeric_system) {} void on_iso_week_based_year() {} void on_iso_week_based_short_year() {} void on_dec_month(numeric_system, pad_type) {} void on_dec0_week_of_year(numeric_system, pad_type) {} void on_dec1_week_of_year(numeric_system, pad_type) {} void on_iso_week_of_year(numeric_system, pad_type) {} void on_day_of_month(numeric_system, pad_type) {} void on_day_of_year(pad_type) { if (handle_nan_inf()) return; write(days(), 0); } void on_24_hour(numeric_system ns, pad_type pad) { if (handle_nan_inf()) return; if (ns == numeric_system::standard) return write(hour(), 2, pad); auto time = tm(); time.tm_hour = to_nonnegative_int(hour(), 24); format_tm(time, &tm_writer_type::on_24_hour, ns, pad); } void on_12_hour(numeric_system ns, pad_type pad) { if (handle_nan_inf()) return; if (ns == numeric_system::standard) return write(hour12(), 2, pad); auto time = tm(); time.tm_hour = to_nonnegative_int(hour12(), 12); format_tm(time, &tm_writer_type::on_12_hour, ns, pad); } void on_minute(numeric_system ns, pad_type pad) { if (handle_nan_inf()) return; if (ns == numeric_system::standard) return write(minute(), 2, pad); auto time = tm(); time.tm_min = to_nonnegative_int(minute(), 60); format_tm(time, &tm_writer_type::on_minute, ns, pad); } void on_second(numeric_system ns, pad_type pad) { if (handle_nan_inf()) return; if (ns == numeric_system::standard) { if (std::is_floating_point::value) { auto buf = memory_buffer(); write_floating_seconds(buf, std::chrono::duration(val), precision); if (negative) *out++ = '-'; if (buf.size() < 2 || buf[1] == '.') { out = detail::write_padding(out, pad); } out = copy(buf.begin(), buf.end(), out); } else { write(second(), 2, pad); write_fractional_seconds( out, std::chrono::duration(val), precision); } return; } auto time = tm(); time.tm_sec = to_nonnegative_int(second(), 60); format_tm(time, &tm_writer_type::on_second, ns, pad); } void on_12_hour_time() { if (handle_nan_inf()) return; format_tm(time(), &tm_writer_type::on_12_hour_time); } void on_24_hour_time() { if (handle_nan_inf()) { *out++ = ':'; handle_nan_inf(); return; } write(hour(), 2); *out++ = ':'; write(minute(), 2); } void on_iso_time() { on_24_hour_time(); *out++ = ':'; if (handle_nan_inf()) return; on_second(numeric_system::standard, pad_type::zero); } void on_am_pm() { if (handle_nan_inf()) return; format_tm(time(), &tm_writer_type::on_am_pm); } void on_duration_value() { if (handle_nan_inf()) return; write_sign(); out = format_duration_value(out, val, precision); } void on_duration_unit() { out = format_duration_unit(out); } }; } // namespace detail #if defined(__cpp_lib_chrono) && __cpp_lib_chrono >= 201907 using weekday = std::chrono::weekday; using day = std::chrono::day; using month = std::chrono::month; using year = std::chrono::year; using year_month_day = std::chrono::year_month_day; #else // A fallback version of weekday. class weekday { private: unsigned char value_; public: weekday() = default; constexpr explicit weekday(unsigned wd) noexcept : value_(static_cast(wd != 7 ? wd : 0)) {} constexpr auto c_encoding() const noexcept -> unsigned { return value_; } }; class day { private: unsigned char value_; public: day() = default; constexpr explicit day(unsigned d) noexcept : value_(static_cast(d)) {} constexpr explicit operator unsigned() const noexcept { return value_; } }; class month { private: unsigned char value_; public: month() = default; constexpr explicit month(unsigned m) noexcept : value_(static_cast(m)) {} constexpr explicit operator unsigned() const noexcept { return value_; } }; class year { private: int value_; public: year() = default; constexpr explicit year(int y) noexcept : value_(y) {} constexpr explicit operator int() const noexcept { return value_; } }; class year_month_day { private: fmt::year year_; fmt::month month_; fmt::day day_; public: year_month_day() = default; constexpr year_month_day(const year& y, const month& m, const day& d) noexcept : year_(y), month_(m), day_(d) {} constexpr auto year() const noexcept -> fmt::year { return year_; } constexpr auto month() const noexcept -> fmt::month { return month_; } constexpr auto day() const noexcept -> fmt::day { return day_; } }; #endif template struct formatter : private formatter { private: bool localized_ = false; bool use_tm_formatter_ = false; public: FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { auto it = ctx.begin(), end = ctx.end(); if (it != end && *it == 'L') { ++it; localized_ = true; return it; } use_tm_formatter_ = it != end && *it != '}'; return use_tm_formatter_ ? formatter::parse(ctx) : it; } template auto format(weekday wd, FormatContext& ctx) const -> decltype(ctx.out()) { auto time = std::tm(); time.tm_wday = static_cast(wd.c_encoding()); if (use_tm_formatter_) return formatter::format(time, ctx); detail::get_locale loc(localized_, ctx.locale()); auto w = detail::tm_writer(loc, ctx.out(), time); w.on_abbr_weekday(); return w.out(); } }; template struct formatter : private formatter { private: bool use_tm_formatter_ = false; public: FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { auto it = ctx.begin(), end = ctx.end(); use_tm_formatter_ = it != end && *it != '}'; return use_tm_formatter_ ? formatter::parse(ctx) : it; } template auto format(day d, FormatContext& ctx) const -> decltype(ctx.out()) { auto time = std::tm(); time.tm_mday = static_cast(static_cast(d)); if (use_tm_formatter_) return formatter::format(time, ctx); detail::get_locale loc(false, ctx.locale()); auto w = detail::tm_writer(loc, ctx.out(), time); w.on_day_of_month(detail::numeric_system::standard, detail::pad_type::zero); return w.out(); } }; template struct formatter : private formatter { private: bool localized_ = false; bool use_tm_formatter_ = false; public: FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { auto it = ctx.begin(), end = ctx.end(); if (it != end && *it == 'L') { ++it; localized_ = true; return it; } use_tm_formatter_ = it != end && *it != '}'; return use_tm_formatter_ ? formatter::parse(ctx) : it; } template auto format(month m, FormatContext& ctx) const -> decltype(ctx.out()) { auto time = std::tm(); time.tm_mon = static_cast(static_cast(m)) - 1; if (use_tm_formatter_) return formatter::format(time, ctx); detail::get_locale loc(localized_, ctx.locale()); auto w = detail::tm_writer(loc, ctx.out(), time); w.on_abbr_month(); return w.out(); } }; template struct formatter : private formatter { private: bool use_tm_formatter_ = false; public: FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { auto it = ctx.begin(), end = ctx.end(); use_tm_formatter_ = it != end && *it != '}'; return use_tm_formatter_ ? formatter::parse(ctx) : it; } template auto format(year y, FormatContext& ctx) const -> decltype(ctx.out()) { auto time = std::tm(); time.tm_year = static_cast(y) - 1900; if (use_tm_formatter_) return formatter::format(time, ctx); detail::get_locale loc(false, ctx.locale()); auto w = detail::tm_writer(loc, ctx.out(), time); w.on_year(detail::numeric_system::standard, detail::pad_type::zero); return w.out(); } }; template struct formatter : private formatter { private: bool use_tm_formatter_ = false; public: FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { auto it = ctx.begin(), end = ctx.end(); use_tm_formatter_ = it != end && *it != '}'; return use_tm_formatter_ ? formatter::parse(ctx) : it; } template auto format(year_month_day val, FormatContext& ctx) const -> decltype(ctx.out()) { auto time = std::tm(); time.tm_year = static_cast(val.year()) - 1900; time.tm_mon = static_cast(static_cast(val.month())) - 1; time.tm_mday = static_cast(static_cast(val.day())); if (use_tm_formatter_) return formatter::format(time, ctx); detail::get_locale loc(true, ctx.locale()); auto w = detail::tm_writer(loc, ctx.out(), time); w.on_iso_date(); return w.out(); } }; template struct formatter, Char> { private: format_specs specs_; detail::arg_ref width_ref_; detail::arg_ref precision_ref_; bool localized_ = false; basic_string_view fmt_; public: FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { auto it = ctx.begin(), end = ctx.end(); if (it == end || *it == '}') return it; it = detail::parse_align(it, end, specs_); if (it == end) return it; Char c = *it; if ((c >= '0' && c <= '9') || c == '{') { it = detail::parse_width(it, end, specs_, width_ref_, ctx); if (it == end) return it; } auto checker = detail::chrono_format_checker(); if (*it == '.') { checker.has_precision_integral = !std::is_floating_point::value; it = detail::parse_precision(it, end, specs_, precision_ref_, ctx); } if (it != end && *it == 'L') { localized_ = true; ++it; } end = detail::parse_chrono_format(it, end, checker); fmt_ = {it, detail::to_unsigned(end - it)}; return end; } template auto format(std::chrono::duration d, FormatContext& ctx) const -> decltype(ctx.out()) { auto specs = specs_; auto precision = specs.precision; specs.precision = -1; auto begin = fmt_.begin(), end = fmt_.end(); // As a possible future optimization, we could avoid extra copying if width // is not specified. auto buf = basic_memory_buffer(); auto out = basic_appender(buf); detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_, ctx); detail::handle_dynamic_spec(specs.dynamic_precision(), precision, precision_ref_, ctx); if (begin == end || *begin == '}') { out = detail::format_duration_value(out, d.count(), precision); detail::format_duration_unit(out); } else { using chrono_formatter = detail::chrono_formatter; auto f = chrono_formatter(ctx, out, d); f.precision = precision; f.localized = localized_; detail::parse_chrono_format(begin, end, f); } return detail::write( ctx.out(), basic_string_view(buf.data(), buf.size()), specs); } }; template struct formatter { private: format_specs specs_; detail::arg_ref width_ref_; protected: basic_string_view fmt_; template auto do_format(const std::tm& tm, FormatContext& ctx, const Duration* subsecs) const -> decltype(ctx.out()) { auto specs = specs_; auto buf = basic_memory_buffer(); auto out = basic_appender(buf); detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_, ctx); auto loc_ref = ctx.locale(); detail::get_locale loc(static_cast(loc_ref), loc_ref); auto w = detail::tm_writer(loc, out, tm, subsecs); detail::parse_chrono_format(fmt_.begin(), fmt_.end(), w); return detail::write( ctx.out(), basic_string_view(buf.data(), buf.size()), specs); } public: FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { auto it = ctx.begin(), end = ctx.end(); if (it == end || *it == '}') return it; it = detail::parse_align(it, end, specs_); if (it == end) return it; Char c = *it; if ((c >= '0' && c <= '9') || c == '{') { it = detail::parse_width(it, end, specs_, width_ref_, ctx); if (it == end) return it; } end = detail::parse_chrono_format(it, end, detail::tm_format_checker()); // Replace the default format string only if the new spec is not empty. if (end != it) fmt_ = {it, detail::to_unsigned(end - it)}; return end; } template auto format(const std::tm& tm, FormatContext& ctx) const -> decltype(ctx.out()) { return do_format(tm, ctx, nullptr); } }; template struct formatter, Char> : formatter { FMT_CONSTEXPR formatter() { this->fmt_ = detail::string_literal(); } template auto format(sys_time val, FormatContext& ctx) const -> decltype(ctx.out()) { std::tm tm = gmtime(val); using period = typename Duration::period; if (detail::const_check( period::num == 1 && period::den == 1 && !std::is_floating_point::value)) { return formatter::format(tm, ctx); } Duration epoch = val.time_since_epoch(); Duration subsecs = detail::duration_cast( epoch - detail::duration_cast(epoch)); if (subsecs.count() < 0) { auto second = detail::duration_cast(std::chrono::seconds(1)); if (tm.tm_sec != 0) --tm.tm_sec; else tm = gmtime(val - second); subsecs += detail::duration_cast(std::chrono::seconds(1)); } return formatter::do_format(tm, ctx, &subsecs); } }; template struct formatter, Char> : formatter, Char> { template auto format(utc_time val, FormatContext& ctx) const -> decltype(ctx.out()) { return formatter, Char>::format( detail::utc_clock::to_sys(val), ctx); } }; template struct formatter, Char> : formatter { FMT_CONSTEXPR formatter() { this->fmt_ = detail::string_literal(); } template auto format(local_time val, FormatContext& ctx) const -> decltype(ctx.out()) { using period = typename Duration::period; if (period::num == 1 && period::den == 1 && !std::is_floating_point::value) { return formatter::format(localtime(val), ctx); } auto epoch = val.time_since_epoch(); auto subsecs = detail::duration_cast( epoch - detail::duration_cast(epoch)); return formatter::do_format(localtime(val), ctx, &subsecs); } }; FMT_END_EXPORT FMT_END_NAMESPACE #endif // FMT_CHRONO_H_ openal-soft-1.24.2/fmt-11.1.1/include/fmt/color.h000066400000000000000000000554521474041540300210470ustar00rootroot00000000000000// Formatting library for C++ - color support // // Copyright (c) 2018 - present, Victor Zverovich and fmt contributors // All rights reserved. // // For the license information refer to format.h. #ifndef FMT_COLOR_H_ #define FMT_COLOR_H_ #include "format.h" FMT_BEGIN_NAMESPACE FMT_BEGIN_EXPORT enum class color : uint32_t { alice_blue = 0xF0F8FF, // rgb(240,248,255) antique_white = 0xFAEBD7, // rgb(250,235,215) aqua = 0x00FFFF, // rgb(0,255,255) aquamarine = 0x7FFFD4, // rgb(127,255,212) azure = 0xF0FFFF, // rgb(240,255,255) beige = 0xF5F5DC, // rgb(245,245,220) bisque = 0xFFE4C4, // rgb(255,228,196) black = 0x000000, // rgb(0,0,0) blanched_almond = 0xFFEBCD, // rgb(255,235,205) blue = 0x0000FF, // rgb(0,0,255) blue_violet = 0x8A2BE2, // rgb(138,43,226) brown = 0xA52A2A, // rgb(165,42,42) burly_wood = 0xDEB887, // rgb(222,184,135) cadet_blue = 0x5F9EA0, // rgb(95,158,160) chartreuse = 0x7FFF00, // rgb(127,255,0) chocolate = 0xD2691E, // rgb(210,105,30) coral = 0xFF7F50, // rgb(255,127,80) cornflower_blue = 0x6495ED, // rgb(100,149,237) cornsilk = 0xFFF8DC, // rgb(255,248,220) crimson = 0xDC143C, // rgb(220,20,60) cyan = 0x00FFFF, // rgb(0,255,255) dark_blue = 0x00008B, // rgb(0,0,139) dark_cyan = 0x008B8B, // rgb(0,139,139) dark_golden_rod = 0xB8860B, // rgb(184,134,11) dark_gray = 0xA9A9A9, // rgb(169,169,169) dark_green = 0x006400, // rgb(0,100,0) dark_khaki = 0xBDB76B, // rgb(189,183,107) dark_magenta = 0x8B008B, // rgb(139,0,139) dark_olive_green = 0x556B2F, // rgb(85,107,47) dark_orange = 0xFF8C00, // rgb(255,140,0) dark_orchid = 0x9932CC, // rgb(153,50,204) dark_red = 0x8B0000, // rgb(139,0,0) dark_salmon = 0xE9967A, // rgb(233,150,122) dark_sea_green = 0x8FBC8F, // rgb(143,188,143) dark_slate_blue = 0x483D8B, // rgb(72,61,139) dark_slate_gray = 0x2F4F4F, // rgb(47,79,79) dark_turquoise = 0x00CED1, // rgb(0,206,209) dark_violet = 0x9400D3, // rgb(148,0,211) deep_pink = 0xFF1493, // rgb(255,20,147) deep_sky_blue = 0x00BFFF, // rgb(0,191,255) dim_gray = 0x696969, // rgb(105,105,105) dodger_blue = 0x1E90FF, // rgb(30,144,255) fire_brick = 0xB22222, // rgb(178,34,34) floral_white = 0xFFFAF0, // rgb(255,250,240) forest_green = 0x228B22, // rgb(34,139,34) fuchsia = 0xFF00FF, // rgb(255,0,255) gainsboro = 0xDCDCDC, // rgb(220,220,220) ghost_white = 0xF8F8FF, // rgb(248,248,255) gold = 0xFFD700, // rgb(255,215,0) golden_rod = 0xDAA520, // rgb(218,165,32) gray = 0x808080, // rgb(128,128,128) green = 0x008000, // rgb(0,128,0) green_yellow = 0xADFF2F, // rgb(173,255,47) honey_dew = 0xF0FFF0, // rgb(240,255,240) hot_pink = 0xFF69B4, // rgb(255,105,180) indian_red = 0xCD5C5C, // rgb(205,92,92) indigo = 0x4B0082, // rgb(75,0,130) ivory = 0xFFFFF0, // rgb(255,255,240) khaki = 0xF0E68C, // rgb(240,230,140) lavender = 0xE6E6FA, // rgb(230,230,250) lavender_blush = 0xFFF0F5, // rgb(255,240,245) lawn_green = 0x7CFC00, // rgb(124,252,0) lemon_chiffon = 0xFFFACD, // rgb(255,250,205) light_blue = 0xADD8E6, // rgb(173,216,230) light_coral = 0xF08080, // rgb(240,128,128) light_cyan = 0xE0FFFF, // rgb(224,255,255) light_golden_rod_yellow = 0xFAFAD2, // rgb(250,250,210) light_gray = 0xD3D3D3, // rgb(211,211,211) light_green = 0x90EE90, // rgb(144,238,144) light_pink = 0xFFB6C1, // rgb(255,182,193) light_salmon = 0xFFA07A, // rgb(255,160,122) light_sea_green = 0x20B2AA, // rgb(32,178,170) light_sky_blue = 0x87CEFA, // rgb(135,206,250) light_slate_gray = 0x778899, // rgb(119,136,153) light_steel_blue = 0xB0C4DE, // rgb(176,196,222) light_yellow = 0xFFFFE0, // rgb(255,255,224) lime = 0x00FF00, // rgb(0,255,0) lime_green = 0x32CD32, // rgb(50,205,50) linen = 0xFAF0E6, // rgb(250,240,230) magenta = 0xFF00FF, // rgb(255,0,255) maroon = 0x800000, // rgb(128,0,0) medium_aquamarine = 0x66CDAA, // rgb(102,205,170) medium_blue = 0x0000CD, // rgb(0,0,205) medium_orchid = 0xBA55D3, // rgb(186,85,211) medium_purple = 0x9370DB, // rgb(147,112,219) medium_sea_green = 0x3CB371, // rgb(60,179,113) medium_slate_blue = 0x7B68EE, // rgb(123,104,238) medium_spring_green = 0x00FA9A, // rgb(0,250,154) medium_turquoise = 0x48D1CC, // rgb(72,209,204) medium_violet_red = 0xC71585, // rgb(199,21,133) midnight_blue = 0x191970, // rgb(25,25,112) mint_cream = 0xF5FFFA, // rgb(245,255,250) misty_rose = 0xFFE4E1, // rgb(255,228,225) moccasin = 0xFFE4B5, // rgb(255,228,181) navajo_white = 0xFFDEAD, // rgb(255,222,173) navy = 0x000080, // rgb(0,0,128) old_lace = 0xFDF5E6, // rgb(253,245,230) olive = 0x808000, // rgb(128,128,0) olive_drab = 0x6B8E23, // rgb(107,142,35) orange = 0xFFA500, // rgb(255,165,0) orange_red = 0xFF4500, // rgb(255,69,0) orchid = 0xDA70D6, // rgb(218,112,214) pale_golden_rod = 0xEEE8AA, // rgb(238,232,170) pale_green = 0x98FB98, // rgb(152,251,152) pale_turquoise = 0xAFEEEE, // rgb(175,238,238) pale_violet_red = 0xDB7093, // rgb(219,112,147) papaya_whip = 0xFFEFD5, // rgb(255,239,213) peach_puff = 0xFFDAB9, // rgb(255,218,185) peru = 0xCD853F, // rgb(205,133,63) pink = 0xFFC0CB, // rgb(255,192,203) plum = 0xDDA0DD, // rgb(221,160,221) powder_blue = 0xB0E0E6, // rgb(176,224,230) purple = 0x800080, // rgb(128,0,128) rebecca_purple = 0x663399, // rgb(102,51,153) red = 0xFF0000, // rgb(255,0,0) rosy_brown = 0xBC8F8F, // rgb(188,143,143) royal_blue = 0x4169E1, // rgb(65,105,225) saddle_brown = 0x8B4513, // rgb(139,69,19) salmon = 0xFA8072, // rgb(250,128,114) sandy_brown = 0xF4A460, // rgb(244,164,96) sea_green = 0x2E8B57, // rgb(46,139,87) sea_shell = 0xFFF5EE, // rgb(255,245,238) sienna = 0xA0522D, // rgb(160,82,45) silver = 0xC0C0C0, // rgb(192,192,192) sky_blue = 0x87CEEB, // rgb(135,206,235) slate_blue = 0x6A5ACD, // rgb(106,90,205) slate_gray = 0x708090, // rgb(112,128,144) snow = 0xFFFAFA, // rgb(255,250,250) spring_green = 0x00FF7F, // rgb(0,255,127) steel_blue = 0x4682B4, // rgb(70,130,180) tan = 0xD2B48C, // rgb(210,180,140) teal = 0x008080, // rgb(0,128,128) thistle = 0xD8BFD8, // rgb(216,191,216) tomato = 0xFF6347, // rgb(255,99,71) turquoise = 0x40E0D0, // rgb(64,224,208) violet = 0xEE82EE, // rgb(238,130,238) wheat = 0xF5DEB3, // rgb(245,222,179) white = 0xFFFFFF, // rgb(255,255,255) white_smoke = 0xF5F5F5, // rgb(245,245,245) yellow = 0xFFFF00, // rgb(255,255,0) yellow_green = 0x9ACD32 // rgb(154,205,50) }; // enum class color enum class terminal_color : uint8_t { black = 30, red, green, yellow, blue, magenta, cyan, white, bright_black = 90, bright_red, bright_green, bright_yellow, bright_blue, bright_magenta, bright_cyan, bright_white }; enum class emphasis : uint8_t { bold = 1, faint = 1 << 1, italic = 1 << 2, underline = 1 << 3, blink = 1 << 4, reverse = 1 << 5, conceal = 1 << 6, strikethrough = 1 << 7, }; // rgb is a struct for red, green and blue colors. // Using the name "rgb" makes some editors show the color in a tooltip. struct rgb { FMT_CONSTEXPR rgb() : r(0), g(0), b(0) {} FMT_CONSTEXPR rgb(uint8_t r_, uint8_t g_, uint8_t b_) : r(r_), g(g_), b(b_) {} FMT_CONSTEXPR rgb(uint32_t hex) : r((hex >> 16) & 0xFF), g((hex >> 8) & 0xFF), b(hex & 0xFF) {} FMT_CONSTEXPR rgb(color hex) : r((uint32_t(hex) >> 16) & 0xFF), g((uint32_t(hex) >> 8) & 0xFF), b(uint32_t(hex) & 0xFF) {} uint8_t r; uint8_t g; uint8_t b; }; namespace detail { // color is a struct of either a rgb color or a terminal color. struct color_type { FMT_CONSTEXPR color_type() noexcept : is_rgb(), value{} {} FMT_CONSTEXPR color_type(color rgb_color) noexcept : is_rgb(true), value{} { value.rgb_color = static_cast(rgb_color); } FMT_CONSTEXPR color_type(rgb rgb_color) noexcept : is_rgb(true), value{} { value.rgb_color = (static_cast(rgb_color.r) << 16) | (static_cast(rgb_color.g) << 8) | rgb_color.b; } FMT_CONSTEXPR color_type(terminal_color term_color) noexcept : is_rgb(), value{} { value.term_color = static_cast(term_color); } bool is_rgb; union color_union { uint8_t term_color; uint32_t rgb_color; } value; }; } // namespace detail /// A text style consisting of foreground and background colors and emphasis. class text_style { public: FMT_CONSTEXPR text_style(emphasis em = emphasis()) noexcept : set_foreground_color(), set_background_color(), ems(em) {} FMT_CONSTEXPR auto operator|=(const text_style& rhs) -> text_style& { if (!set_foreground_color) { set_foreground_color = rhs.set_foreground_color; foreground_color = rhs.foreground_color; } else if (rhs.set_foreground_color) { if (!foreground_color.is_rgb || !rhs.foreground_color.is_rgb) report_error("can't OR a terminal color"); foreground_color.value.rgb_color |= rhs.foreground_color.value.rgb_color; } if (!set_background_color) { set_background_color = rhs.set_background_color; background_color = rhs.background_color; } else if (rhs.set_background_color) { if (!background_color.is_rgb || !rhs.background_color.is_rgb) report_error("can't OR a terminal color"); background_color.value.rgb_color |= rhs.background_color.value.rgb_color; } ems = static_cast(static_cast(ems) | static_cast(rhs.ems)); return *this; } friend FMT_CONSTEXPR auto operator|(text_style lhs, const text_style& rhs) -> text_style { return lhs |= rhs; } FMT_CONSTEXPR auto has_foreground() const noexcept -> bool { return set_foreground_color; } FMT_CONSTEXPR auto has_background() const noexcept -> bool { return set_background_color; } FMT_CONSTEXPR auto has_emphasis() const noexcept -> bool { return static_cast(ems) != 0; } FMT_CONSTEXPR auto get_foreground() const noexcept -> detail::color_type { FMT_ASSERT(has_foreground(), "no foreground specified for this style"); return foreground_color; } FMT_CONSTEXPR auto get_background() const noexcept -> detail::color_type { FMT_ASSERT(has_background(), "no background specified for this style"); return background_color; } FMT_CONSTEXPR auto get_emphasis() const noexcept -> emphasis { FMT_ASSERT(has_emphasis(), "no emphasis specified for this style"); return ems; } private: FMT_CONSTEXPR text_style(bool is_foreground, detail::color_type text_color) noexcept : set_foreground_color(), set_background_color(), ems() { if (is_foreground) { foreground_color = text_color; set_foreground_color = true; } else { background_color = text_color; set_background_color = true; } } friend FMT_CONSTEXPR auto fg(detail::color_type foreground) noexcept -> text_style; friend FMT_CONSTEXPR auto bg(detail::color_type background) noexcept -> text_style; detail::color_type foreground_color; detail::color_type background_color; bool set_foreground_color; bool set_background_color; emphasis ems; }; /// Creates a text style from the foreground (text) color. FMT_CONSTEXPR inline auto fg(detail::color_type foreground) noexcept -> text_style { return text_style(true, foreground); } /// Creates a text style from the background color. FMT_CONSTEXPR inline auto bg(detail::color_type background) noexcept -> text_style { return text_style(false, background); } FMT_CONSTEXPR inline auto operator|(emphasis lhs, emphasis rhs) noexcept -> text_style { return text_style(lhs) | rhs; } namespace detail { template struct ansi_color_escape { FMT_CONSTEXPR ansi_color_escape(color_type text_color, const char* esc) noexcept { // If we have a terminal color, we need to output another escape code // sequence. if (!text_color.is_rgb) { bool is_background = esc == string_view("\x1b[48;2;"); uint32_t value = text_color.value.term_color; // Background ASCII codes are the same as the foreground ones but with // 10 more. if (is_background) value += 10u; size_t index = 0; buffer[index++] = static_cast('\x1b'); buffer[index++] = static_cast('['); if (value >= 100u) { buffer[index++] = static_cast('1'); value %= 100u; } buffer[index++] = static_cast('0' + value / 10u); buffer[index++] = static_cast('0' + value % 10u); buffer[index++] = static_cast('m'); buffer[index++] = static_cast('\0'); return; } for (int i = 0; i < 7; i++) { buffer[i] = static_cast(esc[i]); } rgb color(text_color.value.rgb_color); to_esc(color.r, buffer + 7, ';'); to_esc(color.g, buffer + 11, ';'); to_esc(color.b, buffer + 15, 'm'); buffer[19] = static_cast(0); } FMT_CONSTEXPR ansi_color_escape(emphasis em) noexcept { uint8_t em_codes[num_emphases] = {}; if (has_emphasis(em, emphasis::bold)) em_codes[0] = 1; if (has_emphasis(em, emphasis::faint)) em_codes[1] = 2; if (has_emphasis(em, emphasis::italic)) em_codes[2] = 3; if (has_emphasis(em, emphasis::underline)) em_codes[3] = 4; if (has_emphasis(em, emphasis::blink)) em_codes[4] = 5; if (has_emphasis(em, emphasis::reverse)) em_codes[5] = 7; if (has_emphasis(em, emphasis::conceal)) em_codes[6] = 8; if (has_emphasis(em, emphasis::strikethrough)) em_codes[7] = 9; size_t index = 0; for (size_t i = 0; i < num_emphases; ++i) { if (!em_codes[i]) continue; buffer[index++] = static_cast('\x1b'); buffer[index++] = static_cast('['); buffer[index++] = static_cast('0' + em_codes[i]); buffer[index++] = static_cast('m'); } buffer[index++] = static_cast(0); } FMT_CONSTEXPR operator const Char*() const noexcept { return buffer; } FMT_CONSTEXPR auto begin() const noexcept -> const Char* { return buffer; } FMT_CONSTEXPR20 auto end() const noexcept -> const Char* { return buffer + basic_string_view(buffer).size(); } private: static constexpr size_t num_emphases = 8; Char buffer[7u + 3u * num_emphases + 1u]; static FMT_CONSTEXPR void to_esc(uint8_t c, Char* out, char delimiter) noexcept { out[0] = static_cast('0' + c / 100); out[1] = static_cast('0' + c / 10 % 10); out[2] = static_cast('0' + c % 10); out[3] = static_cast(delimiter); } static FMT_CONSTEXPR auto has_emphasis(emphasis em, emphasis mask) noexcept -> bool { return static_cast(em) & static_cast(mask); } }; template FMT_CONSTEXPR auto make_foreground_color(color_type foreground) noexcept -> ansi_color_escape { return ansi_color_escape(foreground, "\x1b[38;2;"); } template FMT_CONSTEXPR auto make_background_color(color_type background) noexcept -> ansi_color_escape { return ansi_color_escape(background, "\x1b[48;2;"); } template FMT_CONSTEXPR auto make_emphasis(emphasis em) noexcept -> ansi_color_escape { return ansi_color_escape(em); } template inline void reset_color(buffer& buffer) { auto reset_color = string_view("\x1b[0m"); buffer.append(reset_color.begin(), reset_color.end()); } template struct styled_arg : view { const T& value; text_style style; styled_arg(const T& v, text_style s) : value(v), style(s) {} }; template void vformat_to(buffer& buf, const text_style& ts, basic_string_view fmt, basic_format_args> args) { bool has_style = false; if (ts.has_emphasis()) { has_style = true; auto emphasis = make_emphasis(ts.get_emphasis()); buf.append(emphasis.begin(), emphasis.end()); } if (ts.has_foreground()) { has_style = true; auto foreground = make_foreground_color(ts.get_foreground()); buf.append(foreground.begin(), foreground.end()); } if (ts.has_background()) { has_style = true; auto background = make_background_color(ts.get_background()); buf.append(background.begin(), background.end()); } vformat_to(buf, fmt, args); if (has_style) reset_color(buf); } } // namespace detail inline void vprint(FILE* f, const text_style& ts, string_view fmt, format_args args) { auto buf = memory_buffer(); detail::vformat_to(buf, ts, fmt, args); print(f, FMT_STRING("{}"), string_view(buf.begin(), buf.size())); } /** * Formats a string and prints it to the specified file stream using ANSI * escape sequences to specify text formatting. * * **Example**: * * fmt::print(fmt::emphasis::bold | fg(fmt::color::red), * "Elapsed time: {0:.2f} seconds", 1.23); */ template void print(FILE* f, const text_style& ts, format_string fmt, T&&... args) { vprint(f, ts, fmt.str, vargs{{args...}}); } /** * Formats a string and prints it to stdout using ANSI escape sequences to * specify text formatting. * * **Example**: * * fmt::print(fmt::emphasis::bold | fg(fmt::color::red), * "Elapsed time: {0:.2f} seconds", 1.23); */ template void print(const text_style& ts, format_string fmt, T&&... args) { return print(stdout, ts, fmt, std::forward(args)...); } inline auto vformat(const text_style& ts, string_view fmt, format_args args) -> std::string { auto buf = memory_buffer(); detail::vformat_to(buf, ts, fmt, args); return fmt::to_string(buf); } /** * Formats arguments and returns the result as a string using ANSI escape * sequences to specify text formatting. * * **Example**: * * ``` * #include * std::string message = fmt::format(fmt::emphasis::bold | fg(fmt::color::red), * "The answer is {}", 42); * ``` */ template inline auto format(const text_style& ts, format_string fmt, T&&... args) -> std::string { return fmt::vformat(ts, fmt.str, vargs{{args...}}); } /// Formats a string with the given text_style and writes the output to `out`. template ::value)> auto vformat_to(OutputIt out, const text_style& ts, string_view fmt, format_args args) -> OutputIt { auto&& buf = detail::get_buffer(out); detail::vformat_to(buf, ts, fmt, args); return detail::get_iterator(buf, out); } /** * Formats arguments with the given text style, writes the result to the output * iterator `out` and returns the iterator past the end of the output range. * * **Example**: * * std::vector out; * fmt::format_to(std::back_inserter(out), * fmt::emphasis::bold | fg(fmt::color::red), "{}", 42); */ template ::value)> inline auto format_to(OutputIt out, const text_style& ts, format_string fmt, T&&... args) -> OutputIt { return vformat_to(out, ts, fmt.str, vargs{{args...}}); } template struct formatter, Char> : formatter { template auto format(const detail::styled_arg& arg, FormatContext& ctx) const -> decltype(ctx.out()) { const auto& ts = arg.style; auto out = ctx.out(); bool has_style = false; if (ts.has_emphasis()) { has_style = true; auto emphasis = detail::make_emphasis(ts.get_emphasis()); out = detail::copy(emphasis.begin(), emphasis.end(), out); } if (ts.has_foreground()) { has_style = true; auto foreground = detail::make_foreground_color(ts.get_foreground()); out = detail::copy(foreground.begin(), foreground.end(), out); } if (ts.has_background()) { has_style = true; auto background = detail::make_background_color(ts.get_background()); out = detail::copy(background.begin(), background.end(), out); } out = formatter::format(arg.value, ctx); if (has_style) { auto reset_color = string_view("\x1b[0m"); out = detail::copy(reset_color.begin(), reset_color.end(), out); } return out; } }; /** * Returns an argument that will be formatted using ANSI escape sequences, * to be used in a formatting function. * * **Example**: * * fmt::print("Elapsed time: {0:.2f} seconds", * fmt::styled(1.23, fmt::fg(fmt::color::green) | * fmt::bg(fmt::color::blue))); */ template FMT_CONSTEXPR auto styled(const T& value, text_style ts) -> detail::styled_arg> { return detail::styled_arg>{value, ts}; } FMT_END_EXPORT FMT_END_NAMESPACE #endif // FMT_COLOR_H_ openal-soft-1.24.2/fmt-11.1.1/include/fmt/compile.h000066400000000000000000000455351474041540300213620ustar00rootroot00000000000000// Formatting library for C++ - experimental format string compilation // // Copyright (c) 2012 - present, Victor Zverovich and fmt contributors // All rights reserved. // // For the license information refer to format.h. #ifndef FMT_COMPILE_H_ #define FMT_COMPILE_H_ #ifndef FMT_MODULE # include // std::back_inserter #endif #include "format.h" FMT_BEGIN_NAMESPACE // A compile-time string which is compiled into fast formatting code. FMT_EXPORT class compiled_string {}; namespace detail { template struct is_compiled_string : std::is_base_of {}; /** * Converts a string literal `s` into a format string that will be parsed at * compile time and converted into efficient formatting code. Requires C++17 * `constexpr if` compiler support. * * **Example**: * * // Converts 42 into std::string using the most efficient method and no * // runtime format string processing. * std::string s = fmt::format(FMT_COMPILE("{}"), 42); */ #if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction) # define FMT_COMPILE(s) FMT_STRING_IMPL(s, fmt::compiled_string) #else # define FMT_COMPILE(s) FMT_STRING(s) #endif #if FMT_USE_NONTYPE_TEMPLATE_ARGS template Str> struct udl_compiled_string : compiled_string { using char_type = Char; constexpr explicit operator basic_string_view() const { return {Str.data, N - 1}; } }; #endif template auto first(const T& value, const Tail&...) -> const T& { return value; } #if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction) template struct type_list {}; // Returns a reference to the argument at index N from [first, rest...]. template constexpr const auto& get([[maybe_unused]] const T& first, [[maybe_unused]] const Args&... rest) { static_assert(N < 1 + sizeof...(Args), "index is out of bounds"); if constexpr (N == 0) return first; else return detail::get(rest...); } # if FMT_USE_NONTYPE_TEMPLATE_ARGS template constexpr auto get_arg_index_by_name(basic_string_view name) -> int { if constexpr (is_static_named_arg()) { if (name == T::name) return N; } if constexpr (sizeof...(Args) > 0) return get_arg_index_by_name(name); (void)name; // Workaround an MSVC bug about "unused" parameter. return -1; } # endif template FMT_CONSTEXPR auto get_arg_index_by_name(basic_string_view name) -> int { # if FMT_USE_NONTYPE_TEMPLATE_ARGS if constexpr (sizeof...(Args) > 0) return get_arg_index_by_name<0, Args...>(name); # endif (void)name; return -1; } template constexpr int get_arg_index_by_name(basic_string_view name, type_list) { return get_arg_index_by_name(name); } template struct get_type_impl; template struct get_type_impl> { using type = remove_cvref_t(std::declval()...))>; }; template using get_type = typename get_type_impl::type; template struct is_compiled_format : std::false_type {}; template struct text { basic_string_view data; using char_type = Char; template constexpr OutputIt format(OutputIt out, const Args&...) const { return write(out, data); } }; template struct is_compiled_format> : std::true_type {}; template constexpr text make_text(basic_string_view s, size_t pos, size_t size) { return {{&s[pos], size}}; } template struct code_unit { Char value; using char_type = Char; template constexpr OutputIt format(OutputIt out, const Args&...) const { *out++ = value; return out; } }; // This ensures that the argument type is convertible to `const T&`. template constexpr const T& get_arg_checked(const Args&... args) { const auto& arg = detail::get(args...); if constexpr (detail::is_named_arg>()) { return arg.value; } else { return arg; } } template struct is_compiled_format> : std::true_type {}; // A replacement field that refers to argument N. template struct field { using char_type = Char; template constexpr OutputIt format(OutputIt out, const Args&... args) const { const T& arg = get_arg_checked(args...); if constexpr (std::is_convertible>::value) { auto s = basic_string_view(arg); return copy(s.begin(), s.end(), out); } else { return write(out, arg); } } }; template struct is_compiled_format> : std::true_type {}; // A replacement field that refers to argument with name. template struct runtime_named_field { using char_type = Char; basic_string_view name; template constexpr static bool try_format_argument( OutputIt& out, // [[maybe_unused]] due to unused-but-set-parameter warning in GCC 7,8,9 [[maybe_unused]] basic_string_view arg_name, const T& arg) { if constexpr (is_named_arg::type>::value) { if (arg_name == arg.name) { out = write(out, arg.value); return true; } } return false; } template constexpr OutputIt format(OutputIt out, const Args&... args) const { bool found = (try_format_argument(out, name, args) || ...); if (!found) { FMT_THROW(format_error("argument with specified name is not found")); } return out; } }; template struct is_compiled_format> : std::true_type {}; // A replacement field that refers to argument N and has format specifiers. template struct spec_field { using char_type = Char; formatter fmt; template constexpr FMT_INLINE OutputIt format(OutputIt out, const Args&... args) const { const auto& vargs = fmt::make_format_args>(args...); basic_format_context ctx(out, vargs); return fmt.format(get_arg_checked(args...), ctx); } }; template struct is_compiled_format> : std::true_type {}; template struct concat { L lhs; R rhs; using char_type = typename L::char_type; template constexpr OutputIt format(OutputIt out, const Args&... args) const { out = lhs.format(out, args...); return rhs.format(out, args...); } }; template struct is_compiled_format> : std::true_type {}; template constexpr concat make_concat(L lhs, R rhs) { return {lhs, rhs}; } struct unknown_format {}; template constexpr size_t parse_text(basic_string_view str, size_t pos) { for (size_t size = str.size(); pos != size; ++pos) { if (str[pos] == '{' || str[pos] == '}') break; } return pos; } template constexpr auto compile_format_string(S fmt); template constexpr auto parse_tail(T head, S fmt) { if constexpr (POS != basic_string_view(fmt).size()) { constexpr auto tail = compile_format_string(fmt); if constexpr (std::is_same, unknown_format>()) return tail; else return make_concat(head, tail); } else { return head; } } template struct parse_specs_result { formatter fmt; size_t end; int next_arg_id; }; enum { manual_indexing_id = -1 }; template constexpr parse_specs_result parse_specs(basic_string_view str, size_t pos, int next_arg_id) { str.remove_prefix(pos); auto ctx = compile_parse_context(str, max_value(), nullptr, next_arg_id); auto f = formatter(); auto end = f.parse(ctx); return {f, pos + fmt::detail::to_unsigned(end - str.data()), next_arg_id == 0 ? manual_indexing_id : ctx.next_arg_id()}; } template struct arg_id_handler { arg_id_kind kind; arg_ref arg_id; constexpr int on_auto() { FMT_ASSERT(false, "handler cannot be used with automatic indexing"); return 0; } constexpr int on_index(int id) { kind = arg_id_kind::index; arg_id = arg_ref(id); return 0; } constexpr int on_name(basic_string_view id) { kind = arg_id_kind::name; arg_id = arg_ref(id); return 0; } }; template struct parse_arg_id_result { arg_id_kind kind; arg_ref arg_id; const Char* arg_id_end; }; template constexpr auto parse_arg_id(const Char* begin, const Char* end) { auto handler = arg_id_handler{arg_id_kind::none, arg_ref{}}; auto arg_id_end = parse_arg_id(begin, end, handler); return parse_arg_id_result{handler.kind, handler.arg_id, arg_id_end}; } template struct field_type { using type = remove_cvref_t; }; template struct field_type::value>> { using type = remove_cvref_t; }; template constexpr auto parse_replacement_field_then_tail(S fmt) { using char_type = typename S::char_type; constexpr auto str = basic_string_view(fmt); constexpr char_type c = END_POS != str.size() ? str[END_POS] : char_type(); if constexpr (c == '}') { return parse_tail( field::type, ARG_INDEX>(), fmt); } else if constexpr (c != ':') { FMT_THROW(format_error("expected ':'")); } else { constexpr auto result = parse_specs::type>( str, END_POS + 1, NEXT_ID == manual_indexing_id ? 0 : NEXT_ID); if constexpr (result.end >= str.size() || str[result.end] != '}') { FMT_THROW(format_error("expected '}'")); return 0; } else { return parse_tail( spec_field::type, ARG_INDEX>{ result.fmt}, fmt); } } } // Compiles a non-empty format string and returns the compiled representation // or unknown_format() on unrecognized input. template constexpr auto compile_format_string(S fmt) { using char_type = typename S::char_type; constexpr auto str = basic_string_view(fmt); if constexpr (str[POS] == '{') { if constexpr (POS + 1 == str.size()) FMT_THROW(format_error("unmatched '{' in format string")); if constexpr (str[POS + 1] == '{') { return parse_tail(make_text(str, POS, 1), fmt); } else if constexpr (str[POS + 1] == '}' || str[POS + 1] == ':') { static_assert(ID != manual_indexing_id, "cannot switch from manual to automatic argument indexing"); constexpr auto next_id = ID != manual_indexing_id ? ID + 1 : manual_indexing_id; return parse_replacement_field_then_tail, Args, POS + 1, ID, next_id>(fmt); } else { constexpr auto arg_id_result = parse_arg_id(str.data() + POS + 1, str.data() + str.size()); constexpr auto arg_id_end_pos = arg_id_result.arg_id_end - str.data(); constexpr char_type c = arg_id_end_pos != str.size() ? str[arg_id_end_pos] : char_type(); static_assert(c == '}' || c == ':', "missing '}' in format string"); if constexpr (arg_id_result.kind == arg_id_kind::index) { static_assert( ID == manual_indexing_id || ID == 0, "cannot switch from automatic to manual argument indexing"); constexpr auto arg_index = arg_id_result.arg_id.index; return parse_replacement_field_then_tail, Args, arg_id_end_pos, arg_index, manual_indexing_id>( fmt); } else if constexpr (arg_id_result.kind == arg_id_kind::name) { constexpr auto arg_index = get_arg_index_by_name(arg_id_result.arg_id.name, Args{}); if constexpr (arg_index >= 0) { constexpr auto next_id = ID != manual_indexing_id ? ID + 1 : manual_indexing_id; return parse_replacement_field_then_tail< decltype(get_type::value), Args, arg_id_end_pos, arg_index, next_id>(fmt); } else if constexpr (c == '}') { return parse_tail( runtime_named_field{arg_id_result.arg_id.name}, fmt); } else if constexpr (c == ':') { return unknown_format(); // no type info for specs parsing } } } } else if constexpr (str[POS] == '}') { if constexpr (POS + 1 == str.size()) FMT_THROW(format_error("unmatched '}' in format string")); return parse_tail(make_text(str, POS, 1), fmt); } else { constexpr auto end = parse_text(str, POS + 1); if constexpr (end - POS > 1) { return parse_tail(make_text(str, POS, end - POS), fmt); } else { return parse_tail(code_unit{str[POS]}, fmt); } } } template ::value)> constexpr auto compile(S fmt) { constexpr auto str = basic_string_view(fmt); if constexpr (str.size() == 0) { return detail::make_text(str, 0, 0); } else { constexpr auto result = detail::compile_format_string, 0, 0>(fmt); return result; } } #endif // defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction) } // namespace detail FMT_BEGIN_EXPORT #if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction) template ::value)> FMT_INLINE std::basic_string format(const CompiledFormat& cf, const Args&... args) { auto s = std::basic_string(); cf.format(std::back_inserter(s), args...); return s; } template ::value)> constexpr FMT_INLINE OutputIt format_to(OutputIt out, const CompiledFormat& cf, const Args&... args) { return cf.format(out, args...); } template ::value)> FMT_INLINE std::basic_string format(const S&, Args&&... args) { if constexpr (std::is_same::value) { constexpr auto str = basic_string_view(S()); if constexpr (str.size() == 2 && str[0] == '{' && str[1] == '}') { const auto& first = detail::first(args...); if constexpr (detail::is_named_arg< remove_cvref_t>::value) { return fmt::to_string(first.value); } else { return fmt::to_string(first); } } } constexpr auto compiled = detail::compile(S()); if constexpr (std::is_same, detail::unknown_format>()) { return fmt::format( static_cast>(S()), std::forward(args)...); } else { return fmt::format(compiled, std::forward(args)...); } } template ::value)> FMT_CONSTEXPR OutputIt format_to(OutputIt out, const S&, Args&&... args) { constexpr auto compiled = detail::compile(S()); if constexpr (std::is_same, detail::unknown_format>()) { return fmt::format_to( out, static_cast>(S()), std::forward(args)...); } else { return fmt::format_to(out, compiled, std::forward(args)...); } } #endif template ::value)> auto format_to_n(OutputIt out, size_t n, const S& fmt, Args&&... args) -> format_to_n_result { using traits = detail::fixed_buffer_traits; auto buf = detail::iterator_buffer(out, n); fmt::format_to(std::back_inserter(buf), fmt, std::forward(args)...); return {buf.out(), buf.count()}; } template ::value)> FMT_CONSTEXPR20 auto formatted_size(const S& fmt, const Args&... args) -> size_t { auto buf = detail::counting_buffer<>(); fmt::format_to(appender(buf), fmt, args...); return buf.count(); } template ::value)> void print(std::FILE* f, const S& fmt, const Args&... args) { auto buf = memory_buffer(); fmt::format_to(appender(buf), fmt, args...); detail::print(f, {buf.data(), buf.size()}); } template ::value)> void print(const S& fmt, const Args&... args) { print(stdout, fmt, args...); } #if FMT_USE_NONTYPE_TEMPLATE_ARGS inline namespace literals { template constexpr auto operator""_cf() { using char_t = remove_cvref_t; return detail::udl_compiled_string(); } } // namespace literals #endif FMT_END_EXPORT FMT_END_NAMESPACE #endif // FMT_COMPILE_H_ openal-soft-1.24.2/fmt-11.1.1/include/fmt/core.h000066400000000000000000000002731474041540300206500ustar00rootroot00000000000000// This file is only provided for compatibility and may be removed in future // versions. Use fmt/base.h if you don't need fmt::format and fmt/format.h // otherwise. #include "format.h" openal-soft-1.24.2/fmt-11.1.1/include/fmt/format-inl.h000066400000000000000000002360651474041540300220020ustar00rootroot00000000000000// Formatting library for C++ - implementation // // Copyright (c) 2012 - 2016, Victor Zverovich // All rights reserved. // // For the license information refer to format.h. #ifndef FMT_FORMAT_INL_H_ #define FMT_FORMAT_INL_H_ #ifndef FMT_MODULE # include # include // errno # include # include # include #endif #if defined(_WIN32) && !defined(FMT_USE_WRITE_CONSOLE) # include // _isatty #endif #include "format.h" #if FMT_USE_LOCALE # include #endif #ifndef FMT_FUNC # define FMT_FUNC #endif FMT_BEGIN_NAMESPACE namespace detail { FMT_FUNC void assert_fail(const char* file, int line, const char* message) { // Use unchecked std::fprintf to avoid triggering another assertion when // writing to stderr fails. fprintf(stderr, "%s:%d: assertion failed: %s", file, line, message); abort(); } FMT_FUNC void format_error_code(detail::buffer& out, int error_code, string_view message) noexcept { // Report error code making sure that the output fits into // inline_buffer_size to avoid dynamic memory allocation and potential // bad_alloc. out.try_resize(0); static const char SEP[] = ": "; static const char ERROR_STR[] = "error "; // Subtract 2 to account for terminating null characters in SEP and ERROR_STR. size_t error_code_size = sizeof(SEP) + sizeof(ERROR_STR) - 2; auto abs_value = static_cast>(error_code); if (detail::is_negative(error_code)) { abs_value = 0 - abs_value; ++error_code_size; } error_code_size += detail::to_unsigned(detail::count_digits(abs_value)); auto it = appender(out); if (message.size() <= inline_buffer_size - error_code_size) fmt::format_to(it, FMT_STRING("{}{}"), message, SEP); fmt::format_to(it, FMT_STRING("{}{}"), ERROR_STR, error_code); FMT_ASSERT(out.size() <= inline_buffer_size, ""); } FMT_FUNC void do_report_error(format_func func, int error_code, const char* message) noexcept { memory_buffer full_message; func(full_message, error_code, message); // Don't use fwrite_all because the latter may throw. if (std::fwrite(full_message.data(), full_message.size(), 1, stderr) > 0) std::fputc('\n', stderr); } // A wrapper around fwrite that throws on error. inline void fwrite_all(const void* ptr, size_t count, FILE* stream) { size_t written = std::fwrite(ptr, 1, count, stream); if (written < count) FMT_THROW(system_error(errno, FMT_STRING("cannot write to file"))); } #if FMT_USE_LOCALE using std::locale; using std::numpunct; using std::use_facet; template > locale_ref::locale_ref(const Locale& loc) : locale_(&loc) { static_assert(std::is_same::value, ""); } #else struct locale {}; template struct numpunct { auto grouping() const -> std::string { return "\03"; } auto thousands_sep() const -> Char { return ','; } auto decimal_point() const -> Char { return '.'; } }; template Facet use_facet(locale) { return {}; } #endif // FMT_USE_LOCALE template auto locale_ref::get() const -> Locale { static_assert(std::is_same::value, ""); #if FMT_USE_LOCALE if (locale_) return *static_cast(locale_); #endif return locale(); } template FMT_FUNC auto thousands_sep_impl(locale_ref loc) -> thousands_sep_result { auto&& facet = use_facet>(loc.get()); auto grouping = facet.grouping(); auto thousands_sep = grouping.empty() ? Char() : facet.thousands_sep(); return {std::move(grouping), thousands_sep}; } template FMT_FUNC auto decimal_point_impl(locale_ref loc) -> Char { return use_facet>(loc.get()).decimal_point(); } #if FMT_USE_LOCALE FMT_FUNC auto write_loc(appender out, loc_value value, const format_specs& specs, locale_ref loc) -> bool { auto locale = loc.get(); // We cannot use the num_put facet because it may produce output in // a wrong encoding. using facet = format_facet; if (std::has_facet(locale)) return use_facet(locale).put(out, value, specs); return facet(locale).put(out, value, specs); } #endif } // namespace detail FMT_FUNC void report_error(const char* message) { #if FMT_USE_EXCEPTIONS throw format_error(message); #else fputs(message, stderr); abort(); #endif } template typename Locale::id format_facet::id; template format_facet::format_facet(Locale& loc) { auto& np = detail::use_facet>(loc); grouping_ = np.grouping(); if (!grouping_.empty()) separator_ = std::string(1, np.thousands_sep()); } #if FMT_USE_LOCALE template <> FMT_API FMT_FUNC auto format_facet::do_put( appender out, loc_value val, const format_specs& specs) const -> bool { return val.visit( detail::loc_writer<>{out, specs, separator_, grouping_, decimal_point_}); } #endif FMT_FUNC auto vsystem_error(int error_code, string_view fmt, format_args args) -> std::system_error { auto ec = std::error_code(error_code, std::generic_category()); return std::system_error(ec, vformat(fmt, args)); } namespace detail { template inline auto operator==(basic_fp x, basic_fp y) -> bool { return x.f == y.f && x.e == y.e; } // Compilers should be able to optimize this into the ror instruction. FMT_CONSTEXPR inline auto rotr(uint32_t n, uint32_t r) noexcept -> uint32_t { r &= 31; return (n >> r) | (n << (32 - r)); } FMT_CONSTEXPR inline auto rotr(uint64_t n, uint32_t r) noexcept -> uint64_t { r &= 63; return (n >> r) | (n << (64 - r)); } // Implementation of Dragonbox algorithm: https://github.com/jk-jeon/dragonbox. namespace dragonbox { // Computes upper 64 bits of multiplication of a 32-bit unsigned integer and a // 64-bit unsigned integer. inline auto umul96_upper64(uint32_t x, uint64_t y) noexcept -> uint64_t { return umul128_upper64(static_cast(x) << 32, y); } // Computes lower 128 bits of multiplication of a 64-bit unsigned integer and a // 128-bit unsigned integer. inline auto umul192_lower128(uint64_t x, uint128_fallback y) noexcept -> uint128_fallback { uint64_t high = x * y.high(); uint128_fallback high_low = umul128(x, y.low()); return {high + high_low.high(), high_low.low()}; } // Computes lower 64 bits of multiplication of a 32-bit unsigned integer and a // 64-bit unsigned integer. inline auto umul96_lower64(uint32_t x, uint64_t y) noexcept -> uint64_t { return x * y; } // Various fast log computations. inline auto floor_log10_pow2_minus_log10_4_over_3(int e) noexcept -> int { FMT_ASSERT(e <= 2936 && e >= -2985, "too large exponent"); return (e * 631305 - 261663) >> 21; } FMT_INLINE_VARIABLE constexpr struct { uint32_t divisor; int shift_amount; } div_small_pow10_infos[] = {{10, 16}, {100, 16}}; // Replaces n by floor(n / pow(10, N)) returning true if and only if n is // divisible by pow(10, N). // Precondition: n <= pow(10, N + 1). template auto check_divisibility_and_divide_by_pow10(uint32_t& n) noexcept -> bool { // The numbers below are chosen such that: // 1. floor(n/d) = floor(nm / 2^k) where d=10 or d=100, // 2. nm mod 2^k < m if and only if n is divisible by d, // where m is magic_number, k is shift_amount // and d is divisor. // // Item 1 is a common technique of replacing division by a constant with // multiplication, see e.g. "Division by Invariant Integers Using // Multiplication" by Granlund and Montgomery (1994). magic_number (m) is set // to ceil(2^k/d) for large enough k. // The idea for item 2 originates from Schubfach. constexpr auto info = div_small_pow10_infos[N - 1]; FMT_ASSERT(n <= info.divisor * 10, "n is too large"); constexpr uint32_t magic_number = (1u << info.shift_amount) / info.divisor + 1; n *= magic_number; const uint32_t comparison_mask = (1u << info.shift_amount) - 1; bool result = (n & comparison_mask) < magic_number; n >>= info.shift_amount; return result; } // Computes floor(n / pow(10, N)) for small n and N. // Precondition: n <= pow(10, N + 1). template auto small_division_by_pow10(uint32_t n) noexcept -> uint32_t { constexpr auto info = div_small_pow10_infos[N - 1]; FMT_ASSERT(n <= info.divisor * 10, "n is too large"); constexpr uint32_t magic_number = (1u << info.shift_amount) / info.divisor + 1; return (n * magic_number) >> info.shift_amount; } // Computes floor(n / 10^(kappa + 1)) (float) inline auto divide_by_10_to_kappa_plus_1(uint32_t n) noexcept -> uint32_t { // 1374389535 = ceil(2^37/100) return static_cast((static_cast(n) * 1374389535) >> 37); } // Computes floor(n / 10^(kappa + 1)) (double) inline auto divide_by_10_to_kappa_plus_1(uint64_t n) noexcept -> uint64_t { // 2361183241434822607 = ceil(2^(64+7)/1000) return umul128_upper64(n, 2361183241434822607ull) >> 7; } // Various subroutines using pow10 cache template struct cache_accessor; template <> struct cache_accessor { using carrier_uint = float_info::carrier_uint; using cache_entry_type = uint64_t; static auto get_cached_power(int k) noexcept -> uint64_t { FMT_ASSERT(k >= float_info::min_k && k <= float_info::max_k, "k is out of range"); static constexpr const uint64_t pow10_significands[] = { 0x81ceb32c4b43fcf5, 0xa2425ff75e14fc32, 0xcad2f7f5359a3b3f, 0xfd87b5f28300ca0e, 0x9e74d1b791e07e49, 0xc612062576589ddb, 0xf79687aed3eec552, 0x9abe14cd44753b53, 0xc16d9a0095928a28, 0xf1c90080baf72cb2, 0x971da05074da7bef, 0xbce5086492111aeb, 0xec1e4a7db69561a6, 0x9392ee8e921d5d08, 0xb877aa3236a4b44a, 0xe69594bec44de15c, 0x901d7cf73ab0acda, 0xb424dc35095cd810, 0xe12e13424bb40e14, 0x8cbccc096f5088cc, 0xafebff0bcb24aaff, 0xdbe6fecebdedd5bf, 0x89705f4136b4a598, 0xabcc77118461cefd, 0xd6bf94d5e57a42bd, 0x8637bd05af6c69b6, 0xa7c5ac471b478424, 0xd1b71758e219652c, 0x83126e978d4fdf3c, 0xa3d70a3d70a3d70b, 0xcccccccccccccccd, 0x8000000000000000, 0xa000000000000000, 0xc800000000000000, 0xfa00000000000000, 0x9c40000000000000, 0xc350000000000000, 0xf424000000000000, 0x9896800000000000, 0xbebc200000000000, 0xee6b280000000000, 0x9502f90000000000, 0xba43b74000000000, 0xe8d4a51000000000, 0x9184e72a00000000, 0xb5e620f480000000, 0xe35fa931a0000000, 0x8e1bc9bf04000000, 0xb1a2bc2ec5000000, 0xde0b6b3a76400000, 0x8ac7230489e80000, 0xad78ebc5ac620000, 0xd8d726b7177a8000, 0x878678326eac9000, 0xa968163f0a57b400, 0xd3c21bcecceda100, 0x84595161401484a0, 0xa56fa5b99019a5c8, 0xcecb8f27f4200f3a, 0x813f3978f8940985, 0xa18f07d736b90be6, 0xc9f2c9cd04674edf, 0xfc6f7c4045812297, 0x9dc5ada82b70b59e, 0xc5371912364ce306, 0xf684df56c3e01bc7, 0x9a130b963a6c115d, 0xc097ce7bc90715b4, 0xf0bdc21abb48db21, 0x96769950b50d88f5, 0xbc143fa4e250eb32, 0xeb194f8e1ae525fe, 0x92efd1b8d0cf37bf, 0xb7abc627050305ae, 0xe596b7b0c643c71a, 0x8f7e32ce7bea5c70, 0xb35dbf821ae4f38c, 0xe0352f62a19e306f}; return pow10_significands[k - float_info::min_k]; } struct compute_mul_result { carrier_uint result; bool is_integer; }; struct compute_mul_parity_result { bool parity; bool is_integer; }; static auto compute_mul(carrier_uint u, const cache_entry_type& cache) noexcept -> compute_mul_result { auto r = umul96_upper64(u, cache); return {static_cast(r >> 32), static_cast(r) == 0}; } static auto compute_delta(const cache_entry_type& cache, int beta) noexcept -> uint32_t { return static_cast(cache >> (64 - 1 - beta)); } static auto compute_mul_parity(carrier_uint two_f, const cache_entry_type& cache, int beta) noexcept -> compute_mul_parity_result { FMT_ASSERT(beta >= 1, ""); FMT_ASSERT(beta < 64, ""); auto r = umul96_lower64(two_f, cache); return {((r >> (64 - beta)) & 1) != 0, static_cast(r >> (32 - beta)) == 0}; } static auto compute_left_endpoint_for_shorter_interval_case( const cache_entry_type& cache, int beta) noexcept -> carrier_uint { return static_cast( (cache - (cache >> (num_significand_bits() + 2))) >> (64 - num_significand_bits() - 1 - beta)); } static auto compute_right_endpoint_for_shorter_interval_case( const cache_entry_type& cache, int beta) noexcept -> carrier_uint { return static_cast( (cache + (cache >> (num_significand_bits() + 1))) >> (64 - num_significand_bits() - 1 - beta)); } static auto compute_round_up_for_shorter_interval_case( const cache_entry_type& cache, int beta) noexcept -> carrier_uint { return (static_cast( cache >> (64 - num_significand_bits() - 2 - beta)) + 1) / 2; } }; template <> struct cache_accessor { using carrier_uint = float_info::carrier_uint; using cache_entry_type = uint128_fallback; static auto get_cached_power(int k) noexcept -> uint128_fallback { FMT_ASSERT(k >= float_info::min_k && k <= float_info::max_k, "k is out of range"); static constexpr const uint128_fallback pow10_significands[] = { #if FMT_USE_FULL_CACHE_DRAGONBOX {0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7b}, {0x9faacf3df73609b1, 0x77b191618c54e9ad}, {0xc795830d75038c1d, 0xd59df5b9ef6a2418}, {0xf97ae3d0d2446f25, 0x4b0573286b44ad1e}, {0x9becce62836ac577, 0x4ee367f9430aec33}, {0xc2e801fb244576d5, 0x229c41f793cda740}, {0xf3a20279ed56d48a, 0x6b43527578c11110}, {0x9845418c345644d6, 0x830a13896b78aaaa}, {0xbe5691ef416bd60c, 0x23cc986bc656d554}, {0xedec366b11c6cb8f, 0x2cbfbe86b7ec8aa9}, {0x94b3a202eb1c3f39, 0x7bf7d71432f3d6aa}, {0xb9e08a83a5e34f07, 0xdaf5ccd93fb0cc54}, {0xe858ad248f5c22c9, 0xd1b3400f8f9cff69}, {0x91376c36d99995be, 0x23100809b9c21fa2}, {0xb58547448ffffb2d, 0xabd40a0c2832a78b}, {0xe2e69915b3fff9f9, 0x16c90c8f323f516d}, {0x8dd01fad907ffc3b, 0xae3da7d97f6792e4}, {0xb1442798f49ffb4a, 0x99cd11cfdf41779d}, {0xdd95317f31c7fa1d, 0x40405643d711d584}, {0x8a7d3eef7f1cfc52, 0x482835ea666b2573}, {0xad1c8eab5ee43b66, 0xda3243650005eed0}, {0xd863b256369d4a40, 0x90bed43e40076a83}, {0x873e4f75e2224e68, 0x5a7744a6e804a292}, {0xa90de3535aaae202, 0x711515d0a205cb37}, {0xd3515c2831559a83, 0x0d5a5b44ca873e04}, {0x8412d9991ed58091, 0xe858790afe9486c3}, {0xa5178fff668ae0b6, 0x626e974dbe39a873}, {0xce5d73ff402d98e3, 0xfb0a3d212dc81290}, {0x80fa687f881c7f8e, 0x7ce66634bc9d0b9a}, {0xa139029f6a239f72, 0x1c1fffc1ebc44e81}, {0xc987434744ac874e, 0xa327ffb266b56221}, {0xfbe9141915d7a922, 0x4bf1ff9f0062baa9}, {0x9d71ac8fada6c9b5, 0x6f773fc3603db4aa}, {0xc4ce17b399107c22, 0xcb550fb4384d21d4}, {0xf6019da07f549b2b, 0x7e2a53a146606a49}, {0x99c102844f94e0fb, 0x2eda7444cbfc426e}, {0xc0314325637a1939, 0xfa911155fefb5309}, {0xf03d93eebc589f88, 0x793555ab7eba27cb}, {0x96267c7535b763b5, 0x4bc1558b2f3458df}, {0xbbb01b9283253ca2, 0x9eb1aaedfb016f17}, {0xea9c227723ee8bcb, 0x465e15a979c1cadd}, {0x92a1958a7675175f, 0x0bfacd89ec191eca}, {0xb749faed14125d36, 0xcef980ec671f667c}, {0xe51c79a85916f484, 0x82b7e12780e7401b}, {0x8f31cc0937ae58d2, 0xd1b2ecb8b0908811}, {0xb2fe3f0b8599ef07, 0x861fa7e6dcb4aa16}, {0xdfbdcece67006ac9, 0x67a791e093e1d49b}, {0x8bd6a141006042bd, 0xe0c8bb2c5c6d24e1}, {0xaecc49914078536d, 0x58fae9f773886e19}, {0xda7f5bf590966848, 0xaf39a475506a899f}, {0x888f99797a5e012d, 0x6d8406c952429604}, {0xaab37fd7d8f58178, 0xc8e5087ba6d33b84}, {0xd5605fcdcf32e1d6, 0xfb1e4a9a90880a65}, {0x855c3be0a17fcd26, 0x5cf2eea09a550680}, {0xa6b34ad8c9dfc06f, 0xf42faa48c0ea481f}, {0xd0601d8efc57b08b, 0xf13b94daf124da27}, {0x823c12795db6ce57, 0x76c53d08d6b70859}, {0xa2cb1717b52481ed, 0x54768c4b0c64ca6f}, {0xcb7ddcdda26da268, 0xa9942f5dcf7dfd0a}, {0xfe5d54150b090b02, 0xd3f93b35435d7c4d}, {0x9efa548d26e5a6e1, 0xc47bc5014a1a6db0}, {0xc6b8e9b0709f109a, 0x359ab6419ca1091c}, {0xf867241c8cc6d4c0, 0xc30163d203c94b63}, {0x9b407691d7fc44f8, 0x79e0de63425dcf1e}, {0xc21094364dfb5636, 0x985915fc12f542e5}, {0xf294b943e17a2bc4, 0x3e6f5b7b17b2939e}, {0x979cf3ca6cec5b5a, 0xa705992ceecf9c43}, {0xbd8430bd08277231, 0x50c6ff782a838354}, {0xece53cec4a314ebd, 0xa4f8bf5635246429}, {0x940f4613ae5ed136, 0x871b7795e136be9a}, {0xb913179899f68584, 0x28e2557b59846e40}, {0xe757dd7ec07426e5, 0x331aeada2fe589d0}, {0x9096ea6f3848984f, 0x3ff0d2c85def7622}, {0xb4bca50b065abe63, 0x0fed077a756b53aa}, {0xe1ebce4dc7f16dfb, 0xd3e8495912c62895}, {0x8d3360f09cf6e4bd, 0x64712dd7abbbd95d}, {0xb080392cc4349dec, 0xbd8d794d96aacfb4}, {0xdca04777f541c567, 0xecf0d7a0fc5583a1}, {0x89e42caaf9491b60, 0xf41686c49db57245}, {0xac5d37d5b79b6239, 0x311c2875c522ced6}, {0xd77485cb25823ac7, 0x7d633293366b828c}, {0x86a8d39ef77164bc, 0xae5dff9c02033198}, {0xa8530886b54dbdeb, 0xd9f57f830283fdfd}, {0xd267caa862a12d66, 0xd072df63c324fd7c}, {0x8380dea93da4bc60, 0x4247cb9e59f71e6e}, {0xa46116538d0deb78, 0x52d9be85f074e609}, {0xcd795be870516656, 0x67902e276c921f8c}, {0x806bd9714632dff6, 0x00ba1cd8a3db53b7}, {0xa086cfcd97bf97f3, 0x80e8a40eccd228a5}, {0xc8a883c0fdaf7df0, 0x6122cd128006b2ce}, {0xfad2a4b13d1b5d6c, 0x796b805720085f82}, {0x9cc3a6eec6311a63, 0xcbe3303674053bb1}, {0xc3f490aa77bd60fc, 0xbedbfc4411068a9d}, {0xf4f1b4d515acb93b, 0xee92fb5515482d45}, {0x991711052d8bf3c5, 0x751bdd152d4d1c4b}, {0xbf5cd54678eef0b6, 0xd262d45a78a0635e}, {0xef340a98172aace4, 0x86fb897116c87c35}, {0x9580869f0e7aac0e, 0xd45d35e6ae3d4da1}, {0xbae0a846d2195712, 0x8974836059cca10a}, {0xe998d258869facd7, 0x2bd1a438703fc94c}, {0x91ff83775423cc06, 0x7b6306a34627ddd0}, {0xb67f6455292cbf08, 0x1a3bc84c17b1d543}, {0xe41f3d6a7377eeca, 0x20caba5f1d9e4a94}, {0x8e938662882af53e, 0x547eb47b7282ee9d}, {0xb23867fb2a35b28d, 0xe99e619a4f23aa44}, {0xdec681f9f4c31f31, 0x6405fa00e2ec94d5}, {0x8b3c113c38f9f37e, 0xde83bc408dd3dd05}, {0xae0b158b4738705e, 0x9624ab50b148d446}, {0xd98ddaee19068c76, 0x3badd624dd9b0958}, {0x87f8a8d4cfa417c9, 0xe54ca5d70a80e5d7}, {0xa9f6d30a038d1dbc, 0x5e9fcf4ccd211f4d}, {0xd47487cc8470652b, 0x7647c32000696720}, {0x84c8d4dfd2c63f3b, 0x29ecd9f40041e074}, {0xa5fb0a17c777cf09, 0xf468107100525891}, {0xcf79cc9db955c2cc, 0x7182148d4066eeb5}, {0x81ac1fe293d599bf, 0xc6f14cd848405531}, {0xa21727db38cb002f, 0xb8ada00e5a506a7d}, {0xca9cf1d206fdc03b, 0xa6d90811f0e4851d}, {0xfd442e4688bd304a, 0x908f4a166d1da664}, {0x9e4a9cec15763e2e, 0x9a598e4e043287ff}, {0xc5dd44271ad3cdba, 0x40eff1e1853f29fe}, {0xf7549530e188c128, 0xd12bee59e68ef47d}, {0x9a94dd3e8cf578b9, 0x82bb74f8301958cf}, {0xc13a148e3032d6e7, 0xe36a52363c1faf02}, {0xf18899b1bc3f8ca1, 0xdc44e6c3cb279ac2}, {0x96f5600f15a7b7e5, 0x29ab103a5ef8c0ba}, {0xbcb2b812db11a5de, 0x7415d448f6b6f0e8}, {0xebdf661791d60f56, 0x111b495b3464ad22}, {0x936b9fcebb25c995, 0xcab10dd900beec35}, {0xb84687c269ef3bfb, 0x3d5d514f40eea743}, {0xe65829b3046b0afa, 0x0cb4a5a3112a5113}, {0x8ff71a0fe2c2e6dc, 0x47f0e785eaba72ac}, {0xb3f4e093db73a093, 0x59ed216765690f57}, {0xe0f218b8d25088b8, 0x306869c13ec3532d}, {0x8c974f7383725573, 0x1e414218c73a13fc}, {0xafbd2350644eeacf, 0xe5d1929ef90898fb}, {0xdbac6c247d62a583, 0xdf45f746b74abf3a}, {0x894bc396ce5da772, 0x6b8bba8c328eb784}, {0xab9eb47c81f5114f, 0x066ea92f3f326565}, {0xd686619ba27255a2, 0xc80a537b0efefebe}, {0x8613fd0145877585, 0xbd06742ce95f5f37}, {0xa798fc4196e952e7, 0x2c48113823b73705}, {0xd17f3b51fca3a7a0, 0xf75a15862ca504c6}, {0x82ef85133de648c4, 0x9a984d73dbe722fc}, {0xa3ab66580d5fdaf5, 0xc13e60d0d2e0ebbb}, {0xcc963fee10b7d1b3, 0x318df905079926a9}, {0xffbbcfe994e5c61f, 0xfdf17746497f7053}, {0x9fd561f1fd0f9bd3, 0xfeb6ea8bedefa634}, {0xc7caba6e7c5382c8, 0xfe64a52ee96b8fc1}, {0xf9bd690a1b68637b, 0x3dfdce7aa3c673b1}, {0x9c1661a651213e2d, 0x06bea10ca65c084f}, {0xc31bfa0fe5698db8, 0x486e494fcff30a63}, {0xf3e2f893dec3f126, 0x5a89dba3c3efccfb}, {0x986ddb5c6b3a76b7, 0xf89629465a75e01d}, {0xbe89523386091465, 0xf6bbb397f1135824}, {0xee2ba6c0678b597f, 0x746aa07ded582e2d}, {0x94db483840b717ef, 0xa8c2a44eb4571cdd}, {0xba121a4650e4ddeb, 0x92f34d62616ce414}, {0xe896a0d7e51e1566, 0x77b020baf9c81d18}, {0x915e2486ef32cd60, 0x0ace1474dc1d122f}, {0xb5b5ada8aaff80b8, 0x0d819992132456bb}, {0xe3231912d5bf60e6, 0x10e1fff697ed6c6a}, {0x8df5efabc5979c8f, 0xca8d3ffa1ef463c2}, {0xb1736b96b6fd83b3, 0xbd308ff8a6b17cb3}, {0xddd0467c64bce4a0, 0xac7cb3f6d05ddbdf}, {0x8aa22c0dbef60ee4, 0x6bcdf07a423aa96c}, {0xad4ab7112eb3929d, 0x86c16c98d2c953c7}, {0xd89d64d57a607744, 0xe871c7bf077ba8b8}, {0x87625f056c7c4a8b, 0x11471cd764ad4973}, {0xa93af6c6c79b5d2d, 0xd598e40d3dd89bd0}, {0xd389b47879823479, 0x4aff1d108d4ec2c4}, {0x843610cb4bf160cb, 0xcedf722a585139bb}, {0xa54394fe1eedb8fe, 0xc2974eb4ee658829}, {0xce947a3da6a9273e, 0x733d226229feea33}, {0x811ccc668829b887, 0x0806357d5a3f5260}, {0xa163ff802a3426a8, 0xca07c2dcb0cf26f8}, {0xc9bcff6034c13052, 0xfc89b393dd02f0b6}, {0xfc2c3f3841f17c67, 0xbbac2078d443ace3}, {0x9d9ba7832936edc0, 0xd54b944b84aa4c0e}, {0xc5029163f384a931, 0x0a9e795e65d4df12}, {0xf64335bcf065d37d, 0x4d4617b5ff4a16d6}, {0x99ea0196163fa42e, 0x504bced1bf8e4e46}, {0xc06481fb9bcf8d39, 0xe45ec2862f71e1d7}, {0xf07da27a82c37088, 0x5d767327bb4e5a4d}, {0x964e858c91ba2655, 0x3a6a07f8d510f870}, {0xbbe226efb628afea, 0x890489f70a55368c}, {0xeadab0aba3b2dbe5, 0x2b45ac74ccea842f}, {0x92c8ae6b464fc96f, 0x3b0b8bc90012929e}, {0xb77ada0617e3bbcb, 0x09ce6ebb40173745}, {0xe55990879ddcaabd, 0xcc420a6a101d0516}, {0x8f57fa54c2a9eab6, 0x9fa946824a12232e}, {0xb32df8e9f3546564, 0x47939822dc96abfa}, {0xdff9772470297ebd, 0x59787e2b93bc56f8}, {0x8bfbea76c619ef36, 0x57eb4edb3c55b65b}, {0xaefae51477a06b03, 0xede622920b6b23f2}, {0xdab99e59958885c4, 0xe95fab368e45ecee}, {0x88b402f7fd75539b, 0x11dbcb0218ebb415}, {0xaae103b5fcd2a881, 0xd652bdc29f26a11a}, {0xd59944a37c0752a2, 0x4be76d3346f04960}, {0x857fcae62d8493a5, 0x6f70a4400c562ddc}, {0xa6dfbd9fb8e5b88e, 0xcb4ccd500f6bb953}, {0xd097ad07a71f26b2, 0x7e2000a41346a7a8}, {0x825ecc24c873782f, 0x8ed400668c0c28c9}, {0xa2f67f2dfa90563b, 0x728900802f0f32fb}, {0xcbb41ef979346bca, 0x4f2b40a03ad2ffba}, {0xfea126b7d78186bc, 0xe2f610c84987bfa9}, {0x9f24b832e6b0f436, 0x0dd9ca7d2df4d7ca}, {0xc6ede63fa05d3143, 0x91503d1c79720dbc}, {0xf8a95fcf88747d94, 0x75a44c6397ce912b}, {0x9b69dbe1b548ce7c, 0xc986afbe3ee11abb}, {0xc24452da229b021b, 0xfbe85badce996169}, {0xf2d56790ab41c2a2, 0xfae27299423fb9c4}, {0x97c560ba6b0919a5, 0xdccd879fc967d41b}, {0xbdb6b8e905cb600f, 0x5400e987bbc1c921}, {0xed246723473e3813, 0x290123e9aab23b69}, {0x9436c0760c86e30b, 0xf9a0b6720aaf6522}, {0xb94470938fa89bce, 0xf808e40e8d5b3e6a}, {0xe7958cb87392c2c2, 0xb60b1d1230b20e05}, {0x90bd77f3483bb9b9, 0xb1c6f22b5e6f48c3}, {0xb4ecd5f01a4aa828, 0x1e38aeb6360b1af4}, {0xe2280b6c20dd5232, 0x25c6da63c38de1b1}, {0x8d590723948a535f, 0x579c487e5a38ad0f}, {0xb0af48ec79ace837, 0x2d835a9df0c6d852}, {0xdcdb1b2798182244, 0xf8e431456cf88e66}, {0x8a08f0f8bf0f156b, 0x1b8e9ecb641b5900}, {0xac8b2d36eed2dac5, 0xe272467e3d222f40}, {0xd7adf884aa879177, 0x5b0ed81dcc6abb10}, {0x86ccbb52ea94baea, 0x98e947129fc2b4ea}, {0xa87fea27a539e9a5, 0x3f2398d747b36225}, {0xd29fe4b18e88640e, 0x8eec7f0d19a03aae}, {0x83a3eeeef9153e89, 0x1953cf68300424ad}, {0xa48ceaaab75a8e2b, 0x5fa8c3423c052dd8}, {0xcdb02555653131b6, 0x3792f412cb06794e}, {0x808e17555f3ebf11, 0xe2bbd88bbee40bd1}, {0xa0b19d2ab70e6ed6, 0x5b6aceaeae9d0ec5}, {0xc8de047564d20a8b, 0xf245825a5a445276}, {0xfb158592be068d2e, 0xeed6e2f0f0d56713}, {0x9ced737bb6c4183d, 0x55464dd69685606c}, {0xc428d05aa4751e4c, 0xaa97e14c3c26b887}, {0xf53304714d9265df, 0xd53dd99f4b3066a9}, {0x993fe2c6d07b7fab, 0xe546a8038efe402a}, {0xbf8fdb78849a5f96, 0xde98520472bdd034}, {0xef73d256a5c0f77c, 0x963e66858f6d4441}, {0x95a8637627989aad, 0xdde7001379a44aa9}, {0xbb127c53b17ec159, 0x5560c018580d5d53}, {0xe9d71b689dde71af, 0xaab8f01e6e10b4a7}, {0x9226712162ab070d, 0xcab3961304ca70e9}, {0xb6b00d69bb55c8d1, 0x3d607b97c5fd0d23}, {0xe45c10c42a2b3b05, 0x8cb89a7db77c506b}, {0x8eb98a7a9a5b04e3, 0x77f3608e92adb243}, {0xb267ed1940f1c61c, 0x55f038b237591ed4}, {0xdf01e85f912e37a3, 0x6b6c46dec52f6689}, {0x8b61313bbabce2c6, 0x2323ac4b3b3da016}, {0xae397d8aa96c1b77, 0xabec975e0a0d081b}, {0xd9c7dced53c72255, 0x96e7bd358c904a22}, {0x881cea14545c7575, 0x7e50d64177da2e55}, {0xaa242499697392d2, 0xdde50bd1d5d0b9ea}, {0xd4ad2dbfc3d07787, 0x955e4ec64b44e865}, {0x84ec3c97da624ab4, 0xbd5af13bef0b113f}, {0xa6274bbdd0fadd61, 0xecb1ad8aeacdd58f}, {0xcfb11ead453994ba, 0x67de18eda5814af3}, {0x81ceb32c4b43fcf4, 0x80eacf948770ced8}, {0xa2425ff75e14fc31, 0xa1258379a94d028e}, {0xcad2f7f5359a3b3e, 0x096ee45813a04331}, {0xfd87b5f28300ca0d, 0x8bca9d6e188853fd}, {0x9e74d1b791e07e48, 0x775ea264cf55347e}, {0xc612062576589dda, 0x95364afe032a819e}, {0xf79687aed3eec551, 0x3a83ddbd83f52205}, {0x9abe14cd44753b52, 0xc4926a9672793543}, {0xc16d9a0095928a27, 0x75b7053c0f178294}, {0xf1c90080baf72cb1, 0x5324c68b12dd6339}, {0x971da05074da7bee, 0xd3f6fc16ebca5e04}, {0xbce5086492111aea, 0x88f4bb1ca6bcf585}, {0xec1e4a7db69561a5, 0x2b31e9e3d06c32e6}, {0x9392ee8e921d5d07, 0x3aff322e62439fd0}, {0xb877aa3236a4b449, 0x09befeb9fad487c3}, {0xe69594bec44de15b, 0x4c2ebe687989a9b4}, {0x901d7cf73ab0acd9, 0x0f9d37014bf60a11}, {0xb424dc35095cd80f, 0x538484c19ef38c95}, {0xe12e13424bb40e13, 0x2865a5f206b06fba}, {0x8cbccc096f5088cb, 0xf93f87b7442e45d4}, {0xafebff0bcb24aafe, 0xf78f69a51539d749}, {0xdbe6fecebdedd5be, 0xb573440e5a884d1c}, {0x89705f4136b4a597, 0x31680a88f8953031}, {0xabcc77118461cefc, 0xfdc20d2b36ba7c3e}, {0xd6bf94d5e57a42bc, 0x3d32907604691b4d}, {0x8637bd05af6c69b5, 0xa63f9a49c2c1b110}, {0xa7c5ac471b478423, 0x0fcf80dc33721d54}, {0xd1b71758e219652b, 0xd3c36113404ea4a9}, {0x83126e978d4fdf3b, 0x645a1cac083126ea}, {0xa3d70a3d70a3d70a, 0x3d70a3d70a3d70a4}, {0xcccccccccccccccc, 0xcccccccccccccccd}, {0x8000000000000000, 0x0000000000000000}, {0xa000000000000000, 0x0000000000000000}, {0xc800000000000000, 0x0000000000000000}, {0xfa00000000000000, 0x0000000000000000}, {0x9c40000000000000, 0x0000000000000000}, {0xc350000000000000, 0x0000000000000000}, {0xf424000000000000, 0x0000000000000000}, {0x9896800000000000, 0x0000000000000000}, {0xbebc200000000000, 0x0000000000000000}, {0xee6b280000000000, 0x0000000000000000}, {0x9502f90000000000, 0x0000000000000000}, {0xba43b74000000000, 0x0000000000000000}, {0xe8d4a51000000000, 0x0000000000000000}, {0x9184e72a00000000, 0x0000000000000000}, {0xb5e620f480000000, 0x0000000000000000}, {0xe35fa931a0000000, 0x0000000000000000}, {0x8e1bc9bf04000000, 0x0000000000000000}, {0xb1a2bc2ec5000000, 0x0000000000000000}, {0xde0b6b3a76400000, 0x0000000000000000}, {0x8ac7230489e80000, 0x0000000000000000}, {0xad78ebc5ac620000, 0x0000000000000000}, {0xd8d726b7177a8000, 0x0000000000000000}, {0x878678326eac9000, 0x0000000000000000}, {0xa968163f0a57b400, 0x0000000000000000}, {0xd3c21bcecceda100, 0x0000000000000000}, {0x84595161401484a0, 0x0000000000000000}, {0xa56fa5b99019a5c8, 0x0000000000000000}, {0xcecb8f27f4200f3a, 0x0000000000000000}, {0x813f3978f8940984, 0x4000000000000000}, {0xa18f07d736b90be5, 0x5000000000000000}, {0xc9f2c9cd04674ede, 0xa400000000000000}, {0xfc6f7c4045812296, 0x4d00000000000000}, {0x9dc5ada82b70b59d, 0xf020000000000000}, {0xc5371912364ce305, 0x6c28000000000000}, {0xf684df56c3e01bc6, 0xc732000000000000}, {0x9a130b963a6c115c, 0x3c7f400000000000}, {0xc097ce7bc90715b3, 0x4b9f100000000000}, {0xf0bdc21abb48db20, 0x1e86d40000000000}, {0x96769950b50d88f4, 0x1314448000000000}, {0xbc143fa4e250eb31, 0x17d955a000000000}, {0xeb194f8e1ae525fd, 0x5dcfab0800000000}, {0x92efd1b8d0cf37be, 0x5aa1cae500000000}, {0xb7abc627050305ad, 0xf14a3d9e40000000}, {0xe596b7b0c643c719, 0x6d9ccd05d0000000}, {0x8f7e32ce7bea5c6f, 0xe4820023a2000000}, {0xb35dbf821ae4f38b, 0xdda2802c8a800000}, {0xe0352f62a19e306e, 0xd50b2037ad200000}, {0x8c213d9da502de45, 0x4526f422cc340000}, {0xaf298d050e4395d6, 0x9670b12b7f410000}, {0xdaf3f04651d47b4c, 0x3c0cdd765f114000}, {0x88d8762bf324cd0f, 0xa5880a69fb6ac800}, {0xab0e93b6efee0053, 0x8eea0d047a457a00}, {0xd5d238a4abe98068, 0x72a4904598d6d880}, {0x85a36366eb71f041, 0x47a6da2b7f864750}, {0xa70c3c40a64e6c51, 0x999090b65f67d924}, {0xd0cf4b50cfe20765, 0xfff4b4e3f741cf6d}, {0x82818f1281ed449f, 0xbff8f10e7a8921a5}, {0xa321f2d7226895c7, 0xaff72d52192b6a0e}, {0xcbea6f8ceb02bb39, 0x9bf4f8a69f764491}, {0xfee50b7025c36a08, 0x02f236d04753d5b5}, {0x9f4f2726179a2245, 0x01d762422c946591}, {0xc722f0ef9d80aad6, 0x424d3ad2b7b97ef6}, {0xf8ebad2b84e0d58b, 0xd2e0898765a7deb3}, {0x9b934c3b330c8577, 0x63cc55f49f88eb30}, {0xc2781f49ffcfa6d5, 0x3cbf6b71c76b25fc}, {0xf316271c7fc3908a, 0x8bef464e3945ef7b}, {0x97edd871cfda3a56, 0x97758bf0e3cbb5ad}, {0xbde94e8e43d0c8ec, 0x3d52eeed1cbea318}, {0xed63a231d4c4fb27, 0x4ca7aaa863ee4bde}, {0x945e455f24fb1cf8, 0x8fe8caa93e74ef6b}, {0xb975d6b6ee39e436, 0xb3e2fd538e122b45}, {0xe7d34c64a9c85d44, 0x60dbbca87196b617}, {0x90e40fbeea1d3a4a, 0xbc8955e946fe31ce}, {0xb51d13aea4a488dd, 0x6babab6398bdbe42}, {0xe264589a4dcdab14, 0xc696963c7eed2dd2}, {0x8d7eb76070a08aec, 0xfc1e1de5cf543ca3}, {0xb0de65388cc8ada8, 0x3b25a55f43294bcc}, {0xdd15fe86affad912, 0x49ef0eb713f39ebf}, {0x8a2dbf142dfcc7ab, 0x6e3569326c784338}, {0xacb92ed9397bf996, 0x49c2c37f07965405}, {0xd7e77a8f87daf7fb, 0xdc33745ec97be907}, {0x86f0ac99b4e8dafd, 0x69a028bb3ded71a4}, {0xa8acd7c0222311bc, 0xc40832ea0d68ce0d}, {0xd2d80db02aabd62b, 0xf50a3fa490c30191}, {0x83c7088e1aab65db, 0x792667c6da79e0fb}, {0xa4b8cab1a1563f52, 0x577001b891185939}, {0xcde6fd5e09abcf26, 0xed4c0226b55e6f87}, {0x80b05e5ac60b6178, 0x544f8158315b05b5}, {0xa0dc75f1778e39d6, 0x696361ae3db1c722}, {0xc913936dd571c84c, 0x03bc3a19cd1e38ea}, {0xfb5878494ace3a5f, 0x04ab48a04065c724}, {0x9d174b2dcec0e47b, 0x62eb0d64283f9c77}, {0xc45d1df942711d9a, 0x3ba5d0bd324f8395}, {0xf5746577930d6500, 0xca8f44ec7ee3647a}, {0x9968bf6abbe85f20, 0x7e998b13cf4e1ecc}, {0xbfc2ef456ae276e8, 0x9e3fedd8c321a67f}, {0xefb3ab16c59b14a2, 0xc5cfe94ef3ea101f}, {0x95d04aee3b80ece5, 0xbba1f1d158724a13}, {0xbb445da9ca61281f, 0x2a8a6e45ae8edc98}, {0xea1575143cf97226, 0xf52d09d71a3293be}, {0x924d692ca61be758, 0x593c2626705f9c57}, {0xb6e0c377cfa2e12e, 0x6f8b2fb00c77836d}, {0xe498f455c38b997a, 0x0b6dfb9c0f956448}, {0x8edf98b59a373fec, 0x4724bd4189bd5ead}, {0xb2977ee300c50fe7, 0x58edec91ec2cb658}, {0xdf3d5e9bc0f653e1, 0x2f2967b66737e3ee}, {0x8b865b215899f46c, 0xbd79e0d20082ee75}, {0xae67f1e9aec07187, 0xecd8590680a3aa12}, {0xda01ee641a708de9, 0xe80e6f4820cc9496}, {0x884134fe908658b2, 0x3109058d147fdcde}, {0xaa51823e34a7eede, 0xbd4b46f0599fd416}, {0xd4e5e2cdc1d1ea96, 0x6c9e18ac7007c91b}, {0x850fadc09923329e, 0x03e2cf6bc604ddb1}, {0xa6539930bf6bff45, 0x84db8346b786151d}, {0xcfe87f7cef46ff16, 0xe612641865679a64}, {0x81f14fae158c5f6e, 0x4fcb7e8f3f60c07f}, {0xa26da3999aef7749, 0xe3be5e330f38f09e}, {0xcb090c8001ab551c, 0x5cadf5bfd3072cc6}, {0xfdcb4fa002162a63, 0x73d9732fc7c8f7f7}, {0x9e9f11c4014dda7e, 0x2867e7fddcdd9afb}, {0xc646d63501a1511d, 0xb281e1fd541501b9}, {0xf7d88bc24209a565, 0x1f225a7ca91a4227}, {0x9ae757596946075f, 0x3375788de9b06959}, {0xc1a12d2fc3978937, 0x0052d6b1641c83af}, {0xf209787bb47d6b84, 0xc0678c5dbd23a49b}, {0x9745eb4d50ce6332, 0xf840b7ba963646e1}, {0xbd176620a501fbff, 0xb650e5a93bc3d899}, {0xec5d3fa8ce427aff, 0xa3e51f138ab4cebf}, {0x93ba47c980e98cdf, 0xc66f336c36b10138}, {0xb8a8d9bbe123f017, 0xb80b0047445d4185}, {0xe6d3102ad96cec1d, 0xa60dc059157491e6}, {0x9043ea1ac7e41392, 0x87c89837ad68db30}, {0xb454e4a179dd1877, 0x29babe4598c311fc}, {0xe16a1dc9d8545e94, 0xf4296dd6fef3d67b}, {0x8ce2529e2734bb1d, 0x1899e4a65f58660d}, {0xb01ae745b101e9e4, 0x5ec05dcff72e7f90}, {0xdc21a1171d42645d, 0x76707543f4fa1f74}, {0x899504ae72497eba, 0x6a06494a791c53a9}, {0xabfa45da0edbde69, 0x0487db9d17636893}, {0xd6f8d7509292d603, 0x45a9d2845d3c42b7}, {0x865b86925b9bc5c2, 0x0b8a2392ba45a9b3}, {0xa7f26836f282b732, 0x8e6cac7768d7141f}, {0xd1ef0244af2364ff, 0x3207d795430cd927}, {0x8335616aed761f1f, 0x7f44e6bd49e807b9}, {0xa402b9c5a8d3a6e7, 0x5f16206c9c6209a7}, {0xcd036837130890a1, 0x36dba887c37a8c10}, {0x802221226be55a64, 0xc2494954da2c978a}, {0xa02aa96b06deb0fd, 0xf2db9baa10b7bd6d}, {0xc83553c5c8965d3d, 0x6f92829494e5acc8}, {0xfa42a8b73abbf48c, 0xcb772339ba1f17fa}, {0x9c69a97284b578d7, 0xff2a760414536efc}, {0xc38413cf25e2d70d, 0xfef5138519684abb}, {0xf46518c2ef5b8cd1, 0x7eb258665fc25d6a}, {0x98bf2f79d5993802, 0xef2f773ffbd97a62}, {0xbeeefb584aff8603, 0xaafb550ffacfd8fb}, {0xeeaaba2e5dbf6784, 0x95ba2a53f983cf39}, {0x952ab45cfa97a0b2, 0xdd945a747bf26184}, {0xba756174393d88df, 0x94f971119aeef9e5}, {0xe912b9d1478ceb17, 0x7a37cd5601aab85e}, {0x91abb422ccb812ee, 0xac62e055c10ab33b}, {0xb616a12b7fe617aa, 0x577b986b314d600a}, {0xe39c49765fdf9d94, 0xed5a7e85fda0b80c}, {0x8e41ade9fbebc27d, 0x14588f13be847308}, {0xb1d219647ae6b31c, 0x596eb2d8ae258fc9}, {0xde469fbd99a05fe3, 0x6fca5f8ed9aef3bc}, {0x8aec23d680043bee, 0x25de7bb9480d5855}, {0xada72ccc20054ae9, 0xaf561aa79a10ae6b}, {0xd910f7ff28069da4, 0x1b2ba1518094da05}, {0x87aa9aff79042286, 0x90fb44d2f05d0843}, {0xa99541bf57452b28, 0x353a1607ac744a54}, {0xd3fa922f2d1675f2, 0x42889b8997915ce9}, {0x847c9b5d7c2e09b7, 0x69956135febada12}, {0xa59bc234db398c25, 0x43fab9837e699096}, {0xcf02b2c21207ef2e, 0x94f967e45e03f4bc}, {0x8161afb94b44f57d, 0x1d1be0eebac278f6}, {0xa1ba1ba79e1632dc, 0x6462d92a69731733}, {0xca28a291859bbf93, 0x7d7b8f7503cfdcff}, {0xfcb2cb35e702af78, 0x5cda735244c3d43f}, {0x9defbf01b061adab, 0x3a0888136afa64a8}, {0xc56baec21c7a1916, 0x088aaa1845b8fdd1}, {0xf6c69a72a3989f5b, 0x8aad549e57273d46}, {0x9a3c2087a63f6399, 0x36ac54e2f678864c}, {0xc0cb28a98fcf3c7f, 0x84576a1bb416a7de}, {0xf0fdf2d3f3c30b9f, 0x656d44a2a11c51d6}, {0x969eb7c47859e743, 0x9f644ae5a4b1b326}, {0xbc4665b596706114, 0x873d5d9f0dde1fef}, {0xeb57ff22fc0c7959, 0xa90cb506d155a7eb}, {0x9316ff75dd87cbd8, 0x09a7f12442d588f3}, {0xb7dcbf5354e9bece, 0x0c11ed6d538aeb30}, {0xe5d3ef282a242e81, 0x8f1668c8a86da5fb}, {0x8fa475791a569d10, 0xf96e017d694487bd}, {0xb38d92d760ec4455, 0x37c981dcc395a9ad}, {0xe070f78d3927556a, 0x85bbe253f47b1418}, {0x8c469ab843b89562, 0x93956d7478ccec8f}, {0xaf58416654a6babb, 0x387ac8d1970027b3}, {0xdb2e51bfe9d0696a, 0x06997b05fcc0319f}, {0x88fcf317f22241e2, 0x441fece3bdf81f04}, {0xab3c2fddeeaad25a, 0xd527e81cad7626c4}, {0xd60b3bd56a5586f1, 0x8a71e223d8d3b075}, {0x85c7056562757456, 0xf6872d5667844e4a}, {0xa738c6bebb12d16c, 0xb428f8ac016561dc}, {0xd106f86e69d785c7, 0xe13336d701beba53}, {0x82a45b450226b39c, 0xecc0024661173474}, {0xa34d721642b06084, 0x27f002d7f95d0191}, {0xcc20ce9bd35c78a5, 0x31ec038df7b441f5}, {0xff290242c83396ce, 0x7e67047175a15272}, {0x9f79a169bd203e41, 0x0f0062c6e984d387}, {0xc75809c42c684dd1, 0x52c07b78a3e60869}, {0xf92e0c3537826145, 0xa7709a56ccdf8a83}, {0x9bbcc7a142b17ccb, 0x88a66076400bb692}, {0xc2abf989935ddbfe, 0x6acff893d00ea436}, {0xf356f7ebf83552fe, 0x0583f6b8c4124d44}, {0x98165af37b2153de, 0xc3727a337a8b704b}, {0xbe1bf1b059e9a8d6, 0x744f18c0592e4c5d}, {0xeda2ee1c7064130c, 0x1162def06f79df74}, {0x9485d4d1c63e8be7, 0x8addcb5645ac2ba9}, {0xb9a74a0637ce2ee1, 0x6d953e2bd7173693}, {0xe8111c87c5c1ba99, 0xc8fa8db6ccdd0438}, {0x910ab1d4db9914a0, 0x1d9c9892400a22a3}, {0xb54d5e4a127f59c8, 0x2503beb6d00cab4c}, {0xe2a0b5dc971f303a, 0x2e44ae64840fd61e}, {0x8da471a9de737e24, 0x5ceaecfed289e5d3}, {0xb10d8e1456105dad, 0x7425a83e872c5f48}, {0xdd50f1996b947518, 0xd12f124e28f7771a}, {0x8a5296ffe33cc92f, 0x82bd6b70d99aaa70}, {0xace73cbfdc0bfb7b, 0x636cc64d1001550c}, {0xd8210befd30efa5a, 0x3c47f7e05401aa4f}, {0x8714a775e3e95c78, 0x65acfaec34810a72}, {0xa8d9d1535ce3b396, 0x7f1839a741a14d0e}, {0xd31045a8341ca07c, 0x1ede48111209a051}, {0x83ea2b892091e44d, 0x934aed0aab460433}, {0xa4e4b66b68b65d60, 0xf81da84d56178540}, {0xce1de40642e3f4b9, 0x36251260ab9d668f}, {0x80d2ae83e9ce78f3, 0xc1d72b7c6b42601a}, {0xa1075a24e4421730, 0xb24cf65b8612f820}, {0xc94930ae1d529cfc, 0xdee033f26797b628}, {0xfb9b7cd9a4a7443c, 0x169840ef017da3b2}, {0x9d412e0806e88aa5, 0x8e1f289560ee864f}, {0xc491798a08a2ad4e, 0xf1a6f2bab92a27e3}, {0xf5b5d7ec8acb58a2, 0xae10af696774b1dc}, {0x9991a6f3d6bf1765, 0xacca6da1e0a8ef2a}, {0xbff610b0cc6edd3f, 0x17fd090a58d32af4}, {0xeff394dcff8a948e, 0xddfc4b4cef07f5b1}, {0x95f83d0a1fb69cd9, 0x4abdaf101564f98f}, {0xbb764c4ca7a4440f, 0x9d6d1ad41abe37f2}, {0xea53df5fd18d5513, 0x84c86189216dc5ee}, {0x92746b9be2f8552c, 0x32fd3cf5b4e49bb5}, {0xb7118682dbb66a77, 0x3fbc8c33221dc2a2}, {0xe4d5e82392a40515, 0x0fabaf3feaa5334b}, {0x8f05b1163ba6832d, 0x29cb4d87f2a7400f}, {0xb2c71d5bca9023f8, 0x743e20e9ef511013}, {0xdf78e4b2bd342cf6, 0x914da9246b255417}, {0x8bab8eefb6409c1a, 0x1ad089b6c2f7548f}, {0xae9672aba3d0c320, 0xa184ac2473b529b2}, {0xda3c0f568cc4f3e8, 0xc9e5d72d90a2741f}, {0x8865899617fb1871, 0x7e2fa67c7a658893}, {0xaa7eebfb9df9de8d, 0xddbb901b98feeab8}, {0xd51ea6fa85785631, 0x552a74227f3ea566}, {0x8533285c936b35de, 0xd53a88958f872760}, {0xa67ff273b8460356, 0x8a892abaf368f138}, {0xd01fef10a657842c, 0x2d2b7569b0432d86}, {0x8213f56a67f6b29b, 0x9c3b29620e29fc74}, {0xa298f2c501f45f42, 0x8349f3ba91b47b90}, {0xcb3f2f7642717713, 0x241c70a936219a74}, {0xfe0efb53d30dd4d7, 0xed238cd383aa0111}, {0x9ec95d1463e8a506, 0xf4363804324a40ab}, {0xc67bb4597ce2ce48, 0xb143c6053edcd0d6}, {0xf81aa16fdc1b81da, 0xdd94b7868e94050b}, {0x9b10a4e5e9913128, 0xca7cf2b4191c8327}, {0xc1d4ce1f63f57d72, 0xfd1c2f611f63a3f1}, {0xf24a01a73cf2dccf, 0xbc633b39673c8ced}, {0x976e41088617ca01, 0xd5be0503e085d814}, {0xbd49d14aa79dbc82, 0x4b2d8644d8a74e19}, {0xec9c459d51852ba2, 0xddf8e7d60ed1219f}, {0x93e1ab8252f33b45, 0xcabb90e5c942b504}, {0xb8da1662e7b00a17, 0x3d6a751f3b936244}, {0xe7109bfba19c0c9d, 0x0cc512670a783ad5}, {0x906a617d450187e2, 0x27fb2b80668b24c6}, {0xb484f9dc9641e9da, 0xb1f9f660802dedf7}, {0xe1a63853bbd26451, 0x5e7873f8a0396974}, {0x8d07e33455637eb2, 0xdb0b487b6423e1e9}, {0xb049dc016abc5e5f, 0x91ce1a9a3d2cda63}, {0xdc5c5301c56b75f7, 0x7641a140cc7810fc}, {0x89b9b3e11b6329ba, 0xa9e904c87fcb0a9e}, {0xac2820d9623bf429, 0x546345fa9fbdcd45}, {0xd732290fbacaf133, 0xa97c177947ad4096}, {0x867f59a9d4bed6c0, 0x49ed8eabcccc485e}, {0xa81f301449ee8c70, 0x5c68f256bfff5a75}, {0xd226fc195c6a2f8c, 0x73832eec6fff3112}, {0x83585d8fd9c25db7, 0xc831fd53c5ff7eac}, {0xa42e74f3d032f525, 0xba3e7ca8b77f5e56}, {0xcd3a1230c43fb26f, 0x28ce1bd2e55f35ec}, {0x80444b5e7aa7cf85, 0x7980d163cf5b81b4}, {0xa0555e361951c366, 0xd7e105bcc3326220}, {0xc86ab5c39fa63440, 0x8dd9472bf3fefaa8}, {0xfa856334878fc150, 0xb14f98f6f0feb952}, {0x9c935e00d4b9d8d2, 0x6ed1bf9a569f33d4}, {0xc3b8358109e84f07, 0x0a862f80ec4700c9}, {0xf4a642e14c6262c8, 0xcd27bb612758c0fb}, {0x98e7e9cccfbd7dbd, 0x8038d51cb897789d}, {0xbf21e44003acdd2c, 0xe0470a63e6bd56c4}, {0xeeea5d5004981478, 0x1858ccfce06cac75}, {0x95527a5202df0ccb, 0x0f37801e0c43ebc9}, {0xbaa718e68396cffd, 0xd30560258f54e6bb}, {0xe950df20247c83fd, 0x47c6b82ef32a206a}, {0x91d28b7416cdd27e, 0x4cdc331d57fa5442}, {0xb6472e511c81471d, 0xe0133fe4adf8e953}, {0xe3d8f9e563a198e5, 0x58180fddd97723a7}, {0x8e679c2f5e44ff8f, 0x570f09eaa7ea7649}, {0xb201833b35d63f73, 0x2cd2cc6551e513db}, {0xde81e40a034bcf4f, 0xf8077f7ea65e58d2}, {0x8b112e86420f6191, 0xfb04afaf27faf783}, {0xadd57a27d29339f6, 0x79c5db9af1f9b564}, {0xd94ad8b1c7380874, 0x18375281ae7822bd}, {0x87cec76f1c830548, 0x8f2293910d0b15b6}, {0xa9c2794ae3a3c69a, 0xb2eb3875504ddb23}, {0xd433179d9c8cb841, 0x5fa60692a46151ec}, {0x849feec281d7f328, 0xdbc7c41ba6bcd334}, {0xa5c7ea73224deff3, 0x12b9b522906c0801}, {0xcf39e50feae16bef, 0xd768226b34870a01}, {0x81842f29f2cce375, 0xe6a1158300d46641}, {0xa1e53af46f801c53, 0x60495ae3c1097fd1}, {0xca5e89b18b602368, 0x385bb19cb14bdfc5}, {0xfcf62c1dee382c42, 0x46729e03dd9ed7b6}, {0x9e19db92b4e31ba9, 0x6c07a2c26a8346d2}, {0xc5a05277621be293, 0xc7098b7305241886}, {0xf70867153aa2db38, 0xb8cbee4fc66d1ea8}, {0x9a65406d44a5c903, 0x737f74f1dc043329}, {0xc0fe908895cf3b44, 0x505f522e53053ff3}, {0xf13e34aabb430a15, 0x647726b9e7c68ff0}, {0x96c6e0eab509e64d, 0x5eca783430dc19f6}, {0xbc789925624c5fe0, 0xb67d16413d132073}, {0xeb96bf6ebadf77d8, 0xe41c5bd18c57e890}, {0x933e37a534cbaae7, 0x8e91b962f7b6f15a}, {0xb80dc58e81fe95a1, 0x723627bbb5a4adb1}, {0xe61136f2227e3b09, 0xcec3b1aaa30dd91d}, {0x8fcac257558ee4e6, 0x213a4f0aa5e8a7b2}, {0xb3bd72ed2af29e1f, 0xa988e2cd4f62d19e}, {0xe0accfa875af45a7, 0x93eb1b80a33b8606}, {0x8c6c01c9498d8b88, 0xbc72f130660533c4}, {0xaf87023b9bf0ee6a, 0xeb8fad7c7f8680b5}, {0xdb68c2ca82ed2a05, 0xa67398db9f6820e2}, #else {0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7b}, {0xce5d73ff402d98e3, 0xfb0a3d212dc81290}, {0xa6b34ad8c9dfc06f, 0xf42faa48c0ea481f}, {0x86a8d39ef77164bc, 0xae5dff9c02033198}, {0xd98ddaee19068c76, 0x3badd624dd9b0958}, {0xafbd2350644eeacf, 0xe5d1929ef90898fb}, {0x8df5efabc5979c8f, 0xca8d3ffa1ef463c2}, {0xe55990879ddcaabd, 0xcc420a6a101d0516}, {0xb94470938fa89bce, 0xf808e40e8d5b3e6a}, {0x95a8637627989aad, 0xdde7001379a44aa9}, {0xf1c90080baf72cb1, 0x5324c68b12dd6339}, {0xc350000000000000, 0x0000000000000000}, {0x9dc5ada82b70b59d, 0xf020000000000000}, {0xfee50b7025c36a08, 0x02f236d04753d5b5}, {0xcde6fd5e09abcf26, 0xed4c0226b55e6f87}, {0xa6539930bf6bff45, 0x84db8346b786151d}, {0x865b86925b9bc5c2, 0x0b8a2392ba45a9b3}, {0xd910f7ff28069da4, 0x1b2ba1518094da05}, {0xaf58416654a6babb, 0x387ac8d1970027b3}, {0x8da471a9de737e24, 0x5ceaecfed289e5d3}, {0xe4d5e82392a40515, 0x0fabaf3feaa5334b}, {0xb8da1662e7b00a17, 0x3d6a751f3b936244}, {0x95527a5202df0ccb, 0x0f37801e0c43ebc9}, {0xf13e34aabb430a15, 0x647726b9e7c68ff0} #endif }; #if FMT_USE_FULL_CACHE_DRAGONBOX return pow10_significands[k - float_info::min_k]; #else static constexpr const uint64_t powers_of_5_64[] = { 0x0000000000000001, 0x0000000000000005, 0x0000000000000019, 0x000000000000007d, 0x0000000000000271, 0x0000000000000c35, 0x0000000000003d09, 0x000000000001312d, 0x000000000005f5e1, 0x00000000001dcd65, 0x00000000009502f9, 0x0000000002e90edd, 0x000000000e8d4a51, 0x0000000048c27395, 0x000000016bcc41e9, 0x000000071afd498d, 0x0000002386f26fc1, 0x000000b1a2bc2ec5, 0x000003782dace9d9, 0x00001158e460913d, 0x000056bc75e2d631, 0x0001b1ae4d6e2ef5, 0x000878678326eac9, 0x002a5a058fc295ed, 0x00d3c21bcecceda1, 0x0422ca8b0a00a425, 0x14adf4b7320334b9}; static const int compression_ratio = 27; // Compute base index. int cache_index = (k - float_info::min_k) / compression_ratio; int kb = cache_index * compression_ratio + float_info::min_k; int offset = k - kb; // Get base cache. uint128_fallback base_cache = pow10_significands[cache_index]; if (offset == 0) return base_cache; // Compute the required amount of bit-shift. int alpha = floor_log2_pow10(kb + offset) - floor_log2_pow10(kb) - offset; FMT_ASSERT(alpha > 0 && alpha < 64, "shifting error detected"); // Try to recover the real cache. uint64_t pow5 = powers_of_5_64[offset]; uint128_fallback recovered_cache = umul128(base_cache.high(), pow5); uint128_fallback middle_low = umul128(base_cache.low(), pow5); recovered_cache += middle_low.high(); uint64_t high_to_middle = recovered_cache.high() << (64 - alpha); uint64_t middle_to_low = recovered_cache.low() << (64 - alpha); recovered_cache = uint128_fallback{(recovered_cache.low() >> alpha) | high_to_middle, ((middle_low.low() >> alpha) | middle_to_low)}; FMT_ASSERT(recovered_cache.low() + 1 != 0, ""); return {recovered_cache.high(), recovered_cache.low() + 1}; #endif } struct compute_mul_result { carrier_uint result; bool is_integer; }; struct compute_mul_parity_result { bool parity; bool is_integer; }; static auto compute_mul(carrier_uint u, const cache_entry_type& cache) noexcept -> compute_mul_result { auto r = umul192_upper128(u, cache); return {r.high(), r.low() == 0}; } static auto compute_delta(cache_entry_type const& cache, int beta) noexcept -> uint32_t { return static_cast(cache.high() >> (64 - 1 - beta)); } static auto compute_mul_parity(carrier_uint two_f, const cache_entry_type& cache, int beta) noexcept -> compute_mul_parity_result { FMT_ASSERT(beta >= 1, ""); FMT_ASSERT(beta < 64, ""); auto r = umul192_lower128(two_f, cache); return {((r.high() >> (64 - beta)) & 1) != 0, ((r.high() << beta) | (r.low() >> (64 - beta))) == 0}; } static auto compute_left_endpoint_for_shorter_interval_case( const cache_entry_type& cache, int beta) noexcept -> carrier_uint { return (cache.high() - (cache.high() >> (num_significand_bits() + 2))) >> (64 - num_significand_bits() - 1 - beta); } static auto compute_right_endpoint_for_shorter_interval_case( const cache_entry_type& cache, int beta) noexcept -> carrier_uint { return (cache.high() + (cache.high() >> (num_significand_bits() + 1))) >> (64 - num_significand_bits() - 1 - beta); } static auto compute_round_up_for_shorter_interval_case( const cache_entry_type& cache, int beta) noexcept -> carrier_uint { return ((cache.high() >> (64 - num_significand_bits() - 2 - beta)) + 1) / 2; } }; FMT_FUNC auto get_cached_power(int k) noexcept -> uint128_fallback { return cache_accessor::get_cached_power(k); } // Various integer checks template auto is_left_endpoint_integer_shorter_interval(int exponent) noexcept -> bool { const int case_shorter_interval_left_endpoint_lower_threshold = 2; const int case_shorter_interval_left_endpoint_upper_threshold = 3; return exponent >= case_shorter_interval_left_endpoint_lower_threshold && exponent <= case_shorter_interval_left_endpoint_upper_threshold; } // Remove trailing zeros from n and return the number of zeros removed (float) FMT_INLINE int remove_trailing_zeros(uint32_t& n, int s = 0) noexcept { FMT_ASSERT(n != 0, ""); // Modular inverse of 5 (mod 2^32): (mod_inv_5 * 5) mod 2^32 = 1. constexpr uint32_t mod_inv_5 = 0xcccccccd; constexpr uint32_t mod_inv_25 = 0xc28f5c29; // = mod_inv_5 * mod_inv_5 while (true) { auto q = rotr(n * mod_inv_25, 2); if (q > max_value() / 100) break; n = q; s += 2; } auto q = rotr(n * mod_inv_5, 1); if (q <= max_value() / 10) { n = q; s |= 1; } return s; } // Removes trailing zeros and returns the number of zeros removed (double) FMT_INLINE int remove_trailing_zeros(uint64_t& n) noexcept { FMT_ASSERT(n != 0, ""); // This magic number is ceil(2^90 / 10^8). constexpr uint64_t magic_number = 12379400392853802749ull; auto nm = umul128(n, magic_number); // Is n is divisible by 10^8? if ((nm.high() & ((1ull << (90 - 64)) - 1)) == 0 && nm.low() < magic_number) { // If yes, work with the quotient... auto n32 = static_cast(nm.high() >> (90 - 64)); // ... and use the 32 bit variant of the function int s = remove_trailing_zeros(n32, 8); n = n32; return s; } // If n is not divisible by 10^8, work with n itself. constexpr uint64_t mod_inv_5 = 0xcccccccccccccccd; constexpr uint64_t mod_inv_25 = 0x8f5c28f5c28f5c29; // mod_inv_5 * mod_inv_5 int s = 0; while (true) { auto q = rotr(n * mod_inv_25, 2); if (q > max_value() / 100) break; n = q; s += 2; } auto q = rotr(n * mod_inv_5, 1); if (q <= max_value() / 10) { n = q; s |= 1; } return s; } // The main algorithm for shorter interval case template FMT_INLINE decimal_fp shorter_interval_case(int exponent) noexcept { decimal_fp ret_value; // Compute k and beta const int minus_k = floor_log10_pow2_minus_log10_4_over_3(exponent); const int beta = exponent + floor_log2_pow10(-minus_k); // Compute xi and zi using cache_entry_type = typename cache_accessor::cache_entry_type; const cache_entry_type cache = cache_accessor::get_cached_power(-minus_k); auto xi = cache_accessor::compute_left_endpoint_for_shorter_interval_case( cache, beta); auto zi = cache_accessor::compute_right_endpoint_for_shorter_interval_case( cache, beta); // If the left endpoint is not an integer, increase it if (!is_left_endpoint_integer_shorter_interval(exponent)) ++xi; // Try bigger divisor ret_value.significand = zi / 10; // If succeed, remove trailing zeros if necessary and return if (ret_value.significand * 10 >= xi) { ret_value.exponent = minus_k + 1; ret_value.exponent += remove_trailing_zeros(ret_value.significand); return ret_value; } // Otherwise, compute the round-up of y ret_value.significand = cache_accessor::compute_round_up_for_shorter_interval_case(cache, beta); ret_value.exponent = minus_k; // When tie occurs, choose one of them according to the rule if (exponent >= float_info::shorter_interval_tie_lower_threshold && exponent <= float_info::shorter_interval_tie_upper_threshold) { ret_value.significand = ret_value.significand % 2 == 0 ? ret_value.significand : ret_value.significand - 1; } else if (ret_value.significand < xi) { ++ret_value.significand; } return ret_value; } template auto to_decimal(T x) noexcept -> decimal_fp { // Step 1: integer promotion & Schubfach multiplier calculation. using carrier_uint = typename float_info::carrier_uint; using cache_entry_type = typename cache_accessor::cache_entry_type; auto br = bit_cast(x); // Extract significand bits and exponent bits. const carrier_uint significand_mask = (static_cast(1) << num_significand_bits()) - 1; carrier_uint significand = (br & significand_mask); int exponent = static_cast((br & exponent_mask()) >> num_significand_bits()); if (exponent != 0) { // Check if normal. exponent -= exponent_bias() + num_significand_bits(); // Shorter interval case; proceed like Schubfach. // In fact, when exponent == 1 and significand == 0, the interval is // regular. However, it can be shown that the end-results are anyway same. if (significand == 0) return shorter_interval_case(exponent); significand |= (static_cast(1) << num_significand_bits()); } else { // Subnormal case; the interval is always regular. if (significand == 0) return {0, 0}; exponent = std::numeric_limits::min_exponent - num_significand_bits() - 1; } const bool include_left_endpoint = (significand % 2 == 0); const bool include_right_endpoint = include_left_endpoint; // Compute k and beta. const int minus_k = floor_log10_pow2(exponent) - float_info::kappa; const cache_entry_type cache = cache_accessor::get_cached_power(-minus_k); const int beta = exponent + floor_log2_pow10(-minus_k); // Compute zi and deltai. // 10^kappa <= deltai < 10^(kappa + 1) const uint32_t deltai = cache_accessor::compute_delta(cache, beta); const carrier_uint two_fc = significand << 1; // For the case of binary32, the result of integer check is not correct for // 29711844 * 2^-82 // = 6.1442653300000000008655037797566933477355632930994033813476... * 10^-18 // and 29711844 * 2^-81 // = 1.2288530660000000001731007559513386695471126586198806762695... * 10^-17, // and they are the unique counterexamples. However, since 29711844 is even, // this does not cause any problem for the endpoints calculations; it can only // cause a problem when we need to perform integer check for the center. // Fortunately, with these inputs, that branch is never executed, so we are // fine. const typename cache_accessor::compute_mul_result z_mul = cache_accessor::compute_mul((two_fc | 1) << beta, cache); // Step 2: Try larger divisor; remove trailing zeros if necessary. // Using an upper bound on zi, we might be able to optimize the division // better than the compiler; we are computing zi / big_divisor here. decimal_fp ret_value; ret_value.significand = divide_by_10_to_kappa_plus_1(z_mul.result); uint32_t r = static_cast(z_mul.result - float_info::big_divisor * ret_value.significand); if (r < deltai) { // Exclude the right endpoint if necessary. if (r == 0 && (z_mul.is_integer & !include_right_endpoint)) { --ret_value.significand; r = float_info::big_divisor; goto small_divisor_case_label; } } else if (r > deltai) { goto small_divisor_case_label; } else { // r == deltai; compare fractional parts. const typename cache_accessor::compute_mul_parity_result x_mul = cache_accessor::compute_mul_parity(two_fc - 1, cache, beta); if (!(x_mul.parity | (x_mul.is_integer & include_left_endpoint))) goto small_divisor_case_label; } ret_value.exponent = minus_k + float_info::kappa + 1; // We may need to remove trailing zeros. ret_value.exponent += remove_trailing_zeros(ret_value.significand); return ret_value; // Step 3: Find the significand with the smaller divisor. small_divisor_case_label: ret_value.significand *= 10; ret_value.exponent = minus_k + float_info::kappa; uint32_t dist = r - (deltai / 2) + (float_info::small_divisor / 2); const bool approx_y_parity = ((dist ^ (float_info::small_divisor / 2)) & 1) != 0; // Is dist divisible by 10^kappa? const bool divisible_by_small_divisor = check_divisibility_and_divide_by_pow10::kappa>(dist); // Add dist / 10^kappa to the significand. ret_value.significand += dist; if (!divisible_by_small_divisor) return ret_value; // Check z^(f) >= epsilon^(f). // We have either yi == zi - epsiloni or yi == (zi - epsiloni) - 1, // where yi == zi - epsiloni if and only if z^(f) >= epsilon^(f). // Since there are only 2 possibilities, we only need to care about the // parity. Also, zi and r should have the same parity since the divisor // is an even number. const auto y_mul = cache_accessor::compute_mul_parity(two_fc, cache, beta); // If z^(f) >= epsilon^(f), we might have a tie when z^(f) == epsilon^(f), // or equivalently, when y is an integer. if (y_mul.parity != approx_y_parity) --ret_value.significand; else if (y_mul.is_integer & (ret_value.significand % 2 != 0)) --ret_value.significand; return ret_value; } } // namespace dragonbox } // namespace detail template <> struct formatter { FMT_CONSTEXPR auto parse(format_parse_context& ctx) -> format_parse_context::iterator { return ctx.begin(); } auto format(const detail::bigint& n, format_context& ctx) const -> format_context::iterator { auto out = ctx.out(); bool first = true; for (auto i = n.bigits_.size(); i > 0; --i) { auto value = n.bigits_[i - 1u]; if (first) { out = fmt::format_to(out, FMT_STRING("{:x}"), value); first = false; continue; } out = fmt::format_to(out, FMT_STRING("{:08x}"), value); } if (n.exp_ > 0) out = fmt::format_to(out, FMT_STRING("p{}"), n.exp_ * detail::bigint::bigit_bits); return out; } }; FMT_FUNC detail::utf8_to_utf16::utf8_to_utf16(string_view s) { for_each_codepoint(s, [this](uint32_t cp, string_view) { if (cp == invalid_code_point) FMT_THROW(std::runtime_error("invalid utf8")); if (cp <= 0xFFFF) { buffer_.push_back(static_cast(cp)); } else { cp -= 0x10000; buffer_.push_back(static_cast(0xD800 + (cp >> 10))); buffer_.push_back(static_cast(0xDC00 + (cp & 0x3FF))); } return true; }); buffer_.push_back(0); } FMT_FUNC void format_system_error(detail::buffer& out, int error_code, const char* message) noexcept { FMT_TRY { auto ec = std::error_code(error_code, std::generic_category()); detail::write(appender(out), std::system_error(ec, message).what()); return; } FMT_CATCH(...) {} format_error_code(out, error_code, message); } FMT_FUNC void report_system_error(int error_code, const char* message) noexcept { do_report_error(format_system_error, error_code, message); } FMT_FUNC auto vformat(string_view fmt, format_args args) -> std::string { // Don't optimize the "{}" case to keep the binary size small and because it // can be better optimized in fmt::format anyway. auto buffer = memory_buffer(); detail::vformat_to(buffer, fmt, args); return to_string(buffer); } namespace detail { FMT_FUNC void vformat_to(buffer& buf, string_view fmt, format_args args, locale_ref loc) { auto out = appender(buf); if (fmt.size() == 2 && equal2(fmt.data(), "{}")) return args.get(0).visit(default_arg_formatter{out}); parse_format_string( fmt, format_handler{parse_context(fmt), {out, args, loc}}); } template struct span { T* data; size_t size; }; template auto flockfile(F* f) -> decltype(_lock_file(f)) { _lock_file(f); } template auto funlockfile(F* f) -> decltype(_unlock_file(f)) { _unlock_file(f); } #ifndef getc_unlocked template auto getc_unlocked(F* f) -> decltype(_fgetc_nolock(f)) { return _fgetc_nolock(f); } #endif template struct has_flockfile : std::false_type {}; template struct has_flockfile()))>> : std::true_type {}; // A FILE wrapper. F is FILE defined as a template parameter to make system API // detection work. template class file_base { public: F* file_; public: file_base(F* file) : file_(file) {} operator F*() const { return file_; } // Reads a code unit from the stream. auto get() -> int { int result = getc_unlocked(file_); if (result == EOF && ferror(file_) != 0) FMT_THROW(system_error(errno, FMT_STRING("getc failed"))); return result; } // Puts the code unit back into the stream buffer. void unget(char c) { if (ungetc(c, file_) == EOF) FMT_THROW(system_error(errno, FMT_STRING("ungetc failed"))); } void flush() { fflush(this->file_); } }; // A FILE wrapper for glibc. template class glibc_file : public file_base { private: enum { line_buffered = 0x200, // _IO_LINE_BUF unbuffered = 2 // _IO_UNBUFFERED }; public: using file_base::file_base; auto is_buffered() const -> bool { return (this->file_->_flags & unbuffered) == 0; } void init_buffer() { if (this->file_->_IO_write_ptr) return; // Force buffer initialization by placing and removing a char in a buffer. assume(this->file_->_IO_write_ptr >= this->file_->_IO_write_end); putc_unlocked(0, this->file_); --this->file_->_IO_write_ptr; } // Returns the file's read buffer. auto get_read_buffer() const -> span { auto ptr = this->file_->_IO_read_ptr; return {ptr, to_unsigned(this->file_->_IO_read_end - ptr)}; } // Returns the file's write buffer. auto get_write_buffer() const -> span { auto ptr = this->file_->_IO_write_ptr; return {ptr, to_unsigned(this->file_->_IO_buf_end - ptr)}; } void advance_write_buffer(size_t size) { this->file_->_IO_write_ptr += size; } bool needs_flush() const { if ((this->file_->_flags & line_buffered) == 0) return false; char* end = this->file_->_IO_write_end; return memchr(end, '\n', to_unsigned(this->file_->_IO_write_ptr - end)); } void flush() { fflush_unlocked(this->file_); } }; // A FILE wrapper for Apple's libc. template class apple_file : public file_base { private: enum { line_buffered = 1, // __SNBF unbuffered = 2 // __SLBF }; public: using file_base::file_base; auto is_buffered() const -> bool { return (this->file_->_flags & unbuffered) == 0; } void init_buffer() { if (this->file_->_p) return; // Force buffer initialization by placing and removing a char in a buffer. putc_unlocked(0, this->file_); --this->file_->_p; ++this->file_->_w; } auto get_read_buffer() const -> span { return {reinterpret_cast(this->file_->_p), to_unsigned(this->file_->_r)}; } auto get_write_buffer() const -> span { return {reinterpret_cast(this->file_->_p), to_unsigned(this->file_->_bf._base + this->file_->_bf._size - this->file_->_p)}; } void advance_write_buffer(size_t size) { this->file_->_p += size; this->file_->_w -= size; } bool needs_flush() const { if ((this->file_->_flags & line_buffered) == 0) return false; return memchr(this->file_->_p + this->file_->_w, '\n', to_unsigned(-this->file_->_w)); } }; // A fallback FILE wrapper. template class fallback_file : public file_base { private: char next_; // The next unconsumed character in the buffer. bool has_next_ = false; public: using file_base::file_base; auto is_buffered() const -> bool { return false; } auto needs_flush() const -> bool { return false; } void init_buffer() {} auto get_read_buffer() const -> span { return {&next_, has_next_ ? 1u : 0u}; } auto get_write_buffer() const -> span { return {nullptr, 0}; } void advance_write_buffer(size_t) {} auto get() -> int { has_next_ = false; return file_base::get(); } void unget(char c) { file_base::unget(c); next_ = c; has_next_ = true; } }; #ifndef FMT_USE_FALLBACK_FILE # define FMT_USE_FALLBACK_FILE 0 #endif template auto get_file(F* f, int) -> apple_file { return f; } template inline auto get_file(F* f, int) -> glibc_file { return f; } inline auto get_file(FILE* f, ...) -> fallback_file { return f; } using file_ref = decltype(get_file(static_cast(nullptr), 0)); template class file_print_buffer : public buffer { public: explicit file_print_buffer(F*) : buffer(nullptr, size_t()) {} }; template class file_print_buffer::value>> : public buffer { private: file_ref file_; static void grow(buffer& base, size_t) { auto& self = static_cast(base); self.file_.advance_write_buffer(self.size()); if (self.file_.get_write_buffer().size == 0) self.file_.flush(); auto buf = self.file_.get_write_buffer(); FMT_ASSERT(buf.size > 0, ""); self.set(buf.data, buf.size); self.clear(); } public: explicit file_print_buffer(F* f) : buffer(grow, size_t()), file_(f) { flockfile(f); file_.init_buffer(); auto buf = file_.get_write_buffer(); set(buf.data, buf.size); } ~file_print_buffer() { file_.advance_write_buffer(size()); bool flush = file_.needs_flush(); F* f = file_; // Make funlockfile depend on the template parameter F funlockfile(f); // for the system API detection to work. if (flush) fflush(file_); } }; #if !defined(_WIN32) || defined(FMT_USE_WRITE_CONSOLE) FMT_FUNC auto write_console(int, string_view) -> bool { return false; } #else using dword = conditional_t; extern "C" __declspec(dllimport) int __stdcall WriteConsoleW( // void*, const void*, dword, dword*, void*); FMT_FUNC bool write_console(int fd, string_view text) { auto u16 = utf8_to_utf16(text); return WriteConsoleW(reinterpret_cast(_get_osfhandle(fd)), u16.c_str(), static_cast(u16.size()), nullptr, nullptr) != 0; } #endif #ifdef _WIN32 // Print assuming legacy (non-Unicode) encoding. FMT_FUNC void vprint_mojibake(std::FILE* f, string_view fmt, format_args args, bool newline) { auto buffer = memory_buffer(); detail::vformat_to(buffer, fmt, args); if (newline) buffer.push_back('\n'); fwrite_all(buffer.data(), buffer.size(), f); } #endif FMT_FUNC void print(std::FILE* f, string_view text) { #if defined(_WIN32) && !defined(FMT_USE_WRITE_CONSOLE) int fd = _fileno(f); if (_isatty(fd)) { std::fflush(f); if (write_console(fd, text)) return; } #endif fwrite_all(text.data(), text.size(), f); } } // namespace detail FMT_FUNC void vprint_buffered(std::FILE* f, string_view fmt, format_args args) { auto buffer = memory_buffer(); detail::vformat_to(buffer, fmt, args); detail::print(f, {buffer.data(), buffer.size()}); } FMT_FUNC void vprint(std::FILE* f, string_view fmt, format_args args) { if (!detail::file_ref(f).is_buffered() || !detail::has_flockfile<>()) return vprint_buffered(f, fmt, args); auto&& buffer = detail::file_print_buffer<>(f); return detail::vformat_to(buffer, fmt, args); } FMT_FUNC void vprintln(std::FILE* f, string_view fmt, format_args args) { auto buffer = memory_buffer(); detail::vformat_to(buffer, fmt, args); buffer.push_back('\n'); detail::print(f, {buffer.data(), buffer.size()}); } FMT_FUNC void vprint(string_view fmt, format_args args) { vprint(stdout, fmt, args); } namespace detail { struct singleton { unsigned char upper; unsigned char lower_count; }; inline auto is_printable(uint16_t x, const singleton* singletons, size_t singletons_size, const unsigned char* singleton_lowers, const unsigned char* normal, size_t normal_size) -> bool { auto upper = x >> 8; auto lower_start = 0; for (size_t i = 0; i < singletons_size; ++i) { auto s = singletons[i]; auto lower_end = lower_start + s.lower_count; if (upper < s.upper) break; if (upper == s.upper) { for (auto j = lower_start; j < lower_end; ++j) { if (singleton_lowers[j] == (x & 0xff)) return false; } } lower_start = lower_end; } auto xsigned = static_cast(x); auto current = true; for (size_t i = 0; i < normal_size; ++i) { auto v = static_cast(normal[i]); auto len = (v & 0x80) != 0 ? (v & 0x7f) << 8 | normal[++i] : v; xsigned -= len; if (xsigned < 0) break; current = !current; } return current; } // This code is generated by support/printable.py. FMT_FUNC auto is_printable(uint32_t cp) -> bool { static constexpr singleton singletons0[] = { {0x00, 1}, {0x03, 5}, {0x05, 6}, {0x06, 3}, {0x07, 6}, {0x08, 8}, {0x09, 17}, {0x0a, 28}, {0x0b, 25}, {0x0c, 20}, {0x0d, 16}, {0x0e, 13}, {0x0f, 4}, {0x10, 3}, {0x12, 18}, {0x13, 9}, {0x16, 1}, {0x17, 5}, {0x18, 2}, {0x19, 3}, {0x1a, 7}, {0x1c, 2}, {0x1d, 1}, {0x1f, 22}, {0x20, 3}, {0x2b, 3}, {0x2c, 2}, {0x2d, 11}, {0x2e, 1}, {0x30, 3}, {0x31, 2}, {0x32, 1}, {0xa7, 2}, {0xa9, 2}, {0xaa, 4}, {0xab, 8}, {0xfa, 2}, {0xfb, 5}, {0xfd, 4}, {0xfe, 3}, {0xff, 9}, }; static constexpr unsigned char singletons0_lower[] = { 0xad, 0x78, 0x79, 0x8b, 0x8d, 0xa2, 0x30, 0x57, 0x58, 0x8b, 0x8c, 0x90, 0x1c, 0x1d, 0xdd, 0x0e, 0x0f, 0x4b, 0x4c, 0xfb, 0xfc, 0x2e, 0x2f, 0x3f, 0x5c, 0x5d, 0x5f, 0xb5, 0xe2, 0x84, 0x8d, 0x8e, 0x91, 0x92, 0xa9, 0xb1, 0xba, 0xbb, 0xc5, 0xc6, 0xc9, 0xca, 0xde, 0xe4, 0xe5, 0xff, 0x00, 0x04, 0x11, 0x12, 0x29, 0x31, 0x34, 0x37, 0x3a, 0x3b, 0x3d, 0x49, 0x4a, 0x5d, 0x84, 0x8e, 0x92, 0xa9, 0xb1, 0xb4, 0xba, 0xbb, 0xc6, 0xca, 0xce, 0xcf, 0xe4, 0xe5, 0x00, 0x04, 0x0d, 0x0e, 0x11, 0x12, 0x29, 0x31, 0x34, 0x3a, 0x3b, 0x45, 0x46, 0x49, 0x4a, 0x5e, 0x64, 0x65, 0x84, 0x91, 0x9b, 0x9d, 0xc9, 0xce, 0xcf, 0x0d, 0x11, 0x29, 0x45, 0x49, 0x57, 0x64, 0x65, 0x8d, 0x91, 0xa9, 0xb4, 0xba, 0xbb, 0xc5, 0xc9, 0xdf, 0xe4, 0xe5, 0xf0, 0x0d, 0x11, 0x45, 0x49, 0x64, 0x65, 0x80, 0x84, 0xb2, 0xbc, 0xbe, 0xbf, 0xd5, 0xd7, 0xf0, 0xf1, 0x83, 0x85, 0x8b, 0xa4, 0xa6, 0xbe, 0xbf, 0xc5, 0xc7, 0xce, 0xcf, 0xda, 0xdb, 0x48, 0x98, 0xbd, 0xcd, 0xc6, 0xce, 0xcf, 0x49, 0x4e, 0x4f, 0x57, 0x59, 0x5e, 0x5f, 0x89, 0x8e, 0x8f, 0xb1, 0xb6, 0xb7, 0xbf, 0xc1, 0xc6, 0xc7, 0xd7, 0x11, 0x16, 0x17, 0x5b, 0x5c, 0xf6, 0xf7, 0xfe, 0xff, 0x80, 0x0d, 0x6d, 0x71, 0xde, 0xdf, 0x0e, 0x0f, 0x1f, 0x6e, 0x6f, 0x1c, 0x1d, 0x5f, 0x7d, 0x7e, 0xae, 0xaf, 0xbb, 0xbc, 0xfa, 0x16, 0x17, 0x1e, 0x1f, 0x46, 0x47, 0x4e, 0x4f, 0x58, 0x5a, 0x5c, 0x5e, 0x7e, 0x7f, 0xb5, 0xc5, 0xd4, 0xd5, 0xdc, 0xf0, 0xf1, 0xf5, 0x72, 0x73, 0x8f, 0x74, 0x75, 0x96, 0x2f, 0x5f, 0x26, 0x2e, 0x2f, 0xa7, 0xaf, 0xb7, 0xbf, 0xc7, 0xcf, 0xd7, 0xdf, 0x9a, 0x40, 0x97, 0x98, 0x30, 0x8f, 0x1f, 0xc0, 0xc1, 0xce, 0xff, 0x4e, 0x4f, 0x5a, 0x5b, 0x07, 0x08, 0x0f, 0x10, 0x27, 0x2f, 0xee, 0xef, 0x6e, 0x6f, 0x37, 0x3d, 0x3f, 0x42, 0x45, 0x90, 0x91, 0xfe, 0xff, 0x53, 0x67, 0x75, 0xc8, 0xc9, 0xd0, 0xd1, 0xd8, 0xd9, 0xe7, 0xfe, 0xff, }; static constexpr singleton singletons1[] = { {0x00, 6}, {0x01, 1}, {0x03, 1}, {0x04, 2}, {0x08, 8}, {0x09, 2}, {0x0a, 5}, {0x0b, 2}, {0x0e, 4}, {0x10, 1}, {0x11, 2}, {0x12, 5}, {0x13, 17}, {0x14, 1}, {0x15, 2}, {0x17, 2}, {0x19, 13}, {0x1c, 5}, {0x1d, 8}, {0x24, 1}, {0x6a, 3}, {0x6b, 2}, {0xbc, 2}, {0xd1, 2}, {0xd4, 12}, {0xd5, 9}, {0xd6, 2}, {0xd7, 2}, {0xda, 1}, {0xe0, 5}, {0xe1, 2}, {0xe8, 2}, {0xee, 32}, {0xf0, 4}, {0xf8, 2}, {0xf9, 2}, {0xfa, 2}, {0xfb, 1}, }; static constexpr unsigned char singletons1_lower[] = { 0x0c, 0x27, 0x3b, 0x3e, 0x4e, 0x4f, 0x8f, 0x9e, 0x9e, 0x9f, 0x06, 0x07, 0x09, 0x36, 0x3d, 0x3e, 0x56, 0xf3, 0xd0, 0xd1, 0x04, 0x14, 0x18, 0x36, 0x37, 0x56, 0x57, 0x7f, 0xaa, 0xae, 0xaf, 0xbd, 0x35, 0xe0, 0x12, 0x87, 0x89, 0x8e, 0x9e, 0x04, 0x0d, 0x0e, 0x11, 0x12, 0x29, 0x31, 0x34, 0x3a, 0x45, 0x46, 0x49, 0x4a, 0x4e, 0x4f, 0x64, 0x65, 0x5c, 0xb6, 0xb7, 0x1b, 0x1c, 0x07, 0x08, 0x0a, 0x0b, 0x14, 0x17, 0x36, 0x39, 0x3a, 0xa8, 0xa9, 0xd8, 0xd9, 0x09, 0x37, 0x90, 0x91, 0xa8, 0x07, 0x0a, 0x3b, 0x3e, 0x66, 0x69, 0x8f, 0x92, 0x6f, 0x5f, 0xee, 0xef, 0x5a, 0x62, 0x9a, 0x9b, 0x27, 0x28, 0x55, 0x9d, 0xa0, 0xa1, 0xa3, 0xa4, 0xa7, 0xa8, 0xad, 0xba, 0xbc, 0xc4, 0x06, 0x0b, 0x0c, 0x15, 0x1d, 0x3a, 0x3f, 0x45, 0x51, 0xa6, 0xa7, 0xcc, 0xcd, 0xa0, 0x07, 0x19, 0x1a, 0x22, 0x25, 0x3e, 0x3f, 0xc5, 0xc6, 0x04, 0x20, 0x23, 0x25, 0x26, 0x28, 0x33, 0x38, 0x3a, 0x48, 0x4a, 0x4c, 0x50, 0x53, 0x55, 0x56, 0x58, 0x5a, 0x5c, 0x5e, 0x60, 0x63, 0x65, 0x66, 0x6b, 0x73, 0x78, 0x7d, 0x7f, 0x8a, 0xa4, 0xaa, 0xaf, 0xb0, 0xc0, 0xd0, 0xae, 0xaf, 0x79, 0xcc, 0x6e, 0x6f, 0x93, }; static constexpr unsigned char normal0[] = { 0x00, 0x20, 0x5f, 0x22, 0x82, 0xdf, 0x04, 0x82, 0x44, 0x08, 0x1b, 0x04, 0x06, 0x11, 0x81, 0xac, 0x0e, 0x80, 0xab, 0x35, 0x28, 0x0b, 0x80, 0xe0, 0x03, 0x19, 0x08, 0x01, 0x04, 0x2f, 0x04, 0x34, 0x04, 0x07, 0x03, 0x01, 0x07, 0x06, 0x07, 0x11, 0x0a, 0x50, 0x0f, 0x12, 0x07, 0x55, 0x07, 0x03, 0x04, 0x1c, 0x0a, 0x09, 0x03, 0x08, 0x03, 0x07, 0x03, 0x02, 0x03, 0x03, 0x03, 0x0c, 0x04, 0x05, 0x03, 0x0b, 0x06, 0x01, 0x0e, 0x15, 0x05, 0x3a, 0x03, 0x11, 0x07, 0x06, 0x05, 0x10, 0x07, 0x57, 0x07, 0x02, 0x07, 0x15, 0x0d, 0x50, 0x04, 0x43, 0x03, 0x2d, 0x03, 0x01, 0x04, 0x11, 0x06, 0x0f, 0x0c, 0x3a, 0x04, 0x1d, 0x25, 0x5f, 0x20, 0x6d, 0x04, 0x6a, 0x25, 0x80, 0xc8, 0x05, 0x82, 0xb0, 0x03, 0x1a, 0x06, 0x82, 0xfd, 0x03, 0x59, 0x07, 0x15, 0x0b, 0x17, 0x09, 0x14, 0x0c, 0x14, 0x0c, 0x6a, 0x06, 0x0a, 0x06, 0x1a, 0x06, 0x59, 0x07, 0x2b, 0x05, 0x46, 0x0a, 0x2c, 0x04, 0x0c, 0x04, 0x01, 0x03, 0x31, 0x0b, 0x2c, 0x04, 0x1a, 0x06, 0x0b, 0x03, 0x80, 0xac, 0x06, 0x0a, 0x06, 0x21, 0x3f, 0x4c, 0x04, 0x2d, 0x03, 0x74, 0x08, 0x3c, 0x03, 0x0f, 0x03, 0x3c, 0x07, 0x38, 0x08, 0x2b, 0x05, 0x82, 0xff, 0x11, 0x18, 0x08, 0x2f, 0x11, 0x2d, 0x03, 0x20, 0x10, 0x21, 0x0f, 0x80, 0x8c, 0x04, 0x82, 0x97, 0x19, 0x0b, 0x15, 0x88, 0x94, 0x05, 0x2f, 0x05, 0x3b, 0x07, 0x02, 0x0e, 0x18, 0x09, 0x80, 0xb3, 0x2d, 0x74, 0x0c, 0x80, 0xd6, 0x1a, 0x0c, 0x05, 0x80, 0xff, 0x05, 0x80, 0xdf, 0x0c, 0xee, 0x0d, 0x03, 0x84, 0x8d, 0x03, 0x37, 0x09, 0x81, 0x5c, 0x14, 0x80, 0xb8, 0x08, 0x80, 0xcb, 0x2a, 0x38, 0x03, 0x0a, 0x06, 0x38, 0x08, 0x46, 0x08, 0x0c, 0x06, 0x74, 0x0b, 0x1e, 0x03, 0x5a, 0x04, 0x59, 0x09, 0x80, 0x83, 0x18, 0x1c, 0x0a, 0x16, 0x09, 0x4c, 0x04, 0x80, 0x8a, 0x06, 0xab, 0xa4, 0x0c, 0x17, 0x04, 0x31, 0xa1, 0x04, 0x81, 0xda, 0x26, 0x07, 0x0c, 0x05, 0x05, 0x80, 0xa5, 0x11, 0x81, 0x6d, 0x10, 0x78, 0x28, 0x2a, 0x06, 0x4c, 0x04, 0x80, 0x8d, 0x04, 0x80, 0xbe, 0x03, 0x1b, 0x03, 0x0f, 0x0d, }; static constexpr unsigned char normal1[] = { 0x5e, 0x22, 0x7b, 0x05, 0x03, 0x04, 0x2d, 0x03, 0x66, 0x03, 0x01, 0x2f, 0x2e, 0x80, 0x82, 0x1d, 0x03, 0x31, 0x0f, 0x1c, 0x04, 0x24, 0x09, 0x1e, 0x05, 0x2b, 0x05, 0x44, 0x04, 0x0e, 0x2a, 0x80, 0xaa, 0x06, 0x24, 0x04, 0x24, 0x04, 0x28, 0x08, 0x34, 0x0b, 0x01, 0x80, 0x90, 0x81, 0x37, 0x09, 0x16, 0x0a, 0x08, 0x80, 0x98, 0x39, 0x03, 0x63, 0x08, 0x09, 0x30, 0x16, 0x05, 0x21, 0x03, 0x1b, 0x05, 0x01, 0x40, 0x38, 0x04, 0x4b, 0x05, 0x2f, 0x04, 0x0a, 0x07, 0x09, 0x07, 0x40, 0x20, 0x27, 0x04, 0x0c, 0x09, 0x36, 0x03, 0x3a, 0x05, 0x1a, 0x07, 0x04, 0x0c, 0x07, 0x50, 0x49, 0x37, 0x33, 0x0d, 0x33, 0x07, 0x2e, 0x08, 0x0a, 0x81, 0x26, 0x52, 0x4e, 0x28, 0x08, 0x2a, 0x56, 0x1c, 0x14, 0x17, 0x09, 0x4e, 0x04, 0x1e, 0x0f, 0x43, 0x0e, 0x19, 0x07, 0x0a, 0x06, 0x48, 0x08, 0x27, 0x09, 0x75, 0x0b, 0x3f, 0x41, 0x2a, 0x06, 0x3b, 0x05, 0x0a, 0x06, 0x51, 0x06, 0x01, 0x05, 0x10, 0x03, 0x05, 0x80, 0x8b, 0x62, 0x1e, 0x48, 0x08, 0x0a, 0x80, 0xa6, 0x5e, 0x22, 0x45, 0x0b, 0x0a, 0x06, 0x0d, 0x13, 0x39, 0x07, 0x0a, 0x36, 0x2c, 0x04, 0x10, 0x80, 0xc0, 0x3c, 0x64, 0x53, 0x0c, 0x48, 0x09, 0x0a, 0x46, 0x45, 0x1b, 0x48, 0x08, 0x53, 0x1d, 0x39, 0x81, 0x07, 0x46, 0x0a, 0x1d, 0x03, 0x47, 0x49, 0x37, 0x03, 0x0e, 0x08, 0x0a, 0x06, 0x39, 0x07, 0x0a, 0x81, 0x36, 0x19, 0x80, 0xb7, 0x01, 0x0f, 0x32, 0x0d, 0x83, 0x9b, 0x66, 0x75, 0x0b, 0x80, 0xc4, 0x8a, 0xbc, 0x84, 0x2f, 0x8f, 0xd1, 0x82, 0x47, 0xa1, 0xb9, 0x82, 0x39, 0x07, 0x2a, 0x04, 0x02, 0x60, 0x26, 0x0a, 0x46, 0x0a, 0x28, 0x05, 0x13, 0x82, 0xb0, 0x5b, 0x65, 0x4b, 0x04, 0x39, 0x07, 0x11, 0x40, 0x05, 0x0b, 0x02, 0x0e, 0x97, 0xf8, 0x08, 0x84, 0xd6, 0x2a, 0x09, 0xa2, 0xf7, 0x81, 0x1f, 0x31, 0x03, 0x11, 0x04, 0x08, 0x81, 0x8c, 0x89, 0x04, 0x6b, 0x05, 0x0d, 0x03, 0x09, 0x07, 0x10, 0x93, 0x60, 0x80, 0xf6, 0x0a, 0x73, 0x08, 0x6e, 0x17, 0x46, 0x80, 0x9a, 0x14, 0x0c, 0x57, 0x09, 0x19, 0x80, 0x87, 0x81, 0x47, 0x03, 0x85, 0x42, 0x0f, 0x15, 0x85, 0x50, 0x2b, 0x80, 0xd5, 0x2d, 0x03, 0x1a, 0x04, 0x02, 0x81, 0x70, 0x3a, 0x05, 0x01, 0x85, 0x00, 0x80, 0xd7, 0x29, 0x4c, 0x04, 0x0a, 0x04, 0x02, 0x83, 0x11, 0x44, 0x4c, 0x3d, 0x80, 0xc2, 0x3c, 0x06, 0x01, 0x04, 0x55, 0x05, 0x1b, 0x34, 0x02, 0x81, 0x0e, 0x2c, 0x04, 0x64, 0x0c, 0x56, 0x0a, 0x80, 0xae, 0x38, 0x1d, 0x0d, 0x2c, 0x04, 0x09, 0x07, 0x02, 0x0e, 0x06, 0x80, 0x9a, 0x83, 0xd8, 0x08, 0x0d, 0x03, 0x0d, 0x03, 0x74, 0x0c, 0x59, 0x07, 0x0c, 0x14, 0x0c, 0x04, 0x38, 0x08, 0x0a, 0x06, 0x28, 0x08, 0x22, 0x4e, 0x81, 0x54, 0x0c, 0x15, 0x03, 0x03, 0x05, 0x07, 0x09, 0x19, 0x07, 0x07, 0x09, 0x03, 0x0d, 0x07, 0x29, 0x80, 0xcb, 0x25, 0x0a, 0x84, 0x06, }; auto lower = static_cast(cp); if (cp < 0x10000) { return is_printable(lower, singletons0, sizeof(singletons0) / sizeof(*singletons0), singletons0_lower, normal0, sizeof(normal0)); } if (cp < 0x20000) { return is_printable(lower, singletons1, sizeof(singletons1) / sizeof(*singletons1), singletons1_lower, normal1, sizeof(normal1)); } if (0x2a6de <= cp && cp < 0x2a700) return false; if (0x2b735 <= cp && cp < 0x2b740) return false; if (0x2b81e <= cp && cp < 0x2b820) return false; if (0x2cea2 <= cp && cp < 0x2ceb0) return false; if (0x2ebe1 <= cp && cp < 0x2f800) return false; if (0x2fa1e <= cp && cp < 0x30000) return false; if (0x3134b <= cp && cp < 0xe0100) return false; if (0xe01f0 <= cp && cp < 0x110000) return false; return cp < 0x110000; } } // namespace detail FMT_END_NAMESPACE #endif // FMT_FORMAT_INL_H_ openal-soft-1.24.2/fmt-11.1.1/include/fmt/format.h000066400000000000000000004616371474041540300212270ustar00rootroot00000000000000/* Formatting library for C++ Copyright (c) 2012 - present, Victor Zverovich Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --- Optional exception to the license --- As an exception, if, as a result of your compiling your source code, portions of this Software are embedded into a machine-executable object form of such source code, you may redistribute such embedded portions in such object form without including the above copyright and permission notices. */ #ifndef FMT_FORMAT_H_ #define FMT_FORMAT_H_ #ifndef _LIBCPP_REMOVE_TRANSITIVE_INCLUDES # define _LIBCPP_REMOVE_TRANSITIVE_INCLUDES # define FMT_REMOVE_TRANSITIVE_INCLUDES #endif #include "base.h" #ifndef FMT_MODULE # include // std::signbit # include // std::byte # include // uint32_t # include // std::memcpy # include // std::numeric_limits # include // std::bad_alloc # if defined(__GLIBCXX__) && !defined(_GLIBCXX_USE_DUAL_ABI) // Workaround for pre gcc 5 libstdc++. # include // std::allocator_traits # endif # include // std::runtime_error # include // std::string # include // std::system_error // Check FMT_CPLUSPLUS to avoid a warning in MSVC. # if FMT_HAS_INCLUDE() && FMT_CPLUSPLUS > 201703L # include // std::bit_cast # endif // libc++ supports string_view in pre-c++17. # if FMT_HAS_INCLUDE() && \ (FMT_CPLUSPLUS >= 201703L || defined(_LIBCPP_VERSION)) # include # define FMT_USE_STRING_VIEW # endif # if FMT_MSC_VERSION # include // _BitScanReverse[64], _umul128 # endif #endif // FMT_MODULE #if defined(FMT_USE_NONTYPE_TEMPLATE_ARGS) // Use the provided definition. #elif defined(__NVCOMPILER) # define FMT_USE_NONTYPE_TEMPLATE_ARGS 0 #elif FMT_GCC_VERSION >= 903 && FMT_CPLUSPLUS >= 201709L # define FMT_USE_NONTYPE_TEMPLATE_ARGS 1 #elif defined(__cpp_nontype_template_args) && \ __cpp_nontype_template_args >= 201911L # define FMT_USE_NONTYPE_TEMPLATE_ARGS 1 #elif FMT_CLANG_VERSION >= 1200 && FMT_CPLUSPLUS >= 202002L # define FMT_USE_NONTYPE_TEMPLATE_ARGS 1 #else # define FMT_USE_NONTYPE_TEMPLATE_ARGS 0 #endif #if defined __cpp_inline_variables && __cpp_inline_variables >= 201606L # define FMT_INLINE_VARIABLE inline #else # define FMT_INLINE_VARIABLE #endif // Check if RTTI is disabled. #ifdef FMT_USE_RTTI // Use the provided definition. #elif defined(__GXX_RTTI) || FMT_HAS_FEATURE(cxx_rtti) || defined(_CPPRTTI) || \ defined(__INTEL_RTTI__) || defined(__RTTI) // __RTTI is for EDG compilers. _CPPRTTI is for MSVC. # define FMT_USE_RTTI 1 #else # define FMT_USE_RTTI 0 #endif // Visibility when compiled as a shared library/object. #if defined(FMT_LIB_EXPORT) || defined(FMT_SHARED) # define FMT_SO_VISIBILITY(value) FMT_VISIBILITY(value) #else # define FMT_SO_VISIBILITY(value) #endif #if FMT_GCC_VERSION || FMT_CLANG_VERSION # define FMT_NOINLINE __attribute__((noinline)) #else # define FMT_NOINLINE #endif namespace std { template struct iterator_traits> { using iterator_category = output_iterator_tag; using value_type = T; using difference_type = decltype(static_cast(nullptr) - static_cast(nullptr)); using pointer = void; using reference = void; }; } // namespace std #ifndef FMT_THROW # if FMT_USE_EXCEPTIONS # if FMT_MSC_VERSION || defined(__NVCC__) FMT_BEGIN_NAMESPACE namespace detail { template inline void do_throw(const Exception& x) { // Silence unreachable code warnings in MSVC and NVCC because these // are nearly impossible to fix in a generic code. volatile bool b = true; if (b) throw x; } } // namespace detail FMT_END_NAMESPACE # define FMT_THROW(x) detail::do_throw(x) # else # define FMT_THROW(x) throw x # endif # else # define FMT_THROW(x) \ ::fmt::detail::assert_fail(__FILE__, __LINE__, (x).what()) # endif // FMT_USE_EXCEPTIONS #endif // FMT_THROW // Defining FMT_REDUCE_INT_INSTANTIATIONS to 1, will reduce the number of // integer formatter template instantiations to just one by only using the // largest integer type. This results in a reduction in binary size but will // cause a decrease in integer formatting performance. #if !defined(FMT_REDUCE_INT_INSTANTIATIONS) # define FMT_REDUCE_INT_INSTANTIATIONS 0 #endif FMT_BEGIN_NAMESPACE template struct is_contiguous> : std::true_type {}; namespace detail { // __builtin_clz is broken in clang with Microsoft codegen: // https://github.com/fmtlib/fmt/issues/519. #if !FMT_MSC_VERSION # if FMT_HAS_BUILTIN(__builtin_clz) || FMT_GCC_VERSION || FMT_ICC_VERSION # define FMT_BUILTIN_CLZ(n) __builtin_clz(n) # endif # if FMT_HAS_BUILTIN(__builtin_clzll) || FMT_GCC_VERSION || FMT_ICC_VERSION # define FMT_BUILTIN_CLZLL(n) __builtin_clzll(n) # endif #endif // Some compilers masquerade as both MSVC and GCC but otherwise support // __builtin_clz and __builtin_clzll, so only define FMT_BUILTIN_CLZ using the // MSVC intrinsics if the clz and clzll builtins are not available. #if FMT_MSC_VERSION && !defined(FMT_BUILTIN_CLZLL) // Avoid Clang with Microsoft CodeGen's -Wunknown-pragmas warning. # ifndef __clang__ # pragma intrinsic(_BitScanReverse) # ifdef _WIN64 # pragma intrinsic(_BitScanReverse64) # endif # endif inline auto clz(uint32_t x) -> int { FMT_ASSERT(x != 0, ""); FMT_MSC_WARNING(suppress : 6102) // Suppress a bogus static analysis warning. unsigned long r = 0; _BitScanReverse(&r, x); return 31 ^ static_cast(r); } # define FMT_BUILTIN_CLZ(n) detail::clz(n) inline auto clzll(uint64_t x) -> int { FMT_ASSERT(x != 0, ""); FMT_MSC_WARNING(suppress : 6102) // Suppress a bogus static analysis warning. unsigned long r = 0; # ifdef _WIN64 _BitScanReverse64(&r, x); # else // Scan the high 32 bits. if (_BitScanReverse(&r, static_cast(x >> 32))) return 63 ^ static_cast(r + 32); // Scan the low 32 bits. _BitScanReverse(&r, static_cast(x)); # endif return 63 ^ static_cast(r); } # define FMT_BUILTIN_CLZLL(n) detail::clzll(n) #endif // FMT_MSC_VERSION && !defined(FMT_BUILTIN_CLZLL) FMT_CONSTEXPR inline void abort_fuzzing_if(bool condition) { ignore_unused(condition); #ifdef FMT_FUZZ if (condition) throw std::runtime_error("fuzzing limit reached"); #endif } #if defined(FMT_USE_STRING_VIEW) template using std_string_view = std::basic_string_view; #else template struct std_string_view {}; #endif template struct string_literal { static constexpr Char value[sizeof...(C)] = {C...}; constexpr operator basic_string_view() const { return {value, sizeof...(C)}; } }; #if FMT_CPLUSPLUS < 201703L template constexpr Char string_literal::value[sizeof...(C)]; #endif // Implementation of std::bit_cast for pre-C++20. template FMT_CONSTEXPR20 auto bit_cast(const From& from) -> To { #ifdef __cpp_lib_bit_cast if (is_constant_evaluated()) return std::bit_cast(from); #endif auto to = To(); // The cast suppresses a bogus -Wclass-memaccess on GCC. std::memcpy(static_cast(&to), &from, sizeof(to)); return to; } inline auto is_big_endian() -> bool { #ifdef _WIN32 return false; #elif defined(__BIG_ENDIAN__) return true; #elif defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) return __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__; #else struct bytes { char data[sizeof(int)]; }; return bit_cast(1).data[0] == 0; #endif } class uint128_fallback { private: uint64_t lo_, hi_; public: constexpr uint128_fallback(uint64_t hi, uint64_t lo) : lo_(lo), hi_(hi) {} constexpr uint128_fallback(uint64_t value = 0) : lo_(value), hi_(0) {} constexpr auto high() const noexcept -> uint64_t { return hi_; } constexpr auto low() const noexcept -> uint64_t { return lo_; } template ::value)> constexpr explicit operator T() const { return static_cast(lo_); } friend constexpr auto operator==(const uint128_fallback& lhs, const uint128_fallback& rhs) -> bool { return lhs.hi_ == rhs.hi_ && lhs.lo_ == rhs.lo_; } friend constexpr auto operator!=(const uint128_fallback& lhs, const uint128_fallback& rhs) -> bool { return !(lhs == rhs); } friend constexpr auto operator>(const uint128_fallback& lhs, const uint128_fallback& rhs) -> bool { return lhs.hi_ != rhs.hi_ ? lhs.hi_ > rhs.hi_ : lhs.lo_ > rhs.lo_; } friend constexpr auto operator|(const uint128_fallback& lhs, const uint128_fallback& rhs) -> uint128_fallback { return {lhs.hi_ | rhs.hi_, lhs.lo_ | rhs.lo_}; } friend constexpr auto operator&(const uint128_fallback& lhs, const uint128_fallback& rhs) -> uint128_fallback { return {lhs.hi_ & rhs.hi_, lhs.lo_ & rhs.lo_}; } friend constexpr auto operator~(const uint128_fallback& n) -> uint128_fallback { return {~n.hi_, ~n.lo_}; } friend FMT_CONSTEXPR auto operator+(const uint128_fallback& lhs, const uint128_fallback& rhs) -> uint128_fallback { auto result = uint128_fallback(lhs); result += rhs; return result; } friend FMT_CONSTEXPR auto operator*(const uint128_fallback& lhs, uint32_t rhs) -> uint128_fallback { FMT_ASSERT(lhs.hi_ == 0, ""); uint64_t hi = (lhs.lo_ >> 32) * rhs; uint64_t lo = (lhs.lo_ & ~uint32_t()) * rhs; uint64_t new_lo = (hi << 32) + lo; return {(hi >> 32) + (new_lo < lo ? 1 : 0), new_lo}; } friend constexpr auto operator-(const uint128_fallback& lhs, uint64_t rhs) -> uint128_fallback { return {lhs.hi_ - (lhs.lo_ < rhs ? 1 : 0), lhs.lo_ - rhs}; } FMT_CONSTEXPR auto operator>>(int shift) const -> uint128_fallback { if (shift == 64) return {0, hi_}; if (shift > 64) return uint128_fallback(0, hi_) >> (shift - 64); return {hi_ >> shift, (hi_ << (64 - shift)) | (lo_ >> shift)}; } FMT_CONSTEXPR auto operator<<(int shift) const -> uint128_fallback { if (shift == 64) return {lo_, 0}; if (shift > 64) return uint128_fallback(lo_, 0) << (shift - 64); return {hi_ << shift | (lo_ >> (64 - shift)), (lo_ << shift)}; } FMT_CONSTEXPR auto operator>>=(int shift) -> uint128_fallback& { return *this = *this >> shift; } FMT_CONSTEXPR void operator+=(uint128_fallback n) { uint64_t new_lo = lo_ + n.lo_; uint64_t new_hi = hi_ + n.hi_ + (new_lo < lo_ ? 1 : 0); FMT_ASSERT(new_hi >= hi_, ""); lo_ = new_lo; hi_ = new_hi; } FMT_CONSTEXPR void operator&=(uint128_fallback n) { lo_ &= n.lo_; hi_ &= n.hi_; } FMT_CONSTEXPR20 auto operator+=(uint64_t n) noexcept -> uint128_fallback& { if (is_constant_evaluated()) { lo_ += n; hi_ += (lo_ < n ? 1 : 0); return *this; } #if FMT_HAS_BUILTIN(__builtin_addcll) && !defined(__ibmxl__) unsigned long long carry; lo_ = __builtin_addcll(lo_, n, 0, &carry); hi_ += carry; #elif FMT_HAS_BUILTIN(__builtin_ia32_addcarryx_u64) && !defined(__ibmxl__) unsigned long long result; auto carry = __builtin_ia32_addcarryx_u64(0, lo_, n, &result); lo_ = result; hi_ += carry; #elif defined(_MSC_VER) && defined(_M_X64) auto carry = _addcarry_u64(0, lo_, n, &lo_); _addcarry_u64(carry, hi_, 0, &hi_); #else lo_ += n; hi_ += (lo_ < n ? 1 : 0); #endif return *this; } }; using uint128_t = conditional_t; #ifdef UINTPTR_MAX using uintptr_t = ::uintptr_t; #else using uintptr_t = uint128_t; #endif // Returns the largest possible value for type T. Same as // std::numeric_limits::max() but shorter and not affected by the max macro. template constexpr auto max_value() -> T { return (std::numeric_limits::max)(); } template constexpr auto num_bits() -> int { return std::numeric_limits::digits; } // std::numeric_limits::digits may return 0 for 128-bit ints. template <> constexpr auto num_bits() -> int { return 128; } template <> constexpr auto num_bits() -> int { return 128; } template <> constexpr auto num_bits() -> int { return 128; } // A heterogeneous bit_cast used for converting 96-bit long double to uint128_t // and 128-bit pointers to uint128_fallback. template sizeof(From))> inline auto bit_cast(const From& from) -> To { constexpr auto size = static_cast(sizeof(From) / sizeof(unsigned short)); struct data_t { unsigned short value[static_cast(size)]; } data = bit_cast(from); auto result = To(); if (const_check(is_big_endian())) { for (int i = 0; i < size; ++i) result = (result << num_bits()) | data.value[i]; } else { for (int i = size - 1; i >= 0; --i) result = (result << num_bits()) | data.value[i]; } return result; } template FMT_CONSTEXPR20 inline auto countl_zero_fallback(UInt n) -> int { int lz = 0; constexpr UInt msb_mask = static_cast(1) << (num_bits() - 1); for (; (n & msb_mask) == 0; n <<= 1) lz++; return lz; } FMT_CONSTEXPR20 inline auto countl_zero(uint32_t n) -> int { #ifdef FMT_BUILTIN_CLZ if (!is_constant_evaluated()) return FMT_BUILTIN_CLZ(n); #endif return countl_zero_fallback(n); } FMT_CONSTEXPR20 inline auto countl_zero(uint64_t n) -> int { #ifdef FMT_BUILTIN_CLZLL if (!is_constant_evaluated()) return FMT_BUILTIN_CLZLL(n); #endif return countl_zero_fallback(n); } FMT_INLINE void assume(bool condition) { (void)condition; #if FMT_HAS_BUILTIN(__builtin_assume) && !FMT_ICC_VERSION __builtin_assume(condition); #elif FMT_GCC_VERSION if (!condition) __builtin_unreachable(); #endif } // Attempts to reserve space for n extra characters in the output range. // Returns a pointer to the reserved range or a reference to it. template ::value&& is_contiguous::value)> #if FMT_CLANG_VERSION >= 307 && !FMT_ICC_VERSION __attribute__((no_sanitize("undefined"))) #endif FMT_CONSTEXPR20 inline auto reserve(OutputIt it, size_t n) -> typename OutputIt::value_type* { auto& c = get_container(it); size_t size = c.size(); c.resize(size + n); return &c[size]; } template FMT_CONSTEXPR20 inline auto reserve(basic_appender it, size_t n) -> basic_appender { buffer& buf = get_container(it); buf.try_reserve(buf.size() + n); return it; } template constexpr auto reserve(Iterator& it, size_t) -> Iterator& { return it; } template using reserve_iterator = remove_reference_t(), 0))>; template constexpr auto to_pointer(OutputIt, size_t) -> T* { return nullptr; } template FMT_CONSTEXPR20 auto to_pointer(basic_appender it, size_t n) -> T* { buffer& buf = get_container(it); buf.try_reserve(buf.size() + n); auto size = buf.size(); if (buf.capacity() < size + n) return nullptr; buf.try_resize(size + n); return buf.data() + size; } template ::value&& is_contiguous::value)> inline auto base_iterator(OutputIt it, typename OutputIt::container_type::value_type*) -> OutputIt { return it; } template constexpr auto base_iterator(Iterator, Iterator it) -> Iterator { return it; } // is spectacularly slow to compile in C++20 so use a simple fill_n // instead (#1998). template FMT_CONSTEXPR auto fill_n(OutputIt out, Size count, const T& value) -> OutputIt { for (Size i = 0; i < count; ++i) *out++ = value; return out; } template FMT_CONSTEXPR20 auto fill_n(T* out, Size count, char value) -> T* { if (is_constant_evaluated()) return fill_n(out, count, value); std::memset(out, value, to_unsigned(count)); return out + count; } template FMT_CONSTEXPR FMT_NOINLINE auto copy_noinline(InputIt begin, InputIt end, OutputIt out) -> OutputIt { return copy(begin, end, out); } // A public domain branchless UTF-8 decoder by Christopher Wellons: // https://github.com/skeeto/branchless-utf8 /* Decode the next character, c, from s, reporting errors in e. * * Since this is a branchless decoder, four bytes will be read from the * buffer regardless of the actual length of the next character. This * means the buffer _must_ have at least three bytes of zero padding * following the end of the data stream. * * Errors are reported in e, which will be non-zero if the parsed * character was somehow invalid: invalid byte sequence, non-canonical * encoding, or a surrogate half. * * The function returns a pointer to the next character. When an error * occurs, this pointer will be a guess that depends on the particular * error, but it will always advance at least one byte. */ FMT_CONSTEXPR inline auto utf8_decode(const char* s, uint32_t* c, int* e) -> const char* { constexpr const int masks[] = {0x00, 0x7f, 0x1f, 0x0f, 0x07}; constexpr const uint32_t mins[] = {4194304, 0, 128, 2048, 65536}; constexpr const int shiftc[] = {0, 18, 12, 6, 0}; constexpr const int shifte[] = {0, 6, 4, 2, 0}; int len = "\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\0\0\0\0\0\0\0\2\2\2\2\3\3\4" [static_cast(*s) >> 3]; // Compute the pointer to the next character early so that the next // iteration can start working on the next character. Neither Clang // nor GCC figure out this reordering on their own. const char* next = s + len + !len; using uchar = unsigned char; // Assume a four-byte character and load four bytes. Unused bits are // shifted out. *c = uint32_t(uchar(s[0]) & masks[len]) << 18; *c |= uint32_t(uchar(s[1]) & 0x3f) << 12; *c |= uint32_t(uchar(s[2]) & 0x3f) << 6; *c |= uint32_t(uchar(s[3]) & 0x3f) << 0; *c >>= shiftc[len]; // Accumulate the various error conditions. *e = (*c < mins[len]) << 6; // non-canonical encoding *e |= ((*c >> 11) == 0x1b) << 7; // surrogate half? *e |= (*c > 0x10FFFF) << 8; // out of range? *e |= (uchar(s[1]) & 0xc0) >> 2; *e |= (uchar(s[2]) & 0xc0) >> 4; *e |= uchar(s[3]) >> 6; *e ^= 0x2a; // top two bits of each tail byte correct? *e >>= shifte[len]; return next; } constexpr FMT_INLINE_VARIABLE uint32_t invalid_code_point = ~uint32_t(); // Invokes f(cp, sv) for every code point cp in s with sv being the string view // corresponding to the code point. cp is invalid_code_point on error. template FMT_CONSTEXPR void for_each_codepoint(string_view s, F f) { auto decode = [f](const char* buf_ptr, const char* ptr) { auto cp = uint32_t(); auto error = 0; auto end = utf8_decode(buf_ptr, &cp, &error); bool result = f(error ? invalid_code_point : cp, string_view(ptr, error ? 1 : to_unsigned(end - buf_ptr))); return result ? (error ? buf_ptr + 1 : end) : nullptr; }; auto p = s.data(); const size_t block_size = 4; // utf8_decode always reads blocks of 4 chars. if (s.size() >= block_size) { for (auto end = p + s.size() - block_size + 1; p < end;) { p = decode(p, p); if (!p) return; } } auto num_chars_left = to_unsigned(s.data() + s.size() - p); if (num_chars_left == 0) return; // Suppress bogus -Wstringop-overflow. if (FMT_GCC_VERSION) num_chars_left &= 3; char buf[2 * block_size - 1] = {}; copy(p, p + num_chars_left, buf); const char* buf_ptr = buf; do { auto end = decode(buf_ptr, p); if (!end) return; p += end - buf_ptr; buf_ptr = end; } while (buf_ptr < buf + num_chars_left); } template inline auto compute_width(basic_string_view s) -> size_t { return s.size(); } // Computes approximate display width of a UTF-8 string. FMT_CONSTEXPR inline auto compute_width(string_view s) -> size_t { size_t num_code_points = 0; // It is not a lambda for compatibility with C++14. struct count_code_points { size_t* count; FMT_CONSTEXPR auto operator()(uint32_t cp, string_view) const -> bool { *count += to_unsigned( 1 + (cp >= 0x1100 && (cp <= 0x115f || // Hangul Jamo init. consonants cp == 0x2329 || // LEFT-POINTING ANGLE BRACKET cp == 0x232a || // RIGHT-POINTING ANGLE BRACKET // CJK ... Yi except IDEOGRAPHIC HALF FILL SPACE: (cp >= 0x2e80 && cp <= 0xa4cf && cp != 0x303f) || (cp >= 0xac00 && cp <= 0xd7a3) || // Hangul Syllables (cp >= 0xf900 && cp <= 0xfaff) || // CJK Compatibility Ideographs (cp >= 0xfe10 && cp <= 0xfe19) || // Vertical Forms (cp >= 0xfe30 && cp <= 0xfe6f) || // CJK Compatibility Forms (cp >= 0xff00 && cp <= 0xff60) || // Fullwidth Forms (cp >= 0xffe0 && cp <= 0xffe6) || // Fullwidth Forms (cp >= 0x20000 && cp <= 0x2fffd) || // CJK (cp >= 0x30000 && cp <= 0x3fffd) || // Miscellaneous Symbols and Pictographs + Emoticons: (cp >= 0x1f300 && cp <= 0x1f64f) || // Supplemental Symbols and Pictographs: (cp >= 0x1f900 && cp <= 0x1f9ff)))); return true; } }; // We could avoid branches by using utf8_decode directly. for_each_codepoint(s, count_code_points{&num_code_points}); return num_code_points; } template inline auto code_point_index(basic_string_view s, size_t n) -> size_t { return min_of(n, s.size()); } // Calculates the index of the nth code point in a UTF-8 string. inline auto code_point_index(string_view s, size_t n) -> size_t { size_t result = s.size(); const char* begin = s.begin(); for_each_codepoint(s, [begin, &n, &result](uint32_t, string_view sv) { if (n != 0) { --n; return true; } result = to_unsigned(sv.begin() - begin); return false; }); return result; } template struct is_integral : std::is_integral {}; template <> struct is_integral : std::true_type {}; template <> struct is_integral : std::true_type {}; template using is_signed = std::integral_constant::is_signed || std::is_same::value>; template using is_integer = bool_constant::value && !std::is_same::value && !std::is_same::value && !std::is_same::value>; #if defined(FMT_USE_FLOAT128) // Use the provided definition. #elif FMT_CLANG_VERSION && FMT_HAS_INCLUDE() # define FMT_USE_FLOAT128 1 #elif FMT_GCC_VERSION && defined(_GLIBCXX_USE_FLOAT128) && \ !defined(__STRICT_ANSI__) # define FMT_USE_FLOAT128 1 #else # define FMT_USE_FLOAT128 0 #endif #if FMT_USE_FLOAT128 using float128 = __float128; #else struct float128 {}; #endif template using is_float128 = std::is_same; template using is_floating_point = bool_constant::value || is_float128::value>; template ::value> struct is_fast_float : bool_constant::is_iec559 && sizeof(T) <= sizeof(double)> {}; template struct is_fast_float : std::false_type {}; template using is_double_double = bool_constant::digits == 106>; #ifndef FMT_USE_FULL_CACHE_DRAGONBOX # define FMT_USE_FULL_CACHE_DRAGONBOX 0 #endif // An allocator that uses malloc/free to allow removing dependency on the C++ // standard libary runtime. template struct allocator { using value_type = T; T* allocate(size_t n) { FMT_ASSERT(n <= max_value() / sizeof(T), ""); T* p = static_cast(malloc(n * sizeof(T))); if (!p) FMT_THROW(std::bad_alloc()); return p; } void deallocate(T* p, size_t) { free(p); } }; } // namespace detail FMT_BEGIN_EXPORT // The number of characters to store in the basic_memory_buffer object itself // to avoid dynamic memory allocation. enum { inline_buffer_size = 500 }; /** * A dynamically growing memory buffer for trivially copyable/constructible * types with the first `SIZE` elements stored in the object itself. Most * commonly used via the `memory_buffer` alias for `char`. * * **Example**: * * auto out = fmt::memory_buffer(); * fmt::format_to(std::back_inserter(out), "The answer is {}.", 42); * * This will append "The answer is 42." to `out`. The buffer content can be * converted to `std::string` with `to_string(out)`. */ template > class basic_memory_buffer : public detail::buffer { private: T store_[SIZE]; // Don't inherit from Allocator to avoid generating type_info for it. FMT_NO_UNIQUE_ADDRESS Allocator alloc_; // Deallocate memory allocated by the buffer. FMT_CONSTEXPR20 void deallocate() { T* data = this->data(); if (data != store_) alloc_.deallocate(data, this->capacity()); } static FMT_CONSTEXPR20 void grow(detail::buffer& buf, size_t size) { detail::abort_fuzzing_if(size > 5000); auto& self = static_cast(buf); const size_t max_size = std::allocator_traits::max_size(self.alloc_); size_t old_capacity = buf.capacity(); size_t new_capacity = old_capacity + old_capacity / 2; if (size > new_capacity) new_capacity = size; else if (new_capacity > max_size) new_capacity = max_of(size, max_size); T* old_data = buf.data(); T* new_data = self.alloc_.allocate(new_capacity); // Suppress a bogus -Wstringop-overflow in gcc 13.1 (#3481). detail::assume(buf.size() <= new_capacity); // The following code doesn't throw, so the raw pointer above doesn't leak. memcpy(new_data, old_data, buf.size() * sizeof(T)); self.set(new_data, new_capacity); // deallocate must not throw according to the standard, but even if it does, // the buffer already uses the new storage and will deallocate it in // destructor. if (old_data != self.store_) self.alloc_.deallocate(old_data, old_capacity); } public: using value_type = T; using const_reference = const T&; FMT_CONSTEXPR explicit basic_memory_buffer( const Allocator& alloc = Allocator()) : detail::buffer(grow), alloc_(alloc) { this->set(store_, SIZE); if (detail::is_constant_evaluated()) detail::fill_n(store_, SIZE, T()); } FMT_CONSTEXPR20 ~basic_memory_buffer() { deallocate(); } private: // Move data from other to this buffer. FMT_CONSTEXPR20 void move(basic_memory_buffer& other) { alloc_ = std::move(other.alloc_); T* data = other.data(); size_t size = other.size(), capacity = other.capacity(); if (data == other.store_) { this->set(store_, capacity); detail::copy(other.store_, other.store_ + size, store_); } else { this->set(data, capacity); // Set pointer to the inline array so that delete is not called // when deallocating. other.set(other.store_, 0); other.clear(); } this->resize(size); } public: /// Constructs a `basic_memory_buffer` object moving the content of the other /// object to it. FMT_CONSTEXPR20 basic_memory_buffer(basic_memory_buffer&& other) noexcept : detail::buffer(grow) { move(other); } /// Moves the content of the other `basic_memory_buffer` object to this one. auto operator=(basic_memory_buffer&& other) noexcept -> basic_memory_buffer& { FMT_ASSERT(this != &other, ""); deallocate(); move(other); return *this; } // Returns a copy of the allocator associated with this buffer. auto get_allocator() const -> Allocator { return alloc_; } /// Resizes the buffer to contain `count` elements. If T is a POD type new /// elements may not be initialized. FMT_CONSTEXPR void resize(size_t count) { this->try_resize(count); } /// Increases the buffer capacity to `new_capacity`. void reserve(size_t new_capacity) { this->try_reserve(new_capacity); } using detail::buffer::append; template FMT_CONSTEXPR20 void append(const ContiguousRange& range) { append(range.data(), range.data() + range.size()); } }; using memory_buffer = basic_memory_buffer; template FMT_NODISCARD auto to_string(const basic_memory_buffer& buf) -> std::string { auto size = buf.size(); detail::assume(size < std::string().max_size()); return {buf.data(), size}; } // A writer to a buffered stream. It doesn't own the underlying stream. class writer { private: detail::buffer* buf_; // We cannot create a file buffer in advance because any write to a FILE may // invalidate it. FILE* file_; public: inline writer(FILE* f) : buf_(nullptr), file_(f) {} inline writer(detail::buffer& buf) : buf_(&buf) {} /// Formats `args` according to specifications in `fmt` and writes the /// output to the file. template void print(format_string fmt, T&&... args) { if (buf_) fmt::format_to(appender(*buf_), fmt, std::forward(args)...); else fmt::print(file_, fmt, std::forward(args)...); } }; class string_buffer { private: std::string str_; detail::container_buffer buf_; public: inline string_buffer() : buf_(str_) {} inline operator writer() { return buf_; } inline std::string& str() { return str_; } }; template struct is_contiguous> : std::true_type { }; // Suppress a misleading warning in older versions of clang. FMT_PRAGMA_CLANG(diagnostic ignored "-Wweak-vtables") /// An error reported from a formatting function. class FMT_SO_VISIBILITY("default") format_error : public std::runtime_error { public: using std::runtime_error::runtime_error; }; class loc_value; FMT_END_EXPORT namespace detail { FMT_API auto write_console(int fd, string_view text) -> bool; FMT_API void print(FILE*, string_view); } // namespace detail namespace detail { template struct fixed_string { FMT_CONSTEXPR20 fixed_string(const Char (&s)[N]) { detail::copy(static_cast(s), s + N, data); } Char data[N] = {}; }; // Converts a compile-time string to basic_string_view. FMT_EXPORT template constexpr auto compile_string_to_view(const Char (&s)[N]) -> basic_string_view { // Remove trailing NUL character if needed. Won't be present if this is used // with a raw character array (i.e. not defined as a string). return {s, N - (std::char_traits::to_int_type(s[N - 1]) == 0 ? 1 : 0)}; } FMT_EXPORT template constexpr auto compile_string_to_view(basic_string_view s) -> basic_string_view { return s; } // Returns true if value is negative, false otherwise. // Same as `value < 0` but doesn't produce warnings if T is an unsigned type. template ::value)> constexpr auto is_negative(T value) -> bool { return value < 0; } template ::value)> constexpr auto is_negative(T) -> bool { return false; } // Smallest of uint32_t, uint64_t, uint128_t that is large enough to // represent all values of an integral type T. template using uint32_or_64_or_128_t = conditional_t() <= 32 && !FMT_REDUCE_INT_INSTANTIATIONS, uint32_t, conditional_t() <= 64, uint64_t, uint128_t>>; template using uint64_or_128_t = conditional_t() <= 64, uint64_t, uint128_t>; #define FMT_POWERS_OF_10(factor) \ factor * 10, (factor) * 100, (factor) * 1000, (factor) * 10000, \ (factor) * 100000, (factor) * 1000000, (factor) * 10000000, \ (factor) * 100000000, (factor) * 1000000000 // Converts value in the range [0, 100) to a string. // GCC generates slightly better code when value is pointer-size. inline auto digits2(size_t value) -> const char* { // Align data since unaligned access may be slower when crossing a // hardware-specific boundary. alignas(2) static const char data[] = "0001020304050607080910111213141516171819" "2021222324252627282930313233343536373839" "4041424344454647484950515253545556575859" "6061626364656667686970717273747576777879" "8081828384858687888990919293949596979899"; return &data[value * 2]; } template constexpr auto getsign(sign s) -> Char { return static_cast(((' ' << 24) | ('+' << 16) | ('-' << 8)) >> (static_cast(s) * 8)); } template FMT_CONSTEXPR auto count_digits_fallback(T n) -> int { int count = 1; for (;;) { // Integer division is slow so do it for a group of four digits instead // of for every digit. The idea comes from the talk by Alexandrescu // "Three Optimization Tips for C++". See speed-test for a comparison. if (n < 10) return count; if (n < 100) return count + 1; if (n < 1000) return count + 2; if (n < 10000) return count + 3; n /= 10000u; count += 4; } } #if FMT_USE_INT128 FMT_CONSTEXPR inline auto count_digits(uint128_opt n) -> int { return count_digits_fallback(n); } #endif #ifdef FMT_BUILTIN_CLZLL // It is a separate function rather than a part of count_digits to workaround // the lack of static constexpr in constexpr functions. inline auto do_count_digits(uint64_t n) -> int { // This has comparable performance to the version by Kendall Willets // (https://github.com/fmtlib/format-benchmark/blob/master/digits10) // but uses smaller tables. // Maps bsr(n) to ceil(log10(pow(2, bsr(n) + 1) - 1)). static constexpr uint8_t bsr2log10[] = { 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10, 10, 11, 11, 11, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 15, 15, 15, 16, 16, 16, 16, 17, 17, 17, 18, 18, 18, 19, 19, 19, 19, 20}; auto t = bsr2log10[FMT_BUILTIN_CLZLL(n | 1) ^ 63]; static constexpr const uint64_t zero_or_powers_of_10[] = { 0, 0, FMT_POWERS_OF_10(1U), FMT_POWERS_OF_10(1000000000ULL), 10000000000000000000ULL}; return t - (n < zero_or_powers_of_10[t]); } #endif // Returns the number of decimal digits in n. Leading zeros are not counted // except for n == 0 in which case count_digits returns 1. FMT_CONSTEXPR20 inline auto count_digits(uint64_t n) -> int { #ifdef FMT_BUILTIN_CLZLL if (!is_constant_evaluated() && !FMT_OPTIMIZE_SIZE) return do_count_digits(n); #endif return count_digits_fallback(n); } // Counts the number of digits in n. BITS = log2(radix). template FMT_CONSTEXPR auto count_digits(UInt n) -> int { #ifdef FMT_BUILTIN_CLZ if (!is_constant_evaluated() && num_bits() == 32) return (FMT_BUILTIN_CLZ(static_cast(n) | 1) ^ 31) / BITS + 1; #endif // Lambda avoids unreachable code warnings from NVHPC. return [](UInt m) { int num_digits = 0; do { ++num_digits; } while ((m >>= BITS) != 0); return num_digits; }(n); } #ifdef FMT_BUILTIN_CLZ // It is a separate function rather than a part of count_digits to workaround // the lack of static constexpr in constexpr functions. FMT_INLINE auto do_count_digits(uint32_t n) -> int { // An optimization by Kendall Willets from https://bit.ly/3uOIQrB. // This increments the upper 32 bits (log10(T) - 1) when >= T is added. # define FMT_INC(T) (((sizeof(#T) - 1ull) << 32) - T) static constexpr uint64_t table[] = { FMT_INC(0), FMT_INC(0), FMT_INC(0), // 8 FMT_INC(10), FMT_INC(10), FMT_INC(10), // 64 FMT_INC(100), FMT_INC(100), FMT_INC(100), // 512 FMT_INC(1000), FMT_INC(1000), FMT_INC(1000), // 4096 FMT_INC(10000), FMT_INC(10000), FMT_INC(10000), // 32k FMT_INC(100000), FMT_INC(100000), FMT_INC(100000), // 256k FMT_INC(1000000), FMT_INC(1000000), FMT_INC(1000000), // 2048k FMT_INC(10000000), FMT_INC(10000000), FMT_INC(10000000), // 16M FMT_INC(100000000), FMT_INC(100000000), FMT_INC(100000000), // 128M FMT_INC(1000000000), FMT_INC(1000000000), FMT_INC(1000000000), // 1024M FMT_INC(1000000000), FMT_INC(1000000000) // 4B }; auto inc = table[FMT_BUILTIN_CLZ(n | 1) ^ 31]; return static_cast((n + inc) >> 32); } #endif // Optional version of count_digits for better performance on 32-bit platforms. FMT_CONSTEXPR20 inline auto count_digits(uint32_t n) -> int { #ifdef FMT_BUILTIN_CLZ if (!is_constant_evaluated() && !FMT_OPTIMIZE_SIZE) return do_count_digits(n); #endif return count_digits_fallback(n); } template constexpr auto digits10() noexcept -> int { return std::numeric_limits::digits10; } template <> constexpr auto digits10() noexcept -> int { return 38; } template <> constexpr auto digits10() noexcept -> int { return 38; } template struct thousands_sep_result { std::string grouping; Char thousands_sep; }; template FMT_API auto thousands_sep_impl(locale_ref loc) -> thousands_sep_result; template inline auto thousands_sep(locale_ref loc) -> thousands_sep_result { auto result = thousands_sep_impl(loc); return {result.grouping, Char(result.thousands_sep)}; } template <> inline auto thousands_sep(locale_ref loc) -> thousands_sep_result { return thousands_sep_impl(loc); } template FMT_API auto decimal_point_impl(locale_ref loc) -> Char; template inline auto decimal_point(locale_ref loc) -> Char { return Char(decimal_point_impl(loc)); } template <> inline auto decimal_point(locale_ref loc) -> wchar_t { return decimal_point_impl(loc); } #ifndef FMT_HEADER_ONLY FMT_BEGIN_EXPORT extern template FMT_API auto thousands_sep_impl(locale_ref) -> thousands_sep_result; extern template FMT_API auto thousands_sep_impl(locale_ref) -> thousands_sep_result; extern template FMT_API auto decimal_point_impl(locale_ref) -> char; extern template FMT_API auto decimal_point_impl(locale_ref) -> wchar_t; FMT_END_EXPORT #endif // FMT_HEADER_ONLY // Compares two characters for equality. template auto equal2(const Char* lhs, const char* rhs) -> bool { return lhs[0] == Char(rhs[0]) && lhs[1] == Char(rhs[1]); } inline auto equal2(const char* lhs, const char* rhs) -> bool { return memcmp(lhs, rhs, 2) == 0; } // Writes a two-digit value to out. template FMT_CONSTEXPR20 FMT_INLINE void write2digits(Char* out, size_t value) { if (!is_constant_evaluated() && std::is_same::value && !FMT_OPTIMIZE_SIZE) { memcpy(out, digits2(value), 2); return; } *out++ = static_cast('0' + value / 10); *out = static_cast('0' + value % 10); } // Formats a decimal unsigned integer value writing to out pointing to a buffer // of specified size. The caller must ensure that the buffer is large enough. template FMT_CONSTEXPR20 auto do_format_decimal(Char* out, UInt value, int size) -> Char* { FMT_ASSERT(size >= count_digits(value), "invalid digit count"); unsigned n = to_unsigned(size); while (value >= 100) { // Integer division is slow so do it for a group of two digits instead // of for every digit. The idea comes from the talk by Alexandrescu // "Three Optimization Tips for C++". See speed-test for a comparison. n -= 2; write2digits(out + n, static_cast(value % 100)); value /= 100; } if (value >= 10) { n -= 2; write2digits(out + n, static_cast(value)); } else { out[--n] = static_cast('0' + value); } return out + n; } template FMT_CONSTEXPR FMT_INLINE auto format_decimal(Char* out, UInt value, int num_digits) -> Char* { do_format_decimal(out, value, num_digits); return out + num_digits; } template ::value)> FMT_CONSTEXPR auto format_decimal(OutputIt out, UInt value, int num_digits) -> OutputIt { if (auto ptr = to_pointer(out, to_unsigned(num_digits))) { do_format_decimal(ptr, value, num_digits); return out; } // Buffer is large enough to hold all digits (digits10 + 1). char buffer[digits10() + 1]; if (is_constant_evaluated()) fill_n(buffer, sizeof(buffer), '\0'); do_format_decimal(buffer, value, num_digits); return copy_noinline(buffer, buffer + num_digits, out); } template FMT_CONSTEXPR auto do_format_base2e(int base_bits, Char* out, UInt value, int size, bool upper = false) -> Char* { out += size; do { const char* digits = upper ? "0123456789ABCDEF" : "0123456789abcdef"; unsigned digit = static_cast(value & ((1 << base_bits) - 1)); *--out = static_cast(base_bits < 4 ? static_cast('0' + digit) : digits[digit]); } while ((value >>= base_bits) != 0); return out; } // Formats an unsigned integer in the power of two base (binary, octal, hex). template FMT_CONSTEXPR auto format_base2e(int base_bits, Char* out, UInt value, int num_digits, bool upper = false) -> Char* { do_format_base2e(base_bits, out, value, num_digits, upper); return out + num_digits; } template ::value)> FMT_CONSTEXPR inline auto format_base2e(int base_bits, OutputIt out, UInt value, int num_digits, bool upper = false) -> OutputIt { if (auto ptr = to_pointer(out, to_unsigned(num_digits))) { format_base2e(base_bits, ptr, value, num_digits, upper); return out; } // Make buffer large enough for any base. char buffer[num_bits()]; if (is_constant_evaluated()) fill_n(buffer, sizeof(buffer), '\0'); format_base2e(base_bits, buffer, value, num_digits, upper); return detail::copy_noinline(buffer, buffer + num_digits, out); } // A converter from UTF-8 to UTF-16. class utf8_to_utf16 { private: basic_memory_buffer buffer_; public: FMT_API explicit utf8_to_utf16(string_view s); inline operator basic_string_view() const { return {&buffer_[0], size()}; } inline auto size() const -> size_t { return buffer_.size() - 1; } inline auto c_str() const -> const wchar_t* { return &buffer_[0]; } inline auto str() const -> std::wstring { return {&buffer_[0], size()}; } }; enum class to_utf8_error_policy { abort, replace }; // A converter from UTF-16/UTF-32 (host endian) to UTF-8. template class to_utf8 { private: Buffer buffer_; public: to_utf8() {} explicit to_utf8(basic_string_view s, to_utf8_error_policy policy = to_utf8_error_policy::abort) { static_assert(sizeof(WChar) == 2 || sizeof(WChar) == 4, "Expect utf16 or utf32"); if (!convert(s, policy)) FMT_THROW(std::runtime_error(sizeof(WChar) == 2 ? "invalid utf16" : "invalid utf32")); } operator string_view() const { return string_view(&buffer_[0], size()); } auto size() const -> size_t { return buffer_.size() - 1; } auto c_str() const -> const char* { return &buffer_[0]; } auto str() const -> std::string { return std::string(&buffer_[0], size()); } // Performs conversion returning a bool instead of throwing exception on // conversion error. This method may still throw in case of memory allocation // error. auto convert(basic_string_view s, to_utf8_error_policy policy = to_utf8_error_policy::abort) -> bool { if (!convert(buffer_, s, policy)) return false; buffer_.push_back(0); return true; } static auto convert(Buffer& buf, basic_string_view s, to_utf8_error_policy policy = to_utf8_error_policy::abort) -> bool { for (auto p = s.begin(); p != s.end(); ++p) { uint32_t c = static_cast(*p); if (sizeof(WChar) == 2 && c >= 0xd800 && c <= 0xdfff) { // Handle a surrogate pair. ++p; if (p == s.end() || (c & 0xfc00) != 0xd800 || (*p & 0xfc00) != 0xdc00) { if (policy == to_utf8_error_policy::abort) return false; buf.append(string_view("\xEF\xBF\xBD")); --p; continue; } else { c = (c << 10) + static_cast(*p) - 0x35fdc00; } } if (c < 0x80) { buf.push_back(static_cast(c)); } else if (c < 0x800) { buf.push_back(static_cast(0xc0 | (c >> 6))); buf.push_back(static_cast(0x80 | (c & 0x3f))); } else if ((c >= 0x800 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xffff)) { buf.push_back(static_cast(0xe0 | (c >> 12))); buf.push_back(static_cast(0x80 | ((c & 0xfff) >> 6))); buf.push_back(static_cast(0x80 | (c & 0x3f))); } else if (c >= 0x10000 && c <= 0x10ffff) { buf.push_back(static_cast(0xf0 | (c >> 18))); buf.push_back(static_cast(0x80 | ((c & 0x3ffff) >> 12))); buf.push_back(static_cast(0x80 | ((c & 0xfff) >> 6))); buf.push_back(static_cast(0x80 | (c & 0x3f))); } else { return false; } } return true; } }; // Computes 128-bit result of multiplication of two 64-bit unsigned integers. inline auto umul128(uint64_t x, uint64_t y) noexcept -> uint128_fallback { #if FMT_USE_INT128 auto p = static_cast(x) * static_cast(y); return {static_cast(p >> 64), static_cast(p)}; #elif defined(_MSC_VER) && defined(_M_X64) auto hi = uint64_t(); auto lo = _umul128(x, y, &hi); return {hi, lo}; #else const uint64_t mask = static_cast(max_value()); uint64_t a = x >> 32; uint64_t b = x & mask; uint64_t c = y >> 32; uint64_t d = y & mask; uint64_t ac = a * c; uint64_t bc = b * c; uint64_t ad = a * d; uint64_t bd = b * d; uint64_t intermediate = (bd >> 32) + (ad & mask) + (bc & mask); return {ac + (intermediate >> 32) + (ad >> 32) + (bc >> 32), (intermediate << 32) + (bd & mask)}; #endif } namespace dragonbox { // Computes floor(log10(pow(2, e))) for e in [-2620, 2620] using the method from // https://fmt.dev/papers/Dragonbox.pdf#page=28, section 6.1. inline auto floor_log10_pow2(int e) noexcept -> int { FMT_ASSERT(e <= 2620 && e >= -2620, "too large exponent"); static_assert((-1 >> 1) == -1, "right shift is not arithmetic"); return (e * 315653) >> 20; } inline auto floor_log2_pow10(int e) noexcept -> int { FMT_ASSERT(e <= 1233 && e >= -1233, "too large exponent"); return (e * 1741647) >> 19; } // Computes upper 64 bits of multiplication of two 64-bit unsigned integers. inline auto umul128_upper64(uint64_t x, uint64_t y) noexcept -> uint64_t { #if FMT_USE_INT128 auto p = static_cast(x) * static_cast(y); return static_cast(p >> 64); #elif defined(_MSC_VER) && defined(_M_X64) return __umulh(x, y); #else return umul128(x, y).high(); #endif } // Computes upper 128 bits of multiplication of a 64-bit unsigned integer and a // 128-bit unsigned integer. inline auto umul192_upper128(uint64_t x, uint128_fallback y) noexcept -> uint128_fallback { uint128_fallback r = umul128(x, y.high()); r += umul128_upper64(x, y.low()); return r; } FMT_API auto get_cached_power(int k) noexcept -> uint128_fallback; // Type-specific information that Dragonbox uses. template struct float_info; template <> struct float_info { using carrier_uint = uint32_t; static const int exponent_bits = 8; static const int kappa = 1; static const int big_divisor = 100; static const int small_divisor = 10; static const int min_k = -31; static const int max_k = 46; static const int shorter_interval_tie_lower_threshold = -35; static const int shorter_interval_tie_upper_threshold = -35; }; template <> struct float_info { using carrier_uint = uint64_t; static const int exponent_bits = 11; static const int kappa = 2; static const int big_divisor = 1000; static const int small_divisor = 100; static const int min_k = -292; static const int max_k = 341; static const int shorter_interval_tie_lower_threshold = -77; static const int shorter_interval_tie_upper_threshold = -77; }; // An 80- or 128-bit floating point number. template struct float_info::digits == 64 || std::numeric_limits::digits == 113 || is_float128::value>> { using carrier_uint = detail::uint128_t; static const int exponent_bits = 15; }; // A double-double floating point number. template struct float_info::value>> { using carrier_uint = detail::uint128_t; }; template struct decimal_fp { using significand_type = typename float_info::carrier_uint; significand_type significand; int exponent; }; template FMT_API auto to_decimal(T x) noexcept -> decimal_fp; } // namespace dragonbox // Returns true iff Float has the implicit bit which is not stored. template constexpr auto has_implicit_bit() -> bool { // An 80-bit FP number has a 64-bit significand an no implicit bit. return std::numeric_limits::digits != 64; } // Returns the number of significand bits stored in Float. The implicit bit is // not counted since it is not stored. template constexpr auto num_significand_bits() -> int { // std::numeric_limits may not support __float128. return is_float128() ? 112 : (std::numeric_limits::digits - (has_implicit_bit() ? 1 : 0)); } template constexpr auto exponent_mask() -> typename dragonbox::float_info::carrier_uint { using float_uint = typename dragonbox::float_info::carrier_uint; return ((float_uint(1) << dragonbox::float_info::exponent_bits) - 1) << num_significand_bits(); } template constexpr auto exponent_bias() -> int { // std::numeric_limits may not support __float128. return is_float128() ? 16383 : std::numeric_limits::max_exponent - 1; } // Writes the exponent exp in the form "[+-]d{2,3}" to buffer. template FMT_CONSTEXPR auto write_exponent(int exp, OutputIt out) -> OutputIt { FMT_ASSERT(-10000 < exp && exp < 10000, "exponent out of range"); if (exp < 0) { *out++ = static_cast('-'); exp = -exp; } else { *out++ = static_cast('+'); } auto uexp = static_cast(exp); if (is_constant_evaluated()) { if (uexp < 10) *out++ = '0'; return format_decimal(out, uexp, count_digits(uexp)); } if (uexp >= 100u) { const char* top = digits2(uexp / 100); if (uexp >= 1000u) *out++ = static_cast(top[0]); *out++ = static_cast(top[1]); uexp %= 100; } const char* d = digits2(uexp); *out++ = static_cast(d[0]); *out++ = static_cast(d[1]); return out; } // A floating-point number f * pow(2, e) where F is an unsigned type. template struct basic_fp { F f; int e; static constexpr const int num_significand_bits = static_cast(sizeof(F) * num_bits()); constexpr basic_fp() : f(0), e(0) {} constexpr basic_fp(uint64_t f_val, int e_val) : f(f_val), e(e_val) {} // Constructs fp from an IEEE754 floating-point number. template FMT_CONSTEXPR basic_fp(Float n) { assign(n); } // Assigns n to this and return true iff predecessor is closer than successor. template ::value)> FMT_CONSTEXPR auto assign(Float n) -> bool { static_assert(std::numeric_limits::digits <= 113, "unsupported FP"); // Assume Float is in the format [sign][exponent][significand]. using carrier_uint = typename dragonbox::float_info::carrier_uint; const auto num_float_significand_bits = detail::num_significand_bits(); const auto implicit_bit = carrier_uint(1) << num_float_significand_bits; const auto significand_mask = implicit_bit - 1; auto u = bit_cast(n); f = static_cast(u & significand_mask); auto biased_e = static_cast((u & exponent_mask()) >> num_float_significand_bits); // The predecessor is closer if n is a normalized power of 2 (f == 0) // other than the smallest normalized number (biased_e > 1). auto is_predecessor_closer = f == 0 && biased_e > 1; if (biased_e == 0) biased_e = 1; // Subnormals use biased exponent 1 (min exponent). else if (has_implicit_bit()) f += static_cast(implicit_bit); e = biased_e - exponent_bias() - num_float_significand_bits; if (!has_implicit_bit()) ++e; return is_predecessor_closer; } template ::value)> FMT_CONSTEXPR auto assign(Float n) -> bool { static_assert(std::numeric_limits::is_iec559, "unsupported FP"); return assign(static_cast(n)); } }; using fp = basic_fp; // Normalizes the value converted from double and multiplied by (1 << SHIFT). template FMT_CONSTEXPR auto normalize(basic_fp value) -> basic_fp { // Handle subnormals. const auto implicit_bit = F(1) << num_significand_bits(); const auto shifted_implicit_bit = implicit_bit << SHIFT; while ((value.f & shifted_implicit_bit) == 0) { value.f <<= 1; --value.e; } // Subtract 1 to account for hidden bit. const auto offset = basic_fp::num_significand_bits - num_significand_bits() - SHIFT - 1; value.f <<= offset; value.e -= offset; return value; } // Computes lhs * rhs / pow(2, 64) rounded to nearest with half-up tie breaking. FMT_CONSTEXPR inline auto multiply(uint64_t lhs, uint64_t rhs) -> uint64_t { #if FMT_USE_INT128 auto product = static_cast<__uint128_t>(lhs) * rhs; auto f = static_cast(product >> 64); return (static_cast(product) & (1ULL << 63)) != 0 ? f + 1 : f; #else // Multiply 32-bit parts of significands. uint64_t mask = (1ULL << 32) - 1; uint64_t a = lhs >> 32, b = lhs & mask; uint64_t c = rhs >> 32, d = rhs & mask; uint64_t ac = a * c, bc = b * c, ad = a * d, bd = b * d; // Compute mid 64-bit of result and round. uint64_t mid = (bd >> 32) + (ad & mask) + (bc & mask) + (1U << 31); return ac + (ad >> 32) + (bc >> 32) + (mid >> 32); #endif } FMT_CONSTEXPR inline auto operator*(fp x, fp y) -> fp { return {multiply(x.f, y.f), x.e + y.e + 64}; } template () == num_bits()> using convert_float_result = conditional_t::value || doublish, double, T>; template constexpr auto convert_float(T value) -> convert_float_result { return static_cast>(value); } template FMT_NOINLINE FMT_CONSTEXPR auto fill(OutputIt it, size_t n, const basic_specs& specs) -> OutputIt { auto fill_size = specs.fill_size(); if (fill_size == 1) return detail::fill_n(it, n, specs.fill_unit()); if (const Char* data = specs.fill()) { for (size_t i = 0; i < n; ++i) it = copy(data, data + fill_size, it); } return it; } // Writes the output of f, padded according to format specifications in specs. // size: output size in code units. // width: output display width in (terminal) column positions. template FMT_CONSTEXPR auto write_padded(OutputIt out, const format_specs& specs, size_t size, size_t width, F&& f) -> OutputIt { static_assert(default_align == align::left || default_align == align::right, ""); unsigned spec_width = to_unsigned(specs.width); size_t padding = spec_width > width ? spec_width - width : 0; // Shifts are encoded as string literals because static constexpr is not // supported in constexpr functions. auto* shifts = default_align == align::left ? "\x1f\x1f\x00\x01" : "\x00\x1f\x00\x01"; size_t left_padding = padding >> shifts[static_cast(specs.align())]; size_t right_padding = padding - left_padding; auto it = reserve(out, size + padding * specs.fill_size()); if (left_padding != 0) it = fill(it, left_padding, specs); it = f(it); if (right_padding != 0) it = fill(it, right_padding, specs); return base_iterator(out, it); } template constexpr auto write_padded(OutputIt out, const format_specs& specs, size_t size, F&& f) -> OutputIt { return write_padded(out, specs, size, size, f); } template FMT_CONSTEXPR auto write_bytes(OutputIt out, string_view bytes, const format_specs& specs = {}) -> OutputIt { return write_padded( out, specs, bytes.size(), [bytes](reserve_iterator it) { const char* data = bytes.data(); return copy(data, data + bytes.size(), it); }); } template auto write_ptr(OutputIt out, UIntPtr value, const format_specs* specs) -> OutputIt { int num_digits = count_digits<4>(value); auto size = to_unsigned(num_digits) + size_t(2); auto write = [=](reserve_iterator it) { *it++ = static_cast('0'); *it++ = static_cast('x'); return format_base2e(4, it, value, num_digits); }; return specs ? write_padded(out, *specs, size, write) : base_iterator(out, write(reserve(out, size))); } // Returns true iff the code point cp is printable. FMT_API auto is_printable(uint32_t cp) -> bool; inline auto needs_escape(uint32_t cp) -> bool { if (cp < 0x20 || cp == 0x7f || cp == '"' || cp == '\\') return true; if (const_check(FMT_OPTIMIZE_SIZE > 1)) return false; return !is_printable(cp); } template struct find_escape_result { const Char* begin; const Char* end; uint32_t cp; }; template auto find_escape(const Char* begin, const Char* end) -> find_escape_result { for (; begin != end; ++begin) { uint32_t cp = static_cast>(*begin); if (const_check(sizeof(Char) == 1) && cp >= 0x80) continue; if (needs_escape(cp)) return {begin, begin + 1, cp}; } return {begin, nullptr, 0}; } inline auto find_escape(const char* begin, const char* end) -> find_escape_result { if (const_check(!use_utf8)) return find_escape(begin, end); auto result = find_escape_result{end, nullptr, 0}; for_each_codepoint(string_view(begin, to_unsigned(end - begin)), [&](uint32_t cp, string_view sv) { if (needs_escape(cp)) { result = {sv.begin(), sv.end(), cp}; return false; } return true; }); return result; } template auto write_codepoint(OutputIt out, char prefix, uint32_t cp) -> OutputIt { *out++ = static_cast('\\'); *out++ = static_cast(prefix); Char buf[width]; fill_n(buf, width, static_cast('0')); format_base2e(4, buf, cp, width); return copy(buf, buf + width, out); } template auto write_escaped_cp(OutputIt out, const find_escape_result& escape) -> OutputIt { auto c = static_cast(escape.cp); switch (escape.cp) { case '\n': *out++ = static_cast('\\'); c = static_cast('n'); break; case '\r': *out++ = static_cast('\\'); c = static_cast('r'); break; case '\t': *out++ = static_cast('\\'); c = static_cast('t'); break; case '"': FMT_FALLTHROUGH; case '\'': FMT_FALLTHROUGH; case '\\': *out++ = static_cast('\\'); break; default: if (escape.cp < 0x100) return write_codepoint<2, Char>(out, 'x', escape.cp); if (escape.cp < 0x10000) return write_codepoint<4, Char>(out, 'u', escape.cp); if (escape.cp < 0x110000) return write_codepoint<8, Char>(out, 'U', escape.cp); for (Char escape_char : basic_string_view( escape.begin, to_unsigned(escape.end - escape.begin))) { out = write_codepoint<2, Char>(out, 'x', static_cast(escape_char) & 0xFF); } return out; } *out++ = c; return out; } template auto write_escaped_string(OutputIt out, basic_string_view str) -> OutputIt { *out++ = static_cast('"'); auto begin = str.begin(), end = str.end(); do { auto escape = find_escape(begin, end); out = copy(begin, escape.begin, out); begin = escape.end; if (!begin) break; out = write_escaped_cp(out, escape); } while (begin != end); *out++ = static_cast('"'); return out; } template auto write_escaped_char(OutputIt out, Char v) -> OutputIt { Char v_array[1] = {v}; *out++ = static_cast('\''); if ((needs_escape(static_cast(v)) && v != static_cast('"')) || v == static_cast('\'')) { out = write_escaped_cp(out, find_escape_result{v_array, v_array + 1, static_cast(v)}); } else { *out++ = v; } *out++ = static_cast('\''); return out; } template FMT_CONSTEXPR auto write_char(OutputIt out, Char value, const format_specs& specs) -> OutputIt { bool is_debug = specs.type() == presentation_type::debug; return write_padded(out, specs, 1, [=](reserve_iterator it) { if (is_debug) return write_escaped_char(it, value); *it++ = value; return it; }); } template FMT_CONSTEXPR auto write(OutputIt out, Char value, const format_specs& specs, locale_ref loc = {}) -> OutputIt { // char is formatted as unsigned char for consistency across platforms. using unsigned_type = conditional_t::value, unsigned char, unsigned>; return check_char_specs(specs) ? write_char(out, value, specs) : write(out, static_cast(value), specs, loc); } template class digit_grouping { private: std::string grouping_; std::basic_string thousands_sep_; struct next_state { std::string::const_iterator group; int pos; }; auto initial_state() const -> next_state { return {grouping_.begin(), 0}; } // Returns the next digit group separator position. auto next(next_state& state) const -> int { if (thousands_sep_.empty()) return max_value(); if (state.group == grouping_.end()) return state.pos += grouping_.back(); if (*state.group <= 0 || *state.group == max_value()) return max_value(); state.pos += *state.group++; return state.pos; } public: explicit digit_grouping(locale_ref loc, bool localized = true) { if (!localized) return; auto sep = thousands_sep(loc); grouping_ = sep.grouping; if (sep.thousands_sep) thousands_sep_.assign(1, sep.thousands_sep); } digit_grouping(std::string grouping, std::basic_string sep) : grouping_(std::move(grouping)), thousands_sep_(std::move(sep)) {} auto has_separator() const -> bool { return !thousands_sep_.empty(); } auto count_separators(int num_digits) const -> int { int count = 0; auto state = initial_state(); while (num_digits > next(state)) ++count; return count; } // Applies grouping to digits and write the output to out. template auto apply(Out out, basic_string_view digits) const -> Out { auto num_digits = static_cast(digits.size()); auto separators = basic_memory_buffer(); separators.push_back(0); auto state = initial_state(); while (int i = next(state)) { if (i >= num_digits) break; separators.push_back(i); } for (int i = 0, sep_index = static_cast(separators.size() - 1); i < num_digits; ++i) { if (num_digits - i == separators[sep_index]) { out = copy(thousands_sep_.data(), thousands_sep_.data() + thousands_sep_.size(), out); --sep_index; } *out++ = static_cast(digits[to_unsigned(i)]); } return out; } }; FMT_CONSTEXPR inline void prefix_append(unsigned& prefix, unsigned value) { prefix |= prefix != 0 ? value << 8 : value; prefix += (1u + (value > 0xff ? 1 : 0)) << 24; } // Writes a decimal integer with digit grouping. template auto write_int(OutputIt out, UInt value, unsigned prefix, const format_specs& specs, const digit_grouping& grouping) -> OutputIt { static_assert(std::is_same, UInt>::value, ""); int num_digits = 0; auto buffer = memory_buffer(); switch (specs.type()) { default: FMT_ASSERT(false, ""); FMT_FALLTHROUGH; case presentation_type::none: case presentation_type::dec: num_digits = count_digits(value); format_decimal(appender(buffer), value, num_digits); break; case presentation_type::hex: if (specs.alt()) prefix_append(prefix, unsigned(specs.upper() ? 'X' : 'x') << 8 | '0'); num_digits = count_digits<4>(value); format_base2e(4, appender(buffer), value, num_digits, specs.upper()); break; case presentation_type::oct: num_digits = count_digits<3>(value); // Octal prefix '0' is counted as a digit, so only add it if precision // is not greater than the number of digits. if (specs.alt() && specs.precision <= num_digits && value != 0) prefix_append(prefix, '0'); format_base2e(3, appender(buffer), value, num_digits); break; case presentation_type::bin: if (specs.alt()) prefix_append(prefix, unsigned(specs.upper() ? 'B' : 'b') << 8 | '0'); num_digits = count_digits<1>(value); format_base2e(1, appender(buffer), value, num_digits); break; case presentation_type::chr: return write_char(out, static_cast(value), specs); } unsigned size = (prefix != 0 ? prefix >> 24 : 0) + to_unsigned(num_digits) + to_unsigned(grouping.count_separators(num_digits)); return write_padded( out, specs, size, size, [&](reserve_iterator it) { for (unsigned p = prefix & 0xffffff; p != 0; p >>= 8) *it++ = static_cast(p & 0xff); return grouping.apply(it, string_view(buffer.data(), buffer.size())); }); } #if FMT_USE_LOCALE // Writes a localized value. FMT_API auto write_loc(appender out, loc_value value, const format_specs& specs, locale_ref loc) -> bool; #endif template inline auto write_loc(OutputIt, const loc_value&, const format_specs&, locale_ref) -> bool { return false; } template struct write_int_arg { UInt abs_value; unsigned prefix; }; template FMT_CONSTEXPR auto make_write_int_arg(T value, sign s) -> write_int_arg> { auto prefix = 0u; auto abs_value = static_cast>(value); if (is_negative(value)) { prefix = 0x01000000 | '-'; abs_value = 0 - abs_value; } else { constexpr const unsigned prefixes[4] = {0, 0, 0x1000000u | '+', 0x1000000u | ' '}; prefix = prefixes[static_cast(s)]; } return {abs_value, prefix}; } template struct loc_writer { basic_appender out; const format_specs& specs; std::basic_string sep; std::string grouping; std::basic_string decimal_point; template ::value)> auto operator()(T value) -> bool { auto arg = make_write_int_arg(value, specs.sign()); write_int(out, static_cast>(arg.abs_value), arg.prefix, specs, digit_grouping(grouping, sep)); return true; } template ::value)> auto operator()(T) -> bool { return false; } }; // Size and padding computation separate from write_int to avoid template bloat. struct size_padding { unsigned size; unsigned padding; FMT_CONSTEXPR size_padding(int num_digits, unsigned prefix, const format_specs& specs) : size((prefix >> 24) + to_unsigned(num_digits)), padding(0) { if (specs.align() == align::numeric) { auto width = to_unsigned(specs.width); if (width > size) { padding = width - size; size = width; } } else if (specs.precision > num_digits) { size = (prefix >> 24) + to_unsigned(specs.precision); padding = to_unsigned(specs.precision - num_digits); } } }; template FMT_CONSTEXPR FMT_INLINE auto write_int(OutputIt out, write_int_arg arg, const format_specs& specs) -> OutputIt { static_assert(std::is_same>::value, ""); constexpr int buffer_size = num_bits(); char buffer[buffer_size]; if (is_constant_evaluated()) fill_n(buffer, buffer_size, '\0'); const char* begin = nullptr; const char* end = buffer + buffer_size; auto abs_value = arg.abs_value; auto prefix = arg.prefix; switch (specs.type()) { default: FMT_ASSERT(false, ""); FMT_FALLTHROUGH; case presentation_type::none: case presentation_type::dec: begin = do_format_decimal(buffer, abs_value, buffer_size); break; case presentation_type::hex: begin = do_format_base2e(4, buffer, abs_value, buffer_size, specs.upper()); if (specs.alt()) prefix_append(prefix, unsigned(specs.upper() ? 'X' : 'x') << 8 | '0'); break; case presentation_type::oct: { begin = do_format_base2e(3, buffer, abs_value, buffer_size); // Octal prefix '0' is counted as a digit, so only add it if precision // is not greater than the number of digits. auto num_digits = end - begin; if (specs.alt() && specs.precision <= num_digits && abs_value != 0) prefix_append(prefix, '0'); break; } case presentation_type::bin: begin = do_format_base2e(1, buffer, abs_value, buffer_size); if (specs.alt()) prefix_append(prefix, unsigned(specs.upper() ? 'B' : 'b') << 8 | '0'); break; case presentation_type::chr: return write_char(out, static_cast(abs_value), specs); } // Write an integer in the format // // prefix contains chars in three lower bytes and the size in the fourth byte. int num_digits = static_cast(end - begin); // Slightly faster check for specs.width == 0 && specs.precision == -1. if ((specs.width | (specs.precision + 1)) == 0) { auto it = reserve(out, to_unsigned(num_digits) + (prefix >> 24)); for (unsigned p = prefix & 0xffffff; p != 0; p >>= 8) *it++ = static_cast(p & 0xff); return base_iterator(out, copy(begin, end, it)); } auto sp = size_padding(num_digits, prefix, specs); unsigned padding = sp.padding; return write_padded( out, specs, sp.size, [=](reserve_iterator it) { for (unsigned p = prefix & 0xffffff; p != 0; p >>= 8) *it++ = static_cast(p & 0xff); it = detail::fill_n(it, padding, static_cast('0')); return copy(begin, end, it); }); } template FMT_CONSTEXPR FMT_NOINLINE auto write_int_noinline(OutputIt out, write_int_arg arg, const format_specs& specs) -> OutputIt { return write_int(out, arg, specs); } template ::value && !std::is_same::value && !std::is_same::value)> FMT_CONSTEXPR FMT_INLINE auto write(basic_appender out, T value, const format_specs& specs, locale_ref loc) -> basic_appender { if (specs.localized() && write_loc(out, value, specs, loc)) return out; return write_int_noinline(out, make_write_int_arg(value, specs.sign()), specs); } // An inlined version of write used in format string compilation. template ::value && !std::is_same::value && !std::is_same::value && !std::is_same>::value)> FMT_CONSTEXPR FMT_INLINE auto write(OutputIt out, T value, const format_specs& specs, locale_ref loc) -> OutputIt { if (specs.localized() && write_loc(out, value, specs, loc)) return out; return write_int(out, make_write_int_arg(value, specs.sign()), specs); } template FMT_CONSTEXPR auto write(OutputIt out, basic_string_view s, const format_specs& specs) -> OutputIt { auto data = s.data(); auto size = s.size(); if (specs.precision >= 0 && to_unsigned(specs.precision) < size) size = code_point_index(s, to_unsigned(specs.precision)); bool is_debug = specs.type() == presentation_type::debug; if (is_debug) { auto buf = counting_buffer(); write_escaped_string(basic_appender(buf), s); size = buf.count(); } size_t width = 0; if (specs.width != 0) { width = is_debug ? size : compute_width(basic_string_view(data, size)); } return write_padded( out, specs, size, width, [=](reserve_iterator it) { return is_debug ? write_escaped_string(it, s) : copy(data, data + size, it); }); } template FMT_CONSTEXPR auto write(OutputIt out, basic_string_view s, const format_specs& specs, locale_ref) -> OutputIt { return write(out, s, specs); } template FMT_CONSTEXPR auto write(OutputIt out, const Char* s, const format_specs& specs, locale_ref) -> OutputIt { if (specs.type() == presentation_type::pointer) return write_ptr(out, bit_cast(s), &specs); if (!s) report_error("string pointer is null"); return write(out, basic_string_view(s), specs, {}); } template ::value && !std::is_same::value && !std::is_same::value)> FMT_CONSTEXPR auto write(OutputIt out, T value) -> OutputIt { auto abs_value = static_cast>(value); bool negative = is_negative(value); // Don't do -abs_value since it trips unsigned-integer-overflow sanitizer. if (negative) abs_value = ~abs_value + 1; int num_digits = count_digits(abs_value); auto size = (negative ? 1 : 0) + static_cast(num_digits); if (auto ptr = to_pointer(out, size)) { if (negative) *ptr++ = static_cast('-'); format_decimal(ptr, abs_value, num_digits); return out; } if (negative) *out++ = static_cast('-'); return format_decimal(out, abs_value, num_digits); } template FMT_CONSTEXPR auto parse_align(const Char* begin, const Char* end, format_specs& specs) -> const Char* { FMT_ASSERT(begin != end, ""); auto alignment = align::none; auto p = begin + code_point_length(begin); if (end - p <= 0) p = begin; for (;;) { switch (to_ascii(*p)) { case '<': alignment = align::left; break; case '>': alignment = align::right; break; case '^': alignment = align::center; break; } if (alignment != align::none) { if (p != begin) { auto c = *begin; if (c == '}') return begin; if (c == '{') { report_error("invalid fill character '{'"); return begin; } specs.set_fill(basic_string_view(begin, to_unsigned(p - begin))); begin = p + 1; } else { ++begin; } break; } else if (p == begin) { break; } p = begin; } specs.set_align(alignment); return begin; } template FMT_CONSTEXPR20 auto write_nonfinite(OutputIt out, bool isnan, format_specs specs, sign s) -> OutputIt { auto str = isnan ? (specs.upper() ? "NAN" : "nan") : (specs.upper() ? "INF" : "inf"); constexpr size_t str_size = 3; auto size = str_size + (s != sign::none ? 1 : 0); // Replace '0'-padding with space for non-finite values. const bool is_zero_fill = specs.fill_size() == 1 && specs.fill_unit() == '0'; if (is_zero_fill) specs.set_fill(' '); return write_padded(out, specs, size, [=](reserve_iterator it) { if (s != sign::none) *it++ = detail::getsign(s); return copy(str, str + str_size, it); }); } // A decimal floating-point number significand * pow(10, exp). struct big_decimal_fp { const char* significand; int significand_size; int exponent; }; constexpr auto get_significand_size(const big_decimal_fp& f) -> int { return f.significand_size; } template inline auto get_significand_size(const dragonbox::decimal_fp& f) -> int { return count_digits(f.significand); } template constexpr auto write_significand(OutputIt out, const char* significand, int significand_size) -> OutputIt { return copy(significand, significand + significand_size, out); } template inline auto write_significand(OutputIt out, UInt significand, int significand_size) -> OutputIt { return format_decimal(out, significand, significand_size); } template FMT_CONSTEXPR20 auto write_significand(OutputIt out, T significand, int significand_size, int exponent, const Grouping& grouping) -> OutputIt { if (!grouping.has_separator()) { out = write_significand(out, significand, significand_size); return detail::fill_n(out, exponent, static_cast('0')); } auto buffer = memory_buffer(); write_significand(appender(buffer), significand, significand_size); detail::fill_n(appender(buffer), exponent, '0'); return grouping.apply(out, string_view(buffer.data(), buffer.size())); } template ::value)> inline auto write_significand(Char* out, UInt significand, int significand_size, int integral_size, Char decimal_point) -> Char* { if (!decimal_point) return format_decimal(out, significand, significand_size); out += significand_size + 1; Char* end = out; int floating_size = significand_size - integral_size; for (int i = floating_size / 2; i > 0; --i) { out -= 2; write2digits(out, static_cast(significand % 100)); significand /= 100; } if (floating_size % 2 != 0) { *--out = static_cast('0' + significand % 10); significand /= 10; } *--out = decimal_point; format_decimal(out - integral_size, significand, integral_size); return end; } template >::value)> inline auto write_significand(OutputIt out, UInt significand, int significand_size, int integral_size, Char decimal_point) -> OutputIt { // Buffer is large enough to hold digits (digits10 + 1) and a decimal point. Char buffer[digits10() + 2]; auto end = write_significand(buffer, significand, significand_size, integral_size, decimal_point); return detail::copy_noinline(buffer, end, out); } template FMT_CONSTEXPR auto write_significand(OutputIt out, const char* significand, int significand_size, int integral_size, Char decimal_point) -> OutputIt { out = detail::copy_noinline(significand, significand + integral_size, out); if (!decimal_point) return out; *out++ = decimal_point; return detail::copy_noinline(significand + integral_size, significand + significand_size, out); } template FMT_CONSTEXPR20 auto write_significand(OutputIt out, T significand, int significand_size, int integral_size, Char decimal_point, const Grouping& grouping) -> OutputIt { if (!grouping.has_separator()) { return write_significand(out, significand, significand_size, integral_size, decimal_point); } auto buffer = basic_memory_buffer(); write_significand(basic_appender(buffer), significand, significand_size, integral_size, decimal_point); grouping.apply( out, basic_string_view(buffer.data(), to_unsigned(integral_size))); return detail::copy_noinline(buffer.data() + integral_size, buffer.end(), out); } template > FMT_CONSTEXPR20 auto do_write_float(OutputIt out, const DecimalFP& f, const format_specs& specs, sign s, locale_ref loc) -> OutputIt { auto significand = f.significand; int significand_size = get_significand_size(f); const Char zero = static_cast('0'); size_t size = to_unsigned(significand_size) + (s != sign::none ? 1 : 0); using iterator = reserve_iterator; Char decimal_point = specs.localized() ? detail::decimal_point(loc) : static_cast('.'); int output_exp = f.exponent + significand_size - 1; auto use_exp_format = [=]() { if (specs.type() == presentation_type::exp) return true; if (specs.type() == presentation_type::fixed) return false; // Use the fixed notation if the exponent is in [exp_lower, exp_upper), // e.g. 0.0001 instead of 1e-04. Otherwise use the exponent notation. const int exp_lower = -4, exp_upper = 16; return output_exp < exp_lower || output_exp >= (specs.precision > 0 ? specs.precision : exp_upper); }; if (use_exp_format()) { int num_zeros = 0; if (specs.alt()) { num_zeros = specs.precision - significand_size; if (num_zeros < 0) num_zeros = 0; size += to_unsigned(num_zeros); } else if (significand_size == 1) { decimal_point = Char(); } auto abs_output_exp = output_exp >= 0 ? output_exp : -output_exp; int exp_digits = 2; if (abs_output_exp >= 100) exp_digits = abs_output_exp >= 1000 ? 4 : 3; size += to_unsigned((decimal_point ? 1 : 0) + 2 + exp_digits); char exp_char = specs.upper() ? 'E' : 'e'; auto write = [=](iterator it) { if (s != sign::none) *it++ = detail::getsign(s); // Insert a decimal point after the first digit and add an exponent. it = write_significand(it, significand, significand_size, 1, decimal_point); if (num_zeros > 0) it = detail::fill_n(it, num_zeros, zero); *it++ = static_cast(exp_char); return write_exponent(output_exp, it); }; return specs.width > 0 ? write_padded(out, specs, size, write) : base_iterator(out, write(reserve(out, size))); } int exp = f.exponent + significand_size; if (f.exponent >= 0) { // 1234e5 -> 123400000[.0+] size += to_unsigned(f.exponent); int num_zeros = specs.precision - exp; abort_fuzzing_if(num_zeros > 5000); if (specs.alt()) { ++size; if (num_zeros <= 0 && specs.type() != presentation_type::fixed) num_zeros = 0; if (num_zeros > 0) size += to_unsigned(num_zeros); } auto grouping = Grouping(loc, specs.localized()); size += to_unsigned(grouping.count_separators(exp)); return write_padded(out, specs, size, [&](iterator it) { if (s != sign::none) *it++ = detail::getsign(s); it = write_significand(it, significand, significand_size, f.exponent, grouping); if (!specs.alt()) return it; *it++ = decimal_point; return num_zeros > 0 ? detail::fill_n(it, num_zeros, zero) : it; }); } else if (exp > 0) { // 1234e-2 -> 12.34[0+] int num_zeros = specs.alt() ? specs.precision - significand_size : 0; size += 1 + static_cast(max_of(num_zeros, 0)); auto grouping = Grouping(loc, specs.localized()); size += to_unsigned(grouping.count_separators(exp)); return write_padded(out, specs, size, [&](iterator it) { if (s != sign::none) *it++ = detail::getsign(s); it = write_significand(it, significand, significand_size, exp, decimal_point, grouping); return num_zeros > 0 ? detail::fill_n(it, num_zeros, zero) : it; }); } // 1234e-6 -> 0.001234 int num_zeros = -exp; if (significand_size == 0 && specs.precision >= 0 && specs.precision < num_zeros) { num_zeros = specs.precision; } bool pointy = num_zeros != 0 || significand_size != 0 || specs.alt(); size += 1 + (pointy ? 1 : 0) + to_unsigned(num_zeros); return write_padded(out, specs, size, [&](iterator it) { if (s != sign::none) *it++ = detail::getsign(s); *it++ = zero; if (!pointy) return it; *it++ = decimal_point; it = detail::fill_n(it, num_zeros, zero); return write_significand(it, significand, significand_size); }); } template class fallback_digit_grouping { public: constexpr fallback_digit_grouping(locale_ref, bool) {} constexpr auto has_separator() const -> bool { return false; } constexpr auto count_separators(int) const -> int { return 0; } template constexpr auto apply(Out out, basic_string_view) const -> Out { return out; } }; template FMT_CONSTEXPR20 auto write_float(OutputIt out, const DecimalFP& f, const format_specs& specs, sign s, locale_ref loc) -> OutputIt { if (is_constant_evaluated()) { return do_write_float>(out, f, specs, s, loc); } else { return do_write_float(out, f, specs, s, loc); } } template constexpr auto isnan(T value) -> bool { return value != value; // std::isnan doesn't support __float128. } template struct has_isfinite : std::false_type {}; template struct has_isfinite> : std::true_type {}; template ::value&& has_isfinite::value)> FMT_CONSTEXPR20 auto isfinite(T value) -> bool { constexpr T inf = T(std::numeric_limits::infinity()); if (is_constant_evaluated()) return !detail::isnan(value) && value < inf && value > -inf; return std::isfinite(value); } template ::value)> FMT_CONSTEXPR auto isfinite(T value) -> bool { T inf = T(std::numeric_limits::infinity()); // std::isfinite doesn't support __float128. return !detail::isnan(value) && value < inf && value > -inf; } template ::value)> FMT_INLINE FMT_CONSTEXPR bool signbit(T value) { if (is_constant_evaluated()) { #ifdef __cpp_if_constexpr if constexpr (std::numeric_limits::is_iec559) { auto bits = detail::bit_cast(static_cast(value)); return (bits >> (num_bits() - 1)) != 0; } #endif } return std::signbit(static_cast(value)); } inline FMT_CONSTEXPR20 void adjust_precision(int& precision, int exp10) { // Adjust fixed precision by exponent because it is relative to decimal // point. if (exp10 > 0 && precision > max_value() - exp10) FMT_THROW(format_error("number is too big")); precision += exp10; } class bigint { private: // A bigint is a number in the form bigit_[N - 1] ... bigit_[0] * 32^exp_. using bigit = uint32_t; // A big digit. using double_bigit = uint64_t; enum { bigit_bits = num_bits() }; enum { bigits_capacity = 32 }; basic_memory_buffer bigits_; int exp_; friend struct formatter; FMT_CONSTEXPR auto get_bigit(int i) const -> bigit { return i >= exp_ && i < num_bigits() ? bigits_[i - exp_] : 0; } FMT_CONSTEXPR void subtract_bigits(int index, bigit other, bigit& borrow) { auto result = double_bigit(bigits_[index]) - other - borrow; bigits_[index] = static_cast(result); borrow = static_cast(result >> (bigit_bits * 2 - 1)); } FMT_CONSTEXPR void remove_leading_zeros() { int num_bigits = static_cast(bigits_.size()) - 1; while (num_bigits > 0 && bigits_[num_bigits] == 0) --num_bigits; bigits_.resize(to_unsigned(num_bigits + 1)); } // Computes *this -= other assuming aligned bigints and *this >= other. FMT_CONSTEXPR void subtract_aligned(const bigint& other) { FMT_ASSERT(other.exp_ >= exp_, "unaligned bigints"); FMT_ASSERT(compare(*this, other) >= 0, ""); bigit borrow = 0; int i = other.exp_ - exp_; for (size_t j = 0, n = other.bigits_.size(); j != n; ++i, ++j) subtract_bigits(i, other.bigits_[j], borrow); if (borrow != 0) subtract_bigits(i, 0, borrow); FMT_ASSERT(borrow == 0, ""); remove_leading_zeros(); } FMT_CONSTEXPR void multiply(uint32_t value) { bigit carry = 0; const double_bigit wide_value = value; for (size_t i = 0, n = bigits_.size(); i < n; ++i) { double_bigit result = bigits_[i] * wide_value + carry; bigits_[i] = static_cast(result); carry = static_cast(result >> bigit_bits); } if (carry != 0) bigits_.push_back(carry); } template ::value || std::is_same::value)> FMT_CONSTEXPR void multiply(UInt value) { using half_uint = conditional_t::value, uint64_t, uint32_t>; const int shift = num_bits() - bigit_bits; const UInt lower = static_cast(value); const UInt upper = value >> num_bits(); UInt carry = 0; for (size_t i = 0, n = bigits_.size(); i < n; ++i) { UInt result = lower * bigits_[i] + static_cast(carry); carry = (upper * bigits_[i] << shift) + (result >> bigit_bits) + (carry >> bigit_bits); bigits_[i] = static_cast(result); } while (carry != 0) { bigits_.push_back(static_cast(carry)); carry >>= bigit_bits; } } template ::value || std::is_same::value)> FMT_CONSTEXPR void assign(UInt n) { size_t num_bigits = 0; do { bigits_[num_bigits++] = static_cast(n); n >>= bigit_bits; } while (n != 0); bigits_.resize(num_bigits); exp_ = 0; } public: FMT_CONSTEXPR bigint() : exp_(0) {} explicit bigint(uint64_t n) { assign(n); } bigint(const bigint&) = delete; void operator=(const bigint&) = delete; FMT_CONSTEXPR void assign(const bigint& other) { auto size = other.bigits_.size(); bigits_.resize(size); auto data = other.bigits_.data(); copy(data, data + size, bigits_.data()); exp_ = other.exp_; } template FMT_CONSTEXPR void operator=(Int n) { FMT_ASSERT(n > 0, ""); assign(uint64_or_128_t(n)); } FMT_CONSTEXPR auto num_bigits() const -> int { return static_cast(bigits_.size()) + exp_; } FMT_CONSTEXPR auto operator<<=(int shift) -> bigint& { FMT_ASSERT(shift >= 0, ""); exp_ += shift / bigit_bits; shift %= bigit_bits; if (shift == 0) return *this; bigit carry = 0; for (size_t i = 0, n = bigits_.size(); i < n; ++i) { bigit c = bigits_[i] >> (bigit_bits - shift); bigits_[i] = (bigits_[i] << shift) + carry; carry = c; } if (carry != 0) bigits_.push_back(carry); return *this; } template FMT_CONSTEXPR auto operator*=(Int value) -> bigint& { FMT_ASSERT(value > 0, ""); multiply(uint32_or_64_or_128_t(value)); return *this; } friend FMT_CONSTEXPR auto compare(const bigint& b1, const bigint& b2) -> int { int num_bigits1 = b1.num_bigits(), num_bigits2 = b2.num_bigits(); if (num_bigits1 != num_bigits2) return num_bigits1 > num_bigits2 ? 1 : -1; int i = static_cast(b1.bigits_.size()) - 1; int j = static_cast(b2.bigits_.size()) - 1; int end = i - j; if (end < 0) end = 0; for (; i >= end; --i, --j) { bigit b1_bigit = b1.bigits_[i], b2_bigit = b2.bigits_[j]; if (b1_bigit != b2_bigit) return b1_bigit > b2_bigit ? 1 : -1; } if (i != j) return i > j ? 1 : -1; return 0; } // Returns compare(lhs1 + lhs2, rhs). friend FMT_CONSTEXPR auto add_compare(const bigint& lhs1, const bigint& lhs2, const bigint& rhs) -> int { int max_lhs_bigits = max_of(lhs1.num_bigits(), lhs2.num_bigits()); int num_rhs_bigits = rhs.num_bigits(); if (max_lhs_bigits + 1 < num_rhs_bigits) return -1; if (max_lhs_bigits > num_rhs_bigits) return 1; double_bigit borrow = 0; int min_exp = min_of(min_of(lhs1.exp_, lhs2.exp_), rhs.exp_); for (int i = num_rhs_bigits - 1; i >= min_exp; --i) { double_bigit sum = double_bigit(lhs1.get_bigit(i)) + lhs2.get_bigit(i); bigit rhs_bigit = rhs.get_bigit(i); if (sum > rhs_bigit + borrow) return 1; borrow = rhs_bigit + borrow - sum; if (borrow > 1) return -1; borrow <<= bigit_bits; } return borrow != 0 ? -1 : 0; } // Assigns pow(10, exp) to this bigint. FMT_CONSTEXPR20 void assign_pow10(int exp) { FMT_ASSERT(exp >= 0, ""); if (exp == 0) return *this = 1; int bitmask = 1 << (num_bits() - countl_zero(static_cast(exp)) - 1); // pow(10, exp) = pow(5, exp) * pow(2, exp). First compute pow(5, exp) by // repeated squaring and multiplication. *this = 5; bitmask >>= 1; while (bitmask != 0) { square(); if ((exp & bitmask) != 0) *this *= 5; bitmask >>= 1; } *this <<= exp; // Multiply by pow(2, exp) by shifting. } FMT_CONSTEXPR20 void square() { int num_bigits = static_cast(bigits_.size()); int num_result_bigits = 2 * num_bigits; basic_memory_buffer n(std::move(bigits_)); bigits_.resize(to_unsigned(num_result_bigits)); auto sum = uint128_t(); for (int bigit_index = 0; bigit_index < num_bigits; ++bigit_index) { // Compute bigit at position bigit_index of the result by adding // cross-product terms n[i] * n[j] such that i + j == bigit_index. for (int i = 0, j = bigit_index; j >= 0; ++i, --j) { // Most terms are multiplied twice which can be optimized in the future. sum += double_bigit(n[i]) * n[j]; } bigits_[bigit_index] = static_cast(sum); sum >>= num_bits(); // Compute the carry. } // Do the same for the top half. for (int bigit_index = num_bigits; bigit_index < num_result_bigits; ++bigit_index) { for (int j = num_bigits - 1, i = bigit_index - j; i < num_bigits;) sum += double_bigit(n[i++]) * n[j--]; bigits_[bigit_index] = static_cast(sum); sum >>= num_bits(); } remove_leading_zeros(); exp_ *= 2; } // If this bigint has a bigger exponent than other, adds trailing zero to make // exponents equal. This simplifies some operations such as subtraction. FMT_CONSTEXPR void align(const bigint& other) { int exp_difference = exp_ - other.exp_; if (exp_difference <= 0) return; int num_bigits = static_cast(bigits_.size()); bigits_.resize(to_unsigned(num_bigits + exp_difference)); for (int i = num_bigits - 1, j = i + exp_difference; i >= 0; --i, --j) bigits_[j] = bigits_[i]; memset(bigits_.data(), 0, to_unsigned(exp_difference) * sizeof(bigit)); exp_ -= exp_difference; } // Divides this bignum by divisor, assigning the remainder to this and // returning the quotient. FMT_CONSTEXPR auto divmod_assign(const bigint& divisor) -> int { FMT_ASSERT(this != &divisor, ""); if (compare(*this, divisor) < 0) return 0; FMT_ASSERT(divisor.bigits_[divisor.bigits_.size() - 1u] != 0, ""); align(divisor); int quotient = 0; do { subtract_aligned(divisor); ++quotient; } while (compare(*this, divisor) >= 0); return quotient; } }; // format_dragon flags. enum dragon { predecessor_closer = 1, fixup = 2, // Run fixup to correct exp10 which can be off by one. fixed = 4, }; // Formats a floating-point number using a variation of the Fixed-Precision // Positive Floating-Point Printout ((FPP)^2) algorithm by Steele & White: // https://fmt.dev/papers/p372-steele.pdf. FMT_CONSTEXPR20 inline void format_dragon(basic_fp value, unsigned flags, int num_digits, buffer& buf, int& exp10) { bigint numerator; // 2 * R in (FPP)^2. bigint denominator; // 2 * S in (FPP)^2. // lower and upper are differences between value and corresponding boundaries. bigint lower; // (M^- in (FPP)^2). bigint upper_store; // upper's value if different from lower. bigint* upper = nullptr; // (M^+ in (FPP)^2). // Shift numerator and denominator by an extra bit or two (if lower boundary // is closer) to make lower and upper integers. This eliminates multiplication // by 2 during later computations. bool is_predecessor_closer = (flags & dragon::predecessor_closer) != 0; int shift = is_predecessor_closer ? 2 : 1; if (value.e >= 0) { numerator = value.f; numerator <<= value.e + shift; lower = 1; lower <<= value.e; if (is_predecessor_closer) { upper_store = 1; upper_store <<= value.e + 1; upper = &upper_store; } denominator.assign_pow10(exp10); denominator <<= shift; } else if (exp10 < 0) { numerator.assign_pow10(-exp10); lower.assign(numerator); if (is_predecessor_closer) { upper_store.assign(numerator); upper_store <<= 1; upper = &upper_store; } numerator *= value.f; numerator <<= shift; denominator = 1; denominator <<= shift - value.e; } else { numerator = value.f; numerator <<= shift; denominator.assign_pow10(exp10); denominator <<= shift - value.e; lower = 1; if (is_predecessor_closer) { upper_store = 1ULL << 1; upper = &upper_store; } } int even = static_cast((value.f & 1) == 0); if (!upper) upper = &lower; bool shortest = num_digits < 0; if ((flags & dragon::fixup) != 0) { if (add_compare(numerator, *upper, denominator) + even <= 0) { --exp10; numerator *= 10; if (num_digits < 0) { lower *= 10; if (upper != &lower) *upper *= 10; } } if ((flags & dragon::fixed) != 0) adjust_precision(num_digits, exp10 + 1); } // Invariant: value == (numerator / denominator) * pow(10, exp10). if (shortest) { // Generate the shortest representation. num_digits = 0; char* data = buf.data(); for (;;) { int digit = numerator.divmod_assign(denominator); bool low = compare(numerator, lower) - even < 0; // numerator <[=] lower. // numerator + upper >[=] pow10: bool high = add_compare(numerator, *upper, denominator) + even > 0; data[num_digits++] = static_cast('0' + digit); if (low || high) { if (!low) { ++data[num_digits - 1]; } else if (high) { int result = add_compare(numerator, numerator, denominator); // Round half to even. if (result > 0 || (result == 0 && (digit % 2) != 0)) ++data[num_digits - 1]; } buf.try_resize(to_unsigned(num_digits)); exp10 -= num_digits - 1; return; } numerator *= 10; lower *= 10; if (upper != &lower) *upper *= 10; } } // Generate the given number of digits. exp10 -= num_digits - 1; if (num_digits <= 0) { auto digit = '0'; if (num_digits == 0) { denominator *= 10; digit = add_compare(numerator, numerator, denominator) > 0 ? '1' : '0'; } buf.push_back(digit); return; } buf.try_resize(to_unsigned(num_digits)); for (int i = 0; i < num_digits - 1; ++i) { int digit = numerator.divmod_assign(denominator); buf[i] = static_cast('0' + digit); numerator *= 10; } int digit = numerator.divmod_assign(denominator); auto result = add_compare(numerator, numerator, denominator); if (result > 0 || (result == 0 && (digit % 2) != 0)) { if (digit == 9) { const auto overflow = '0' + 10; buf[num_digits - 1] = overflow; // Propagate the carry. for (int i = num_digits - 1; i > 0 && buf[i] == overflow; --i) { buf[i] = '0'; ++buf[i - 1]; } if (buf[0] == overflow) { buf[0] = '1'; if ((flags & dragon::fixed) != 0) buf.push_back('0'); else ++exp10; } return; } ++digit; } buf[num_digits - 1] = static_cast('0' + digit); } // Formats a floating-point number using the hexfloat format. template ::value)> FMT_CONSTEXPR20 void format_hexfloat(Float value, format_specs specs, buffer& buf) { // float is passed as double to reduce the number of instantiations and to // simplify implementation. static_assert(!std::is_same::value, ""); using info = dragonbox::float_info; // Assume Float is in the format [sign][exponent][significand]. using carrier_uint = typename info::carrier_uint; const auto num_float_significand_bits = detail::num_significand_bits(); basic_fp f(value); f.e += num_float_significand_bits; if (!has_implicit_bit()) --f.e; const auto num_fraction_bits = num_float_significand_bits + (has_implicit_bit() ? 1 : 0); const auto num_xdigits = (num_fraction_bits + 3) / 4; const auto leading_shift = ((num_xdigits - 1) * 4); const auto leading_mask = carrier_uint(0xF) << leading_shift; const auto leading_xdigit = static_cast((f.f & leading_mask) >> leading_shift); if (leading_xdigit > 1) f.e -= (32 - countl_zero(leading_xdigit) - 1); int print_xdigits = num_xdigits - 1; if (specs.precision >= 0 && print_xdigits > specs.precision) { const int shift = ((print_xdigits - specs.precision - 1) * 4); const auto mask = carrier_uint(0xF) << shift; const auto v = static_cast((f.f & mask) >> shift); if (v >= 8) { const auto inc = carrier_uint(1) << (shift + 4); f.f += inc; f.f &= ~(inc - 1); } // Check long double overflow if (!has_implicit_bit()) { const auto implicit_bit = carrier_uint(1) << num_float_significand_bits; if ((f.f & implicit_bit) == implicit_bit) { f.f >>= 4; f.e += 4; } } print_xdigits = specs.precision; } char xdigits[num_bits() / 4]; detail::fill_n(xdigits, sizeof(xdigits), '0'); format_base2e(4, xdigits, f.f, num_xdigits, specs.upper()); // Remove zero tail while (print_xdigits > 0 && xdigits[print_xdigits] == '0') --print_xdigits; buf.push_back('0'); buf.push_back(specs.upper() ? 'X' : 'x'); buf.push_back(xdigits[0]); if (specs.alt() || print_xdigits > 0 || print_xdigits < specs.precision) buf.push_back('.'); buf.append(xdigits + 1, xdigits + 1 + print_xdigits); for (; print_xdigits < specs.precision; ++print_xdigits) buf.push_back('0'); buf.push_back(specs.upper() ? 'P' : 'p'); uint32_t abs_e; if (f.e < 0) { buf.push_back('-'); abs_e = static_cast(-f.e); } else { buf.push_back('+'); abs_e = static_cast(f.e); } format_decimal(appender(buf), abs_e, detail::count_digits(abs_e)); } template ::value)> FMT_CONSTEXPR20 void format_hexfloat(Float value, format_specs specs, buffer& buf) { format_hexfloat(static_cast(value), specs, buf); } constexpr auto fractional_part_rounding_thresholds(int index) -> uint32_t { // For checking rounding thresholds. // The kth entry is chosen to be the smallest integer such that the // upper 32-bits of 10^(k+1) times it is strictly bigger than 5 * 10^k. // It is equal to ceil(2^31 + 2^32/10^(k + 1)). // These are stored in a string literal because we cannot have static arrays // in constexpr functions and non-static ones are poorly optimized. return U"\x9999999a\x828f5c29\x80418938\x80068db9\x8000a7c6\x800010c7" U"\x800001ae\x8000002b"[index]; } template FMT_CONSTEXPR20 auto format_float(Float value, int precision, const format_specs& specs, bool binary32, buffer& buf) -> int { // float is passed as double to reduce the number of instantiations. static_assert(!std::is_same::value, ""); auto converted_value = convert_float(value); const bool fixed = specs.type() == presentation_type::fixed; if (value == 0) { if (precision <= 0 || !fixed) { buf.push_back('0'); return 0; } buf.try_resize(to_unsigned(precision)); fill_n(buf.data(), precision, '0'); return -precision; } int exp = 0; bool use_dragon = true; unsigned dragon_flags = 0; if (!is_fast_float() || is_constant_evaluated()) { const auto inv_log2_10 = 0.3010299956639812; // 1 / log2(10) using info = dragonbox::float_info; const auto f = basic_fp(converted_value); // Compute exp, an approximate power of 10, such that // 10^(exp - 1) <= value < 10^exp or 10^exp <= value < 10^(exp + 1). // This is based on log10(value) == log2(value) / log2(10) and approximation // of log2(value) by e + num_fraction_bits idea from double-conversion. auto e = (f.e + count_digits<1>(f.f) - 1) * inv_log2_10 - 1e-10; exp = static_cast(e); if (e > exp) ++exp; // Compute ceil. dragon_flags = dragon::fixup; } else { // Extract significand bits and exponent bits. using info = dragonbox::float_info; auto br = bit_cast(static_cast(value)); const uint64_t significand_mask = (static_cast(1) << num_significand_bits()) - 1; uint64_t significand = (br & significand_mask); int exponent = static_cast((br & exponent_mask()) >> num_significand_bits()); if (exponent != 0) { // Check if normal. exponent -= exponent_bias() + num_significand_bits(); significand |= (static_cast(1) << num_significand_bits()); significand <<= 1; } else { // Normalize subnormal inputs. FMT_ASSERT(significand != 0, "zeros should not appear here"); int shift = countl_zero(significand); FMT_ASSERT(shift >= num_bits() - num_significand_bits(), ""); shift -= (num_bits() - num_significand_bits() - 2); exponent = (std::numeric_limits::min_exponent - num_significand_bits()) - shift; significand <<= shift; } // Compute the first several nonzero decimal significand digits. // We call the number we get the first segment. const int k = info::kappa - dragonbox::floor_log10_pow2(exponent); exp = -k; const int beta = exponent + dragonbox::floor_log2_pow10(k); uint64_t first_segment; bool has_more_segments; int digits_in_the_first_segment; { const auto r = dragonbox::umul192_upper128( significand << beta, dragonbox::get_cached_power(k)); first_segment = r.high(); has_more_segments = r.low() != 0; // The first segment can have 18 ~ 19 digits. if (first_segment >= 1000000000000000000ULL) { digits_in_the_first_segment = 19; } else { // When it is of 18-digits, we align it to 19-digits by adding a bogus // zero at the end. digits_in_the_first_segment = 18; first_segment *= 10; } } // Compute the actual number of decimal digits to print. if (fixed) adjust_precision(precision, exp + digits_in_the_first_segment); // Use Dragon4 only when there might be not enough digits in the first // segment. if (digits_in_the_first_segment > precision) { use_dragon = false; if (precision <= 0) { exp += digits_in_the_first_segment; if (precision < 0) { // Nothing to do, since all we have are just leading zeros. buf.try_resize(0); } else { // We may need to round-up. buf.try_resize(1); if ((first_segment | static_cast(has_more_segments)) > 5000000000000000000ULL) { buf[0] = '1'; } else { buf[0] = '0'; } } } // precision <= 0 else { exp += digits_in_the_first_segment - precision; // When precision > 0, we divide the first segment into three // subsegments, each with 9, 9, and 0 ~ 1 digits so that each fits // in 32-bits which usually allows faster calculation than in // 64-bits. Since some compiler (e.g. MSVC) doesn't know how to optimize // division-by-constant for large 64-bit divisors, we do it here // manually. The magic number 7922816251426433760 below is equal to // ceil(2^(64+32) / 10^10). const uint32_t first_subsegment = static_cast( dragonbox::umul128_upper64(first_segment, 7922816251426433760ULL) >> 32); const uint64_t second_third_subsegments = first_segment - first_subsegment * 10000000000ULL; uint64_t prod; uint32_t digits; bool should_round_up; int number_of_digits_to_print = min_of(precision, 9); // Print a 9-digits subsegment, either the first or the second. auto print_subsegment = [&](uint32_t subsegment, char* buffer) { int number_of_digits_printed = 0; // If we want to print an odd number of digits from the subsegment, if ((number_of_digits_to_print & 1) != 0) { // Convert to 64-bit fixed-point fractional form with 1-digit // integer part. The magic number 720575941 is a good enough // approximation of 2^(32 + 24) / 10^8; see // https://jk-jeon.github.io/posts/2022/12/fixed-precision-formatting/#fixed-length-case // for details. prod = ((subsegment * static_cast(720575941)) >> 24) + 1; digits = static_cast(prod >> 32); *buffer = static_cast('0' + digits); number_of_digits_printed++; } // If we want to print an even number of digits from the // first_subsegment, else { // Convert to 64-bit fixed-point fractional form with 2-digits // integer part. The magic number 450359963 is a good enough // approximation of 2^(32 + 20) / 10^7; see // https://jk-jeon.github.io/posts/2022/12/fixed-precision-formatting/#fixed-length-case // for details. prod = ((subsegment * static_cast(450359963)) >> 20) + 1; digits = static_cast(prod >> 32); write2digits(buffer, digits); number_of_digits_printed += 2; } // Print all digit pairs. while (number_of_digits_printed < number_of_digits_to_print) { prod = static_cast(prod) * static_cast(100); digits = static_cast(prod >> 32); write2digits(buffer + number_of_digits_printed, digits); number_of_digits_printed += 2; } }; // Print first subsegment. print_subsegment(first_subsegment, buf.data()); // Perform rounding if the first subsegment is the last subsegment to // print. if (precision <= 9) { // Rounding inside the subsegment. // We round-up if: // - either the fractional part is strictly larger than 1/2, or // - the fractional part is exactly 1/2 and the last digit is odd. // We rely on the following observations: // - If fractional_part >= threshold, then the fractional part is // strictly larger than 1/2. // - If the MSB of fractional_part is set, then the fractional part // must be at least 1/2. // - When the MSB of fractional_part is set, either // second_third_subsegments being nonzero or has_more_segments // being true means there are further digits not printed, so the // fractional part is strictly larger than 1/2. if (precision < 9) { uint32_t fractional_part = static_cast(prod); should_round_up = fractional_part >= fractional_part_rounding_thresholds( 8 - number_of_digits_to_print) || ((fractional_part >> 31) & ((digits & 1) | (second_third_subsegments != 0) | has_more_segments)) != 0; } // Rounding at the subsegment boundary. // In this case, the fractional part is at least 1/2 if and only if // second_third_subsegments >= 5000000000ULL, and is strictly larger // than 1/2 if we further have either second_third_subsegments > // 5000000000ULL or has_more_segments == true. else { should_round_up = second_third_subsegments > 5000000000ULL || (second_third_subsegments == 5000000000ULL && ((digits & 1) != 0 || has_more_segments)); } } // Otherwise, print the second subsegment. else { // Compilers are not aware of how to leverage the maximum value of // second_third_subsegments to find out a better magic number which // allows us to eliminate an additional shift. 1844674407370955162 = // ceil(2^64/10) < ceil(2^64*(10^9/(10^10 - 1))). const uint32_t second_subsegment = static_cast(dragonbox::umul128_upper64( second_third_subsegments, 1844674407370955162ULL)); const uint32_t third_subsegment = static_cast(second_third_subsegments) - second_subsegment * 10; number_of_digits_to_print = precision - 9; print_subsegment(second_subsegment, buf.data() + 9); // Rounding inside the subsegment. if (precision < 18) { // The condition third_subsegment != 0 implies that the segment was // of 19 digits, so in this case the third segment should be // consisting of a genuine digit from the input. uint32_t fractional_part = static_cast(prod); should_round_up = fractional_part >= fractional_part_rounding_thresholds( 8 - number_of_digits_to_print) || ((fractional_part >> 31) & ((digits & 1) | (third_subsegment != 0) | has_more_segments)) != 0; } // Rounding at the subsegment boundary. else { // In this case, the segment must be of 19 digits, thus // the third subsegment should be consisting of a genuine digit from // the input. should_round_up = third_subsegment > 5 || (third_subsegment == 5 && ((digits & 1) != 0 || has_more_segments)); } } // Round-up if necessary. if (should_round_up) { ++buf[precision - 1]; for (int i = precision - 1; i > 0 && buf[i] > '9'; --i) { buf[i] = '0'; ++buf[i - 1]; } if (buf[0] > '9') { buf[0] = '1'; if (fixed) buf[precision++] = '0'; else ++exp; } } buf.try_resize(to_unsigned(precision)); } } // if (digits_in_the_first_segment > precision) else { // Adjust the exponent for its use in Dragon4. exp += digits_in_the_first_segment - 1; } } if (use_dragon) { auto f = basic_fp(); bool is_predecessor_closer = binary32 ? f.assign(static_cast(value)) : f.assign(converted_value); if (is_predecessor_closer) dragon_flags |= dragon::predecessor_closer; if (fixed) dragon_flags |= dragon::fixed; // Limit precision to the maximum possible number of significant digits in // an IEEE754 double because we don't need to generate zeros. const int max_double_digits = 767; if (precision > max_double_digits) precision = max_double_digits; format_dragon(f, dragon_flags, precision, buf, exp); } if (!fixed && !specs.alt()) { // Remove trailing zeros. auto num_digits = buf.size(); while (num_digits > 0 && buf[num_digits - 1] == '0') { --num_digits; ++exp; } buf.try_resize(num_digits); } return exp; } template FMT_CONSTEXPR20 auto write_float(OutputIt out, T value, format_specs specs, locale_ref loc) -> OutputIt { // Use signbit because value < 0 is false for NaN. sign s = detail::signbit(value) ? sign::minus : specs.sign(); if (!detail::isfinite(value)) return write_nonfinite(out, detail::isnan(value), specs, s); if (specs.align() == align::numeric && s != sign::none) { *out++ = detail::getsign(s); s = sign::none; if (specs.width != 0) --specs.width; } int precision = specs.precision; if (precision < 0) { if (specs.type() != presentation_type::none) { precision = 6; } else if (is_fast_float::value && !is_constant_evaluated()) { // Use Dragonbox for the shortest format. using floaty = conditional_t= sizeof(double), double, float>; auto dec = dragonbox::to_decimal(static_cast(value)); return write_float(out, dec, specs, s, loc); } } memory_buffer buffer; if (specs.type() == presentation_type::hexfloat) { if (s != sign::none) buffer.push_back(detail::getsign(s)); format_hexfloat(convert_float(value), specs, buffer); return write_bytes(out, {buffer.data(), buffer.size()}, specs); } if (specs.type() == presentation_type::exp) { if (precision == max_value()) report_error("number is too big"); else ++precision; if (specs.precision != 0) specs.set_alt(); } else if (specs.type() == presentation_type::fixed) { if (specs.precision != 0) specs.set_alt(); } else if (precision == 0) { precision = 1; } int exp = format_float(convert_float(value), precision, specs, std::is_same(), buffer); specs.precision = precision; auto f = big_decimal_fp{buffer.data(), static_cast(buffer.size()), exp}; return write_float(out, f, specs, s, loc); } template ::value)> FMT_CONSTEXPR20 auto write(OutputIt out, T value, format_specs specs, locale_ref loc = {}) -> OutputIt { return specs.localized() && write_loc(out, value, specs, loc) ? out : write_float(out, value, specs, loc); } template ::value)> FMT_CONSTEXPR20 auto write(OutputIt out, T value) -> OutputIt { if (is_constant_evaluated()) return write(out, value, format_specs()); auto s = detail::signbit(value) ? sign::minus : sign::none; constexpr auto specs = format_specs(); using floaty = conditional_t= sizeof(double), double, float>; using floaty_uint = typename dragonbox::float_info::carrier_uint; floaty_uint mask = exponent_mask(); if ((bit_cast(value) & mask) == mask) return write_nonfinite(out, std::isnan(value), specs, s); auto dec = dragonbox::to_decimal(static_cast(value)); return write_float(out, dec, specs, s, {}); } template ::value && !is_fast_float::value)> inline auto write(OutputIt out, T value) -> OutputIt { return write(out, value, format_specs()); } template auto write(OutputIt out, monostate, format_specs = {}, locale_ref = {}) -> OutputIt { FMT_ASSERT(false, ""); return out; } template FMT_CONSTEXPR auto write(OutputIt out, basic_string_view value) -> OutputIt { return copy_noinline(value.begin(), value.end(), out); } template ::value)> constexpr auto write(OutputIt out, const T& value) -> OutputIt { return write(out, to_string_view(value)); } // FMT_ENABLE_IF() condition separated to workaround an MSVC bug. template < typename Char, typename OutputIt, typename T, bool check = std::is_enum::value && !std::is_same::value && mapped_type_constant::value != type::custom_type, FMT_ENABLE_IF(check)> FMT_CONSTEXPR auto write(OutputIt out, T value) -> OutputIt { return write(out, static_cast>(value)); } template ::value)> FMT_CONSTEXPR auto write(OutputIt out, T value, const format_specs& specs = {}, locale_ref = {}) -> OutputIt { return specs.type() != presentation_type::none && specs.type() != presentation_type::string ? write(out, value ? 1 : 0, specs, {}) : write_bytes(out, value ? "true" : "false", specs); } template FMT_CONSTEXPR auto write(OutputIt out, Char value) -> OutputIt { auto it = reserve(out, 1); *it++ = value; return base_iterator(out, it); } template FMT_CONSTEXPR20 auto write(OutputIt out, const Char* value) -> OutputIt { if (value) return write(out, basic_string_view(value)); report_error("string pointer is null"); return out; } template ::value)> auto write(OutputIt out, const T* value, const format_specs& specs = {}, locale_ref = {}) -> OutputIt { return write_ptr(out, bit_cast(value), &specs); } template ::value == type::custom_type && !std::is_fundamental::value)> FMT_CONSTEXPR auto write(OutputIt out, const T& value) -> OutputIt { auto f = formatter(); auto parse_ctx = parse_context({}); f.parse(parse_ctx); auto ctx = basic_format_context(out, {}, {}); return f.format(value, ctx); } template using is_builtin = bool_constant::value || FMT_BUILTIN_TYPES>; // An argument visitor that formats the argument and writes it via the output // iterator. It's a class and not a generic lambda for compatibility with C++11. template struct default_arg_formatter { using context = buffered_context; basic_appender out; void operator()(monostate) { report_error("argument not found"); } template ::value)> void operator()(T value) { write(out, value); } template ::value)> void operator()(T) { FMT_ASSERT(false, ""); } void operator()(typename basic_format_arg::handle h) { // Use a null locale since the default format must be unlocalized. auto parse_ctx = parse_context({}); auto format_ctx = context(out, {}, {}); h.format(parse_ctx, format_ctx); } }; template struct arg_formatter { basic_appender out; const format_specs& specs; FMT_NO_UNIQUE_ADDRESS locale_ref locale; template ::value)> FMT_CONSTEXPR FMT_INLINE void operator()(T value) { detail::write(out, value, specs, locale); } template ::value)> void operator()(T) { FMT_ASSERT(false, ""); } void operator()(typename basic_format_arg>::handle) { // User-defined types are handled separately because they require access // to the parse context. } }; struct dynamic_spec_getter { template ::value)> FMT_CONSTEXPR auto operator()(T value) -> unsigned long long { return is_negative(value) ? ~0ull : static_cast(value); } template ::value)> FMT_CONSTEXPR auto operator()(T) -> unsigned long long { report_error("width/precision is not integer"); return 0; } }; template FMT_CONSTEXPR auto get_arg(Context& ctx, ID id) -> basic_format_arg { auto arg = ctx.arg(id); if (!arg) report_error("argument not found"); return arg; } template FMT_CONSTEXPR int get_dynamic_spec( arg_id_kind kind, const arg_ref& ref, Context& ctx) { FMT_ASSERT(kind != arg_id_kind::none, ""); auto arg = kind == arg_id_kind::index ? ctx.arg(ref.index) : ctx.arg(ref.name); if (!arg) report_error("argument not found"); unsigned long long value = arg.visit(dynamic_spec_getter()); if (value > to_unsigned(max_value())) report_error("width/precision is out of range"); return static_cast(value); } template FMT_CONSTEXPR void handle_dynamic_spec( arg_id_kind kind, int& value, const arg_ref& ref, Context& ctx) { if (kind != arg_id_kind::none) value = get_dynamic_spec(kind, ref, ctx); } #if FMT_USE_NONTYPE_TEMPLATE_ARGS template Str> struct static_named_arg : view { static constexpr auto name = Str.data; const T& value; static_named_arg(const T& v) : value(v) {} }; template Str> struct is_named_arg> : std::true_type {}; template Str> struct is_static_named_arg> : std::true_type { }; template Str> struct udl_arg { template auto operator=(T&& value) const { return static_named_arg(std::forward(value)); } }; #else template struct udl_arg { const Char* str; template auto operator=(T&& value) const -> named_arg { return {str, std::forward(value)}; } }; #endif // FMT_USE_NONTYPE_TEMPLATE_ARGS template struct format_handler { parse_context parse_ctx; buffered_context ctx; void on_text(const Char* begin, const Char* end) { copy_noinline(begin, end, ctx.out()); } FMT_CONSTEXPR auto on_arg_id() -> int { return parse_ctx.next_arg_id(); } FMT_CONSTEXPR auto on_arg_id(int id) -> int { parse_ctx.check_arg_id(id); return id; } FMT_CONSTEXPR auto on_arg_id(basic_string_view id) -> int { parse_ctx.check_arg_id(id); int arg_id = ctx.arg_id(id); if (arg_id < 0) report_error("argument not found"); return arg_id; } FMT_INLINE void on_replacement_field(int id, const Char*) { ctx.arg(id).visit(default_arg_formatter{ctx.out()}); } auto on_format_specs(int id, const Char* begin, const Char* end) -> const Char* { auto arg = get_arg(ctx, id); // Not using a visitor for custom types gives better codegen. if (arg.format_custom(begin, parse_ctx, ctx)) return parse_ctx.begin(); auto specs = dynamic_format_specs(); begin = parse_format_specs(begin, end, specs, parse_ctx, arg.type()); if (specs.dynamic()) { handle_dynamic_spec(specs.dynamic_width(), specs.width, specs.width_ref, ctx); handle_dynamic_spec(specs.dynamic_precision(), specs.precision, specs.precision_ref, ctx); } arg.visit(arg_formatter{ctx.out(), specs, ctx.locale()}); return begin; } FMT_NORETURN void on_error(const char* message) { report_error(message); } }; using format_func = void (*)(detail::buffer&, int, const char*); FMT_API void do_report_error(format_func func, int error_code, const char* message) noexcept; FMT_API void format_error_code(buffer& out, int error_code, string_view message) noexcept; template template FMT_CONSTEXPR auto native_formatter::format( const T& val, FormatContext& ctx) const -> decltype(ctx.out()) { if (!specs_.dynamic()) return write(ctx.out(), val, specs_, ctx.locale()); auto specs = format_specs(specs_); handle_dynamic_spec(specs.dynamic_width(), specs.width, specs_.width_ref, ctx); handle_dynamic_spec(specs.dynamic_precision(), specs.precision, specs_.precision_ref, ctx); return write(ctx.out(), val, specs, ctx.locale()); } // DEPRECATED! template struct vformat_args { using type = basic_format_args>; }; template <> struct vformat_args { using type = format_args; }; template void vformat_to(buffer& buf, basic_string_view fmt, typename vformat_args::type args, locale_ref loc = {}) { auto out = basic_appender(buf); parse_format_string( fmt, format_handler{parse_context(fmt), {out, args, loc}}); } } // namespace detail FMT_BEGIN_EXPORT // A generic formatting context with custom output iterator and character // (code unit) support. Char is the format string code unit type which can be // different from OutputIt::value_type. template class generic_context { private: OutputIt out_; basic_format_args args_; detail::locale_ref loc_; public: using char_type = Char; using iterator = OutputIt; using parse_context_type FMT_DEPRECATED = parse_context; template using formatter_type FMT_DEPRECATED = formatter; enum { builtin_types = FMT_BUILTIN_TYPES }; constexpr generic_context(OutputIt out, basic_format_args args, detail::locale_ref loc = {}) : out_(out), args_(args), loc_(loc) {} generic_context(generic_context&&) = default; generic_context(const generic_context&) = delete; void operator=(const generic_context&) = delete; constexpr auto arg(int id) const -> basic_format_arg { return args_.get(id); } auto arg(basic_string_view name) const -> basic_format_arg { return args_.get(name); } constexpr auto arg_id(basic_string_view name) const -> int { return args_.get_id(name); } constexpr auto out() const -> iterator { return out_; } void advance_to(iterator it) { if (!detail::is_back_insert_iterator()) out_ = it; } constexpr auto locale() const -> detail::locale_ref { return loc_; } }; class loc_value { private: basic_format_arg value_; public: template ::value)> loc_value(T value) : value_(value) {} template ::value)> loc_value(T) {} template auto visit(Visitor&& vis) -> decltype(vis(0)) { return value_.visit(vis); } }; // A locale facet that formats values in UTF-8. // It is parameterized on the locale to avoid the heavy include. template class format_facet : public Locale::facet { private: std::string separator_; std::string grouping_; std::string decimal_point_; protected: virtual auto do_put(appender out, loc_value val, const format_specs& specs) const -> bool; public: static FMT_API typename Locale::id id; explicit format_facet(Locale& loc); explicit format_facet(string_view sep = "", std::string grouping = "\3", std::string decimal_point = ".") : separator_(sep.data(), sep.size()), grouping_(grouping), decimal_point_(decimal_point) {} auto put(appender out, loc_value val, const format_specs& specs) const -> bool { return do_put(out, val, specs); } }; #define FMT_FORMAT_AS(Type, Base) \ template \ struct formatter : formatter { \ template \ FMT_CONSTEXPR auto format(Type value, FormatContext& ctx) const \ -> decltype(ctx.out()) { \ return formatter::format(value, ctx); \ } \ } FMT_FORMAT_AS(signed char, int); FMT_FORMAT_AS(unsigned char, unsigned); FMT_FORMAT_AS(short, int); FMT_FORMAT_AS(unsigned short, unsigned); FMT_FORMAT_AS(long, detail::long_type); FMT_FORMAT_AS(unsigned long, detail::ulong_type); FMT_FORMAT_AS(Char*, const Char*); FMT_FORMAT_AS(detail::std_string_view, basic_string_view); FMT_FORMAT_AS(std::nullptr_t, const void*); FMT_FORMAT_AS(void*, const void*); template struct formatter : formatter, Char> {}; template class formatter, Char> : public formatter, Char> {}; template struct formatter, Char> : formatter {}; template struct formatter, Char> : formatter {}; template struct formatter : detail::native_formatter {}; template struct formatter>> : formatter, Char> { template FMT_CONSTEXPR auto format(const T& value, FormatContext& ctx) const -> decltype(ctx.out()) { auto&& val = format_as(value); // Make an lvalue reference for format. return formatter, Char>::format(val, ctx); } }; /** * Converts `p` to `const void*` for pointer formatting. * * **Example**: * * auto s = fmt::format("{}", fmt::ptr(p)); */ template auto ptr(T p) -> const void* { static_assert(std::is_pointer::value, ""); return detail::bit_cast(p); } /** * Converts `e` to the underlying type. * * **Example**: * * enum class color { red, green, blue }; * auto s = fmt::format("{}", fmt::underlying(color::red)); // s == "0" */ template constexpr auto underlying(Enum e) noexcept -> underlying_t { return static_cast>(e); } namespace enums { template ::value)> constexpr auto format_as(Enum e) noexcept -> underlying_t { return static_cast>(e); } } // namespace enums #ifdef __cpp_lib_byte template <> struct formatter : formatter { static auto format_as(std::byte b) -> unsigned char { return static_cast(b); } template auto format(std::byte b, Context& ctx) const -> decltype(ctx.out()) { return formatter::format(format_as(b), ctx); } }; #endif struct bytes { string_view data; inline explicit bytes(string_view s) : data(s) {} }; template <> struct formatter { private: detail::dynamic_format_specs<> specs_; public: FMT_CONSTEXPR auto parse(parse_context<>& ctx) -> const char* { return parse_format_specs(ctx.begin(), ctx.end(), specs_, ctx, detail::type::string_type); } template auto format(bytes b, FormatContext& ctx) const -> decltype(ctx.out()) { auto specs = specs_; detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, specs.width_ref, ctx); detail::handle_dynamic_spec(specs.dynamic_precision(), specs.precision, specs.precision_ref, ctx); return detail::write_bytes(ctx.out(), b.data, specs); } }; // group_digits_view is not derived from view because it copies the argument. template struct group_digits_view { T value; }; /** * Returns a view that formats an integer value using ',' as a * locale-independent thousands separator. * * **Example**: * * fmt::print("{}", fmt::group_digits(12345)); * // Output: "12,345" */ template auto group_digits(T value) -> group_digits_view { return {value}; } template struct formatter> : formatter { private: detail::dynamic_format_specs<> specs_; public: FMT_CONSTEXPR auto parse(parse_context<>& ctx) -> const char* { return parse_format_specs(ctx.begin(), ctx.end(), specs_, ctx, detail::type::int_type); } template auto format(group_digits_view view, FormatContext& ctx) const -> decltype(ctx.out()) { auto specs = specs_; detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, specs.width_ref, ctx); detail::handle_dynamic_spec(specs.dynamic_precision(), specs.precision, specs.precision_ref, ctx); auto arg = detail::make_write_int_arg(view.value, specs.sign()); return detail::write_int( ctx.out(), static_cast>(arg.abs_value), arg.prefix, specs, detail::digit_grouping("\3", ",")); } }; template struct nested_view { const formatter* fmt; const T* value; }; template struct formatter, Char> { FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { return ctx.begin(); } template auto format(nested_view view, FormatContext& ctx) const -> decltype(ctx.out()) { return view.fmt->format(*view.value, ctx); } }; template struct nested_formatter { private: basic_specs specs_; int width_; formatter formatter_; public: constexpr nested_formatter() : width_(0) {} FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { auto it = ctx.begin(), end = ctx.end(); if (it == end) return it; auto specs = format_specs(); it = detail::parse_align(it, end, specs); specs_ = specs; Char c = *it; auto width_ref = detail::arg_ref(); if ((c >= '0' && c <= '9') || c == '{') { it = detail::parse_width(it, end, specs, width_ref, ctx); width_ = specs.width; } ctx.advance_to(it); return formatter_.parse(ctx); } template auto write_padded(FormatContext& ctx, F write) const -> decltype(ctx.out()) { if (width_ == 0) return write(ctx.out()); auto buf = basic_memory_buffer(); write(basic_appender(buf)); auto specs = format_specs(); specs.width = width_; specs.set_fill( basic_string_view(specs_.fill(), specs_.fill_size())); specs.set_align(specs_.align()); return detail::write( ctx.out(), basic_string_view(buf.data(), buf.size()), specs); } auto nested(const T& value) const -> nested_view { return nested_view{&formatter_, &value}; } }; inline namespace literals { #if FMT_USE_NONTYPE_TEMPLATE_ARGS template constexpr auto operator""_a() { using char_t = remove_cvref_t; return detail::udl_arg(); } #else /** * User-defined literal equivalent of `fmt::arg`. * * **Example**: * * using namespace fmt::literals; * fmt::print("The answer is {answer}.", "answer"_a=42); */ constexpr auto operator""_a(const char* s, size_t) -> detail::udl_arg { return {s}; } #endif // FMT_USE_NONTYPE_TEMPLATE_ARGS } // namespace literals /// A fast integer formatter. class format_int { private: // Buffer should be large enough to hold all digits (digits10 + 1), // a sign and a null character. enum { buffer_size = std::numeric_limits::digits10 + 3 }; mutable char buffer_[buffer_size]; char* str_; template FMT_CONSTEXPR20 auto format_unsigned(UInt value) -> char* { auto n = static_cast>(value); return detail::do_format_decimal(buffer_, n, buffer_size - 1); } template FMT_CONSTEXPR20 auto format_signed(Int value) -> char* { auto abs_value = static_cast>(value); bool negative = value < 0; if (negative) abs_value = 0 - abs_value; auto begin = format_unsigned(abs_value); if (negative) *--begin = '-'; return begin; } public: FMT_CONSTEXPR20 explicit format_int(int value) : str_(format_signed(value)) {} FMT_CONSTEXPR20 explicit format_int(long value) : str_(format_signed(value)) {} FMT_CONSTEXPR20 explicit format_int(long long value) : str_(format_signed(value)) {} FMT_CONSTEXPR20 explicit format_int(unsigned value) : str_(format_unsigned(value)) {} FMT_CONSTEXPR20 explicit format_int(unsigned long value) : str_(format_unsigned(value)) {} FMT_CONSTEXPR20 explicit format_int(unsigned long long value) : str_(format_unsigned(value)) {} /// Returns the number of characters written to the output buffer. FMT_CONSTEXPR20 auto size() const -> size_t { return detail::to_unsigned(buffer_ - str_ + buffer_size - 1); } /// Returns a pointer to the output buffer content. No terminating null /// character is appended. FMT_CONSTEXPR20 auto data() const -> const char* { return str_; } /// Returns a pointer to the output buffer content with terminating null /// character appended. FMT_CONSTEXPR20 auto c_str() const -> const char* { buffer_[buffer_size - 1] = '\0'; return str_; } /// Returns the content of the output buffer as an `std::string`. inline auto str() const -> std::string { return {str_, size()}; } }; #define FMT_STRING_IMPL(s, base) \ [] { \ /* Use the hidden visibility as a workaround for a GCC bug (#1973). */ \ /* Use a macro-like name to avoid shadowing warnings. */ \ struct FMT_VISIBILITY("hidden") FMT_COMPILE_STRING : base { \ using char_type = fmt::remove_cvref_t; \ constexpr explicit operator fmt::basic_string_view() const { \ return fmt::detail::compile_string_to_view(s); \ } \ }; \ using FMT_STRING_VIEW = \ fmt::basic_string_view; \ fmt::detail::ignore_unused(FMT_STRING_VIEW(FMT_COMPILE_STRING())); \ return FMT_COMPILE_STRING(); \ }() /** * Constructs a legacy compile-time format string from a string literal `s`. * * **Example**: * * // A compile-time error because 'd' is an invalid specifier for strings. * std::string s = fmt::format(FMT_STRING("{:d}"), "foo"); */ #define FMT_STRING(s) FMT_STRING_IMPL(s, fmt::detail::compile_string) FMT_API auto vsystem_error(int error_code, string_view fmt, format_args args) -> std::system_error; /** * Constructs `std::system_error` with a message formatted with * `fmt::format(fmt, args...)`. * `error_code` is a system error code as given by `errno`. * * **Example**: * * // This throws std::system_error with the description * // cannot open file 'madeup': No such file or directory * // or similar (system message may vary). * const char* filename = "madeup"; * FILE* file = fopen(filename, "r"); * if (!file) * throw fmt::system_error(errno, "cannot open file '{}'", filename); */ template auto system_error(int error_code, format_string fmt, T&&... args) -> std::system_error { return vsystem_error(error_code, fmt.str, vargs{{args...}}); } /** * Formats an error message for an error returned by an operating system or a * language runtime, for example a file opening error, and writes it to `out`. * The format is the same as the one used by `std::system_error(ec, message)` * where `ec` is `std::error_code(error_code, std::generic_category())`. * It is implementation-defined but normally looks like: * * : * * where `` is the passed message and `` is the system * message corresponding to the error code. * `error_code` is a system error code as given by `errno`. */ FMT_API void format_system_error(detail::buffer& out, int error_code, const char* message) noexcept; // Reports a system error without throwing an exception. // Can be used to report errors from destructors. FMT_API void report_system_error(int error_code, const char* message) noexcept; inline auto vformat(detail::locale_ref loc, string_view fmt, format_args args) -> std::string { auto buf = memory_buffer(); detail::vformat_to(buf, fmt, args, loc); return {buf.data(), buf.size()}; } template FMT_INLINE auto format(detail::locale_ref loc, format_string fmt, T&&... args) -> std::string { return vformat(loc, fmt.str, vargs{{args...}}); } template ::value)> auto vformat_to(OutputIt out, detail::locale_ref loc, string_view fmt, format_args args) -> OutputIt { auto&& buf = detail::get_buffer(out); detail::vformat_to(buf, fmt, args, loc); return detail::get_iterator(buf, out); } template ::value)> FMT_INLINE auto format_to(OutputIt out, detail::locale_ref loc, format_string fmt, T&&... args) -> OutputIt { return fmt::vformat_to(out, loc, fmt.str, vargs{{args...}}); } template FMT_NODISCARD FMT_INLINE auto formatted_size(detail::locale_ref loc, format_string fmt, T&&... args) -> size_t { auto buf = detail::counting_buffer<>(); detail::vformat_to(buf, fmt.str, vargs{{args...}}, loc); return buf.count(); } FMT_API auto vformat(string_view fmt, format_args args) -> std::string; /** * Formats `args` according to specifications in `fmt` and returns the result * as a string. * * **Example**: * * #include * std::string message = fmt::format("The answer is {}.", 42); */ template FMT_NODISCARD FMT_INLINE auto format(format_string fmt, T&&... args) -> std::string { return vformat(fmt.str, vargs{{args...}}); } /** * Converts `value` to `std::string` using the default format for type `T`. * * **Example**: * * std::string answer = fmt::to_string(42); */ template ::value)> FMT_NODISCARD auto to_string(T value) -> std::string { // The buffer should be large enough to store the number including the sign // or "false" for bool. char buffer[max_of(detail::digits10() + 2, 5)]; return {buffer, detail::write(buffer, value)}; } template ::value)> FMT_NODISCARD auto to_string(const T& value) -> std::string { return to_string(format_as(value)); } template ::value && !detail::use_format_as::value)> FMT_NODISCARD auto to_string(const T& value) -> std::string { auto buffer = memory_buffer(); detail::write(appender(buffer), value); return {buffer.data(), buffer.size()}; } FMT_END_EXPORT FMT_END_NAMESPACE #ifdef FMT_HEADER_ONLY # define FMT_FUNC inline # include "format-inl.h" #endif // Restore _LIBCPP_REMOVE_TRANSITIVE_INCLUDES. #ifdef FMT_REMOVE_TRANSITIVE_INCLUDES # undef _LIBCPP_REMOVE_TRANSITIVE_INCLUDES #endif #endif // FMT_FORMAT_H_ openal-soft-1.24.2/fmt-11.1.1/include/fmt/os.h000066400000000000000000000307621474041540300203470ustar00rootroot00000000000000// Formatting library for C++ - optional OS-specific functionality // // Copyright (c) 2012 - present, Victor Zverovich // All rights reserved. // // For the license information refer to format.h. #ifndef FMT_OS_H_ #define FMT_OS_H_ #include "format.h" #ifndef FMT_MODULE # include # include # include # include // std::system_error # if FMT_HAS_INCLUDE() # include // LC_NUMERIC_MASK on macOS # endif #endif // FMT_MODULE #ifndef FMT_USE_FCNTL // UWP doesn't provide _pipe. # if FMT_HAS_INCLUDE("winapifamily.h") # include # endif # if (FMT_HAS_INCLUDE() || defined(__APPLE__) || \ defined(__linux__)) && \ (!defined(WINAPI_FAMILY) || \ (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP)) # include // for O_RDONLY # define FMT_USE_FCNTL 1 # else # define FMT_USE_FCNTL 0 # endif #endif #ifndef FMT_POSIX # if defined(_WIN32) && !defined(__MINGW32__) // Fix warnings about deprecated symbols. # define FMT_POSIX(call) _##call # else # define FMT_POSIX(call) call # endif #endif // Calls to system functions are wrapped in FMT_SYSTEM for testability. #ifdef FMT_SYSTEM # define FMT_HAS_SYSTEM # define FMT_POSIX_CALL(call) FMT_SYSTEM(call) #else # define FMT_SYSTEM(call) ::call # ifdef _WIN32 // Fix warnings about deprecated symbols. # define FMT_POSIX_CALL(call) ::_##call # else # define FMT_POSIX_CALL(call) ::call # endif #endif // Retries the expression while it evaluates to error_result and errno // equals to EINTR. #ifndef _WIN32 # define FMT_RETRY_VAL(result, expression, error_result) \ do { \ (result) = (expression); \ } while ((result) == (error_result) && errno == EINTR) #else # define FMT_RETRY_VAL(result, expression, error_result) result = (expression) #endif #define FMT_RETRY(result, expression) FMT_RETRY_VAL(result, expression, -1) FMT_BEGIN_NAMESPACE FMT_BEGIN_EXPORT /** * A reference to a null-terminated string. It can be constructed from a C * string or `std::string`. * * You can use one of the following type aliases for common character types: * * +---------------+-----------------------------+ * | Type | Definition | * +===============+=============================+ * | cstring_view | basic_cstring_view | * +---------------+-----------------------------+ * | wcstring_view | basic_cstring_view | * +---------------+-----------------------------+ * * This class is most useful as a parameter type for functions that wrap C APIs. */ template class basic_cstring_view { private: const Char* data_; public: /// Constructs a string reference object from a C string. basic_cstring_view(const Char* s) : data_(s) {} /// Constructs a string reference from an `std::string` object. basic_cstring_view(const std::basic_string& s) : data_(s.c_str()) {} /// Returns the pointer to a C string. auto c_str() const -> const Char* { return data_; } }; using cstring_view = basic_cstring_view; using wcstring_view = basic_cstring_view; #ifdef _WIN32 FMT_API const std::error_category& system_category() noexcept; namespace detail { FMT_API void format_windows_error(buffer& out, int error_code, const char* message) noexcept; } FMT_API std::system_error vwindows_error(int error_code, string_view fmt, format_args args); /** * Constructs a `std::system_error` object with the description of the form * * : * * where `` is the formatted message and `` is the * system message corresponding to the error code. * `error_code` is a Windows error code as given by `GetLastError`. * If `error_code` is not a valid error code such as -1, the system message * will look like "error -1". * * **Example**: * * // This throws a system_error with the description * // cannot open file 'madeup': The system cannot find the file * specified. * // or similar (system message may vary). * const char *filename = "madeup"; * LPOFSTRUCT of = LPOFSTRUCT(); * HFILE file = OpenFile(filename, &of, OF_READ); * if (file == HFILE_ERROR) { * throw fmt::windows_error(GetLastError(), * "cannot open file '{}'", filename); * } */ template auto windows_error(int error_code, string_view message, const T&... args) -> std::system_error { return vwindows_error(error_code, message, vargs{{args...}}); } // Reports a Windows error without throwing an exception. // Can be used to report errors from destructors. FMT_API void report_windows_error(int error_code, const char* message) noexcept; #else inline auto system_category() noexcept -> const std::error_category& { return std::system_category(); } #endif // _WIN32 // std::system is not available on some platforms such as iOS (#2248). #ifdef __OSX__ template > void say(const S& fmt, Args&&... args) { std::system(format("say \"{}\"", format(fmt, args...)).c_str()); } #endif // A buffered file. class buffered_file { private: FILE* file_; friend class file; inline explicit buffered_file(FILE* f) : file_(f) {} public: buffered_file(const buffered_file&) = delete; void operator=(const buffered_file&) = delete; // Constructs a buffered_file object which doesn't represent any file. inline buffered_file() noexcept : file_(nullptr) {} // Destroys the object closing the file it represents if any. FMT_API ~buffered_file() noexcept; public: inline buffered_file(buffered_file&& other) noexcept : file_(other.file_) { other.file_ = nullptr; } inline auto operator=(buffered_file&& other) -> buffered_file& { close(); file_ = other.file_; other.file_ = nullptr; return *this; } // Opens a file. FMT_API buffered_file(cstring_view filename, cstring_view mode); // Closes the file. FMT_API void close(); // Returns the pointer to a FILE object representing this file. inline auto get() const noexcept -> FILE* { return file_; } FMT_API auto descriptor() const -> int; template inline void print(string_view fmt, const T&... args) { fmt::vargs vargs = {{args...}}; detail::is_locking() ? fmt::vprint_buffered(file_, fmt, vargs) : fmt::vprint(file_, fmt, vargs); } }; #if FMT_USE_FCNTL // A file. Closed file is represented by a file object with descriptor -1. // Methods that are not declared with noexcept may throw // fmt::system_error in case of failure. Note that some errors such as // closing the file multiple times will cause a crash on Windows rather // than an exception. You can get standard behavior by overriding the // invalid parameter handler with _set_invalid_parameter_handler. class FMT_API file { private: int fd_; // File descriptor. // Constructs a file object with a given descriptor. explicit file(int fd) : fd_(fd) {} friend struct pipe; public: // Possible values for the oflag argument to the constructor. enum { RDONLY = FMT_POSIX(O_RDONLY), // Open for reading only. WRONLY = FMT_POSIX(O_WRONLY), // Open for writing only. RDWR = FMT_POSIX(O_RDWR), // Open for reading and writing. CREATE = FMT_POSIX(O_CREAT), // Create if the file doesn't exist. APPEND = FMT_POSIX(O_APPEND), // Open in append mode. TRUNC = FMT_POSIX(O_TRUNC) // Truncate the content of the file. }; // Constructs a file object which doesn't represent any file. inline file() noexcept : fd_(-1) {} // Opens a file and constructs a file object representing this file. file(cstring_view path, int oflag); public: file(const file&) = delete; void operator=(const file&) = delete; inline file(file&& other) noexcept : fd_(other.fd_) { other.fd_ = -1; } // Move assignment is not noexcept because close may throw. inline auto operator=(file&& other) -> file& { close(); fd_ = other.fd_; other.fd_ = -1; return *this; } // Destroys the object closing the file it represents if any. ~file() noexcept; // Returns the file descriptor. inline auto descriptor() const noexcept -> int { return fd_; } // Closes the file. void close(); // Returns the file size. The size has signed type for consistency with // stat::st_size. auto size() const -> long long; // Attempts to read count bytes from the file into the specified buffer. auto read(void* buffer, size_t count) -> size_t; // Attempts to write count bytes from the specified buffer to the file. auto write(const void* buffer, size_t count) -> size_t; // Duplicates a file descriptor with the dup function and returns // the duplicate as a file object. static auto dup(int fd) -> file; // Makes fd be the copy of this file descriptor, closing fd first if // necessary. void dup2(int fd); // Makes fd be the copy of this file descriptor, closing fd first if // necessary. void dup2(int fd, std::error_code& ec) noexcept; // Creates a buffered_file object associated with this file and detaches // this file object from the file. auto fdopen(const char* mode) -> buffered_file; # if defined(_WIN32) && !defined(__MINGW32__) // Opens a file and constructs a file object representing this file by // wcstring_view filename. Windows only. static file open_windows_file(wcstring_view path, int oflag); # endif }; struct FMT_API pipe { file read_end; file write_end; // Creates a pipe setting up read_end and write_end file objects for reading // and writing respectively. pipe(); }; // Returns the memory page size. auto getpagesize() -> long; namespace detail { struct buffer_size { constexpr buffer_size() = default; size_t value = 0; FMT_CONSTEXPR auto operator=(size_t val) const -> buffer_size { auto bs = buffer_size(); bs.value = val; return bs; } }; struct ostream_params { int oflag = file::WRONLY | file::CREATE | file::TRUNC; size_t buffer_size = BUFSIZ > 32768 ? BUFSIZ : 32768; constexpr ostream_params() {} template ostream_params(T... params, int new_oflag) : ostream_params(params...) { oflag = new_oflag; } template ostream_params(T... params, detail::buffer_size bs) : ostream_params(params...) { this->buffer_size = bs.value; } // Intel has a bug that results in failure to deduce a constructor // for empty parameter packs. # if defined(__INTEL_COMPILER) && __INTEL_COMPILER < 2000 ostream_params(int new_oflag) : oflag(new_oflag) {} ostream_params(detail::buffer_size bs) : buffer_size(bs.value) {} # endif }; } // namespace detail FMT_INLINE_VARIABLE constexpr auto buffer_size = detail::buffer_size(); /// A fast buffered output stream for writing from a single thread. Writing from /// multiple threads without external synchronization may result in a data race. class FMT_API ostream : private detail::buffer { private: file file_; ostream(cstring_view path, const detail::ostream_params& params); static void grow(buffer& buf, size_t); public: ostream(ostream&& other) noexcept; ~ostream(); operator writer() { detail::buffer& buf = *this; return buf; } inline void flush() { if (size() == 0) return; file_.write(data(), size() * sizeof(data()[0])); clear(); } template friend auto output_file(cstring_view path, T... params) -> ostream; inline void close() { flush(); file_.close(); } /// Formats `args` according to specifications in `fmt` and writes the /// output to the file. template void print(format_string fmt, T&&... args) { vformat_to(appender(*this), fmt.str, vargs{{args...}}); } }; /** * Opens a file for writing. Supported parameters passed in `params`: * * - ``: Flags passed to [open]( * https://pubs.opengroup.org/onlinepubs/007904875/functions/open.html) * (`file::WRONLY | file::CREATE | file::TRUNC` by default) * - `buffer_size=`: Output buffer size * * **Example**: * * auto out = fmt::output_file("guide.txt"); * out.print("Don't {}", "Panic"); */ template inline auto output_file(cstring_view path, T... params) -> ostream { return {path, detail::ostream_params(params...)}; } #endif // FMT_USE_FCNTL FMT_END_EXPORT FMT_END_NAMESPACE #endif // FMT_OS_H_ openal-soft-1.24.2/fmt-11.1.1/include/fmt/ostream.h000066400000000000000000000115621474041540300213750ustar00rootroot00000000000000// Formatting library for C++ - std::ostream support // // Copyright (c) 2012 - present, Victor Zverovich // All rights reserved. // // For the license information refer to format.h. #ifndef FMT_OSTREAM_H_ #define FMT_OSTREAM_H_ #ifndef FMT_MODULE # include // std::filebuf #endif #ifdef _WIN32 # ifdef __GLIBCXX__ # include # include # endif # include #endif #include "chrono.h" // formatbuf #ifdef _MSVC_STL_UPDATE # define FMT_MSVC_STL_UPDATE _MSVC_STL_UPDATE #elif defined(_MSC_VER) && _MSC_VER < 1912 // VS 15.5 # define FMT_MSVC_STL_UPDATE _MSVC_LANG #else # define FMT_MSVC_STL_UPDATE 0 #endif FMT_BEGIN_NAMESPACE namespace detail { // Generate a unique explicit instantion in every translation unit using a tag // type in an anonymous namespace. namespace { struct file_access_tag {}; } // namespace template class file_access { friend auto get_file(BufType& obj) -> FILE* { return obj.*FileMemberPtr; } }; #if FMT_MSVC_STL_UPDATE template class file_access; auto get_file(std::filebuf&) -> FILE*; #endif // Write the content of buf to os. // It is a separate function rather than a part of vprint to simplify testing. template void write_buffer(std::basic_ostream& os, buffer& buf) { const Char* buf_data = buf.data(); using unsigned_streamsize = make_unsigned_t; unsigned_streamsize size = buf.size(); unsigned_streamsize max_size = to_unsigned(max_value()); do { unsigned_streamsize n = size <= max_size ? size : max_size; os.write(buf_data, static_cast(n)); buf_data += n; size -= n; } while (size != 0); } template struct streamed_view { const T& value; }; } // namespace detail // Formats an object of type T that has an overloaded ostream operator<<. template struct basic_ostream_formatter : formatter, Char> { void set_debug_format() = delete; template auto format(const T& value, Context& ctx) const -> decltype(ctx.out()) { auto buffer = basic_memory_buffer(); auto&& formatbuf = detail::formatbuf>(buffer); auto&& output = std::basic_ostream(&formatbuf); output.imbue(std::locale::classic()); // The default is always unlocalized. output << value; output.exceptions(std::ios_base::failbit | std::ios_base::badbit); return formatter, Char>::format( {buffer.data(), buffer.size()}, ctx); } }; using ostream_formatter = basic_ostream_formatter; template struct formatter, Char> : basic_ostream_formatter { template auto format(detail::streamed_view view, Context& ctx) const -> decltype(ctx.out()) { return basic_ostream_formatter::format(view.value, ctx); } }; /** * Returns a view that formats `value` via an ostream `operator<<`. * * **Example**: * * fmt::print("Current thread id: {}\n", * fmt::streamed(std::this_thread::get_id())); */ template constexpr auto streamed(const T& value) -> detail::streamed_view { return {value}; } inline void vprint(std::ostream& os, string_view fmt, format_args args) { auto buffer = memory_buffer(); detail::vformat_to(buffer, fmt, args); FILE* f = nullptr; #if FMT_MSVC_STL_UPDATE && FMT_USE_RTTI if (auto* buf = dynamic_cast(os.rdbuf())) f = detail::get_file(*buf); #elif defined(_WIN32) && defined(__GLIBCXX__) && FMT_USE_RTTI auto* rdbuf = os.rdbuf(); if (auto* sfbuf = dynamic_cast<__gnu_cxx::stdio_sync_filebuf*>(rdbuf)) f = sfbuf->file(); else if (auto* fbuf = dynamic_cast<__gnu_cxx::stdio_filebuf*>(rdbuf)) f = fbuf->file(); #endif #ifdef _WIN32 if (f) { int fd = _fileno(f); if (_isatty(fd)) { os.flush(); if (detail::write_console(fd, {buffer.data(), buffer.size()})) return; } } #endif detail::ignore_unused(f); detail::write_buffer(os, buffer); } /** * Prints formatted data to the stream `os`. * * **Example**: * * fmt::print(cerr, "Don't {}!", "panic"); */ FMT_EXPORT template void print(std::ostream& os, format_string fmt, T&&... args) { fmt::vargs vargs = {{args...}}; if (detail::use_utf8) return vprint(os, fmt.str, vargs); auto buffer = memory_buffer(); detail::vformat_to(buffer, fmt.str, vargs); detail::write_buffer(os, buffer); } FMT_EXPORT template void println(std::ostream& os, format_string fmt, T&&... args) { fmt::print(os, "{}\n", fmt::format(fmt, std::forward(args)...)); } FMT_END_NAMESPACE #endif // FMT_OSTREAM_H_ openal-soft-1.24.2/fmt-11.1.1/include/fmt/printf.h000066400000000000000000000477301474041540300212330ustar00rootroot00000000000000// Formatting library for C++ - legacy printf implementation // // Copyright (c) 2012 - 2016, Victor Zverovich // All rights reserved. // // For the license information refer to format.h. #ifndef FMT_PRINTF_H_ #define FMT_PRINTF_H_ #ifndef FMT_MODULE # include // std::max # include // std::numeric_limits #endif #include "format.h" FMT_BEGIN_NAMESPACE FMT_BEGIN_EXPORT template struct printf_formatter { printf_formatter() = delete; }; template class basic_printf_context { private: basic_appender out_; basic_format_args args_; static_assert(std::is_same::value || std::is_same::value, "Unsupported code unit type."); public: using char_type = Char; using parse_context_type = parse_context; template using formatter_type = printf_formatter; enum { builtin_types = 1 }; /// Constructs a `printf_context` object. References to the arguments are /// stored in the context object so make sure they have appropriate lifetimes. basic_printf_context(basic_appender out, basic_format_args args) : out_(out), args_(args) {} auto out() -> basic_appender { return out_; } void advance_to(basic_appender) {} auto locale() -> detail::locale_ref { return {}; } auto arg(int id) const -> basic_format_arg { return args_.get(id); } }; namespace detail { // Return the result via the out param to workaround gcc bug 77539. template FMT_CONSTEXPR auto find(Ptr first, Ptr last, T value, Ptr& out) -> bool { for (out = first; out != last; ++out) { if (*out == value) return true; } return false; } template <> inline auto find(const char* first, const char* last, char value, const char*& out) -> bool { out = static_cast(memchr(first, value, to_unsigned(last - first))); return out != nullptr; } // Checks if a value fits in int - used to avoid warnings about comparing // signed and unsigned integers. template struct int_checker { template static auto fits_in_int(T value) -> bool { unsigned max = to_unsigned(max_value()); return value <= max; } inline static auto fits_in_int(bool) -> bool { return true; } }; template <> struct int_checker { template static auto fits_in_int(T value) -> bool { return value >= (std::numeric_limits::min)() && value <= max_value(); } inline static auto fits_in_int(int) -> bool { return true; } }; struct printf_precision_handler { template ::value)> auto operator()(T value) -> int { if (!int_checker::is_signed>::fits_in_int(value)) report_error("number is too big"); return (std::max)(static_cast(value), 0); } template ::value)> auto operator()(T) -> int { report_error("precision is not integer"); return 0; } }; // An argument visitor that returns true iff arg is a zero integer. struct is_zero_int { template ::value)> auto operator()(T value) -> bool { return value == 0; } template ::value)> auto operator()(T) -> bool { return false; } }; template struct make_unsigned_or_bool : std::make_unsigned {}; template <> struct make_unsigned_or_bool { using type = bool; }; template class arg_converter { private: using char_type = typename Context::char_type; basic_format_arg& arg_; char_type type_; public: arg_converter(basic_format_arg& arg, char_type type) : arg_(arg), type_(type) {} void operator()(bool value) { if (type_ != 's') operator()(value); } template ::value)> void operator()(U value) { bool is_signed = type_ == 'd' || type_ == 'i'; using target_type = conditional_t::value, U, T>; if (const_check(sizeof(target_type) <= sizeof(int))) { // Extra casts are used to silence warnings. using unsigned_type = typename make_unsigned_or_bool::type; if (is_signed) arg_ = static_cast(static_cast(value)); else arg_ = static_cast(static_cast(value)); } else { // glibc's printf doesn't sign extend arguments of smaller types: // std::printf("%lld", -42); // prints "4294967254" // but we don't have to do the same because it's a UB. if (is_signed) arg_ = static_cast(value); else arg_ = static_cast::type>(value); } } template ::value)> void operator()(U) {} // No conversion needed for non-integral types. }; // Converts an integer argument to T for printf, if T is an integral type. // If T is void, the argument is converted to corresponding signed or unsigned // type depending on the type specifier: 'd' and 'i' - signed, other - // unsigned). template void convert_arg(basic_format_arg& arg, Char type) { arg.visit(arg_converter(arg, type)); } // Converts an integer argument to char for printf. template class char_converter { private: basic_format_arg& arg_; public: explicit char_converter(basic_format_arg& arg) : arg_(arg) {} template ::value)> void operator()(T value) { arg_ = static_cast(value); } template ::value)> void operator()(T) {} // No conversion needed for non-integral types. }; // An argument visitor that return a pointer to a C string if argument is a // string or null otherwise. template struct get_cstring { template auto operator()(T) -> const Char* { return nullptr; } auto operator()(const Char* s) -> const Char* { return s; } }; // Checks if an argument is a valid printf width specifier and sets // left alignment if it is negative. class printf_width_handler { private: format_specs& specs_; public: inline explicit printf_width_handler(format_specs& specs) : specs_(specs) {} template ::value)> auto operator()(T value) -> unsigned { auto width = static_cast>(value); if (detail::is_negative(value)) { specs_.set_align(align::left); width = 0 - width; } unsigned int_max = to_unsigned(max_value()); if (width > int_max) report_error("number is too big"); return static_cast(width); } template ::value)> auto operator()(T) -> unsigned { report_error("width is not integer"); return 0; } }; // Workaround for a bug with the XL compiler when initializing // printf_arg_formatter's base class. template auto make_arg_formatter(basic_appender iter, format_specs& s) -> arg_formatter { return {iter, s, locale_ref()}; } // The `printf` argument formatter. template class printf_arg_formatter : public arg_formatter { private: using base = arg_formatter; using context_type = basic_printf_context; context_type& context_; void write_null_pointer(bool is_string = false) { auto s = this->specs; s.set_type(presentation_type::none); write_bytes(this->out, is_string ? "(null)" : "(nil)", s); } template void write(T value) { detail::write(this->out, value, this->specs, this->locale); } public: printf_arg_formatter(basic_appender iter, format_specs& s, context_type& ctx) : base(make_arg_formatter(iter, s)), context_(ctx) {} void operator()(monostate value) { write(value); } template ::value)> void operator()(T value) { // MSVC2013 fails to compile separate overloads for bool and Char so use // std::is_same instead. if (!std::is_same::value) { write(value); return; } format_specs s = this->specs; if (s.type() != presentation_type::none && s.type() != presentation_type::chr) { return (*this)(static_cast(value)); } s.set_sign(sign::none); s.clear_alt(); s.set_fill(' '); // Ignore '0' flag for char types. // align::numeric needs to be overwritten here since the '0' flag is // ignored for non-numeric types if (s.align() == align::none || s.align() == align::numeric) s.set_align(align::right); detail::write(this->out, static_cast(value), s); } template ::value)> void operator()(T value) { write(value); } void operator()(const char* value) { if (value) write(value); else write_null_pointer(this->specs.type() != presentation_type::pointer); } void operator()(const wchar_t* value) { if (value) write(value); else write_null_pointer(this->specs.type() != presentation_type::pointer); } void operator()(basic_string_view value) { write(value); } void operator()(const void* value) { if (value) write(value); else write_null_pointer(); } void operator()(typename basic_format_arg::handle handle) { auto parse_ctx = parse_context({}); handle.format(parse_ctx, context_); } }; template void parse_flags(format_specs& specs, const Char*& it, const Char* end) { for (; it != end; ++it) { switch (*it) { case '-': specs.set_align(align::left); break; case '+': specs.set_sign(sign::plus); break; case '0': specs.set_fill('0'); break; case ' ': if (specs.sign() != sign::plus) specs.set_sign(sign::space); break; case '#': specs.set_alt(); break; default: return; } } } template auto parse_header(const Char*& it, const Char* end, format_specs& specs, GetArg get_arg) -> int { int arg_index = -1; Char c = *it; if (c >= '0' && c <= '9') { // Parse an argument index (if followed by '$') or a width possibly // preceded with '0' flag(s). int value = parse_nonnegative_int(it, end, -1); if (it != end && *it == '$') { // value is an argument index ++it; arg_index = value != -1 ? value : max_value(); } else { if (c == '0') specs.set_fill('0'); if (value != 0) { // Nonzero value means that we parsed width and don't need to // parse it or flags again, so return now. if (value == -1) report_error("number is too big"); specs.width = value; return arg_index; } } } parse_flags(specs, it, end); // Parse width. if (it != end) { if (*it >= '0' && *it <= '9') { specs.width = parse_nonnegative_int(it, end, -1); if (specs.width == -1) report_error("number is too big"); } else if (*it == '*') { ++it; specs.width = static_cast( get_arg(-1).visit(detail::printf_width_handler(specs))); } } return arg_index; } inline auto parse_printf_presentation_type(char c, type t, bool& upper) -> presentation_type { using pt = presentation_type; constexpr auto integral_set = sint_set | uint_set | bool_set | char_set; switch (c) { case 'd': return in(t, integral_set) ? pt::dec : pt::none; case 'o': return in(t, integral_set) ? pt::oct : pt::none; case 'X': upper = true; FMT_FALLTHROUGH; case 'x': return in(t, integral_set) ? pt::hex : pt::none; case 'E': upper = true; FMT_FALLTHROUGH; case 'e': return in(t, float_set) ? pt::exp : pt::none; case 'F': upper = true; FMT_FALLTHROUGH; case 'f': return in(t, float_set) ? pt::fixed : pt::none; case 'G': upper = true; FMT_FALLTHROUGH; case 'g': return in(t, float_set) ? pt::general : pt::none; case 'A': upper = true; FMT_FALLTHROUGH; case 'a': return in(t, float_set) ? pt::hexfloat : pt::none; case 'c': return in(t, integral_set) ? pt::chr : pt::none; case 's': return in(t, string_set | cstring_set) ? pt::string : pt::none; case 'p': return in(t, pointer_set | cstring_set) ? pt::pointer : pt::none; default: return pt::none; } } template void vprintf(buffer& buf, basic_string_view format, basic_format_args args) { using iterator = basic_appender; auto out = iterator(buf); auto context = basic_printf_context(out, args); auto parse_ctx = parse_context(format); // Returns the argument with specified index or, if arg_index is -1, the next // argument. auto get_arg = [&](int arg_index) { if (arg_index < 0) arg_index = parse_ctx.next_arg_id(); else parse_ctx.check_arg_id(--arg_index); return detail::get_arg(context, arg_index); }; const Char* start = parse_ctx.begin(); const Char* end = parse_ctx.end(); auto it = start; while (it != end) { if (!find(it, end, '%', it)) { it = end; // find leaves it == nullptr if it doesn't find '%'. break; } Char c = *it++; if (it != end && *it == c) { write(out, basic_string_view(start, to_unsigned(it - start))); start = ++it; continue; } write(out, basic_string_view(start, to_unsigned(it - 1 - start))); auto specs = format_specs(); specs.set_align(align::right); // Parse argument index, flags and width. int arg_index = parse_header(it, end, specs, get_arg); if (arg_index == 0) report_error("argument not found"); // Parse precision. if (it != end && *it == '.') { ++it; c = it != end ? *it : 0; if ('0' <= c && c <= '9') { specs.precision = parse_nonnegative_int(it, end, 0); } else if (c == '*') { ++it; specs.precision = static_cast(get_arg(-1).visit(printf_precision_handler())); } else { specs.precision = 0; } } auto arg = get_arg(arg_index); // For d, i, o, u, x, and X conversion specifiers, if a precision is // specified, the '0' flag is ignored if (specs.precision >= 0 && is_integral_type(arg.type())) { // Ignore '0' for non-numeric types or if '-' present. specs.set_fill(' '); } if (specs.precision >= 0 && arg.type() == type::cstring_type) { auto str = arg.visit(get_cstring()); auto str_end = str + specs.precision; auto nul = std::find(str, str_end, Char()); auto sv = basic_string_view( str, to_unsigned(nul != str_end ? nul - str : specs.precision)); arg = sv; } if (specs.alt() && arg.visit(is_zero_int())) specs.clear_alt(); if (specs.fill_unit() == '0') { if (is_arithmetic_type(arg.type()) && specs.align() != align::left) { specs.set_align(align::numeric); } else { // Ignore '0' flag for non-numeric types or if '-' flag is also present. specs.set_fill(' '); } } // Parse length and convert the argument to the required type. c = it != end ? *it++ : 0; Char t = it != end ? *it : 0; switch (c) { case 'h': if (t == 'h') { ++it; t = it != end ? *it : 0; convert_arg(arg, t); } else { convert_arg(arg, t); } break; case 'l': if (t == 'l') { ++it; t = it != end ? *it : 0; convert_arg(arg, t); } else { convert_arg(arg, t); } break; case 'j': convert_arg(arg, t); break; case 'z': convert_arg(arg, t); break; case 't': convert_arg(arg, t); break; case 'L': // printf produces garbage when 'L' is omitted for long double, no // need to do the same. break; default: --it; convert_arg(arg, c); } // Parse type. if (it == end) report_error("invalid format string"); char type = static_cast(*it++); if (is_integral_type(arg.type())) { // Normalize type. switch (type) { case 'i': case 'u': type = 'd'; break; case 'c': arg.visit(char_converter>(arg)); break; } } bool upper = false; specs.set_type(parse_printf_presentation_type(type, arg.type(), upper)); if (specs.type() == presentation_type::none) report_error("invalid format specifier"); if (upper) specs.set_upper(); start = it; // Format argument. arg.visit(printf_arg_formatter(out, specs, context)); } write(out, basic_string_view(start, to_unsigned(it - start))); } } // namespace detail using printf_context = basic_printf_context; using wprintf_context = basic_printf_context; using printf_args = basic_format_args; using wprintf_args = basic_format_args; /// Constructs an `format_arg_store` object that contains references to /// arguments and can be implicitly converted to `printf_args`. template inline auto make_printf_args(T&... args) -> decltype(fmt::make_format_args>(args...)) { return fmt::make_format_args>(args...); } template struct vprintf_args { using type = basic_format_args>; }; template inline auto vsprintf(basic_string_view fmt, typename vprintf_args::type args) -> std::basic_string { auto buf = basic_memory_buffer(); detail::vprintf(buf, fmt, args); return {buf.data(), buf.size()}; } /** * Formats `args` according to specifications in `fmt` and returns the result * as as string. * * **Example**: * * std::string message = fmt::sprintf("The answer is %d", 42); */ template > inline auto sprintf(const S& fmt, const T&... args) -> std::basic_string { return vsprintf(detail::to_string_view(fmt), fmt::make_format_args>(args...)); } template inline auto vfprintf(std::FILE* f, basic_string_view fmt, typename vprintf_args::type args) -> int { auto buf = basic_memory_buffer(); detail::vprintf(buf, fmt, args); size_t size = buf.size(); return std::fwrite(buf.data(), sizeof(Char), size, f) < size ? -1 : static_cast(size); } /** * Formats `args` according to specifications in `fmt` and writes the output * to `f`. * * **Example**: * * fmt::fprintf(stderr, "Don't %s!", "panic"); */ template > inline auto fprintf(std::FILE* f, const S& fmt, const T&... args) -> int { return vfprintf(f, detail::to_string_view(fmt), make_printf_args(args...)); } template FMT_DEPRECATED inline auto vprintf(basic_string_view fmt, typename vprintf_args::type args) -> int { return vfprintf(stdout, fmt, args); } /** * Formats `args` according to specifications in `fmt` and writes the output * to `stdout`. * * **Example**: * * fmt::printf("Elapsed time: %.2f seconds", 1.23); */ template inline auto printf(string_view fmt, const T&... args) -> int { return vfprintf(stdout, fmt, make_printf_args(args...)); } template FMT_DEPRECATED inline auto printf(basic_string_view fmt, const T&... args) -> int { return vfprintf(stdout, fmt, make_printf_args(args...)); } FMT_END_EXPORT FMT_END_NAMESPACE #endif // FMT_PRINTF_H_ openal-soft-1.24.2/fmt-11.1.1/include/fmt/ranges.h000066400000000000000000000667161474041540300212150ustar00rootroot00000000000000// Formatting library for C++ - range and tuple support // // Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors // All rights reserved. // // For the license information refer to format.h. #ifndef FMT_RANGES_H_ #define FMT_RANGES_H_ #ifndef FMT_MODULE # include # include # include # include # include # include #endif #include "format.h" FMT_BEGIN_NAMESPACE FMT_EXPORT enum class range_format { disabled, map, set, sequence, string, debug_string }; namespace detail { template class is_map { template static auto check(U*) -> typename U::mapped_type; template static void check(...); public: static constexpr const bool value = !std::is_void(nullptr))>::value; }; template class is_set { template static auto check(U*) -> typename U::key_type; template static void check(...); public: static constexpr const bool value = !std::is_void(nullptr))>::value && !is_map::value; }; // C array overload template auto range_begin(const T (&arr)[N]) -> const T* { return arr; } template auto range_end(const T (&arr)[N]) -> const T* { return arr + N; } template struct has_member_fn_begin_end_t : std::false_type {}; template struct has_member_fn_begin_end_t().begin()), decltype(std::declval().end())>> : std::true_type {}; // Member function overloads. template auto range_begin(T&& rng) -> decltype(static_cast(rng).begin()) { return static_cast(rng).begin(); } template auto range_end(T&& rng) -> decltype(static_cast(rng).end()) { return static_cast(rng).end(); } // ADL overloads. Only participate in overload resolution if member functions // are not found. template auto range_begin(T&& rng) -> enable_if_t::value, decltype(begin(static_cast(rng)))> { return begin(static_cast(rng)); } template auto range_end(T&& rng) -> enable_if_t::value, decltype(end(static_cast(rng)))> { return end(static_cast(rng)); } template struct has_const_begin_end : std::false_type {}; template struct has_mutable_begin_end : std::false_type {}; template struct has_const_begin_end< T, void_t&>())), decltype(detail::range_end( std::declval&>()))>> : std::true_type {}; template struct has_mutable_begin_end< T, void_t())), decltype(detail::range_end(std::declval())), // the extra int here is because older versions of MSVC don't // SFINAE properly unless there are distinct types int>> : std::true_type {}; template struct is_range_ : std::false_type {}; template struct is_range_ : std::integral_constant::value || has_mutable_begin_end::value)> {}; // tuple_size and tuple_element check. template class is_tuple_like_ { template ::type> static auto check(U* p) -> decltype(std::tuple_size::value, 0); template static void check(...); public: static constexpr const bool value = !std::is_void(nullptr))>::value; }; // Check for integer_sequence #if defined(__cpp_lib_integer_sequence) || FMT_MSC_VERSION >= 1900 template using integer_sequence = std::integer_sequence; template using index_sequence = std::index_sequence; template using make_index_sequence = std::make_index_sequence; #else template struct integer_sequence { using value_type = T; static FMT_CONSTEXPR auto size() -> size_t { return sizeof...(N); } }; template using index_sequence = integer_sequence; template struct make_integer_sequence : make_integer_sequence {}; template struct make_integer_sequence : integer_sequence {}; template using make_index_sequence = make_integer_sequence; #endif template using tuple_index_sequence = make_index_sequence::value>; template ::value> class is_tuple_formattable_ { public: static constexpr const bool value = false; }; template class is_tuple_formattable_ { template static auto all_true(index_sequence, integer_sequence= 0)...>) -> std::true_type; static auto all_true(...) -> std::false_type; template static auto check(index_sequence) -> decltype(all_true( index_sequence{}, integer_sequence::type, C>::value)...>{})); public: static constexpr const bool value = decltype(check(tuple_index_sequence{}))::value; }; template FMT_CONSTEXPR void for_each(index_sequence, Tuple&& t, F&& f) { using std::get; // Using a free function get(Tuple) now. const int unused[] = {0, ((void)f(get(t)), 0)...}; ignore_unused(unused); } template FMT_CONSTEXPR void for_each(Tuple&& t, F&& f) { for_each(tuple_index_sequence>(), std::forward(t), std::forward(f)); } template void for_each2(index_sequence, Tuple1&& t1, Tuple2&& t2, F&& f) { using std::get; const int unused[] = {0, ((void)f(get(t1), get(t2)), 0)...}; ignore_unused(unused); } template void for_each2(Tuple1&& t1, Tuple2&& t2, F&& f) { for_each2(tuple_index_sequence>(), std::forward(t1), std::forward(t2), std::forward(f)); } namespace tuple { // Workaround a bug in MSVC 2019 (v140). template using result_t = std::tuple, Char>...>; using std::get; template auto get_formatters(index_sequence) -> result_t(std::declval()))...>; } // namespace tuple #if FMT_MSC_VERSION && FMT_MSC_VERSION < 1920 // Older MSVC doesn't get the reference type correctly for arrays. template struct range_reference_type_impl { using type = decltype(*detail::range_begin(std::declval())); }; template struct range_reference_type_impl { using type = T&; }; template using range_reference_type = typename range_reference_type_impl::type; #else template using range_reference_type = decltype(*detail::range_begin(std::declval())); #endif // We don't use the Range's value_type for anything, but we do need the Range's // reference type, with cv-ref stripped. template using uncvref_type = remove_cvref_t>; template FMT_CONSTEXPR auto maybe_set_debug_format(Formatter& f, bool set) -> decltype(f.set_debug_format(set)) { f.set_debug_format(set); } template FMT_CONSTEXPR void maybe_set_debug_format(Formatter&, ...) {} template struct range_format_kind_ : std::integral_constant, T>::value ? range_format::disabled : is_map::value ? range_format::map : is_set::value ? range_format::set : range_format::sequence> {}; template using range_format_constant = std::integral_constant; // These are not generic lambdas for compatibility with C++11. template struct parse_empty_specs { template FMT_CONSTEXPR void operator()(Formatter& f) { f.parse(ctx); detail::maybe_set_debug_format(f, true); } parse_context& ctx; }; template struct format_tuple_element { using char_type = typename FormatContext::char_type; template void operator()(const formatter& f, const T& v) { if (i > 0) ctx.advance_to(detail::copy(separator, ctx.out())); ctx.advance_to(f.format(v, ctx)); ++i; } int i; FormatContext& ctx; basic_string_view separator; }; } // namespace detail template struct is_tuple_like { static constexpr const bool value = detail::is_tuple_like_::value && !detail::is_range_::value; }; template struct is_tuple_formattable { static constexpr const bool value = detail::is_tuple_formattable_::value; }; template struct formatter::value && fmt::is_tuple_formattable::value>> { private: decltype(detail::tuple::get_formatters( detail::tuple_index_sequence())) formatters_; basic_string_view separator_ = detail::string_literal{}; basic_string_view opening_bracket_ = detail::string_literal{}; basic_string_view closing_bracket_ = detail::string_literal{}; public: FMT_CONSTEXPR formatter() {} FMT_CONSTEXPR void set_separator(basic_string_view sep) { separator_ = sep; } FMT_CONSTEXPR void set_brackets(basic_string_view open, basic_string_view close) { opening_bracket_ = open; closing_bracket_ = close; } FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { auto it = ctx.begin(); auto end = ctx.end(); if (it != end && detail::to_ascii(*it) == 'n') { ++it; set_brackets({}, {}); set_separator({}); } if (it != end && *it != '}') report_error("invalid format specifier"); ctx.advance_to(it); detail::for_each(formatters_, detail::parse_empty_specs{ctx}); return it; } template auto format(const Tuple& value, FormatContext& ctx) const -> decltype(ctx.out()) { ctx.advance_to(detail::copy(opening_bracket_, ctx.out())); detail::for_each2( formatters_, value, detail::format_tuple_element{0, ctx, separator_}); return detail::copy(closing_bracket_, ctx.out()); } }; template struct is_range { static constexpr const bool value = detail::is_range_::value && !detail::has_to_string_view::value; }; namespace detail { template using range_formatter_type = formatter, Char>; template using maybe_const_range = conditional_t::value, const R, R>; template struct is_formattable_delayed : is_formattable>, Char> {}; } // namespace detail template struct conjunction : std::true_type {}; template struct conjunction

: P {}; template struct conjunction : conditional_t, P1> {}; template struct range_formatter; template struct range_formatter< T, Char, enable_if_t>, is_formattable>::value>> { private: detail::range_formatter_type underlying_; basic_string_view separator_ = detail::string_literal{}; basic_string_view opening_bracket_ = detail::string_literal{}; basic_string_view closing_bracket_ = detail::string_literal{}; bool is_debug = false; template ::value)> auto write_debug_string(Output& out, It it, Sentinel end) const -> Output { auto buf = basic_memory_buffer(); for (; it != end; ++it) buf.push_back(*it); auto specs = format_specs(); specs.set_type(presentation_type::debug); return detail::write( out, basic_string_view(buf.data(), buf.size()), specs); } template ::value)> auto write_debug_string(Output& out, It, Sentinel) const -> Output { return out; } public: FMT_CONSTEXPR range_formatter() {} FMT_CONSTEXPR auto underlying() -> detail::range_formatter_type& { return underlying_; } FMT_CONSTEXPR void set_separator(basic_string_view sep) { separator_ = sep; } FMT_CONSTEXPR void set_brackets(basic_string_view open, basic_string_view close) { opening_bracket_ = open; closing_bracket_ = close; } FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { auto it = ctx.begin(); auto end = ctx.end(); detail::maybe_set_debug_format(underlying_, true); if (it == end) return underlying_.parse(ctx); switch (detail::to_ascii(*it)) { case 'n': set_brackets({}, {}); ++it; break; case '?': is_debug = true; set_brackets({}, {}); ++it; if (it == end || *it != 's') report_error("invalid format specifier"); FMT_FALLTHROUGH; case 's': if (!std::is_same::value) report_error("invalid format specifier"); if (!is_debug) { set_brackets(detail::string_literal{}, detail::string_literal{}); set_separator({}); detail::maybe_set_debug_format(underlying_, false); } ++it; return it; } if (it != end && *it != '}') { if (*it != ':') report_error("invalid format specifier"); detail::maybe_set_debug_format(underlying_, false); ++it; } ctx.advance_to(it); return underlying_.parse(ctx); } template auto format(R&& range, FormatContext& ctx) const -> decltype(ctx.out()) { auto out = ctx.out(); auto it = detail::range_begin(range); auto end = detail::range_end(range); if (is_debug) return write_debug_string(out, std::move(it), end); out = detail::copy(opening_bracket_, out); int i = 0; for (; it != end; ++it) { if (i > 0) out = detail::copy(separator_, out); ctx.advance_to(out); auto&& item = *it; // Need an lvalue out = underlying_.format(item, ctx); ++i; } out = detail::copy(closing_bracket_, out); return out; } }; FMT_EXPORT template struct range_format_kind : conditional_t< is_range::value, detail::range_format_kind_, std::integral_constant> {}; template struct formatter< R, Char, enable_if_t::value != range_format::disabled && range_format_kind::value != range_format::map && range_format_kind::value != range_format::string && range_format_kind::value != range_format::debug_string>, detail::is_formattable_delayed>::value>> { private: using range_type = detail::maybe_const_range; range_formatter, Char> range_formatter_; public: using nonlocking = void; FMT_CONSTEXPR formatter() { if (detail::const_check(range_format_kind::value != range_format::set)) return; range_formatter_.set_brackets(detail::string_literal{}, detail::string_literal{}); } FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { return range_formatter_.parse(ctx); } template auto format(range_type& range, FormatContext& ctx) const -> decltype(ctx.out()) { return range_formatter_.format(range, ctx); } }; // A map formatter. template struct formatter< R, Char, enable_if_t::value == range_format::map>> { private: using map_type = detail::maybe_const_range; using element_type = detail::uncvref_type; decltype(detail::tuple::get_formatters( detail::tuple_index_sequence())) formatters_; bool no_delimiters_ = false; public: FMT_CONSTEXPR formatter() {} FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { auto it = ctx.begin(); auto end = ctx.end(); if (it != end) { if (detail::to_ascii(*it) == 'n') { no_delimiters_ = true; ++it; } if (it != end && *it != '}') { if (*it != ':') report_error("invalid format specifier"); ++it; } ctx.advance_to(it); } detail::for_each(formatters_, detail::parse_empty_specs{ctx}); return it; } template auto format(map_type& map, FormatContext& ctx) const -> decltype(ctx.out()) { auto out = ctx.out(); basic_string_view open = detail::string_literal{}; if (!no_delimiters_) out = detail::copy(open, out); int i = 0; basic_string_view sep = detail::string_literal{}; for (auto&& value : map) { if (i > 0) out = detail::copy(sep, out); ctx.advance_to(out); detail::for_each2(formatters_, value, detail::format_tuple_element{ 0, ctx, detail::string_literal{}}); ++i; } basic_string_view close = detail::string_literal{}; if (!no_delimiters_) out = detail::copy(close, out); return out; } }; // A (debug_)string formatter. template struct formatter< R, Char, enable_if_t::value == range_format::string || range_format_kind::value == range_format::debug_string>> { private: using range_type = detail::maybe_const_range; using string_type = conditional_t, decltype(detail::range_begin(std::declval())), decltype(detail::range_end(std::declval()))>::value, detail::std_string_view, std::basic_string>; formatter underlying_; public: FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { return underlying_.parse(ctx); } template auto format(range_type& range, FormatContext& ctx) const -> decltype(ctx.out()) { auto out = ctx.out(); if (detail::const_check(range_format_kind::value == range_format::debug_string)) *out++ = '"'; out = underlying_.format( string_type{detail::range_begin(range), detail::range_end(range)}, ctx); if (detail::const_check(range_format_kind::value == range_format::debug_string)) *out++ = '"'; return out; } }; template struct join_view : detail::view { It begin; Sentinel end; basic_string_view sep; join_view(It b, Sentinel e, basic_string_view s) : begin(std::move(b)), end(e), sep(s) {} }; template struct formatter, Char> { private: using value_type = #ifdef __cpp_lib_ranges std::iter_value_t; #else typename std::iterator_traits::value_type; #endif formatter, Char> value_formatter_; using view = conditional_t::value, const join_view, join_view>; public: using nonlocking = void; FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { return value_formatter_.parse(ctx); } template auto format(view& value, FormatContext& ctx) const -> decltype(ctx.out()) { using iter = conditional_t::value, It, It&>; iter it = value.begin; auto out = ctx.out(); if (it == value.end) return out; out = value_formatter_.format(*it, ctx); ++it; while (it != value.end) { out = detail::copy(value.sep.begin(), value.sep.end(), out); ctx.advance_to(out); out = value_formatter_.format(*it, ctx); ++it; } return out; } }; template struct tuple_join_view : detail::view { const Tuple& tuple; basic_string_view sep; tuple_join_view(const Tuple& t, basic_string_view s) : tuple(t), sep{s} {} }; // Define FMT_TUPLE_JOIN_SPECIFIERS to enable experimental format specifiers // support in tuple_join. It is disabled by default because of issues with // the dynamic width and precision. #ifndef FMT_TUPLE_JOIN_SPECIFIERS # define FMT_TUPLE_JOIN_SPECIFIERS 0 #endif template struct formatter, Char, enable_if_t::value>> { FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { return do_parse(ctx, std::tuple_size()); } template auto format(const tuple_join_view& value, FormatContext& ctx) const -> typename FormatContext::iterator { return do_format(value, ctx, std::tuple_size()); } private: decltype(detail::tuple::get_formatters( detail::tuple_index_sequence())) formatters_; FMT_CONSTEXPR auto do_parse(parse_context& ctx, std::integral_constant) -> const Char* { return ctx.begin(); } template FMT_CONSTEXPR auto do_parse(parse_context& ctx, std::integral_constant) -> const Char* { auto end = ctx.begin(); #if FMT_TUPLE_JOIN_SPECIFIERS end = std::get::value - N>(formatters_).parse(ctx); if (N > 1) { auto end1 = do_parse(ctx, std::integral_constant()); if (end != end1) report_error("incompatible format specs for tuple elements"); } #endif return end; } template auto do_format(const tuple_join_view&, FormatContext& ctx, std::integral_constant) const -> typename FormatContext::iterator { return ctx.out(); } template auto do_format(const tuple_join_view& value, FormatContext& ctx, std::integral_constant) const -> typename FormatContext::iterator { using std::get; auto out = std::get::value - N>(formatters_) .format(get::value - N>(value.tuple), ctx); if (N <= 1) return out; out = detail::copy(value.sep, out); ctx.advance_to(out); return do_format(value, ctx, std::integral_constant()); } }; namespace detail { // Check if T has an interface like a container adaptor (e.g. std::stack, // std::queue, std::priority_queue). template class is_container_adaptor_like { template static auto check(U* p) -> typename U::container_type; template static void check(...); public: static constexpr const bool value = !std::is_void(nullptr))>::value; }; template struct all { const Container& c; auto begin() const -> typename Container::const_iterator { return c.begin(); } auto end() const -> typename Container::const_iterator { return c.end(); } }; } // namespace detail template struct formatter< T, Char, enable_if_t, bool_constant::value == range_format::disabled>>::value>> : formatter, Char> { using all = detail::all; template auto format(const T& t, FormatContext& ctx) const -> decltype(ctx.out()) { struct getter : T { static auto get(const T& t) -> all { return {t.*(&getter::c)}; // Access c through the derived class. } }; return formatter::format(getter::get(t), ctx); } }; FMT_BEGIN_EXPORT /// Returns a view that formats the iterator range `[begin, end)` with elements /// separated by `sep`. template auto join(It begin, Sentinel end, string_view sep) -> join_view { return {std::move(begin), end, sep}; } /** * Returns a view that formats `range` with elements separated by `sep`. * * **Example**: * * auto v = std::vector{1, 2, 3}; * fmt::print("{}", fmt::join(v, ", ")); * // Output: 1, 2, 3 * * `fmt::join` applies passed format specifiers to the range elements: * * fmt::print("{:02}", fmt::join(v, ", ")); * // Output: 01, 02, 03 */ template ::value)> auto join(Range&& r, string_view sep) -> join_view { return {detail::range_begin(r), detail::range_end(r), sep}; } /** * Returns an object that formats `std::tuple` with elements separated by `sep`. * * **Example**: * * auto t = std::tuple{1, 'a'}; * fmt::print("{}", fmt::join(t, ", ")); * // Output: 1, a */ template ::value)> FMT_CONSTEXPR auto join(const Tuple& tuple, string_view sep) -> tuple_join_view { return {tuple, sep}; } /** * Returns an object that formats `std::initializer_list` with elements * separated by `sep`. * * **Example**: * * fmt::print("{}", fmt::join({1, 2, 3}, ", ")); * // Output: "1, 2, 3" */ template auto join(std::initializer_list list, string_view sep) -> join_view { return join(std::begin(list), std::end(list), sep); } FMT_END_EXPORT FMT_END_NAMESPACE #endif // FMT_RANGES_H_ openal-soft-1.24.2/fmt-11.1.1/include/fmt/std.h000066400000000000000000000530271474041540300205170ustar00rootroot00000000000000// Formatting library for C++ - formatters for standard library types // // Copyright (c) 2012 - present, Victor Zverovich // All rights reserved. // // For the license information refer to format.h. #ifndef FMT_STD_H_ #define FMT_STD_H_ #include "format.h" #include "ostream.h" #ifndef FMT_MODULE # include # include # include # include # include # include # include # include # include # include # include # include // Check FMT_CPLUSPLUS to suppress a bogus warning in MSVC. # if FMT_CPLUSPLUS >= 201703L # if FMT_HAS_INCLUDE() && \ (!defined(FMT_CPP_LIB_FILESYSTEM) || FMT_CPP_LIB_FILESYSTEM != 0) # include # endif # if FMT_HAS_INCLUDE() # include # endif # if FMT_HAS_INCLUDE() # include # endif # endif // Use > instead of >= in the version check because may be // available after C++17 but before C++20 is marked as implemented. # if FMT_CPLUSPLUS > 201703L && FMT_HAS_INCLUDE() # include # endif # if FMT_CPLUSPLUS > 202002L && FMT_HAS_INCLUDE() # include # endif #endif // FMT_MODULE #if FMT_HAS_INCLUDE() # include #endif // GCC 4 does not support FMT_HAS_INCLUDE. #if FMT_HAS_INCLUDE() || defined(__GLIBCXX__) # include // Android NDK with gabi++ library on some architectures does not implement // abi::__cxa_demangle(). # ifndef __GABIXX_CXXABI_H__ # define FMT_HAS_ABI_CXA_DEMANGLE # endif #endif // For older Xcode versions, __cpp_lib_xxx flags are inaccurately defined. #ifndef FMT_CPP_LIB_FILESYSTEM # ifdef __cpp_lib_filesystem # define FMT_CPP_LIB_FILESYSTEM __cpp_lib_filesystem # else # define FMT_CPP_LIB_FILESYSTEM 0 # endif #endif #ifndef FMT_CPP_LIB_VARIANT # ifdef __cpp_lib_variant # define FMT_CPP_LIB_VARIANT __cpp_lib_variant # else # define FMT_CPP_LIB_VARIANT 0 # endif #endif #if FMT_CPP_LIB_FILESYSTEM FMT_BEGIN_NAMESPACE namespace detail { template auto get_path_string(const std::filesystem::path& p, const std::basic_string& native) { if constexpr (std::is_same_v && std::is_same_v) return to_utf8(native, to_utf8_error_policy::replace); else return p.string(); } template void write_escaped_path(basic_memory_buffer& quoted, const std::filesystem::path& p, const std::basic_string& native) { if constexpr (std::is_same_v && std::is_same_v) { auto buf = basic_memory_buffer(); write_escaped_string(std::back_inserter(buf), native); bool valid = to_utf8::convert(quoted, {buf.data(), buf.size()}); FMT_ASSERT(valid, "invalid utf16"); } else if constexpr (std::is_same_v) { write_escaped_string( std::back_inserter(quoted), native); } else { write_escaped_string(std::back_inserter(quoted), p.string()); } } } // namespace detail FMT_EXPORT template struct formatter { private: format_specs specs_; detail::arg_ref width_ref_; bool debug_ = false; char path_type_ = 0; public: FMT_CONSTEXPR void set_debug_format(bool set = true) { debug_ = set; } FMT_CONSTEXPR auto parse(parse_context& ctx) { auto it = ctx.begin(), end = ctx.end(); if (it == end) return it; it = detail::parse_align(it, end, specs_); if (it == end) return it; Char c = *it; if ((c >= '0' && c <= '9') || c == '{') it = detail::parse_width(it, end, specs_, width_ref_, ctx); if (it != end && *it == '?') { debug_ = true; ++it; } if (it != end && (*it == 'g')) path_type_ = detail::to_ascii(*it++); return it; } template auto format(const std::filesystem::path& p, FormatContext& ctx) const { auto specs = specs_; auto path_string = !path_type_ ? p.native() : p.generic_string(); detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_, ctx); if (!debug_) { auto s = detail::get_path_string(p, path_string); return detail::write(ctx.out(), basic_string_view(s), specs); } auto quoted = basic_memory_buffer(); detail::write_escaped_path(quoted, p, path_string); return detail::write(ctx.out(), basic_string_view(quoted.data(), quoted.size()), specs); } }; class path : public std::filesystem::path { public: auto display_string() const -> std::string { const std::filesystem::path& base = *this; return fmt::format(FMT_STRING("{}"), base); } auto system_string() const -> std::string { return string(); } auto generic_display_string() const -> std::string { const std::filesystem::path& base = *this; return fmt::format(FMT_STRING("{:g}"), base); } auto generic_system_string() const -> std::string { return generic_string(); } }; FMT_END_NAMESPACE #endif // FMT_CPP_LIB_FILESYSTEM FMT_BEGIN_NAMESPACE FMT_EXPORT template struct formatter, Char> : nested_formatter { private: // Functor because C++11 doesn't support generic lambdas. struct writer { const std::bitset& bs; template FMT_CONSTEXPR auto operator()(OutputIt out) -> OutputIt { for (auto pos = N; pos > 0; --pos) { out = detail::write(out, bs[pos - 1] ? Char('1') : Char('0')); } return out; } }; public: template auto format(const std::bitset& bs, FormatContext& ctx) const -> decltype(ctx.out()) { return write_padded(ctx, writer{bs}); } }; FMT_EXPORT template struct formatter : basic_ostream_formatter {}; FMT_END_NAMESPACE #ifdef __cpp_lib_optional FMT_BEGIN_NAMESPACE FMT_EXPORT template struct formatter, Char, std::enable_if_t::value>> { private: formatter underlying_; static constexpr basic_string_view optional = detail::string_literal{}; static constexpr basic_string_view none = detail::string_literal{}; template FMT_CONSTEXPR static auto maybe_set_debug_format(U& u, bool set) -> decltype(u.set_debug_format(set)) { u.set_debug_format(set); } template FMT_CONSTEXPR static void maybe_set_debug_format(U&, ...) {} public: FMT_CONSTEXPR auto parse(parse_context& ctx) { maybe_set_debug_format(underlying_, true); return underlying_.parse(ctx); } template auto format(const std::optional& opt, FormatContext& ctx) const -> decltype(ctx.out()) { if (!opt) return detail::write(ctx.out(), none); auto out = ctx.out(); out = detail::write(out, optional); ctx.advance_to(out); out = underlying_.format(*opt, ctx); return detail::write(out, ')'); } }; FMT_END_NAMESPACE #endif // __cpp_lib_optional #if defined(__cpp_lib_expected) || FMT_CPP_LIB_VARIANT FMT_BEGIN_NAMESPACE namespace detail { template auto write_escaped_alternative(OutputIt out, const T& v) -> OutputIt { if constexpr (has_to_string_view::value) return write_escaped_string(out, detail::to_string_view(v)); if constexpr (std::is_same_v) return write_escaped_char(out, v); return write(out, v); } } // namespace detail FMT_END_NAMESPACE #endif #ifdef __cpp_lib_expected FMT_BEGIN_NAMESPACE FMT_EXPORT template struct formatter, Char, std::enable_if_t<(std::is_void::value || is_formattable::value) && is_formattable::value>> { FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { return ctx.begin(); } template auto format(const std::expected& value, FormatContext& ctx) const -> decltype(ctx.out()) { auto out = ctx.out(); if (value.has_value()) { out = detail::write(out, "expected("); if constexpr (!std::is_void::value) out = detail::write_escaped_alternative(out, *value); } else { out = detail::write(out, "unexpected("); out = detail::write_escaped_alternative(out, value.error()); } *out++ = ')'; return out; } }; FMT_END_NAMESPACE #endif // __cpp_lib_expected #ifdef __cpp_lib_source_location FMT_BEGIN_NAMESPACE FMT_EXPORT template <> struct formatter { FMT_CONSTEXPR auto parse(parse_context<>& ctx) { return ctx.begin(); } template auto format(const std::source_location& loc, FormatContext& ctx) const -> decltype(ctx.out()) { auto out = ctx.out(); out = detail::write(out, loc.file_name()); out = detail::write(out, ':'); out = detail::write(out, loc.line()); out = detail::write(out, ':'); out = detail::write(out, loc.column()); out = detail::write(out, ": "); out = detail::write(out, loc.function_name()); return out; } }; FMT_END_NAMESPACE #endif #if FMT_CPP_LIB_VARIANT FMT_BEGIN_NAMESPACE namespace detail { template using variant_index_sequence = std::make_index_sequence::value>; template struct is_variant_like_ : std::false_type {}; template struct is_variant_like_> : std::true_type {}; // formattable element check. template class is_variant_formattable_ { template static std::conjunction< is_formattable, C>...> check(std::index_sequence); public: static constexpr const bool value = decltype(check(variant_index_sequence{}))::value; }; } // namespace detail template struct is_variant_like { static constexpr const bool value = detail::is_variant_like_::value; }; template struct is_variant_formattable { static constexpr const bool value = detail::is_variant_formattable_::value; }; FMT_EXPORT template struct formatter { FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { return ctx.begin(); } template auto format(const std::monostate&, FormatContext& ctx) const -> decltype(ctx.out()) { return detail::write(ctx.out(), "monostate"); } }; FMT_EXPORT template struct formatter< Variant, Char, std::enable_if_t, is_variant_formattable>>> { FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { return ctx.begin(); } template auto format(const Variant& value, FormatContext& ctx) const -> decltype(ctx.out()) { auto out = ctx.out(); out = detail::write(out, "variant("); FMT_TRY { std::visit( [&](const auto& v) { out = detail::write_escaped_alternative(out, v); }, value); } FMT_CATCH(const std::bad_variant_access&) { detail::write(out, "valueless by exception"); } *out++ = ')'; return out; } }; FMT_END_NAMESPACE #endif // FMT_CPP_LIB_VARIANT FMT_BEGIN_NAMESPACE FMT_EXPORT template <> struct formatter { private: format_specs specs_; detail::arg_ref width_ref_; public: FMT_CONSTEXPR auto parse(parse_context<>& ctx) -> const char* { auto it = ctx.begin(), end = ctx.end(); if (it == end) return it; it = detail::parse_align(it, end, specs_); if (it == end) return it; char c = *it; if ((c >= '0' && c <= '9') || c == '{') it = detail::parse_width(it, end, specs_, width_ref_, ctx); return it; } template FMT_CONSTEXPR20 auto format(const std::error_code& ec, FormatContext& ctx) const -> decltype(ctx.out()) { auto specs = specs_; detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_, ctx); memory_buffer buf; buf.append(string_view(ec.category().name())); buf.push_back(':'); detail::write(appender(buf), ec.value()); return detail::write(ctx.out(), string_view(buf.data(), buf.size()), specs); } }; #if FMT_USE_RTTI namespace detail { template auto write_demangled_name(OutputIt out, const std::type_info& ti) -> OutputIt { # ifdef FMT_HAS_ABI_CXA_DEMANGLE int status = 0; std::size_t size = 0; std::unique_ptr demangled_name_ptr( abi::__cxa_demangle(ti.name(), nullptr, &size, &status), &std::free); string_view demangled_name_view; if (demangled_name_ptr) { demangled_name_view = demangled_name_ptr.get(); // Normalization of stdlib inline namespace names. // libc++ inline namespaces. // std::__1::* -> std::* // std::__1::__fs::* -> std::* // libstdc++ inline namespaces. // std::__cxx11::* -> std::* // std::filesystem::__cxx11::* -> std::filesystem::* if (demangled_name_view.starts_with("std::")) { char* begin = demangled_name_ptr.get(); char* to = begin + 5; // std:: for (char *from = to, *end = begin + demangled_name_view.size(); from < end;) { // This is safe, because demangled_name is NUL-terminated. if (from[0] == '_' && from[1] == '_') { char* next = from + 1; while (next < end && *next != ':') next++; if (next[0] == ':' && next[1] == ':') { from = next + 2; continue; } } *to++ = *from++; } demangled_name_view = {begin, detail::to_unsigned(to - begin)}; } } else { demangled_name_view = string_view(ti.name()); } return detail::write_bytes(out, demangled_name_view); # elif FMT_MSC_VERSION const string_view demangled_name(ti.name()); for (std::size_t i = 0; i < demangled_name.size(); ++i) { auto sub = demangled_name; sub.remove_prefix(i); if (sub.starts_with("enum ")) { i += 4; continue; } if (sub.starts_with("class ") || sub.starts_with("union ")) { i += 5; continue; } if (sub.starts_with("struct ")) { i += 6; continue; } if (*sub.begin() != ' ') *out++ = *sub.begin(); } return out; # else return detail::write_bytes(out, string_view(ti.name())); # endif } } // namespace detail FMT_EXPORT template struct formatter { public: FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { return ctx.begin(); } template auto format(const std::type_info& ti, Context& ctx) const -> decltype(ctx.out()) { return detail::write_demangled_name(ctx.out(), ti); } }; #endif FMT_EXPORT template struct formatter< T, Char, // DEPRECATED! Mixing code unit types. typename std::enable_if::value>::type> { private: bool with_typename_ = false; public: FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { auto it = ctx.begin(); auto end = ctx.end(); if (it == end || *it == '}') return it; if (*it == 't') { ++it; with_typename_ = FMT_USE_RTTI != 0; } return it; } template auto format(const std::exception& ex, Context& ctx) const -> decltype(ctx.out()) { auto out = ctx.out(); #if FMT_USE_RTTI if (with_typename_) { out = detail::write_demangled_name(out, typeid(ex)); *out++ = ':'; *out++ = ' '; } #endif return detail::write_bytes(out, string_view(ex.what())); } }; namespace detail { template struct has_flip : std::false_type {}; template struct has_flip().flip())>> : std::true_type {}; template struct is_bit_reference_like { static constexpr const bool value = std::is_convertible::value && std::is_nothrow_assignable::value && has_flip::value; }; #ifdef _LIBCPP_VERSION // Workaround for libc++ incompatibility with C++ standard. // According to the Standard, `bitset::operator[] const` returns bool. template struct is_bit_reference_like> { static constexpr const bool value = true; }; #endif } // namespace detail // We can't use std::vector::reference and // std::bitset::reference because the compiler can't deduce Allocator and N // in partial specialization. FMT_EXPORT template struct formatter::value>> : formatter { template FMT_CONSTEXPR auto format(const BitRef& v, FormatContext& ctx) const -> decltype(ctx.out()) { return formatter::format(v, ctx); } }; template auto ptr(const std::unique_ptr& p) -> const void* { return p.get(); } template auto ptr(const std::shared_ptr& p) -> const void* { return p.get(); } FMT_EXPORT template struct formatter, Char, enable_if_t::value>> : formatter { template auto format(const std::atomic& v, FormatContext& ctx) const -> decltype(ctx.out()) { return formatter::format(v.load(), ctx); } }; #ifdef __cpp_lib_atomic_flag_test FMT_EXPORT template struct formatter : formatter { template auto format(const std::atomic_flag& v, FormatContext& ctx) const -> decltype(ctx.out()) { return formatter::format(v.test(), ctx); } }; #endif // __cpp_lib_atomic_flag_test FMT_EXPORT template struct formatter, Char> { private: detail::dynamic_format_specs specs_; template FMT_CONSTEXPR auto do_format(const std::complex& c, detail::dynamic_format_specs& specs, FormatContext& ctx, OutputIt out) const -> OutputIt { if (c.real() != 0) { *out++ = Char('('); out = detail::write(out, c.real(), specs, ctx.locale()); specs.set_sign(sign::plus); out = detail::write(out, c.imag(), specs, ctx.locale()); if (!detail::isfinite(c.imag())) *out++ = Char(' '); *out++ = Char('i'); *out++ = Char(')'); return out; } out = detail::write(out, c.imag(), specs, ctx.locale()); if (!detail::isfinite(c.imag())) *out++ = Char(' '); *out++ = Char('i'); return out; } public: FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { if (ctx.begin() == ctx.end() || *ctx.begin() == '}') return ctx.begin(); return parse_format_specs(ctx.begin(), ctx.end(), specs_, ctx, detail::type_constant::value); } template auto format(const std::complex& c, FormatContext& ctx) const -> decltype(ctx.out()) { auto specs = specs_; if (specs.dynamic()) { detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, specs.width_ref, ctx); detail::handle_dynamic_spec(specs.dynamic_precision(), specs.precision, specs.precision_ref, ctx); } if (specs.width == 0) return do_format(c, specs, ctx, ctx.out()); auto buf = basic_memory_buffer(); auto outer_specs = format_specs(); outer_specs.width = specs.width; auto fill = specs.template fill(); if (fill) outer_specs.set_fill(basic_string_view(fill, specs.fill_size())); outer_specs.set_align(specs.align()); specs.width = 0; specs.set_fill({}); specs.set_align(align::none); do_format(c, specs, ctx, basic_appender(buf)); return detail::write(ctx.out(), basic_string_view(buf.data(), buf.size()), outer_specs); } }; FMT_EXPORT template struct formatter, Char, enable_if_t, Char>::value>> : formatter, Char> { template auto format(std::reference_wrapper ref, FormatContext& ctx) const -> decltype(ctx.out()) { return formatter, Char>::format(ref.get(), ctx); } }; FMT_END_NAMESPACE #endif // FMT_STD_H_ openal-soft-1.24.2/fmt-11.1.1/include/fmt/xchar.h000066400000000000000000000323271474041540300210320ustar00rootroot00000000000000// Formatting library for C++ - optional wchar_t and exotic character support // // Copyright (c) 2012 - present, Victor Zverovich // All rights reserved. // // For the license information refer to format.h. #ifndef FMT_XCHAR_H_ #define FMT_XCHAR_H_ #include "color.h" #include "format.h" #include "ostream.h" #include "ranges.h" #ifndef FMT_MODULE # include # if FMT_USE_LOCALE # include # endif #endif FMT_BEGIN_NAMESPACE namespace detail { template using is_exotic_char = bool_constant::value>; template struct format_string_char {}; template struct format_string_char< S, void_t())))>> { using type = char_t; }; template struct format_string_char< S, enable_if_t::value>> { using type = typename S::char_type; }; template using format_string_char_t = typename format_string_char::type; inline auto write_loc(basic_appender out, loc_value value, const format_specs& specs, locale_ref loc) -> bool { #if FMT_USE_LOCALE auto& numpunct = std::use_facet>(loc.get()); auto separator = std::wstring(); auto grouping = numpunct.grouping(); if (!grouping.empty()) separator = std::wstring(1, numpunct.thousands_sep()); return value.visit(loc_writer{out, specs, separator, grouping, {}}); #endif return false; } } // namespace detail FMT_BEGIN_EXPORT using wstring_view = basic_string_view; using wformat_parse_context = parse_context; using wformat_context = buffered_context; using wformat_args = basic_format_args; using wmemory_buffer = basic_memory_buffer; template struct basic_fstring { private: basic_string_view str_; static constexpr int num_static_named_args = detail::count_static_named_args(); using checker = detail::format_string_checker< Char, static_cast(sizeof...(T)), num_static_named_args, num_static_named_args != detail::count_named_args()>; using arg_pack = detail::arg_pack; public: using t = basic_fstring; template >::value)> FMT_CONSTEVAL FMT_ALWAYS_INLINE basic_fstring(const S& s) : str_(s) { if (FMT_USE_CONSTEVAL) detail::parse_format_string(s, checker(s, arg_pack())); } template ::value&& std::is_same::value)> FMT_ALWAYS_INLINE basic_fstring(const S&) : str_(S()) { FMT_CONSTEXPR auto sv = basic_string_view(S()); FMT_CONSTEXPR int ignore = (parse_format_string(sv, checker(sv, arg_pack())), 0); detail::ignore_unused(ignore); } basic_fstring(runtime_format_string fmt) : str_(fmt.str) {} operator basic_string_view() const { return str_; } auto get() const -> basic_string_view { return str_; } }; template using basic_format_string = basic_fstring; template using wformat_string = typename basic_format_string::t; inline auto runtime(wstring_view s) -> runtime_format_string { return {{s}}; } template <> struct is_char : std::true_type {}; template <> struct is_char : std::true_type {}; template <> struct is_char : std::true_type {}; #ifdef __cpp_char8_t template <> struct is_char : bool_constant {}; #endif template constexpr auto make_wformat_args(T&... args) -> decltype(fmt::make_format_args(args...)) { return fmt::make_format_args(args...); } #if !FMT_USE_NONTYPE_TEMPLATE_ARGS inline namespace literals { inline auto operator""_a(const wchar_t* s, size_t) -> detail::udl_arg { return {s}; } } // namespace literals #endif template auto join(It begin, Sentinel end, wstring_view sep) -> join_view { return {begin, end, sep}; } template ::value)> auto join(Range&& range, wstring_view sep) -> join_view { return join(std::begin(range), std::end(range), sep); } template auto join(std::initializer_list list, wstring_view sep) -> join_view { return join(std::begin(list), std::end(list), sep); } template ::value)> auto join(const Tuple& tuple, basic_string_view sep) -> tuple_join_view { return {tuple, sep}; } template ::value)> auto vformat(basic_string_view fmt, typename detail::vformat_args::type args) -> std::basic_string { auto buf = basic_memory_buffer(); detail::vformat_to(buf, fmt, args); return {buf.data(), buf.size()}; } template auto format(wformat_string fmt, T&&... args) -> std::wstring { return vformat(fmt::wstring_view(fmt), fmt::make_wformat_args(args...)); } template auto format_to(OutputIt out, wformat_string fmt, T&&... args) -> OutputIt { return vformat_to(out, fmt::wstring_view(fmt), fmt::make_wformat_args(args...)); } // Pass char_t as a default template parameter instead of using // std::basic_string> to reduce the symbol size. template , FMT_ENABLE_IF(!std::is_same::value && !std::is_same::value)> auto format(const S& fmt, T&&... args) -> std::basic_string { return vformat(detail::to_string_view(fmt), fmt::make_format_args>(args...)); } template , FMT_ENABLE_IF(detail::is_exotic_char::value)> inline auto vformat(detail::locale_ref loc, const S& fmt, typename detail::vformat_args::type args) -> std::basic_string { auto buf = basic_memory_buffer(); detail::vformat_to(buf, detail::to_string_view(fmt), args, detail::locale_ref(loc)); return {buf.data(), buf.size()}; } template , FMT_ENABLE_IF(detail::is_exotic_char::value)> inline auto format(detail::locale_ref loc, const S& fmt, T&&... args) -> std::basic_string { return vformat(loc, detail::to_string_view(fmt), fmt::make_format_args>(args...)); } template , FMT_ENABLE_IF(detail::is_output_iterator::value&& detail::is_exotic_char::value)> auto vformat_to(OutputIt out, const S& fmt, typename detail::vformat_args::type args) -> OutputIt { auto&& buf = detail::get_buffer(out); detail::vformat_to(buf, detail::to_string_view(fmt), args); return detail::get_iterator(buf, out); } template , FMT_ENABLE_IF(detail::is_output_iterator::value && !std::is_same::value && !std::is_same::value)> inline auto format_to(OutputIt out, const S& fmt, T&&... args) -> OutputIt { return vformat_to(out, detail::to_string_view(fmt), fmt::make_format_args>(args...)); } template , FMT_ENABLE_IF(detail::is_output_iterator::value&& detail::is_exotic_char::value)> inline auto vformat_to(OutputIt out, detail::locale_ref loc, const S& fmt, typename detail::vformat_args::type args) -> OutputIt { auto&& buf = detail::get_buffer(out); vformat_to(buf, detail::to_string_view(fmt), args, detail::locale_ref(loc)); return detail::get_iterator(buf, out); } template , bool enable = detail::is_output_iterator::value && detail::is_exotic_char::value> inline auto format_to(OutputIt out, detail::locale_ref loc, const S& fmt, T&&... args) -> typename std::enable_if::type { return vformat_to(out, loc, detail::to_string_view(fmt), fmt::make_format_args>(args...)); } template ::value&& detail::is_exotic_char::value)> inline auto vformat_to_n(OutputIt out, size_t n, basic_string_view fmt, typename detail::vformat_args::type args) -> format_to_n_result { using traits = detail::fixed_buffer_traits; auto buf = detail::iterator_buffer(out, n); detail::vformat_to(buf, fmt, args); return {buf.out(), buf.count()}; } template , FMT_ENABLE_IF(detail::is_output_iterator::value&& detail::is_exotic_char::value)> inline auto format_to_n(OutputIt out, size_t n, const S& fmt, T&&... args) -> format_to_n_result { return vformat_to_n(out, n, fmt::basic_string_view(fmt), fmt::make_format_args>(args...)); } template , FMT_ENABLE_IF(detail::is_exotic_char::value)> inline auto formatted_size(const S& fmt, T&&... args) -> size_t { auto buf = detail::counting_buffer(); detail::vformat_to(buf, detail::to_string_view(fmt), fmt::make_format_args>(args...)); return buf.count(); } inline void vprint(std::FILE* f, wstring_view fmt, wformat_args args) { auto buf = wmemory_buffer(); detail::vformat_to(buf, fmt, args); buf.push_back(L'\0'); if (std::fputws(buf.data(), f) == -1) FMT_THROW(system_error(errno, FMT_STRING("cannot write to file"))); } inline void vprint(wstring_view fmt, wformat_args args) { vprint(stdout, fmt, args); } template void print(std::FILE* f, wformat_string fmt, T&&... args) { return vprint(f, wstring_view(fmt), fmt::make_wformat_args(args...)); } template void print(wformat_string fmt, T&&... args) { return vprint(wstring_view(fmt), fmt::make_wformat_args(args...)); } template void println(std::FILE* f, wformat_string fmt, T&&... args) { return print(f, L"{}\n", fmt::format(fmt, std::forward(args)...)); } template void println(wformat_string fmt, T&&... args) { return print(L"{}\n", fmt::format(fmt, std::forward(args)...)); } inline auto vformat(const text_style& ts, wstring_view fmt, wformat_args args) -> std::wstring { auto buf = wmemory_buffer(); detail::vformat_to(buf, ts, fmt, args); return {buf.data(), buf.size()}; } template inline auto format(const text_style& ts, wformat_string fmt, T&&... args) -> std::wstring { return fmt::vformat(ts, fmt, fmt::make_wformat_args(args...)); } template FMT_DEPRECATED void print(std::FILE* f, const text_style& ts, wformat_string fmt, const T&... args) { vprint(f, ts, fmt, fmt::make_wformat_args(args...)); } template FMT_DEPRECATED void print(const text_style& ts, wformat_string fmt, const T&... args) { return print(stdout, ts, fmt, args...); } inline void vprint(std::wostream& os, wstring_view fmt, wformat_args args) { auto buffer = basic_memory_buffer(); detail::vformat_to(buffer, fmt, args); detail::write_buffer(os, buffer); } template void print(std::wostream& os, wformat_string fmt, T&&... args) { vprint(os, fmt, fmt::make_format_args>(args...)); } template void println(std::wostream& os, wformat_string fmt, T&&... args) { print(os, L"{}\n", fmt::format(fmt, std::forward(args)...)); } /// Converts `value` to `std::wstring` using the default format for type `T`. template inline auto to_wstring(const T& value) -> std::wstring { return format(FMT_STRING(L"{}"), value); } FMT_END_EXPORT FMT_END_NAMESPACE #endif // FMT_XCHAR_H_ openal-soft-1.24.2/fmt-11.1.1/src/000077500000000000000000000000001474041540300161235ustar00rootroot00000000000000openal-soft-1.24.2/fmt-11.1.1/src/fmt.cc000066400000000000000000000063031474041540300172220ustar00rootroot00000000000000module; #ifdef _MSVC_LANG # define FMT_CPLUSPLUS _MSVC_LANG #else # define FMT_CPLUSPLUS __cplusplus #endif // Put all implementation-provided headers into the global module fragment // to prevent attachment to this module. #ifndef FMT_IMPORT_STD # include # include # include # include # include # include # include # include # include # include # include # include # if FMT_CPLUSPLUS > 202002L # include # endif # include # include # include # include # include # include # include # include # include # include # include # include # include # include # include # include # include # include # include # include #else # include # include # include # include #endif #include #include #include #if __has_include() # include #endif #if defined(_MSC_VER) || defined(__MINGW32__) # include #endif #if defined __APPLE__ || defined(__FreeBSD__) # include #endif #if __has_include() # include #endif #if (__has_include() || defined(__APPLE__) || \ defined(__linux__)) && \ (!defined(WINAPI_FAMILY) || (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP)) # include # include # include # ifndef _WIN32 # include # else # include # endif #endif #ifdef _WIN32 # if defined(__GLIBCXX__) # include # include # endif # define WIN32_LEAN_AND_MEAN # include #endif export module fmt; #ifdef FMT_IMPORT_STD import std; #endif #define FMT_EXPORT export #define FMT_BEGIN_EXPORT export { #define FMT_END_EXPORT } // If you define FMT_ATTACH_TO_GLOBAL_MODULE // - all declarations are detached from module 'fmt' // - the module behaves like a traditional static library, too // - all library symbols are mangled traditionally // - you can mix TUs with either importing or #including the {fmt} API #ifdef FMT_ATTACH_TO_GLOBAL_MODULE extern "C++" { #endif #ifndef FMT_OS # define FMT_OS 1 #endif // All library-provided declarations and definitions must be in the module // purview to be exported. #include "fmt/args.h" #include "fmt/chrono.h" #include "fmt/color.h" #include "fmt/compile.h" #include "fmt/format.h" #if FMT_OS # include "fmt/os.h" #endif #include "fmt/ostream.h" #include "fmt/printf.h" #include "fmt/ranges.h" #include "fmt/std.h" #include "fmt/xchar.h" #ifdef FMT_ATTACH_TO_GLOBAL_MODULE } #endif // gcc doesn't yet implement private module fragments #if !FMT_GCC_VERSION module :private; #endif #ifdef FMT_ATTACH_TO_GLOBAL_MODULE extern "C++" { #endif #if FMT_HAS_INCLUDE("format.cc") # include "format.cc" #endif #if FMT_OS && FMT_HAS_INCLUDE("os.cc") # include "os.cc" #endif #ifdef FMT_ATTACH_TO_GLOBAL_MODULE } #endif openal-soft-1.24.2/fmt-11.1.1/src/format.cc000066400000000000000000000025611474041540300177260ustar00rootroot00000000000000// Formatting library for C++ // // Copyright (c) 2012 - 2016, Victor Zverovich // All rights reserved. // // For the license information refer to format.h. #include "fmt/format-inl.h" FMT_BEGIN_NAMESPACE namespace detail { template FMT_API auto dragonbox::to_decimal(float x) noexcept -> dragonbox::decimal_fp; template FMT_API auto dragonbox::to_decimal(double x) noexcept -> dragonbox::decimal_fp; #if FMT_USE_LOCALE // DEPRECATED! locale_ref in the detail namespace template FMT_API locale_ref::locale_ref(const std::locale& loc); template FMT_API auto locale_ref::get() const -> std::locale; #endif // Explicit instantiations for char. template FMT_API auto thousands_sep_impl(locale_ref) -> thousands_sep_result; template FMT_API auto decimal_point_impl(locale_ref) -> char; // DEPRECATED! template FMT_API void buffer::append(const char*, const char*); // DEPRECATED! template FMT_API void vformat_to(buffer&, string_view, typename vformat_args<>::type, locale_ref); // Explicit instantiations for wchar_t. template FMT_API auto thousands_sep_impl(locale_ref) -> thousands_sep_result; template FMT_API auto decimal_point_impl(locale_ref) -> wchar_t; template FMT_API void buffer::append(const wchar_t*, const wchar_t*); } // namespace detail FMT_END_NAMESPACE openal-soft-1.24.2/fmt-11.1.1/src/os.cc000066400000000000000000000261501474041540300170570ustar00rootroot00000000000000// Formatting library for C++ - optional OS-specific functionality // // Copyright (c) 2012 - 2016, Victor Zverovich // All rights reserved. // // For the license information refer to format.h. // Disable bogus MSVC warnings. #if !defined(_CRT_SECURE_NO_WARNINGS) && defined(_MSC_VER) # define _CRT_SECURE_NO_WARNINGS #endif #include "fmt/os.h" #ifndef FMT_MODULE # include # if FMT_USE_FCNTL # include # include # ifdef _WRS_KERNEL // VxWorks7 kernel # include // getpagesize # endif # ifndef _WIN32 # include # else # ifndef WIN32_LEAN_AND_MEAN # define WIN32_LEAN_AND_MEAN # endif # include # endif // _WIN32 # endif // FMT_USE_FCNTL # ifdef _WIN32 # include # endif #endif #ifdef _WIN32 # ifndef S_IRUSR # define S_IRUSR _S_IREAD # endif # ifndef S_IWUSR # define S_IWUSR _S_IWRITE # endif # ifndef S_IRGRP # define S_IRGRP 0 # endif # ifndef S_IWGRP # define S_IWGRP 0 # endif # ifndef S_IROTH # define S_IROTH 0 # endif # ifndef S_IWOTH # define S_IWOTH 0 # endif #endif namespace { #ifdef _WIN32 // Return type of read and write functions. using rwresult = int; // On Windows the count argument to read and write is unsigned, so convert // it from size_t preventing integer overflow. inline unsigned convert_rwcount(std::size_t count) { return count <= UINT_MAX ? static_cast(count) : UINT_MAX; } #elif FMT_USE_FCNTL // Return type of read and write functions. using rwresult = ssize_t; inline std::size_t convert_rwcount(std::size_t count) { return count; } #endif } // namespace FMT_BEGIN_NAMESPACE #ifdef _WIN32 namespace detail { class system_message { system_message(const system_message&) = delete; void operator=(const system_message&) = delete; unsigned long result_; wchar_t* message_; static bool is_whitespace(wchar_t c) noexcept { return c == L' ' || c == L'\n' || c == L'\r' || c == L'\t' || c == L'\0'; } public: explicit system_message(unsigned long error_code) : result_(0), message_(nullptr) { result_ = FormatMessageW( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), reinterpret_cast(&message_), 0, nullptr); if (result_ != 0) { while (result_ != 0 && is_whitespace(message_[result_ - 1])) { --result_; } } } ~system_message() { LocalFree(message_); } explicit operator bool() const noexcept { return result_ != 0; } operator basic_string_view() const noexcept { return basic_string_view(message_, result_); } }; class utf8_system_category final : public std::error_category { public: const char* name() const noexcept override { return "system"; } std::string message(int error_code) const override { auto&& msg = system_message(error_code); if (msg) { auto utf8_message = to_utf8(); if (utf8_message.convert(msg)) { return utf8_message.str(); } } return "unknown error"; } }; } // namespace detail FMT_API const std::error_category& system_category() noexcept { static const detail::utf8_system_category category; return category; } std::system_error vwindows_error(int err_code, string_view format_str, format_args args) { auto ec = std::error_code(err_code, system_category()); return std::system_error(ec, vformat(format_str, args)); } void detail::format_windows_error(detail::buffer& out, int error_code, const char* message) noexcept { FMT_TRY { auto&& msg = system_message(error_code); if (msg) { auto utf8_message = to_utf8(); if (utf8_message.convert(msg)) { fmt::format_to(appender(out), FMT_STRING("{}: {}"), message, string_view(utf8_message)); return; } } } FMT_CATCH(...) {} format_error_code(out, error_code, message); } void report_windows_error(int error_code, const char* message) noexcept { do_report_error(detail::format_windows_error, error_code, message); } #endif // _WIN32 buffered_file::~buffered_file() noexcept { if (file_ && FMT_SYSTEM(fclose(file_)) != 0) report_system_error(errno, "cannot close file"); } buffered_file::buffered_file(cstring_view filename, cstring_view mode) { FMT_RETRY_VAL(file_, FMT_SYSTEM(fopen(filename.c_str(), mode.c_str())), nullptr); if (!file_) FMT_THROW(system_error(errno, FMT_STRING("cannot open file {}"), filename.c_str())); } void buffered_file::close() { if (!file_) return; int result = FMT_SYSTEM(fclose(file_)); file_ = nullptr; if (result != 0) FMT_THROW(system_error(errno, FMT_STRING("cannot close file"))); } int buffered_file::descriptor() const { #ifdef FMT_HAS_SYSTEM // fileno is a macro on OpenBSD. # ifdef fileno # undef fileno # endif int fd = FMT_POSIX_CALL(fileno(file_)); #elif defined(_WIN32) int fd = _fileno(file_); #else int fd = fileno(file_); #endif if (fd == -1) FMT_THROW(system_error(errno, FMT_STRING("cannot get file descriptor"))); return fd; } #if FMT_USE_FCNTL # ifdef _WIN32 using mode_t = int; # endif constexpr mode_t default_open_mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; file::file(cstring_view path, int oflag) { # if defined(_WIN32) && !defined(__MINGW32__) fd_ = -1; auto converted = detail::utf8_to_utf16(string_view(path.c_str())); *this = file::open_windows_file(converted.c_str(), oflag); # else FMT_RETRY(fd_, FMT_POSIX_CALL(open(path.c_str(), oflag, default_open_mode))); if (fd_ == -1) FMT_THROW( system_error(errno, FMT_STRING("cannot open file {}"), path.c_str())); # endif } file::~file() noexcept { // Don't retry close in case of EINTR! // See http://linux.derkeiler.com/Mailing-Lists/Kernel/2005-09/3000.html if (fd_ != -1 && FMT_POSIX_CALL(close(fd_)) != 0) report_system_error(errno, "cannot close file"); } void file::close() { if (fd_ == -1) return; // Don't retry close in case of EINTR! // See http://linux.derkeiler.com/Mailing-Lists/Kernel/2005-09/3000.html int result = FMT_POSIX_CALL(close(fd_)); fd_ = -1; if (result != 0) FMT_THROW(system_error(errno, FMT_STRING("cannot close file"))); } long long file::size() const { # ifdef _WIN32 // Use GetFileSize instead of GetFileSizeEx for the case when _WIN32_WINNT // is less than 0x0500 as is the case with some default MinGW builds. // Both functions support large file sizes. DWORD size_upper = 0; HANDLE handle = reinterpret_cast(_get_osfhandle(fd_)); DWORD size_lower = FMT_SYSTEM(GetFileSize(handle, &size_upper)); if (size_lower == INVALID_FILE_SIZE) { DWORD error = GetLastError(); if (error != NO_ERROR) FMT_THROW(windows_error(GetLastError(), "cannot get file size")); } unsigned long long long_size = size_upper; return (long_size << sizeof(DWORD) * CHAR_BIT) | size_lower; # else using Stat = struct stat; Stat file_stat = Stat(); if (FMT_POSIX_CALL(fstat(fd_, &file_stat)) == -1) FMT_THROW(system_error(errno, FMT_STRING("cannot get file attributes"))); static_assert(sizeof(long long) >= sizeof(file_stat.st_size), "return type of file::size is not large enough"); return file_stat.st_size; # endif } std::size_t file::read(void* buffer, std::size_t count) { rwresult result = 0; FMT_RETRY(result, FMT_POSIX_CALL(read(fd_, buffer, convert_rwcount(count)))); if (result < 0) FMT_THROW(system_error(errno, FMT_STRING("cannot read from file"))); return detail::to_unsigned(result); } std::size_t file::write(const void* buffer, std::size_t count) { rwresult result = 0; FMT_RETRY(result, FMT_POSIX_CALL(write(fd_, buffer, convert_rwcount(count)))); if (result < 0) FMT_THROW(system_error(errno, FMT_STRING("cannot write to file"))); return detail::to_unsigned(result); } file file::dup(int fd) { // Don't retry as dup doesn't return EINTR. // http://pubs.opengroup.org/onlinepubs/009695399/functions/dup.html int new_fd = FMT_POSIX_CALL(dup(fd)); if (new_fd == -1) FMT_THROW(system_error( errno, FMT_STRING("cannot duplicate file descriptor {}"), fd)); return file(new_fd); } void file::dup2(int fd) { int result = 0; FMT_RETRY(result, FMT_POSIX_CALL(dup2(fd_, fd))); if (result == -1) { FMT_THROW(system_error( errno, FMT_STRING("cannot duplicate file descriptor {} to {}"), fd_, fd)); } } void file::dup2(int fd, std::error_code& ec) noexcept { int result = 0; FMT_RETRY(result, FMT_POSIX_CALL(dup2(fd_, fd))); if (result == -1) ec = std::error_code(errno, std::generic_category()); } buffered_file file::fdopen(const char* mode) { // Don't retry as fdopen doesn't return EINTR. # if defined(__MINGW32__) && defined(_POSIX_) FILE* f = ::fdopen(fd_, mode); # else FILE* f = FMT_POSIX_CALL(fdopen(fd_, mode)); # endif if (!f) { FMT_THROW(system_error( errno, FMT_STRING("cannot associate stream with file descriptor"))); } buffered_file bf(f); fd_ = -1; return bf; } # if defined(_WIN32) && !defined(__MINGW32__) file file::open_windows_file(wcstring_view path, int oflag) { int fd = -1; auto err = _wsopen_s(&fd, path.c_str(), oflag, _SH_DENYNO, default_open_mode); if (fd == -1) { FMT_THROW(system_error(err, FMT_STRING("cannot open file {}"), detail::to_utf8(path.c_str()).c_str())); } return file(fd); } # endif pipe::pipe() { int fds[2] = {}; # ifdef _WIN32 // Make the default pipe capacity same as on Linux 2.6.11+. enum { DEFAULT_CAPACITY = 65536 }; int result = FMT_POSIX_CALL(pipe(fds, DEFAULT_CAPACITY, _O_BINARY)); # else // Don't retry as the pipe function doesn't return EINTR. // http://pubs.opengroup.org/onlinepubs/009696799/functions/pipe.html int result = FMT_POSIX_CALL(pipe(fds)); # endif if (result != 0) FMT_THROW(system_error(errno, FMT_STRING("cannot create pipe"))); // The following assignments don't throw. read_end = file(fds[0]); write_end = file(fds[1]); } # if !defined(__MSDOS__) long getpagesize() { # ifdef _WIN32 SYSTEM_INFO si; GetSystemInfo(&si); return si.dwPageSize; # else # ifdef _WRS_KERNEL long size = FMT_POSIX_CALL(getpagesize()); # else long size = FMT_POSIX_CALL(sysconf(_SC_PAGESIZE)); # endif if (size < 0) FMT_THROW(system_error(errno, FMT_STRING("cannot get memory page size"))); return size; # endif } # endif void ostream::grow(buffer& buf, size_t) { if (buf.size() == buf.capacity()) static_cast(buf).flush(); } ostream::ostream(cstring_view path, const detail::ostream_params& params) : buffer(grow), file_(path, params.oflag) { set(new char[params.buffer_size], params.buffer_size); } ostream::ostream(ostream&& other) noexcept : buffer(grow, other.data(), other.size(), other.capacity()), file_(std::move(other.file_)) { other.clear(); other.set(nullptr, 0); } ostream::~ostream() { flush(); delete[] data(); } #endif // FMT_USE_FCNTL FMT_END_NAMESPACE openal-soft-1.24.2/fmt-11.1.1/support/000077500000000000000000000000001474041540300170505ustar00rootroot00000000000000openal-soft-1.24.2/fmt-11.1.1/support/Android.mk000066400000000000000000000004531474041540300207630ustar00rootroot00000000000000LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := fmt_static LOCAL_MODULE_FILENAME := libfmt LOCAL_SRC_FILES := ../src/format.cc LOCAL_C_INCLUDES := $(LOCAL_PATH) LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH) LOCAL_CFLAGS += -std=c++11 -fexceptions include $(BUILD_STATIC_LIBRARY) openal-soft-1.24.2/fmt-11.1.1/support/AndroidManifest.xml000066400000000000000000000000371474041540300226410ustar00rootroot00000000000000 openal-soft-1.24.2/fmt-11.1.1/support/C++.sublime-syntax000066400000000000000000002152041474041540300222720ustar00rootroot00000000000000%YAML 1.2 --- # http://www.sublimetext.com/docs/3/syntax.html name: C++ (fmt) comment: I don't think anyone uses .hp. .cp tends to be paired with .h. (I could be wrong. :) -- chris file_extensions: - cpp - cc - cp - cxx - c++ - C - h - hh - hpp - hxx - h++ - inl - ipp first_line_match: '-\*- C\+\+ -\*-' scope: source.c++ variables: identifier: \b[[:alpha:]_][[:alnum:]_]*\b # upper and lowercase macro_identifier: \b[[:upper:]_][[:upper:][:digit:]_]{2,}\b # only uppercase, at least 3 chars path_lookahead: '(?:::\s*)?(?:{{identifier}}\s*::\s*)*(?:template\s+)?{{identifier}}' operator_method_name: '\boperator\s*(?:[-+*/%^&|~!=<>]|[-+*/%^&|=!<>]=|<<=?|>>=?|&&|\|\||\+\+|--|,|->\*?|\(\)|\[\]|""\s*{{identifier}})' casts: 'const_cast|dynamic_cast|reinterpret_cast|static_cast' operator_keywords: 'and|and_eq|bitand|bitor|compl|not|not_eq|or|or_eq|xor|xor_eq|noexcept' control_keywords: 'break|case|catch|continue|default|do|else|for|goto|if|_Pragma|return|switch|throw|try|while' memory_operators: 'new|delete' basic_types: 'asm|__asm__|auto|bool|_Bool|char|_Complex|double|float|_Imaginary|int|long|short|signed|unsigned|void' before_tag: 'struct|union|enum\s+class|enum\s+struct|enum|class' declspec: '__declspec\(\s*\w+(?:\([^)]+\))?\s*\)' storage_classes: 'static|export|extern|friend|explicit|virtual|register|thread_local' type_qualifier: 'const|constexpr|mutable|typename|volatile' compiler_directive: 'inline|restrict|__restrict__|__restrict' visibility_modifiers: 'private|protected|public' other_keywords: 'typedef|nullptr|{{visibility_modifiers}}|static_assert|sizeof|using|typeid|alignof|alignas|namespace|template' modifiers: '{{storage_classes}}|{{type_qualifier}}|{{compiler_directive}}' non_angle_brackets: '(?=<<|<=)' regular: '[^(){}&;*^%=<>-]*' paren_open: (?:\( paren_close: '\))?' generic_open: (?:< generic_close: '>)?' balance_parentheses: '{{regular}}{{paren_open}}{{regular}}{{paren_close}}{{regular}}' generic_lookahead: <{{regular}}{{generic_open}}{{regular}}{{generic_open}}{{regular}}{{generic_close}}\s*{{generic_close}}{{balance_parentheses}}> data_structures_forward_decl_lookahead: '(\s+{{macro_identifier}})*\s*(:\s*({{path_lookahead}}|{{visibility_modifiers}}|,|\s|<[^;]*>)+)?;' non_func_keywords: 'if|for|switch|while|decltype|sizeof|__declspec|__attribute__|typeid|alignof|alignas|static_assert' format_spec: |- (?x: (?:.? [<>=^])? # fill align [ +-]? # sign \#? # alternate form # technically, octal and hexadecimal integers are also supported as 'width', but rarely used \d* # width ,? # thousands separator (?:\.\d+)? # precision [bcdeEfFgGnosxX%]? # type ) contexts: main: - include: preprocessor-global - include: global ############################################################################# # Reusable contexts # # The follow contexts are currently constructed to be reused in the # Objetive-C++ syntax. They are specifically constructed to not push into # sub-contexts, which ensures that Objective-C++ code isn't accidentally # lexed as plain C++. # # The "unique-*" contexts are additions that C++ makes over C, and thus can # be directly reused in Objective-C++ along with contexts from Objective-C # and C. ############################################################################# unique-late-expressions: # This is highlighted after all of the other control keywords # to allow operator overloading to be lexed properly - match: \boperator\b scope: keyword.control.c++ unique-modifiers: - match: \b({{modifiers}})\b scope: storage.modifier.c++ unique-variables: - match: \bthis\b scope: variable.language.c++ # common C++ instance var naming idiom -- fMemberName - match: '\b(f|m)[[:upper:]]\w*\b' scope: variable.other.readwrite.member.c++ # common C++ instance var naming idiom -- m_member_name - match: '\bm_[[:alnum:]_]+\b' scope: variable.other.readwrite.member.c++ unique-constants: - match: \bnullptr\b scope: constant.language.c++ unique-keywords: - match: \busing\b scope: keyword.control.c++ - match: \bbreak\b scope: keyword.control.flow.break.c++ - match: \bcontinue\b scope: keyword.control.flow.continue.c++ - match: \bgoto\b scope: keyword.control.flow.goto.c++ - match: \breturn\b scope: keyword.control.flow.return.c++ - match: \bthrow\b scope: keyword.control.flow.throw.c++ - match: \b({{control_keywords}})\b scope: keyword.control.c++ - match: '\bdelete\b(\s*\[\])?|\bnew\b(?!])' scope: keyword.control.c++ - match: \b({{operator_keywords}})\b scope: keyword.operator.word.c++ unique-types: - match: \b(char16_t|char32_t|wchar_t|nullptr_t)\b scope: storage.type.c++ - match: \bclass\b scope: storage.type.c++ unique-strings: - match: '((?:L|u8|u|U)?R)("([^\(\)\\ ]{0,16})\()' captures: 1: storage.type.string.c++ 2: punctuation.definition.string.begin.c++ push: - meta_scope: string.quoted.double.c++ - match: '\)\3"' scope: punctuation.definition.string.end.c++ pop: true - match: '\{\{|\}\}' scope: constant.character.escape.c++ - include: formatting-syntax unique-numbers: - match: |- (?x) (?: # floats (?: (?:\b\d(?:[\d']*\d)?\.\d(?:[\d']*\d)?|\B\.\d(?:[\d']*\d)?)(?:[Ee][+-]?\d(?:[\d']*\d)?)?(?:[fFlL]|(?:i[fl]?|h|min|[mun]?s|_\w*))?\b | (?:\b\d(?:[\d']*\d)?\.)(?:\B|(?:[fFlL]|(?:i[fl]?|h|min|[mun]?s|_\w*))\b|(?:[Ee][+-]?\d(?:[\d']*\d)?)(?:[fFlL]|(?:i[fl]?|h|min|[mun]?s|_\w*))?\b) | \b\d(?:[\d']*\d)?(?:[Ee][+-]?\d(?:[\d']*\d)?)(?:[fFlL]|(?:i[fl]?|h|min|[mun]?s|_\w*))?\b ) | # ints \b(?: (?: # dec [1-9](?:[\d']*\d)? | # oct 0(?:[0-7']*[0-7])? | # hex 0[Xx][\da-fA-F](?:[\da-fA-F']*[\da-fA-F])? | # bin 0[Bb][01](?:[01']*[01])? ) # int suffixes (?:(?:l{1,2}|L{1,2})[uU]?|[uU](?:l{0,2}|L{0,2})|(?:i[fl]?|h|min|[mun]?s|_\w*))?)\b ) (?!\.) # Number must not be followed by a decimal point scope: constant.numeric.c++ identifiers: - match: '{{identifier}}\s*(::)\s*' captures: 1: punctuation.accessor.c++ - match: '(?:(::)\s*)?{{identifier}}' captures: 1: punctuation.accessor.c++ function-specifiers: - match: \b(const|final|noexcept|override)\b scope: storage.modifier.c++ ############################################################################# # The following are C++-specific contexts that should not be reused. This is # because they push into subcontexts and use variables that are C++-specific. ############################################################################# ## Common context layout global: - match: '(?=\btemplate\b)' push: - include: template - match: (?=\S) set: global-modifier - include: namespace - include: keywords-angle-brackets - match: '(?={{path_lookahead}}\s*<)' push: global-modifier # Take care of comments just before a function definition. - match: /\* scope: punctuation.definition.comment.c push: - - match: \s*(?=\w) set: global-modifier - match: "" pop: true - - meta_scope: comment.block.c - match: \*/ scope: punctuation.definition.comment.c pop: true - include: early-expressions - match: ^\s*\b(extern)(?=\s+"C(\+\+)?") scope: storage.modifier.c++ push: - include: comments - include: strings - match: '\{' scope: punctuation.section.block.begin.c++ set: - meta_scope: meta.extern-c.c++ - match: '^\s*(#\s*ifdef)\s*__cplusplus\s*' scope: meta.preprocessor.c++ captures: 1: keyword.control.import.c++ set: - match: '\}' scope: punctuation.section.block.end.c++ pop: true - include: preprocessor-global - include: global - match: '\}' scope: punctuation.section.block.end.c++ pop: true - include: preprocessor-global - include: global - match: (?=\S) set: global-modifier - match: ^\s*(?=\w) push: global-modifier - include: late-expressions statements: - include: preprocessor-statements - include: scope:source.c#label - include: expressions expressions: - include: early-expressions - include: late-expressions early-expressions: - include: early-expressions-before-generic-type - include: generic-type - include: early-expressions-after-generic-type early-expressions-before-generic-type: - include: preprocessor-expressions - include: comments - include: case-default - include: typedef - include: keywords-angle-brackets - include: keywords-parens - include: keywords - include: numbers # Prevent a '<' from getting scoped as the start of another template # parameter list, if in reality a less-than-or-equals sign is meant. - match: <= scope: keyword.operator.comparison.c early-expressions-after-generic-type: - include: members-arrow - include: operators - include: members-dot - include: strings - include: parens - include: brackets - include: block - include: variables - include: constants - match: ',' scope: punctuation.separator.c++ - match: '\)|\}' scope: invalid.illegal.stray-bracket-end.c++ expressions-minus-generic-type: - include: early-expressions-before-generic-type - include: angle-brackets - include: early-expressions-after-generic-type - include: late-expressions expressions-minus-generic-type-function-call: - include: early-expressions-before-generic-type - include: angle-brackets - include: early-expressions-after-generic-type - include: late-expressions-before-function-call - include: identifiers - match: ';' scope: punctuation.terminator.c++ late-expressions: - include: late-expressions-before-function-call - include: function-call - include: identifiers - match: ';' scope: punctuation.terminator.c++ late-expressions-before-function-call: - include: unique-late-expressions - include: modifiers-parens - include: modifiers - include: types expressions-minus-function-call: - include: early-expressions - include: late-expressions-before-function-call - include: identifiers - match: ';' scope: punctuation.terminator.c++ comments: - include: scope:source.c#comments operators: - include: scope:source.c#operators modifiers: - include: unique-modifiers - include: scope:source.c#modifiers variables: - include: unique-variables - include: scope:source.c#variables constants: - include: unique-constants - include: scope:source.c#constants keywords: - include: unique-keywords - include: scope:source.c#keywords types: - include: unique-types - include: types-parens - include: scope:source.c#types strings: - include: unique-strings - match: '(L|u8|u|U)?(")' captures: 1: storage.type.string.c++ 2: punctuation.definition.string.begin.c++ push: - meta_scope: string.quoted.double.c++ - match: '"' scope: punctuation.definition.string.end.c++ pop: true - include: scope:source.c#string_escaped_char - match: |- (?x)% (\d+\$)? # field (argument #) [#0\- +']* # flags [,;:_]? # separator character (AltiVec) ((-?\d+)|\*(-?\d+\$)?)? # minimum field width (\.((-?\d+)|\*(-?\d+\$)?)?)? # precision (hh|h|ll|l|j|t|z|q|L|vh|vl|v|hv|hl)? # length modifier (\[[^\]]+\]|[am]s|[diouxXDOUeEfFgGaACcSspn%]) # conversion type scope: constant.other.placeholder.c++ - match: '\{\{|\}\}' scope: constant.character.escape.c++ - include: formatting-syntax - include: scope:source.c#strings formatting-syntax: # https://docs.python.org/3.6/library/string.html#formatstrings - match: |- # simple form (?x) (\{) (?: [\w.\[\]]+)? # field_name ( ! [ars])? # conversion ( : (?:{{format_spec}}| # format_spec OR [^}%]*%.[^}]*) # any format-like string )? (\}) scope: constant.other.placeholder.c++ captures: 1: punctuation.definition.placeholder.begin.c++ 2: storage.modifier.c++onversion.c++ 3: constant.other.format-spec.c++ 4: punctuation.definition.placeholder.end.c++ - match: \{(?=[^\}"']+\{[^"']*\}) # complex (nested) form scope: punctuation.definition.placeholder.begin.c++ push: - meta_scope: constant.other.placeholder.c++ - match: \} scope: punctuation.definition.placeholder.end.c++ pop: true - match: '[\w.\[\]]+' - match: '![ars]' scope: storage.modifier.conversion.c++ - match: ':' push: - meta_scope: meta.format-spec.c++ constant.other.format-spec.c++ - match: (?=\}) pop: true - include: formatting-syntax numbers: - include: unique-numbers - include: scope:source.c#numbers ## C++-specific contexts case-default: - match: '\b(default|case)\b' scope: keyword.control.c++ push: - match: (?=[);,]) pop: true - match: ':' scope: punctuation.separator.c++ pop: true - include: expressions modifiers-parens: - match: '\b(alignas)\b\s*(\()' captures: 1: storage.modifier.c++ 2: meta.group.c++ punctuation.section.group.begin.c++ push: - meta_content_scope: meta.group.c++ - match: '\)' scope: meta.group.c++ punctuation.section.group.end.c++ pop: true - include: expressions - match: \b(__attribute__)\s*(\(\() captures: 1: storage.modifier.c++ 2: meta.group.c++ punctuation.section.group.begin.c++ push : - meta_scope: meta.attribute.c++ - meta_content_scope: meta.group.c++ - include: parens - include: strings - match: \)\) scope: meta.group.c++ punctuation.section.group.end.c++ pop: true - match: \b(__declspec)(\() captures: 1: storage.modifier.c++ 2: meta.group.c++ punctuation.section.group.begin.c++ push: - meta_content_scope: meta.group.c++ - match: '\)' scope: meta.group.c++ punctuation.section.group.end.c++ pop: true - match: '\b(align|allocate|code_seg|deprecated|property|uuid)\b\s*(\()' captures: 1: storage.modifier.c++ 2: meta.group.c++ punctuation.section.group.begin.c++ push: - meta_content_scope: meta.group.c++ - match: '\)' scope: meta.group.c++ punctuation.section.group.end.c++ pop: true - include: numbers - include: strings - match: \b(get|put)\b scope: variable.parameter.c++ - match: ',' scope: punctuation.separator.c++ - match: '=' scope: keyword.operator.assignment.c++ - match: '\b(appdomain|deprecated|dllimport|dllexport|jintrinsic|naked|noalias|noinline|noreturn|nothrow|novtable|process|restrict|safebuffers|selectany|thread)\b' scope: constant.other.c++ types-parens: - match: '\b(decltype)\b\s*(\()' captures: 1: storage.type.c++ 2: meta.group.c++ punctuation.section.group.begin.c++ push: - meta_content_scope: meta.group.c++ - match: '\)' scope: meta.group.c++ punctuation.section.group.end.c++ pop: true - include: expressions keywords-angle-brackets: - match: \b({{casts}})\b\s* scope: keyword.operator.word.cast.c++ push: - match: '>' scope: punctuation.section.generic.end.c++ pop: true - match: '<' scope: punctuation.section.generic.begin.c++ push: - match: '(?=>)' pop: true - include: expressions-minus-generic-type-function-call keywords-parens: - match: '\b(alignof|typeid|static_assert|sizeof)\b\s*(\()' captures: 1: keyword.operator.word.c++ 2: meta.group.c++ punctuation.section.group.begin.c++ push: - meta_content_scope: meta.group.c++ - match: '\)' scope: meta.group.c++ punctuation.section.group.end.c++ pop: true - include: expressions namespace: - match: '\b(using)\s+(namespace)\s+(?={{path_lookahead}})' captures: 1: keyword.control.c++ 2: keyword.control.c++ push: - include: identifiers - match: '' pop: true - match: '\b(namespace)\s+(?=({{path_lookahead}})?(?!\s*[;,]))' scope: meta.namespace.c++ captures: 1: keyword.control.c++ push: - meta_content_scope: meta.namespace.c++ entity.name.namespace.c++ - include: identifiers - match: '' set: - meta_scope: meta.namespace.c++ - include: comments - match: '=' scope: keyword.operator.alias.c++ - match: '(?=;)' pop: true - match: '\}' scope: meta.block.c++ punctuation.section.block.end.c++ pop: true - match: '\{' scope: punctuation.section.block.begin.c++ push: - meta_scope: meta.block.c++ - match: '(?=\})' pop: true - include: preprocessor-global - include: global - include: expressions template-common: # Exit the template scope if we hit some basic invalid characters. This # helps when a user is in the middle of typing their template types and # prevents re-highlighting the whole file until the next > is found. - match: (?=[{};]) pop: true - include: expressions template: - match: \btemplate\b scope: storage.type.template.c++ push: - meta_scope: meta.template.c++ # Explicitly include comments here at the top, in order to NOT match the # \S lookahead in the case of comments. - include: comments - match: < scope: punctuation.section.generic.begin.c++ set: - meta_content_scope: meta.template.c++ - match: '>' scope: meta.template.c++ punctuation.section.generic.end.c++ pop: true - match: \.{3} scope: keyword.operator.variadic.c++ - match: \b(typename|{{before_tag}})\b scope: storage.type.c++ - include: template # include template here for nested templates - include: template-common - match: (?=\S) set: - meta_content_scope: meta.template.c++ - match: \b({{before_tag}})\b scope: storage.type.c++ - include: template-common generic-type: - match: '(?=(?!template){{path_lookahead}}\s*{{generic_lookahead}}\s*\()' push: - meta_scope: meta.function-call.c++ - match: \btemplate\b scope: storage.type.template.c++ - match: '(?:(::)\s*)?{{identifier}}\s*(::)\s*' captures: 1: punctuation.accessor.double-colon.c++ 2: punctuation.accessor.double-colon.c++ - match: (?:(::)\s*)?({{identifier}})\s*(<) captures: 1: punctuation.accessor.double-colon.c++ 2: variable.function.c++ 3: punctuation.section.generic.begin.c++ push: - match: '>' scope: punctuation.section.generic.end.c++ pop: true - include: expressions-minus-generic-type-function-call - match: (?:(::)\s*)?({{identifier}})\s*(\() captures: 1: punctuation.accessor.double-colon.c++ 2: variable.function.c++ 3: punctuation.section.group.begin.c++ set: - meta_scope: meta.function-call.c++ - meta_content_scope: meta.group.c++ - match: '\)' scope: meta.group.c++ punctuation.section.group.end.c++ pop: true - include: expressions - include: angle-brackets - match: '\(' scope: meta.group.c++ punctuation.section.group.begin.c++ set: - meta_scope: meta.function-call.c++ - meta_content_scope: meta.group.c++ - match: '\)' scope: meta.group.c++ punctuation.section.group.end.c++ pop: true - include: expressions - match: '(?=(?!template){{path_lookahead}}\s*{{generic_lookahead}})' push: - include: identifiers - match: '<' scope: punctuation.section.generic.begin.c++ set: - match: '>' scope: punctuation.section.generic.end.c++ pop: true - include: expressions-minus-generic-type-function-call angle-brackets: - match: '<(?!<)' scope: punctuation.section.generic.begin.c++ push: - match: '>' scope: punctuation.section.generic.end.c++ pop: true - include: expressions-minus-generic-type-function-call block: - match: '\{' scope: punctuation.section.block.begin.c++ push: - meta_scope: meta.block.c++ - match: (?=^\s*#\s*(elif|else|endif)\b) pop: true - match: '\}' scope: punctuation.section.block.end.c++ pop: true - include: statements function-call: - match: (?={{path_lookahead}}\s*\() push: - meta_scope: meta.function-call.c++ - include: scope:source.c#c99 - match: '(?:(::)\s*)?{{identifier}}\s*(::)\s*' scope: variable.function.c++ captures: 1: punctuation.accessor.c++ 2: punctuation.accessor.c++ - match: '(?:(::)\s*)?{{identifier}}' scope: variable.function.c++ captures: 1: punctuation.accessor.c++ - match: '\(' scope: meta.group.c++ punctuation.section.group.begin.c++ set: - meta_content_scope: meta.function-call.c++ meta.group.c++ - match: '\)' scope: meta.function-call.c++ meta.group.c++ punctuation.section.group.end.c++ pop: true - include: expressions members-inside-function-call: - meta_content_scope: meta.method-call.c++ meta.group.c++ - match: \) scope: meta.method-call.c++ meta.group.c++ punctuation.section.group.end.c++ pop: true - include: expressions members-after-accessor-junction: # After we've seen an accessor (dot or arrow), this context decides what # kind of entity we're accessing. - include: comments - match: \btemplate\b scope: meta.method-call.c++ storage.type.template.c++ # Guaranteed to be a template member function call after we match this set: - meta_content_scope: meta.method-call.c++ - include: comments - match: '{{identifier}}' scope: variable.function.member.c++ set: - meta_content_scope: meta.method-call.c++ - match: \( scope: meta.group.c++ punctuation.section.group.begin.c++ set: members-inside-function-call - include: comments - include: angle-brackets - match: (?=\S) # safety pop pop: true - match: (?=\S) # safety pop pop: true # Operator overloading - match: '({{operator_method_name}})\s*(\()' captures: 0: meta.method-call.c++ 1: variable.function.member.c++ 2: meta.group.c++ punctuation.section.group.begin.c++ set: members-inside-function-call # Non-templated member function call - match: (~?{{identifier}})\s*(\() captures: 0: meta.method-call.c++ 1: variable.function.member.c++ 2: meta.group.c++ punctuation.section.group.begin.c++ set: members-inside-function-call # Templated member function call - match: (~?{{identifier}})\s*(?={{generic_lookahead}}) captures: 1: variable.function.member.c++ set: - meta_scope: meta.method-call.c++ - match: < scope: punctuation.section.generic.begin.c++ set: - meta_content_scope: meta.method-call.c++ - match: '>' scope: punctuation.section.generic.end.c++ set: - meta_content_scope: meta.method-call.c++ - include: comments - match: \( scope: punctuation.section.group.begin.c++ set: members-inside-function-call - match: (?=\S) # safety pop pop: true - include: expressions # Explicit base-class access - match: ({{identifier}})\s*(::) captures: 1: variable.other.base-class.c++ 2: punctuation.accessor.double-colon.c++ set: members-after-accessor-junction # reset # Just a regular member variable - match: '{{identifier}}' scope: variable.other.readwrite.member.c++ pop: true members-dot: - include: scope:source.c#access-illegal # No lookahead required because members-dot goes after operators in the # early-expressions-after-generic-type context. This means triple dots # (i.e. "..." or "variadic") is attempted first. - match: \. scope: punctuation.accessor.dot.c++ push: members-after-accessor-junction members-arrow: # This needs to be before operators in the # early-expressions-after-generic-type context because otherwise the "->" # from the C language will match. - match: -> scope: punctuation.accessor.arrow.c++ push: members-after-accessor-junction typedef: - match: \btypedef\b scope: storage.type.c++ push: - match: ({{identifier}})?\s*(?=;) captures: 1: entity.name.type.typedef.c++ pop: true - match: \b(struct)\s+({{identifier}})\b captures: 1: storage.type.c++ - include: expressions-minus-generic-type parens: - match: \( scope: punctuation.section.group.begin.c++ push: - meta_scope: meta.group.c++ - match: \) scope: punctuation.section.group.end.c++ pop: true - include: expressions brackets: - match: \[ scope: punctuation.section.brackets.begin.c++ push: - meta_scope: meta.brackets.c++ - match: \] scope: punctuation.section.brackets.end.c++ pop: true - include: expressions function-trailing-return-type: - match: '{{non_angle_brackets}}' pop: true - include: angle-brackets - include: types - include: modifiers-parens - include: modifiers - include: identifiers - match: \*|& scope: keyword.operator.c++ - include: function-trailing-return-type-parens - match: '(?=\S)' pop: true function-trailing-return-type-parens: - match: \( scope: punctuation.section.group.begin.c++ push: - meta_scope: meta.group.c++ - match: \) scope: punctuation.section.group.end.c++ pop: true - include: function-trailing-return-type ## Detection of function and data structure definitions at the global level global-modifier: - include: comments - include: modifiers-parens - include: modifiers # Constructors and destructors don't have a type - match: '(?={{path_lookahead}}\s*::\s*{{identifier}}\s*(\(|$))' set: - meta_content_scope: meta.function.c++ entity.name.function.constructor.c++ - include: identifiers - match: '(?=[^\w\s])' set: function-definition-params - match: '(?={{path_lookahead}}\s*::\s*~{{identifier}}\s*(\(|$))' set: - meta_content_scope: meta.function.c++ entity.name.function.destructor.c++ - include: identifiers - match: '~{{identifier}}' - match: '(?=[^\w\s])' set: function-definition-params # If we see a path ending in :: before a newline, we don't know if it is # a constructor or destructor, or a long return type, so we are just going # to treat it like a regular function. Most likely it is a constructor, # since it doesn't seem most developers would create such a long typename. - match: '(?={{path_lookahead}}\s*::\s*$)' set: - meta_content_scope: meta.function.c++ entity.name.function.c++ - include: identifiers - match: '~{{identifier}}' - match: '(?=[^\w\s])' set: function-definition-params - include: unique-strings - match: '(?=\S)' set: global-type global-type: - include: comments - match: \*|& scope: keyword.operator.c++ - match: '(?=\b({{control_keywords}}|{{operator_keywords}}|{{casts}}|{{memory_operators}}|{{other_keywords}}|operator)\b)' pop: true - match: '(?=\s)' set: global-maybe-function # If a class/struct/enum followed by a name that is not a macro or declspec # then this is likely a return type of a function. This is uncommon. - match: |- (?x: ({{before_tag}}) \s+ (?= (?![[:upper:][:digit:]_]+\b|__declspec|{{before_tag}}) {{path_lookahead}} (\s+{{identifier}}\s*\(|\s*[*&]) ) ) captures: 1: storage.type.c++ set: - include: identifiers - match: '' set: global-maybe-function # The previous match handles return types of struct/enum/etc from a func, # there this one exits the context to allow matching an actual struct/class - match: '(?=\b({{before_tag}})\b)' set: data-structures - match: '(?=\b({{casts}})\b\s*<)' pop: true - match: '{{non_angle_brackets}}' pop: true - include: angle-brackets - include: types # Allow a macro call - match: '({{identifier}})\s*(\()(?=[^\)]+\))' captures: 1: variable.function.c++ 2: meta.group.c++ punctuation.section.group.begin.c++ push: - meta_scope: meta.function-call.c++ - meta_content_scope: meta.group.c++ - match: '\)' scope: meta.group.c++ punctuation.section.group.end.c++ pop: true - include: expressions - match: '(?={{path_lookahead}}\s*\()' set: - include: function-call - match: '' pop: true - include: variables - include: constants - include: identifiers - match: (?=\W) pop: true global-maybe-function: - include: comments # Consume pointer info, macros and any type info that was offset by macros - match: \*|& scope: keyword.operator.c++ - match: '(?=\b({{control_keywords}}|{{operator_keywords}}|{{casts}}|{{memory_operators}}|{{other_keywords}})\b)' pop: true - match: '\b({{type_qualifier}})\b' scope: storage.modifier.c++ - match: '{{non_angle_brackets}}' pop: true - include: angle-brackets - include: types - include: modifiers-parens - include: modifiers # All uppercase identifier just before a newline is most likely a macro - match: '[[:upper:][:digit:]_]+\s*$' # Operator overloading - match: '(?=({{path_lookahead}}\s*(?:{{generic_lookahead}})?::\s*)?{{operator_method_name}}\s*(\(|$))' set: - meta_content_scope: meta.function.c++ entity.name.function.c++ - include: identifiers - match: '(?=\s*(\(|$))' set: function-definition-params # Identifier that is not the function name - likely a macro or type - match: '(?={{path_lookahead}}([ \t]+|[*&])(?!\s*(<|::|\(|$)))' push: - include: identifiers - match: '' pop: true # Real function definition - match: '(?={{path_lookahead}}({{generic_lookahead}}({{path_lookahead}})?)\s*(\(|$))' set: [function-definition-params, global-function-identifier-generic] - match: '(?={{path_lookahead}}\s*(\(|$))' set: [function-definition-params, global-function-identifier] - match: '(?={{path_lookahead}}\s*::\s*$)' set: [function-definition-params, global-function-identifier] - match: '(?=\S)' pop: true global-function-identifier-generic: - include: angle-brackets - match: '::' scope: punctuation.accessor.c++ - match: '(?={{identifier}}<.*>\s*\()' push: - meta_content_scope: entity.name.function.c++ - include: identifiers - match: '(?=<)' pop: true - match: '(?={{identifier}}\s*\()' push: - meta_content_scope: entity.name.function.c++ - include: identifiers - match: '' pop: true - match: '(?=\()' pop: true global-function-identifier: - meta_content_scope: entity.name.function.c++ - include: identifiers - match: '(?=\S)' pop: true function-definition-params: - meta_content_scope: meta.function.c++ - include: comments - match: '(?=\()' set: - match: \( scope: meta.function.parameters.c++ meta.group.c++ punctuation.section.group.begin.c++ set: - meta_content_scope: meta.function.parameters.c++ meta.group.c++ - match : \) scope: punctuation.section.group.end.c++ set: function-definition-continue - match: '\bvoid\b' scope: storage.type.c++ - match: '{{identifier}}(?=\s*(\[|,|\)|=))' scope: variable.parameter.c++ - match: '=' scope: keyword.operator.assignment.c++ push: - match: '(?=,|\))' pop: true - include: expressions-minus-generic-type - include: scope:source.c#preprocessor-line-continuation - include: expressions-minus-generic-type - include: scope:source.c#preprocessor-line-continuation - match: (?=\S) pop: true function-definition-continue: - meta_content_scope: meta.function.c++ - include: comments - match: '(?=;)' pop: true - match: '->' scope: punctuation.separator.c++ set: function-definition-trailing-return - include: function-specifiers - match: '=' scope: keyword.operator.assignment.c++ - match: '&' scope: keyword.operator.c++ - match: \b0\b scope: constant.numeric.c++ - match: \b(default|delete)\b scope: storage.modifier.c++ - match: '(?=\{)' set: function-definition-body - match: '(?=\S)' pop: true function-definition-trailing-return: - include: comments - match: '(?=;)' pop: true - match: '(?=\{)' set: function-definition-body - include: function-specifiers - include: function-trailing-return-type function-definition-body: - meta_content_scope: meta.function.c++ meta.block.c++ - match: '\{' scope: punctuation.section.block.begin.c++ set: - meta_content_scope: meta.function.c++ meta.block.c++ - match: '\}' scope: meta.function.c++ meta.block.c++ punctuation.section.block.end.c++ pop: true - match: (?=^\s*#\s*(elif|else|endif)\b) pop: true - match: '(?=({{before_tag}})([^(;]+$|.*\{))' push: data-structures - include: statements ## Data structures including classes, structs, unions and enums data-structures: - match: '\bclass\b' scope: storage.type.c++ set: data-structures-class-definition # Detect variable type definitions using struct/enum/union followed by a tag - match: '\b({{before_tag}})(?=\s+{{path_lookahead}}\s+{{path_lookahead}}\s*[=;\[])' scope: storage.type.c++ - match: '\bstruct\b' scope: storage.type.c++ set: data-structures-struct-definition - match: '\benum(\s+(class|struct))?\b' scope: storage.type.c++ set: data-structures-enum-definition - match: '\bunion\b' scope: storage.type.c++ set: data-structures-union-definition - match: '(?=\S)' pop: true preprocessor-workaround-eat-macro-before-identifier: # Handle macros so they aren't matched as the class name - match: ({{macro_identifier}})(?=\s+~?{{identifier}}) captures: 1: meta.assumed-macro.c data-structures-class-definition: - meta_scope: meta.class.c++ - include: data-structures-definition-common-begin - match: '{{identifier}}(?={{data_structures_forward_decl_lookahead}})' scope: entity.name.class.forward-decl.c++ set: data-structures-class-definition-after-identifier - match: '{{identifier}}' scope: entity.name.class.c++ set: data-structures-class-definition-after-identifier - match: '(?=[:{])' set: data-structures-class-definition-after-identifier - match: '(?=;)' pop: true data-structures-class-definition-after-identifier: - meta_content_scope: meta.class.c++ - include: data-structures-definition-common-begin # No matching of identifiers since they should all be macros at this point - include: data-structures-definition-common-end - match: '\{' scope: meta.block.c++ punctuation.section.block.begin.c++ set: - meta_content_scope: meta.class.c++ meta.block.c++ - match: '\}' scope: meta.class.c++ meta.block.c++ punctuation.section.block.end.c++ pop: true - include: data-structures-body data-structures-struct-definition: - meta_scope: meta.struct.c++ - include: data-structures-definition-common-begin - match: '{{identifier}}(?={{data_structures_forward_decl_lookahead}})' scope: entity.name.struct.forward-decl.c++ set: data-structures-struct-definition-after-identifier - match: '{{identifier}}' scope: entity.name.struct.c++ set: data-structures-struct-definition-after-identifier - match: '(?=[:{])' set: data-structures-struct-definition-after-identifier - match: '(?=;)' pop: true data-structures-struct-definition-after-identifier: - meta_content_scope: meta.struct.c++ - include: data-structures-definition-common-begin # No matching of identifiers since they should all be macros at this point - include: data-structures-definition-common-end - match: '\{' scope: meta.block.c++ punctuation.section.block.begin.c++ set: - meta_content_scope: meta.struct.c++ meta.block.c++ - match: '\}' scope: meta.struct.c++ meta.block.c++ punctuation.section.block.end.c++ pop: true - include: data-structures-body data-structures-enum-definition: - meta_scope: meta.enum.c++ - include: data-structures-definition-common-begin - match: '{{identifier}}(?={{data_structures_forward_decl_lookahead}})' scope: entity.name.enum.forward-decl.c++ set: data-structures-enum-definition-after-identifier - match: '{{identifier}}' scope: entity.name.enum.c++ set: data-structures-enum-definition-after-identifier - match: '(?=[:{])' set: data-structures-enum-definition-after-identifier - match: '(?=;)' pop: true data-structures-enum-definition-after-identifier: - meta_content_scope: meta.enum.c++ - include: data-structures-definition-common-begin # No matching of identifiers since they should all be macros at this point - include: data-structures-definition-common-end - match: '\{' scope: meta.block.c++ punctuation.section.block.begin.c++ set: - meta_content_scope: meta.enum.c++ meta.block.c++ # Enums don't support methods so we have a simplified body - match: '\}' scope: meta.enum.c++ meta.block.c++ punctuation.section.block.end.c++ pop: true - include: statements data-structures-union-definition: - meta_scope: meta.union.c++ - include: data-structures-definition-common-begin - match: '{{identifier}}(?={{data_structures_forward_decl_lookahead}})' scope: entity.name.union.forward-decl.c++ set: data-structures-union-definition-after-identifier - match: '{{identifier}}' scope: entity.name.union.c++ set: data-structures-union-definition-after-identifier - match: '(?=[{])' set: data-structures-union-definition-after-identifier - match: '(?=;)' pop: true data-structures-union-definition-after-identifier: - meta_content_scope: meta.union.c++ - include: data-structures-definition-common-begin # No matching of identifiers since they should all be macros at this point # Unions don't support base classes - include: angle-brackets - match: '\{' scope: meta.block.c++ punctuation.section.block.begin.c++ set: - meta_content_scope: meta.union.c++ meta.block.c++ - match: '\}' scope: meta.union.c++ meta.block.c++ punctuation.section.block.end.c++ pop: true - include: data-structures-body - match: '(?=;)' pop: true data-structures-definition-common-begin: - include: comments - match: '(?=\b(?:{{before_tag}}|{{control_keywords}})\b)' pop: true - include: preprocessor-other - include: modifiers-parens - include: modifiers - include: preprocessor-workaround-eat-macro-before-identifier data-structures-definition-common-end: - include: angle-brackets - match: \bfinal\b scope: storage.modifier.c++ - match: ':' scope: punctuation.separator.c++ push: - include: comments - include: preprocessor-other - include: modifiers-parens - include: modifiers - match: '\b(virtual|{{visibility_modifiers}})\b' scope: storage.modifier.c++ - match: (?={{path_lookahead}}) push: - meta_scope: entity.other.inherited-class.c++ - include: identifiers - match: '' pop: true - include: angle-brackets - match: ',' scope: punctuation.separator.c++ - match: (?=\{|;) pop: true - match: '(?=;)' pop: true data-structures-body: - include: preprocessor-data-structures - match: '(?=\btemplate\b)' push: - include: template - match: (?=\S) set: data-structures-modifier - include: typedef - match: \b({{visibility_modifiers}})\s*(:)(?!:) captures: 1: storage.modifier.c++ 2: punctuation.section.class.c++ - match: '^\s*(?=(?:~?\w+|::))' push: data-structures-modifier - include: expressions-minus-generic-type data-structures-modifier: - match: '\bfriend\b' scope: storage.modifier.c++ push: - match: (?=;) pop: true - match: '\{' scope: punctuation.section.block.begin.c++ set: - meta_scope: meta.block.c++ - match: '\}' scope: punctuation.section.block.end.c++ pop: true - include: statements - match: '\b({{before_tag}})\b' scope: storage.type.c++ - include: expressions-minus-function-call - include: comments - include: modifiers-parens - include: modifiers - match: '\bstatic_assert(?=\s*\()' scope: meta.static-assert.c++ keyword.operator.word.c++ push: - match: '\(' scope: meta.group.c++ punctuation.section.group.begin.c++ set: - meta_content_scope: meta.function-call.c++ meta.group.c++ - match: '\)' scope: meta.function-call.c++ meta.group.c++ punctuation.section.group.end.c++ pop: true - include: expressions # Destructor - match: '(?:{{identifier}}\s*(::)\s*)?~{{identifier}}(?=\s*(\(|$))' scope: meta.method.destructor.c++ entity.name.function.destructor.c++ captures: 1: punctuation.accessor.c++ set: method-definition-params # It's a macro, not a constructor if there is no type in the first param - match: '({{identifier}})\s*(\()(?=\s*(?!void){{identifier}}\s*[),])' captures: 1: variable.function.c++ 2: meta.group.c++ punctuation.section.group.begin.c++ push: - meta_scope: meta.function-call.c++ - meta_content_scope: meta.group.c++ - match: '\)' scope: meta.group.c++ punctuation.section.group.end.c++ pop: true - include: expressions # Constructor - include: preprocessor-workaround-eat-macro-before-identifier - match: '((?!{{before_tag}}|template){{identifier}})(?=\s*\()' scope: meta.method.constructor.c++ entity.name.function.constructor.c++ set: method-definition-params # Long form constructor - match: '({{identifier}}\s*(::)\s*{{identifier}})(?=\s*\()' captures: 1: meta.method.constructor.c++ entity.name.function.constructor.c++ 2: punctuation.accessor.c++ push: method-definition-params - match: '(?=\S)' set: data-structures-type data-structures-type: - include: comments - match: \*|& scope: keyword.operator.c++ # Cast methods - match: '(operator)\s+({{identifier}})(?=\s*(\(|$))' captures: 1: keyword.control.c++ 2: meta.method.c++ entity.name.function.c++ set: method-definition-params - match: '(?=\b({{control_keywords}}|{{operator_keywords}}|{{casts}}|{{memory_operators}}|{{other_keywords}}|operator)\b)' pop: true - match: '(?=\s)' set: data-structures-maybe-method # If a class/struct/enum followed by a name that is not a macro or declspec # then this is likely a return type of a function. This is uncommon. - match: |- (?x: ({{before_tag}}) \s+ (?= (?![[:upper:][:digit:]_]+\b|__declspec|{{before_tag}}) {{path_lookahead}} (\s+{{identifier}}\s*\(|\s*[*&]) ) ) captures: 1: storage.type.c++ set: - include: identifiers - match: '' set: data-structures-maybe-method # The previous match handles return types of struct/enum/etc from a func, # there this one exits the context to allow matching an actual struct/class - match: '(?=\b({{before_tag}})\b)' set: data-structures - match: '(?=\b({{casts}})\b\s*<)' pop: true - match: '{{non_angle_brackets}}' pop: true - include: angle-brackets - include: types - include: variables - include: constants - include: identifiers - match: (?=[&*]) set: data-structures-maybe-method - match: (?=\W) pop: true data-structures-maybe-method: - include: comments # Consume pointer info, macros and any type info that was offset by macros - match: \*|& scope: keyword.operator.c++ - match: '(?=\b({{control_keywords}}|{{operator_keywords}}|{{casts}}|{{memory_operators}}|{{other_keywords}})\b)' pop: true - match: '\b({{type_qualifier}})\b' scope: storage.modifier.c++ - match: '{{non_angle_brackets}}' pop: true - include: angle-brackets - include: types - include: modifiers-parens - include: modifiers # Operator overloading - match: '{{operator_method_name}}(?=\s*(\(|$))' scope: meta.method.c++ entity.name.function.c++ set: method-definition-params # Identifier that is not the function name - likely a macro or type - match: '(?={{path_lookahead}}([ \t]+|[*&])(?!\s*(<|::|\()))' push: - include: identifiers - match: '' pop: true # Real function definition - match: '(?={{path_lookahead}}({{generic_lookahead}})\s*(\())' set: [method-definition-params, data-structures-function-identifier-generic] - match: '(?={{path_lookahead}}\s*(\())' set: [method-definition-params, data-structures-function-identifier] - match: '(?={{path_lookahead}}\s*::\s*$)' set: [method-definition-params, data-structures-function-identifier] - match: '(?=\S)' pop: true data-structures-function-identifier-generic: - include: angle-brackets - match: '(?={{identifier}})' push: - meta_content_scope: entity.name.function.c++ - include: identifiers - match: '(?=<)' pop: true - match: '(?=\()' pop: true data-structures-function-identifier: - meta_content_scope: entity.name.function.c++ - include: identifiers - match: '(?=\S)' pop: true method-definition-params: - meta_content_scope: meta.method.c++ - include: comments - match: '(?=\()' set: - match: \( scope: meta.method.parameters.c++ meta.group.c++ punctuation.section.group.begin.c++ set: - meta_content_scope: meta.method.parameters.c++ meta.group.c++ - match : \) scope: punctuation.section.group.end.c++ set: method-definition-continue - match: '\bvoid\b' scope: storage.type.c++ - match: '{{identifier}}(?=\s*(\[|,|\)|=))' scope: variable.parameter.c++ - match: '=' scope: keyword.operator.assignment.c++ push: - match: '(?=,|\))' pop: true - include: expressions-minus-generic-type - include: expressions-minus-generic-type - match: '(?=\S)' pop: true method-definition-continue: - meta_content_scope: meta.method.c++ - include: comments - match: '(?=;)' pop: true - match: '->' scope: punctuation.separator.c++ set: method-definition-trailing-return - include: function-specifiers - match: '=' scope: keyword.operator.assignment.c++ - match: '&' scope: keyword.operator.c++ - match: \b0\b scope: constant.numeric.c++ - match: \b(default|delete)\b scope: storage.modifier.c++ - match: '(?=:)' set: - match: ':' scope: punctuation.separator.initializer-list.c++ set: - meta_scope: meta.method.constructor.initializer-list.c++ - match: '{{identifier}}' scope: variable.other.readwrite.member.c++ push: - match: \( scope: meta.group.c++ punctuation.section.group.begin.c++ set: - meta_content_scope: meta.group.c++ - match: \) scope: meta.group.c++ punctuation.section.group.end.c++ pop: true - include: expressions - match: \{ scope: meta.group.c++ punctuation.section.group.begin.c++ set: - meta_content_scope: meta.group.c++ - match: \} scope: meta.group.c++ punctuation.section.group.end.c++ pop: true - include: expressions - include: comments - match: (?=\{|;) set: method-definition-continue - include: expressions - match: '(?=\{)' set: method-definition-body - match: '(?=\S)' pop: true method-definition-trailing-return: - include: comments - match: '(?=;)' pop: true - match: '(?=\{)' set: method-definition-body - include: function-specifiers - include: function-trailing-return-type method-definition-body: - meta_content_scope: meta.method.c++ meta.block.c++ - match: '\{' scope: punctuation.section.block.begin.c++ set: - meta_content_scope: meta.method.c++ meta.block.c++ - match: '\}' scope: meta.method.c++ meta.block.c++ punctuation.section.block.end.c++ pop: true - match: (?=^\s*#\s*(elif|else|endif)\b) pop: true - match: '(?=({{before_tag}})([^(;]+$|.*\{))' push: data-structures - include: statements ## Preprocessor for data-structures preprocessor-data-structures: - include: preprocessor-rule-enabled-data-structures - include: preprocessor-rule-disabled-data-structures - include: preprocessor-practical-workarounds preprocessor-rule-disabled-data-structures: - match: ^\s*((#if)\s+(0))\b captures: 1: meta.preprocessor.c++ 2: keyword.control.import.c++ 3: constant.numeric.preprocessor.c++ push: - match: ^\s*(#\s*endif)\b captures: 1: meta.preprocessor.c++ keyword.control.import.c++ pop: true - match: ^\s*(#\s*else)\b captures: 1: meta.preprocessor.c++ keyword.control.import.else.c++ push: - match: (?=^\s*#\s*endif\b) pop: true - include: negated-block - include: data-structures-body - match: "" push: - meta_scope: comment.block.preprocessor.if-branch.c++ - match: (?=^\s*#\s*(else|endif)\b) pop: true - include: scope:source.c#preprocessor-disabled preprocessor-rule-enabled-data-structures: - match: ^\s*((#if)\s+(0*1))\b captures: 1: meta.preprocessor.c++ 2: keyword.control.import.c++ 3: constant.numeric.preprocessor.c++ push: - match: ^\s*(#\s*endif)\b captures: 1: meta.preprocessor.c++ keyword.control.import.c++ pop: true - match: ^\s*(#\s*else)\b captures: 1: meta.preprocessor.c++ keyword.control.import.else.c++ push: - meta_content_scope: comment.block.preprocessor.else-branch.c++ - match: (?=^\s*#\s*endif\b) pop: true - include: scope:source.c#preprocessor-disabled - match: "" push: - match: (?=^\s*#\s*(else|endif)\b) pop: true - include: negated-block - include: data-structures-body ## Preprocessor for global preprocessor-global: - include: preprocessor-rule-enabled-global - include: preprocessor-rule-disabled-global - include: preprocessor-rule-other-global preprocessor-statements: - include: preprocessor-rule-enabled-statements - include: preprocessor-rule-disabled-statements - include: preprocessor-rule-other-statements preprocessor-expressions: - include: scope:source.c#incomplete-inc - include: preprocessor-macro-define - include: scope:source.c#pragma-mark - include: preprocessor-other preprocessor-rule-disabled-global: - match: ^\s*((#if)\s+(0))\b captures: 1: meta.preprocessor.c++ 2: keyword.control.import.c++ 3: constant.numeric.preprocessor.c++ push: - match: ^\s*(#\s*endif)\b captures: 1: meta.preprocessor.c++ keyword.control.import.c++ pop: true - match: ^\s*(#\s*else)\b captures: 1: meta.preprocessor.c++ keyword.control.import.else.c++ push: - match: (?=^\s*#\s*endif\b) pop: true - include: preprocessor-global - include: negated-block - include: global - match: "" push: - meta_scope: comment.block.preprocessor.if-branch.c++ - match: (?=^\s*#\s*(else|endif)\b) pop: true - include: scope:source.c#preprocessor-disabled preprocessor-rule-enabled-global: - match: ^\s*((#if)\s+(0*1))\b captures: 1: meta.preprocessor.c++ 2: keyword.control.import.c++ 3: constant.numeric.preprocessor.c++ push: - match: ^\s*(#\s*endif)\b captures: 1: meta.preprocessor.c++ keyword.control.import.c++ pop: true - match: ^\s*(#\s*else)\b captures: 1: meta.preprocessor.c++ keyword.control.import.else.c++ push: - meta_content_scope: comment.block.preprocessor.else-branch.c++ - match: (?=^\s*#\s*endif\b) pop: true - include: scope:source.c#preprocessor-disabled - match: "" push: - match: (?=^\s*#\s*(else|endif)\b) pop: true - include: preprocessor-global - include: negated-block - include: global preprocessor-rule-other-global: - match: ^\s*(#\s*(?:if|ifdef|ifndef))\b captures: 1: keyword.control.import.c++ push: - meta_scope: meta.preprocessor.c++ - include: scope:source.c#preprocessor-line-continuation - include: scope:source.c#preprocessor-comments - match: \bdefined\b scope: keyword.control.c++ # Enter a new scope where all elif/else branches have their # contexts popped by a subsequent elif/else/endif. This ensures that # preprocessor branches don't push multiple meta.block scopes on # the stack, thus messing up the "global" context's detection of # functions. - match: $\n set: preprocessor-if-branch-global # These gymnastics here ensure that we are properly handling scope even # when the preprocessor is used to create different scope beginnings, such # as a different if/while condition preprocessor-if-branch-global: - match: ^\s*(#\s*endif)\b captures: 1: meta.preprocessor.c++ keyword.control.import.c++ pop: true - match: (?=^\s*#\s*(elif|else)\b) push: preprocessor-elif-else-branch-global - match: \{ scope: punctuation.section.block.begin.c++ set: preprocessor-block-if-branch-global - include: preprocessor-global - include: negated-block - include: global preprocessor-block-if-branch-global: - meta_scope: meta.block.c++ - match: ^\s*(#\s*endif)\b captures: 1: meta.preprocessor.c++ keyword.control.import.c++ set: preprocessor-block-finish-global - match: (?=^\s*#\s*(elif|else)\b) push: preprocessor-elif-else-branch-global - match: \} scope: punctuation.section.block.end.c++ set: preprocessor-if-branch-global - include: statements preprocessor-block-finish-global: - meta_scope: meta.block.c++ - match: ^\s*(#\s*(?:if|ifdef|ifndef))\b captures: 1: meta.preprocessor.c++ keyword.control.import.c++ set: preprocessor-block-finish-if-branch-global - match: \} scope: punctuation.section.block.end.c++ pop: true - include: statements preprocessor-block-finish-if-branch-global: - match: ^\s*(#\s*endif)\b captures: 1: keyword.control.import.c++ pop: true - match: \} scope: punctuation.section.block.end.c++ set: preprocessor-if-branch-global - include: statements preprocessor-elif-else-branch-global: - match: (?=^\s*#\s*(endif)\b) pop: true - include: preprocessor-global - include: negated-block - include: global ## Preprocessor for statements preprocessor-rule-disabled-statements: - match: ^\s*((#if)\s+(0))\b captures: 1: meta.preprocessor.c++ 2: keyword.control.import.c++ 3: constant.numeric.preprocessor.c++ push: - match: ^\s*(#\s*endif)\b captures: 1: meta.preprocessor.c++ keyword.control.import.c++ pop: true - match: ^\s*(#\s*else)\b captures: 1: meta.preprocessor.c++ keyword.control.import.else.c++ push: - match: (?=^\s*#\s*endif\b) pop: true - include: negated-block - include: statements - match: "" push: - meta_scope: comment.block.preprocessor.if-branch.c++ - match: (?=^\s*#\s*(else|endif)\b) pop: true - include: scope:source.c#preprocessor-disabled preprocessor-rule-enabled-statements: - match: ^\s*((#if)\s+(0*1))\b captures: 1: meta.preprocessor.c++ 2: keyword.control.import.c++ 3: constant.numeric.preprocessor.c++ push: - match: ^\s*(#\s*endif)\b captures: 1: meta.preprocessor.c++ keyword.control.import.c++ pop: true - match: ^\s*(#\s*else)\b captures: 1: meta.preprocessor.c++ keyword.control.import.else.c++ push: - meta_content_scope: comment.block.preprocessor.else-branch.c++ - match: (?=^\s*#\s*endif\b) pop: true - include: scope:source.c#preprocessor-disabled - match: "" push: - match: (?=^\s*#\s*(else|endif)\b) pop: true - include: negated-block - include: statements preprocessor-rule-other-statements: - match: ^\s*(#\s*(?:if|ifdef|ifndef))\b captures: 1: keyword.control.import.c++ push: - meta_scope: meta.preprocessor.c++ - include: scope:source.c#preprocessor-line-continuation - include: scope:source.c#preprocessor-comments - match: \bdefined\b scope: keyword.control.c++ # Enter a new scope where all elif/else branches have their # contexts popped by a subsequent elif/else/endif. This ensures that # preprocessor branches don't push multiple meta.block scopes on # the stack, thus messing up the "global" context's detection of # functions. - match: $\n set: preprocessor-if-branch-statements # These gymnastics here ensure that we are properly handling scope even # when the preprocessor is used to create different scope beginnings, such # as a different if/while condition preprocessor-if-branch-statements: - match: ^\s*(#\s*endif)\b captures: 1: meta.preprocessor.c++ keyword.control.import.c++ pop: true - match: (?=^\s*#\s*(elif|else)\b) push: preprocessor-elif-else-branch-statements - match: \{ scope: punctuation.section.block.begin.c++ set: preprocessor-block-if-branch-statements - match: (?=(?!{{non_func_keywords}}){{path_lookahead}}\s*\() set: preprocessor-if-branch-function-call - include: negated-block - include: statements preprocessor-if-branch-function-call: - meta_content_scope: meta.function-call.c++ - include: scope:source.c#c99 - match: '(?:(::)\s*)?{{identifier}}\s*(::)\s*' scope: variable.function.c++ captures: 1: punctuation.accessor.c++ 2: punctuation.accessor.c++ - match: '(?:(::)\s*)?{{identifier}}' scope: variable.function.c++ captures: 1: punctuation.accessor.c++ - match: '\(' scope: meta.group.c++ punctuation.section.group.begin.c++ set: preprocessor-if-branch-function-call-arguments preprocessor-if-branch-function-call-arguments: - meta_content_scope: meta.function-call.c++ meta.group.c++ - match : \) scope: meta.function-call.c++ meta.group.c++ punctuation.section.group.end.c++ set: preprocessor-if-branch-statements - match: ^\s*(#\s*(?:elif|else))\b captures: 1: meta.preprocessor.c++ keyword.control.import.c++ set: preprocessor-if-branch-statements - match: ^\s*(#\s*endif)\b captures: 1: meta.preprocessor.c++ keyword.control.import.c++ set: preprocessor-if-branch-function-call-arguments-finish - include: expressions preprocessor-if-branch-function-call-arguments-finish: - meta_content_scope: meta.function-call.c++ meta.group.c++ - match: \) scope: meta.function-call.c++ meta.group.c++ punctuation.section.group.end.c++ pop: true - include: expressions preprocessor-block-if-branch-statements: - meta_scope: meta.block.c++ - match: ^\s*(#\s*endif)\b captures: 1: meta.preprocessor.c++ keyword.control.import.c++ set: preprocessor-block-finish-statements - match: (?=^\s*#\s*(elif|else)\b) push: preprocessor-elif-else-branch-statements - match: \} scope: punctuation.section.block.end.c++ set: preprocessor-if-branch-statements - include: statements preprocessor-block-finish-statements: - meta_scope: meta.block.c++ - match: ^\s*(#\s*(?:if|ifdef|ifndef))\b captures: 1: meta.preprocessor.c++ keyword.control.import.c++ set: preprocessor-block-finish-if-branch-statements - match: \} scope: punctuation.section.block.end.c++ pop: true - include: statements preprocessor-block-finish-if-branch-statements: - match: ^\s*(#\s*endif)\b captures: 1: keyword.control.import.c++ pop: true - match: \} scope: meta.block.c++ punctuation.section.block.end.c++ set: preprocessor-if-branch-statements - include: statements preprocessor-elif-else-branch-statements: - match: (?=^\s*#\s*endif\b) pop: true - include: negated-block - include: statements ## Preprocessor other negated-block: - match: '\}' scope: punctuation.section.block.end.c++ push: - match: '\{' scope: punctuation.section.block.begin.c++ pop: true - match: (?=^\s*#\s*(elif|else|endif)\b) pop: true - include: statements preprocessor-macro-define: - match: ^\s*(\#\s*define)\b captures: 1: meta.preprocessor.macro.c++ keyword.control.import.define.c++ push: - meta_content_scope: meta.preprocessor.macro.c++ - include: scope:source.c#preprocessor-line-continuation - include: scope:source.c#preprocessor-line-ending - include: scope:source.c#preprocessor-comments - match: '({{identifier}})(?=\()' scope: entity.name.function.preprocessor.c++ set: - match: '\(' scope: punctuation.section.group.begin.c++ set: preprocessor-macro-params - match: '{{identifier}}' scope: entity.name.constant.preprocessor.c++ set: preprocessor-macro-definition preprocessor-macro-params: - meta_scope: meta.preprocessor.macro.parameters.c++ meta.group.c++ - match: '{{identifier}}' scope: variable.parameter.c++ - match: \) scope: punctuation.section.group.end.c++ set: preprocessor-macro-definition - match: ',' scope: punctuation.separator.c++ push: - match: '{{identifier}}' scope: variable.parameter.c++ pop: true - include: scope:source.c#preprocessor-line-continuation - include: scope:source.c#preprocessor-comments - match: '\.\.\.' scope: keyword.operator.variadic.c++ - match: '(?=\))' pop: true - match: (/\*).*(\*/) scope: comment.block.c++ captures: 1: punctuation.definition.comment.c++ 2: punctuation.definition.comment.c++ - match: '\S+' scope: invalid.illegal.unexpected-character.c++ - include: scope:source.c#preprocessor-line-continuation - include: scope:source.c#preprocessor-comments - match: '\.\.\.' scope: keyword.operator.variadic.c++ - match: (/\*).*(\*/) scope: comment.block.c++ captures: 1: punctuation.definition.comment.c++ 2: punctuation.definition.comment.c++ - match: $\n scope: invalid.illegal.unexpected-end-of-line.c++ preprocessor-macro-definition: - meta_content_scope: meta.preprocessor.macro.c++ - include: scope:source.c#preprocessor-line-continuation - include: scope:source.c#preprocessor-line-ending - include: scope:source.c#preprocessor-comments # Don't define blocks in define statements - match: '\{' scope: punctuation.section.block.begin.c++ - match: '\}' scope: punctuation.section.block.end.c++ - include: expressions preprocessor-practical-workarounds: - include: preprocessor-convention-ignore-uppercase-ident-lines - include: scope:source.c#preprocessor-convention-ignore-uppercase-calls-without-semicolon preprocessor-convention-ignore-uppercase-ident-lines: - match: ^(\s*{{macro_identifier}})+\s*$ scope: meta.assumed-macro.c++ push: # It's possible that we are dealing with a function return type on its own line, and the # name of the function is on the subsequent line. - match: '(?={{path_lookahead}}({{generic_lookahead}}({{path_lookahead}})?)\s*\()' set: [function-definition-params, global-function-identifier-generic] - match: '(?={{path_lookahead}}\s*\()' set: [function-definition-params, global-function-identifier] - match: ^ pop: true preprocessor-other: - match: ^\s*(#\s*(?:if|ifdef|ifndef|elif|else|line|pragma|undef))\b captures: 1: keyword.control.import.c++ push: - meta_scope: meta.preprocessor.c++ - include: scope:source.c#preprocessor-line-continuation - include: scope:source.c#preprocessor-line-ending - include: scope:source.c#preprocessor-comments - match: \bdefined\b scope: keyword.control.c++ - match: ^\s*(#\s*endif)\b captures: 1: meta.preprocessor.c++ keyword.control.import.c++ - match: ^\s*(#\s*(?:error|warning))\b captures: 1: keyword.control.import.error.c++ push: - meta_scope: meta.preprocessor.diagnostic.c++ - include: scope:source.c#preprocessor-line-continuation - include: scope:source.c#preprocessor-line-ending - include: scope:source.c#preprocessor-comments - include: strings - match: '\S+' scope: string.unquoted.c++ - match: ^\s*(#\s*(?:include|include_next|import))\b captures: 1: keyword.control.import.include.c++ push: - meta_scope: meta.preprocessor.include.c++ - include: scope:source.c#preprocessor-line-continuation - include: scope:source.c#preprocessor-line-ending - include: scope:source.c#preprocessor-comments - match: '"' scope: punctuation.definition.string.begin.c++ push: - meta_scope: string.quoted.double.include.c++ - match: '"' scope: punctuation.definition.string.end.c++ pop: true - match: < scope: punctuation.definition.string.begin.c++ push: - meta_scope: string.quoted.other.lt-gt.include.c++ - match: '>' scope: punctuation.definition.string.end.c++ pop: true - include: preprocessor-practical-workarounds openal-soft-1.24.2/fmt-11.1.1/support/README000066400000000000000000000001251474041540300177260ustar00rootroot00000000000000This directory contains build support files such as * CMake modules * Build scripts openal-soft-1.24.2/fmt-11.1.1/support/Vagrantfile000066400000000000000000000011071474041540300212340ustar00rootroot00000000000000# -*- mode: ruby -*- # vi: set ft=ruby : # A vagrant config for testing against gcc-4.8. Vagrant.configure("2") do |config| config.vm.box = "bento/ubuntu-22.04-arm64" config.vm.provider "vmware_desktop" do |vb| vb.memory = "4096" end config.vm.provision "shell", inline: <<-SHELL apt-get update apt-get install -y g++ make wget git wget -q https://github.com/Kitware/CMake/releases/download/v3.26.0/cmake-3.26.0-Linux-x86_64.tar.gz tar xzf cmake-3.26.0-Linux-x86_64.tar.gz ln -s `pwd`/cmake-3.26.0-Linux-x86_64/bin/cmake /usr/local/bin SHELL end openal-soft-1.24.2/fmt-11.1.1/support/bazel/000077500000000000000000000000001474041540300201455ustar00rootroot00000000000000openal-soft-1.24.2/fmt-11.1.1/support/bazel/.bazelversion000066400000000000000000000000061474041540300226450ustar00rootroot000000000000007.1.2 openal-soft-1.24.2/fmt-11.1.1/support/bazel/BUILD.bazel000066400000000000000000000007571474041540300220340ustar00rootroot00000000000000cc_library( name = "fmt", srcs = [ #"src/fmt.cc", # No C++ module support, yet in Bazel (https://github.com/bazelbuild/bazel/pull/19940) "src/format.cc", "src/os.cc", ], hdrs = glob([ "include/fmt/*.h", ]), copts = select({ "@platforms//os:windows": ["-utf-8"], "//conditions:default": [], }), includes = [ "include", ], strip_include_prefix = "include", visibility = ["//visibility:public"], ) openal-soft-1.24.2/fmt-11.1.1/support/bazel/MODULE.bazel000066400000000000000000000001531474041540300221500ustar00rootroot00000000000000module( name = "fmt", compatibility_level = 10, ) bazel_dep(name = "platforms", version = "0.0.10") openal-soft-1.24.2/fmt-11.1.1/support/bazel/README.md000066400000000000000000000017011474041540300214230ustar00rootroot00000000000000# Bazel support To get [Bazel](https://bazel.build/) working with {fmt} you can copy the files `BUILD.bazel`, `MODULE.bazel`, `WORKSPACE.bazel`, and `.bazelversion` from this folder (`support/bazel`) to the root folder of this project. This way {fmt} gets bazelized and can be used with Bazel (e.g. doing a `bazel build //...` on {fmt}). ## Using {fmt} as a dependency ### Using Bzlmod The [Bazel Central Registry](https://github.com/bazelbuild/bazel-central-registry/tree/main/modules/fmt) provides support for {fmt}. For instance, to use {fmt} add to your `MODULE.bazel` file: ``` bazel_dep(name = "fmt", version = "10.2.1") ``` ### Live at head For a live-at-head approach, you can copy the contents of this repository and move the Bazel-related build files to the root folder of this project as described above and make use of `local_path_override`, e.g.: ``` local_path_override( module_name = "fmt", path = "../third_party/fmt", ) ``` openal-soft-1.24.2/fmt-11.1.1/support/bazel/WORKSPACE.bazel000066400000000000000000000000511474041540300225160ustar00rootroot00000000000000# WORKSPACE marker file needed by Bazel openal-soft-1.24.2/fmt-11.1.1/support/build.gradle000066400000000000000000000076111474041540300213340ustar00rootroot00000000000000import java.nio.file.Paths // General gradle arguments for root project buildscript { repositories { google() jcenter() } dependencies { // // https://developer.android.com/studio/releases/gradle-plugin#updating-gradle // // Notice that 4.0.0 here is the version of [Android Gradle Plugin] // According to URL above you will need Gradle 6.1 or higher // classpath "com.android.tools.build:gradle:4.1.1" } } repositories { google() jcenter() } // Project's root where CMakeLists.txt exists: rootDir/support/.cxx -> rootDir def rootDir = Paths.get(project.buildDir.getParent()).getParent() println("rootDir: ${rootDir}") // Output: Shared library (.so) for Android apply plugin: "com.android.library" android { compileSdkVersion 25 // Android 7.0 // Target ABI // - This option controls target platform of module // - The platform might be limited by compiler's support // some can work with Clang(default), but some can work only with GCC... // if bad, both toolchains might not support it splits { abi { enable true // Specify platforms for Application reset() include "arm64-v8a", "armeabi-v7a", "x86_64" } } ndkVersion "21.3.6528147" // ANDROID_NDK_HOME is deprecated. Be explicit defaultConfig { minSdkVersion 21 // Android 5.0+ targetSdkVersion 25 // Follow Compile SDK versionCode 34 // Follow release count versionName "7.1.2" // Follow Official version externalNativeBuild { cmake { arguments "-DANDROID_STL=c++_shared" // Specify Android STL arguments "-DBUILD_SHARED_LIBS=true" // Build shared object arguments "-DFMT_TEST=false" // Skip test arguments "-DFMT_DOC=false" // Skip document cppFlags "-std=c++17" targets "fmt" } } println(externalNativeBuild.cmake.cppFlags) println(externalNativeBuild.cmake.arguments) } // External Native build // - Use existing CMakeList.txt // - Give path to CMake. This gradle file should be // neighbor of the top level cmake externalNativeBuild { cmake { version "3.10.0+" path "${rootDir}/CMakeLists.txt" // buildStagingDirectory "./build" // Custom path for cmake output } } sourceSets{ // Android Manifest for Gradle main { manifest.srcFile "AndroidManifest.xml" } } // https://developer.android.com/studio/build/native-dependencies#build_system_configuration buildFeatures { prefab true prefabPublishing true } prefab { fmt { headers "${rootDir}/include" } } } assemble.doLast { // Instead of `ninja install`, Gradle will deploy the files. // We are doing this since FMT is dependent to the ANDROID_STL after build copy { from "build/intermediates/cmake" into "${rootDir}/libs" } // Copy debug binaries copy { from "${rootDir}/libs/debug/obj" into "${rootDir}/libs/debug" } // Copy Release binaries copy { from "${rootDir}/libs/release/obj" into "${rootDir}/libs/release" } // Remove empty directory delete "${rootDir}/libs/debug/obj" delete "${rootDir}/libs/release/obj" // Copy AAR files. Notice that the aar is named after the folder of this script. copy { from "build/outputs/aar/support-release.aar" into "${rootDir}/libs" rename "support-release.aar", "fmt-release.aar" } copy { from "build/outputs/aar/support-debug.aar" into "${rootDir}/libs" rename "support-debug.aar", "fmt-debug.aar" } } openal-soft-1.24.2/fmt-11.1.1/support/check-commits000077500000000000000000000025551474041540300215330ustar00rootroot00000000000000#!/usr/bin/env python3 """Compile source on a range of commits Usage: check-commits """ import docopt, os, sys, tempfile from subprocess import check_call, check_output, run args = docopt.docopt(__doc__) start = args.get('') source = args.get('') cwd = os.getcwd() with tempfile.TemporaryDirectory() as work_dir: check_call(['git', 'clone', 'https://github.com/fmtlib/fmt.git'], cwd=work_dir) repo_dir = os.path.join(work_dir, 'fmt') commits = check_output( ['git', 'rev-list', f'{start}..HEAD', '--abbrev-commit', '--', 'include', 'src'], text=True, cwd=repo_dir).rstrip().split('\n') commits.reverse() print('Time\tCommit') for commit in commits: check_call(['git', '-c', 'advice.detachedHead=false', 'checkout', commit], cwd=repo_dir) returncode = run( ['c++', '-std=c++11', '-O3', '-DNDEBUG', '-I', 'include', 'src/format.cc', os.path.join(cwd, source)], cwd=repo_dir).returncode if returncode != 0: continue times = [] for i in range(5): output = check_output([os.path.join(repo_dir, 'a.out')], text=True) times.append(float(output)) message = check_output(['git', 'log', '-1', '--pretty=format:%s', commit], cwd=repo_dir, text=True) print(f'{min(times)}\t{commit} {message[:40]}') sys.stdout.flush() openal-soft-1.24.2/fmt-11.1.1/support/cmake/000077500000000000000000000000001474041540300201305ustar00rootroot00000000000000openal-soft-1.24.2/fmt-11.1.1/support/cmake/FindSetEnv.cmake000066400000000000000000000004531474041540300231410ustar00rootroot00000000000000# A CMake script to find SetEnv.cmd. find_program(WINSDK_SETENV NAMES SetEnv.cmd PATHS "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Microsoft SDKs\\Windows;CurrentInstallFolder]/bin") if (WINSDK_SETENV AND PRINT_PATH) execute_process(COMMAND ${CMAKE_COMMAND} -E echo "${WINSDK_SETENV}") endif () openal-soft-1.24.2/fmt-11.1.1/support/cmake/JoinPaths.cmake000066400000000000000000000016771474041540300230440ustar00rootroot00000000000000# This module provides function for joining paths # known from from most languages # # Original license: # SPDX-License-Identifier: (MIT OR CC0-1.0) # Explicit permission given to distribute this module under # the terms of the project as described in /LICENSE.rst. # Copyright 2020 Jan Tojnar # https://github.com/jtojnar/cmake-snips # # Modelled after Python’s os.path.join # https://docs.python.org/3.7/library/os.path.html#os.path.join # Windows not supported function(join_paths joined_path first_path_segment) set(temp_path "${first_path_segment}") foreach(current_segment IN LISTS ARGN) if(NOT ("${current_segment}" STREQUAL "")) if(IS_ABSOLUTE "${current_segment}") set(temp_path "${current_segment}") else() set(temp_path "${temp_path}/${current_segment}") endif() endif() endforeach() set(${joined_path} "${temp_path}" PARENT_SCOPE) endfunction() openal-soft-1.24.2/fmt-11.1.1/support/cmake/fmt-config.cmake.in000066400000000000000000000002231474041540300235650ustar00rootroot00000000000000@PACKAGE_INIT@ if (NOT TARGET fmt::fmt) include(${CMAKE_CURRENT_LIST_DIR}/@targets_export_name@.cmake) endif () check_required_components(fmt) openal-soft-1.24.2/fmt-11.1.1/support/cmake/fmt.pc.in000066400000000000000000000004101474041540300216420ustar00rootroot00000000000000prefix=@CMAKE_INSTALL_PREFIX@ exec_prefix=@CMAKE_INSTALL_PREFIX@ libdir=@libdir_for_pc_file@ includedir=@includedir_for_pc_file@ Name: fmt Description: A modern formatting library Version: @FMT_VERSION@ Libs: -L${libdir} -l@FMT_LIB_NAME@ Cflags: -I${includedir} openal-soft-1.24.2/fmt-11.1.1/support/docopt.py000066400000000000000000000465101474041540300207200ustar00rootroot00000000000000"""Pythonic command-line interface parser that will make you smile. * http://docopt.org * Repository and issue-tracker: https://github.com/docopt/docopt * Licensed under terms of MIT license (see LICENSE-MIT) * Copyright (c) 2013 Vladimir Keleshev, vladimir@keleshev.com """ import sys import re __all__ = ['docopt'] __version__ = '0.6.1' class DocoptLanguageError(Exception): """Error in construction of usage-message by developer.""" class DocoptExit(SystemExit): """Exit in case user invoked program with incorrect arguments.""" usage = '' def __init__(self, message=''): SystemExit.__init__(self, (message + '\n' + self.usage).strip()) class Pattern(object): def __eq__(self, other): return repr(self) == repr(other) def __hash__(self): return hash(repr(self)) def fix(self): self.fix_identities() self.fix_repeating_arguments() return self def fix_identities(self, uniq=None): """Make pattern-tree tips point to same object if they are equal.""" if not hasattr(self, 'children'): return self uniq = list(set(self.flat())) if uniq is None else uniq for i, child in enumerate(self.children): if not hasattr(child, 'children'): assert child in uniq self.children[i] = uniq[uniq.index(child)] else: child.fix_identities(uniq) def fix_repeating_arguments(self): """Fix elements that should accumulate/increment values.""" either = [list(child.children) for child in transform(self).children] for case in either: for e in [child for child in case if case.count(child) > 1]: if type(e) is Argument or type(e) is Option and e.argcount: if e.value is None: e.value = [] elif type(e.value) is not list: e.value = e.value.split() if type(e) is Command or type(e) is Option and e.argcount == 0: e.value = 0 return self def transform(pattern): """Expand pattern into an (almost) equivalent one, but with single Either. Example: ((-a | -b) (-c | -d)) => (-a -c | -a -d | -b -c | -b -d) Quirks: [-a] => (-a), (-a...) => (-a -a) """ result = [] groups = [[pattern]] while groups: children = groups.pop(0) parents = [Required, Optional, OptionsShortcut, Either, OneOrMore] if any(t in map(type, children) for t in parents): child = [c for c in children if type(c) in parents][0] children.remove(child) if type(child) is Either: for c in child.children: groups.append([c] + children) elif type(child) is OneOrMore: groups.append(child.children * 2 + children) else: groups.append(child.children + children) else: result.append(children) return Either(*[Required(*e) for e in result]) class LeafPattern(Pattern): """Leaf/terminal node of a pattern tree.""" def __init__(self, name, value=None): self.name, self.value = name, value def __repr__(self): return '%s(%r, %r)' % (self.__class__.__name__, self.name, self.value) def flat(self, *types): return [self] if not types or type(self) in types else [] def match(self, left, collected=None): collected = [] if collected is None else collected pos, match = self.single_match(left) if match is None: return False, left, collected left_ = left[:pos] + left[pos + 1:] same_name = [a for a in collected if a.name == self.name] if type(self.value) in (int, list): if type(self.value) is int: increment = 1 else: increment = ([match.value] if type(match.value) is str else match.value) if not same_name: match.value = increment return True, left_, collected + [match] same_name[0].value += increment return True, left_, collected return True, left_, collected + [match] class BranchPattern(Pattern): """Branch/inner node of a pattern tree.""" def __init__(self, *children): self.children = list(children) def __repr__(self): return '%s(%s)' % (self.__class__.__name__, ', '.join(repr(a) for a in self.children)) def flat(self, *types): if type(self) in types: return [self] return sum([child.flat(*types) for child in self.children], []) class Argument(LeafPattern): def single_match(self, left): for n, pattern in enumerate(left): if type(pattern) is Argument: return n, Argument(self.name, pattern.value) return None, None @classmethod def parse(class_, source): name = re.findall('(<\S*?>)', source)[0] value = re.findall('\[default: (.*)\]', source, flags=re.I) return class_(name, value[0] if value else None) class Command(Argument): def __init__(self, name, value=False): self.name, self.value = name, value def single_match(self, left): for n, pattern in enumerate(left): if type(pattern) is Argument: if pattern.value == self.name: return n, Command(self.name, True) else: break return None, None class Option(LeafPattern): def __init__(self, short=None, long=None, argcount=0, value=False): assert argcount in (0, 1) self.short, self.long, self.argcount = short, long, argcount self.value = None if value is False and argcount else value @classmethod def parse(class_, option_description): short, long, argcount, value = None, None, 0, False options, _, description = option_description.strip().partition(' ') options = options.replace(',', ' ').replace('=', ' ') for s in options.split(): if s.startswith('--'): long = s elif s.startswith('-'): short = s else: argcount = 1 if argcount: matched = re.findall('\[default: (.*)\]', description, flags=re.I) value = matched[0] if matched else None return class_(short, long, argcount, value) def single_match(self, left): for n, pattern in enumerate(left): if self.name == pattern.name: return n, pattern return None, None @property def name(self): return self.long or self.short def __repr__(self): return 'Option(%r, %r, %r, %r)' % (self.short, self.long, self.argcount, self.value) class Required(BranchPattern): def match(self, left, collected=None): collected = [] if collected is None else collected l = left c = collected for pattern in self.children: matched, l, c = pattern.match(l, c) if not matched: return False, left, collected return True, l, c class Optional(BranchPattern): def match(self, left, collected=None): collected = [] if collected is None else collected for pattern in self.children: m, left, collected = pattern.match(left, collected) return True, left, collected class OptionsShortcut(Optional): """Marker/placeholder for [options] shortcut.""" class OneOrMore(BranchPattern): def match(self, left, collected=None): assert len(self.children) == 1 collected = [] if collected is None else collected l = left c = collected l_ = None matched = True times = 0 while matched: # could it be that something didn't match but changed l or c? matched, l, c = self.children[0].match(l, c) times += 1 if matched else 0 if l_ == l: break l_ = l if times >= 1: return True, l, c return False, left, collected class Either(BranchPattern): def match(self, left, collected=None): collected = [] if collected is None else collected outcomes = [] for pattern in self.children: matched, _, _ = outcome = pattern.match(left, collected) if matched: outcomes.append(outcome) if outcomes: return min(outcomes, key=lambda outcome: len(outcome[1])) return False, left, collected class Tokens(list): def __init__(self, source, error=DocoptExit): self += source.split() if hasattr(source, 'split') else source self.error = error @staticmethod def from_pattern(source): source = re.sub(r'([\[\]\(\)\|]|\.\.\.)', r' \1 ', source) source = [s for s in re.split('\s+|(\S*<.*?>)', source) if s] return Tokens(source, error=DocoptLanguageError) def move(self): return self.pop(0) if len(self) else None def current(self): return self[0] if len(self) else None def parse_long(tokens, options): """long ::= '--' chars [ ( ' ' | '=' ) chars ] ;""" long, eq, value = tokens.move().partition('=') assert long.startswith('--') value = None if eq == value == '' else value similar = [o for o in options if o.long == long] if tokens.error is DocoptExit and similar == []: # if no exact match similar = [o for o in options if o.long and o.long.startswith(long)] if len(similar) > 1: # might be simply specified ambiguously 2+ times? raise tokens.error('%s is not a unique prefix: %s?' % (long, ', '.join(o.long for o in similar))) elif len(similar) < 1: argcount = 1 if eq == '=' else 0 o = Option(None, long, argcount) options.append(o) if tokens.error is DocoptExit: o = Option(None, long, argcount, value if argcount else True) else: o = Option(similar[0].short, similar[0].long, similar[0].argcount, similar[0].value) if o.argcount == 0: if value is not None: raise tokens.error('%s must not have an argument' % o.long) else: if value is None: if tokens.current() in [None, '--']: raise tokens.error('%s requires argument' % o.long) value = tokens.move() if tokens.error is DocoptExit: o.value = value if value is not None else True return [o] def parse_shorts(tokens, options): """shorts ::= '-' ( chars )* [ [ ' ' ] chars ] ;""" token = tokens.move() assert token.startswith('-') and not token.startswith('--') left = token.lstrip('-') parsed = [] while left != '': short, left = '-' + left[0], left[1:] similar = [o for o in options if o.short == short] if len(similar) > 1: raise tokens.error('%s is specified ambiguously %d times' % (short, len(similar))) elif len(similar) < 1: o = Option(short, None, 0) options.append(o) if tokens.error is DocoptExit: o = Option(short, None, 0, True) else: # why copying is necessary here? o = Option(short, similar[0].long, similar[0].argcount, similar[0].value) value = None if o.argcount != 0: if left == '': if tokens.current() in [None, '--']: raise tokens.error('%s requires argument' % short) value = tokens.move() else: value = left left = '' if tokens.error is DocoptExit: o.value = value if value is not None else True parsed.append(o) return parsed def parse_pattern(source, options): tokens = Tokens.from_pattern(source) result = parse_expr(tokens, options) if tokens.current() is not None: raise tokens.error('unexpected ending: %r' % ' '.join(tokens)) return Required(*result) def parse_expr(tokens, options): """expr ::= seq ( '|' seq )* ;""" seq = parse_seq(tokens, options) if tokens.current() != '|': return seq result = [Required(*seq)] if len(seq) > 1 else seq while tokens.current() == '|': tokens.move() seq = parse_seq(tokens, options) result += [Required(*seq)] if len(seq) > 1 else seq return [Either(*result)] if len(result) > 1 else result def parse_seq(tokens, options): """seq ::= ( atom [ '...' ] )* ;""" result = [] while tokens.current() not in [None, ']', ')', '|']: atom = parse_atom(tokens, options) if tokens.current() == '...': atom = [OneOrMore(*atom)] tokens.move() result += atom return result def parse_atom(tokens, options): """atom ::= '(' expr ')' | '[' expr ']' | 'options' | long | shorts | argument | command ; """ token = tokens.current() result = [] if token in '([': tokens.move() matching, pattern = {'(': [')', Required], '[': [']', Optional]}[token] result = pattern(*parse_expr(tokens, options)) if tokens.move() != matching: raise tokens.error("unmatched '%s'" % token) return [result] elif token == 'options': tokens.move() return [OptionsShortcut()] elif token.startswith('--') and token != '--': return parse_long(tokens, options) elif token.startswith('-') and token not in ('-', '--'): return parse_shorts(tokens, options) elif token.startswith('<') and token.endswith('>') or token.isupper(): return [Argument(tokens.move())] else: return [Command(tokens.move())] def parse_argv(tokens, options, options_first=False): """Parse command-line argument vector. If options_first: argv ::= [ long | shorts ]* [ argument ]* [ '--' [ argument ]* ] ; else: argv ::= [ long | shorts | argument ]* [ '--' [ argument ]* ] ; """ parsed = [] while tokens.current() is not None: if tokens.current() == '--': return parsed + [Argument(None, v) for v in tokens] elif tokens.current().startswith('--'): parsed += parse_long(tokens, options) elif tokens.current().startswith('-') and tokens.current() != '-': parsed += parse_shorts(tokens, options) elif options_first: return parsed + [Argument(None, v) for v in tokens] else: parsed.append(Argument(None, tokens.move())) return parsed def parse_defaults(doc): defaults = [] for s in parse_section('options:', doc): # FIXME corner case "bla: options: --foo" _, _, s = s.partition(':') # get rid of "options:" split = re.split('\n[ \t]*(-\S+?)', '\n' + s)[1:] split = [s1 + s2 for s1, s2 in zip(split[::2], split[1::2])] options = [Option.parse(s) for s in split if s.startswith('-')] defaults += options return defaults def parse_section(name, source): pattern = re.compile('^([^\n]*' + name + '[^\n]*\n?(?:[ \t].*?(?:\n|$))*)', re.IGNORECASE | re.MULTILINE) return [s.strip() for s in pattern.findall(source)] def formal_usage(section): _, _, section = section.partition(':') # drop "usage:" pu = section.split() return '( ' + ' '.join(') | (' if s == pu[0] else s for s in pu[1:]) + ' )' def extras(help, version, options, doc): if help and any((o.name in ('-h', '--help')) and o.value for o in options): print(doc.strip("\n")) sys.exit() if version and any(o.name == '--version' and o.value for o in options): print(version) sys.exit() class Dict(dict): def __repr__(self): return '{%s}' % ',\n '.join('%r: %r' % i for i in sorted(self.items())) def docopt(doc, argv=None, help=True, version=None, options_first=False): """Parse `argv` based on command-line interface described in `doc`. `docopt` creates your command-line interface based on its description that you pass as `doc`. Such description can contain --options, , commands, which could be [optional], (required), (mutually | exclusive) or repeated... Parameters ---------- doc : str Description of your command-line interface. argv : list of str, optional Argument vector to be parsed. sys.argv[1:] is used if not provided. help : bool (default: True) Set to False to disable automatic help on -h or --help options. version : any object If passed, the object will be printed if --version is in `argv`. options_first : bool (default: False) Set to True to require options precede positional arguments, i.e. to forbid options and positional arguments intermix. Returns ------- args : dict A dictionary, where keys are names of command-line elements such as e.g. "--verbose" and "", and values are the parsed values of those elements. Example ------- >>> from docopt import docopt >>> doc = ''' ... Usage: ... my_program tcp [--timeout=] ... my_program serial [--baud=] [--timeout=] ... my_program (-h | --help | --version) ... ... Options: ... -h, --help Show this screen and exit. ... --baud= Baudrate [default: 9600] ... ''' >>> argv = ['tcp', '127.0.0.1', '80', '--timeout', '30'] >>> docopt(doc, argv) {'--baud': '9600', '--help': False, '--timeout': '30', '--version': False, '': '127.0.0.1', '': '80', 'serial': False, 'tcp': True} See also -------- * For video introduction see http://docopt.org * Full documentation is available in README.rst as well as online at https://github.com/docopt/docopt#readme """ argv = sys.argv[1:] if argv is None else argv usage_sections = parse_section('usage:', doc) if len(usage_sections) == 0: raise DocoptLanguageError('"usage:" (case-insensitive) not found.') if len(usage_sections) > 1: raise DocoptLanguageError('More than one "usage:" (case-insensitive).') DocoptExit.usage = usage_sections[0] options = parse_defaults(doc) pattern = parse_pattern(formal_usage(DocoptExit.usage), options) # [default] syntax for argument is disabled #for a in pattern.flat(Argument): # same_name = [d for d in arguments if d.name == a.name] # if same_name: # a.value = same_name[0].value argv = parse_argv(Tokens(argv), list(options), options_first) pattern_options = set(pattern.flat(Option)) for options_shortcut in pattern.flat(OptionsShortcut): doc_options = parse_defaults(doc) options_shortcut.children = list(set(doc_options) - pattern_options) #if any_options: # options_shortcut.children += [Option(o.short, o.long, o.argcount) # for o in argv if type(o) is Option] extras(help, version, argv, doc) matched, left, collected = pattern.fix().match(argv) if matched and left == []: # better error message if left? return Dict((a.name, a.value) for a in (pattern.flat() + collected)) raise DocoptExit() openal-soft-1.24.2/fmt-11.1.1/support/mkdocs000077500000000000000000000044211474041540300202570ustar00rootroot00000000000000#!/usr/bin/env python3 # A script to invoke mkdocs with the correct environment. # Additionally supports deploying via mike: # ./mkdocs deploy [mike-deploy-options] import errno, os, shutil, sys from subprocess import call support_dir = os.path.dirname(os.path.normpath(__file__)) build_dir = os.path.join(os.path.dirname(support_dir), 'build') # Set PYTHONPATH for the mkdocstrings handler. env = os.environ.copy() path = env.get('PYTHONPATH') env['PYTHONPATH'] = \ (path + ':' if path else '') + os.path.join(support_dir, 'python') redirect_page = \ ''' Redirecting Redirecting to api... ''' config_path = os.path.join(support_dir, 'mkdocs.yml') args = sys.argv[1:] if len(args) > 0: command = args[0] if command == 'deploy': git_url = 'https://github.com/' if 'CI' in os.environ else 'git@github.com:' site_repo = git_url + 'fmtlib/fmt.dev.git' site_dir = os.path.join(build_dir, 'fmt.dev') try: shutil.rmtree(site_dir) except OSError as e: if e.errno == errno.ENOENT: pass ret = call(['git', 'clone', '--depth=1', site_repo, site_dir]) if ret != 0: sys.exit(ret) # Copy the config to the build dir because the site is built relative to it. config_build_path = os.path.join(build_dir, 'mkdocs.yml') shutil.copyfile(config_path, config_build_path) version = args[1] ret = call(['mike'] + args + ['--config-file', config_build_path, '--branch', 'master'], cwd=site_dir, env=env) if ret != 0 or version == 'dev': sys.exit(ret) redirect_page_path = os.path.join(site_dir, version, 'api.html') with open(redirect_page_path, "w") as file: file.write(redirect_page) ret = call(['git', 'add', redirect_page_path], cwd=site_dir) if ret != 0: sys.exit(ret) ret = call(['git', 'commit', '--amend', '--no-edit'], cwd=site_dir) sys.exit(ret) elif not command.startswith('-'): args += ['-f', config_path] sys.exit(call(['mkdocs'] + args, env=env)) openal-soft-1.24.2/fmt-11.1.1/support/mkdocs.yml000066400000000000000000000017451474041540300210620ustar00rootroot00000000000000site_name: '{fmt}' docs_dir: ../doc repo_url: https://github.com/fmtlib/fmt theme: name: material features: - navigation.tabs - navigation.top - toc.integrate extra_javascript: - https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.7.2/highlight.min.js - fmt.js extra_css: - https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.7.2/styles/default.min.css - fmt.css markdown_extensions: - pymdownx.highlight: # Use JavaScript syntax highlighter instead of Pygments because it # automatically applies to code blocks extracted through Doxygen. use_pygments: false anchor_linenums: true line_spans: __span pygments_lang_class: true - pymdownx.inlinehilite - pymdownx.snippets plugins: - search - mkdocstrings: default_handler: cxx nav: - Home: index.md - Get Started: get-started.md - API: api.md - Syntax: syntax.md exclude_docs: ChangeLog-old.md extra: version: provider: mike generator: false openal-soft-1.24.2/fmt-11.1.1/support/printable.py000077500000000000000000000137351474041540300214160ustar00rootroot00000000000000#!/usr/bin/env python3 # This script is based on # https://github.com/rust-lang/rust/blob/master/library/core/src/unicode/printable.py # distributed under https://github.com/rust-lang/rust/blob/master/LICENSE-MIT. # This script uses the following Unicode tables: # - UnicodeData.txt from collections import namedtuple import csv import os import subprocess NUM_CODEPOINTS=0x110000 def to_ranges(iter): current = None for i in iter: if current is None or i != current[1] or i in (0x10000, 0x20000): if current is not None: yield tuple(current) current = [i, i + 1] else: current[1] += 1 if current is not None: yield tuple(current) def get_escaped(codepoints): for c in codepoints: if (c.class_ or "Cn") in "Cc Cf Cs Co Cn Zl Zp Zs".split() and c.value != ord(' '): yield c.value def get_file(f): try: return open(os.path.basename(f)) except FileNotFoundError: subprocess.run(["curl", "-O", f], check=True) return open(os.path.basename(f)) Codepoint = namedtuple('Codepoint', 'value class_') def get_codepoints(f): r = csv.reader(f, delimiter=";") prev_codepoint = 0 class_first = None for row in r: codepoint = int(row[0], 16) name = row[1] class_ = row[2] if class_first is not None: if not name.endswith("Last>"): raise ValueError("Missing Last after First") for c in range(prev_codepoint + 1, codepoint): yield Codepoint(c, class_first) class_first = None if name.endswith("First>"): class_first = class_ yield Codepoint(codepoint, class_) prev_codepoint = codepoint if class_first is not None: raise ValueError("Missing Last after First") for c in range(prev_codepoint + 1, NUM_CODEPOINTS): yield Codepoint(c, None) def compress_singletons(singletons): uppers = [] # (upper, # items in lowers) lowers = [] for i in singletons: upper = i >> 8 lower = i & 0xff if len(uppers) == 0 or uppers[-1][0] != upper: uppers.append((upper, 1)) else: upper, count = uppers[-1] uppers[-1] = upper, count + 1 lowers.append(lower) return uppers, lowers def compress_normal(normal): # lengths 0x00..0x7f are encoded as 00, 01, ..., 7e, 7f # lengths 0x80..0x7fff are encoded as 80 80, 80 81, ..., ff fe, ff ff compressed = [] # [truelen, (truelenaux), falselen, (falselenaux)] prev_start = 0 for start, count in normal: truelen = start - prev_start falselen = count prev_start = start + count assert truelen < 0x8000 and falselen < 0x8000 entry = [] if truelen > 0x7f: entry.append(0x80 | (truelen >> 8)) entry.append(truelen & 0xff) else: entry.append(truelen & 0x7f) if falselen > 0x7f: entry.append(0x80 | (falselen >> 8)) entry.append(falselen & 0xff) else: entry.append(falselen & 0x7f) compressed.append(entry) return compressed def print_singletons(uppers, lowers, uppersname, lowersname): print(" static constexpr singleton {}[] = {{".format(uppersname)) for u, c in uppers: print(" {{{:#04x}, {}}},".format(u, c)) print(" };") print(" static constexpr unsigned char {}[] = {{".format(lowersname)) for i in range(0, len(lowers), 8): print(" {}".format(" ".join("{:#04x},".format(l) for l in lowers[i:i+8]))) print(" };") def print_normal(normal, normalname): print(" static constexpr unsigned char {}[] = {{".format(normalname)) for v in normal: print(" {}".format(" ".join("{:#04x},".format(i) for i in v))) print(" };") def main(): file = get_file("https://www.unicode.org/Public/UNIDATA/UnicodeData.txt") codepoints = get_codepoints(file) CUTOFF=0x10000 singletons0 = [] singletons1 = [] normal0 = [] normal1 = [] extra = [] for a, b in to_ranges(get_escaped(codepoints)): if a > 2 * CUTOFF: extra.append((a, b - a)) elif a == b - 1: if a & CUTOFF: singletons1.append(a & ~CUTOFF) else: singletons0.append(a) elif a == b - 2: if a & CUTOFF: singletons1.append(a & ~CUTOFF) singletons1.append((a + 1) & ~CUTOFF) else: singletons0.append(a) singletons0.append(a + 1) else: if a >= 2 * CUTOFF: extra.append((a, b - a)) elif a & CUTOFF: normal1.append((a & ~CUTOFF, b - a)) else: normal0.append((a, b - a)) singletons0u, singletons0l = compress_singletons(singletons0) singletons1u, singletons1l = compress_singletons(singletons1) normal0 = compress_normal(normal0) normal1 = compress_normal(normal1) print("""\ FMT_FUNC auto is_printable(uint32_t cp) -> bool {\ """) print_singletons(singletons0u, singletons0l, 'singletons0', 'singletons0_lower') print_singletons(singletons1u, singletons1l, 'singletons1', 'singletons1_lower') print_normal(normal0, 'normal0') print_normal(normal1, 'normal1') print("""\ auto lower = static_cast(cp); if (cp < 0x10000) { return is_printable(lower, singletons0, sizeof(singletons0) / sizeof(*singletons0), singletons0_lower, normal0, sizeof(normal0)); } if (cp < 0x20000) { return is_printable(lower, singletons1, sizeof(singletons1) / sizeof(*singletons1), singletons1_lower, normal1, sizeof(normal1)); }\ """) for a, b in extra: print(" if (0x{:x} <= cp && cp < 0x{:x}) return false;".format(a, a + b)) print("""\ return cp < 0x{:x}; }}\ """.format(NUM_CODEPOINTS)) if __name__ == '__main__': main() openal-soft-1.24.2/fmt-11.1.1/support/python/000077500000000000000000000000001474041540300203715ustar00rootroot00000000000000openal-soft-1.24.2/fmt-11.1.1/support/python/mkdocstrings_handlers/000077500000000000000000000000001474041540300247605ustar00rootroot00000000000000openal-soft-1.24.2/fmt-11.1.1/support/python/mkdocstrings_handlers/cxx/000077500000000000000000000000001474041540300255625ustar00rootroot00000000000000openal-soft-1.24.2/fmt-11.1.1/support/python/mkdocstrings_handlers/cxx/__init__.py000066400000000000000000000274731474041540300277100ustar00rootroot00000000000000# A basic mkdocstrings handler for {fmt}. # Copyright (c) 2012 - present, Victor Zverovich # https://github.com/fmtlib/fmt/blob/master/LICENSE import os import xml.etree.ElementTree as ElementTree from pathlib import Path from subprocess import PIPE, STDOUT, CalledProcessError, Popen from typing import Any, List, Mapping, Optional from mkdocstrings.handlers.base import BaseHandler class Definition: """A definition extracted by Doxygen.""" def __init__(self, name: str, kind: Optional[str] = None, node: Optional[ElementTree.Element] = None, is_member: bool = False): self.name = name self.kind = kind if kind is not None else node.get('kind') self.desc = None self.id = name if not is_member else None self.members = None self.params = None self.template_params = None self.trailing_return_type = None self.type = None # A map from Doxygen to HTML tags. tag_map = { 'bold': 'b', 'emphasis': 'em', 'computeroutput': 'code', 'para': 'p', 'programlisting': 'pre', 'verbatim': 'pre' } # A map from Doxygen tags to text. tag_text_map = { 'codeline': '', 'highlight': '', 'sp': ' ' } def escape_html(s: str) -> str: return s.replace("<", "<") def doxyxml2html(nodes: List[ElementTree.Element]): out = '' for n in nodes: tag = tag_map.get(n.tag) if not tag: out += tag_text_map[n.tag] out += '<' + tag + '>' if tag else '' out += '' if tag == 'pre' else '' if n.text: out += escape_html(n.text) out += doxyxml2html(list(n)) out += '' if tag == 'pre' else '' out += '' if tag else '' if n.tail: out += n.tail return out def convert_template_params(node: ElementTree.Element) -> Optional[List[Definition]]: template_param_list = node.find('templateparamlist') if template_param_list is None: return None params = [] for param_node in template_param_list.findall('param'): name = param_node.find('declname') param = Definition(name.text if name is not None else '', 'param') param.type = param_node.find('type').text params.append(param) return params def get_description(node: ElementTree.Element) -> List[ElementTree.Element]: return node.findall('briefdescription/para') + \ node.findall('detaileddescription/para') def normalize_type(type_: str) -> str: type_ = type_.replace('< ', '<').replace(' >', '>') return type_.replace(' &', '&').replace(' *', '*') def convert_type(type_: ElementTree.Element) -> Optional[str]: if type_ is None: return None result = type_.text if type_.text else '' for ref in type_: result += ref.text if ref.tail: result += ref.tail result += type_.tail.strip() return normalize_type(result) def convert_params(func: ElementTree.Element) -> List[Definition]: params = [] for p in func.findall('param'): d = Definition(p.find('declname').text, 'param') d.type = convert_type(p.find('type')) params.append(d) return params def convert_return_type(d: Definition, node: ElementTree.Element) -> None: d.trailing_return_type = None if d.type == 'auto' or d.type == 'constexpr auto': parts = node.find('argsstring').text.split(' -> ') if len(parts) > 1: d.trailing_return_type = normalize_type(parts[1]) def render_param(param: Definition) -> str: return param.type + (f' {param.name}' if len(param.name) > 0 else '') def render_decl(d: Definition) -> str: text = '' if d.id is not None: text += f'\n' text += '

'

    text += '
' if d.template_params is not None: text += 'template <' text += ', '.join([render_param(p) for p in d.template_params]) text += '>\n' text += '
' text += '
' end = ';' if d.kind == 'function' or d.kind == 'variable': text += d.type + ' ' if len(d.type) > 0 else '' elif d.kind == 'typedef': text += 'using ' elif d.kind == 'define': end = '' else: text += d.kind + ' ' text += d.name if d.params is not None: params = ', '.join([ (p.type + ' ' if p.type else '') + p.name for p in d.params]) text += '(' + escape_html(params) + ')' if d.trailing_return_type: text += ' -⁠> ' + escape_html(d.trailing_return_type) elif d.kind == 'typedef': text += ' = ' + escape_html(d.type) text += end text += '
' text += '
\n' if d.id is not None: text += f'\n' return text class CxxHandler(BaseHandler): def __init__(self, **kwargs: Any) -> None: super().__init__(handler='cxx', **kwargs) headers = [ 'args.h', 'base.h', 'chrono.h', 'color.h', 'compile.h', 'format.h', 'os.h', 'ostream.h', 'printf.h', 'ranges.h', 'std.h', 'xchar.h' ] # Run doxygen. cmd = ['doxygen', '-'] support_dir = Path(__file__).parents[3] top_dir = os.path.dirname(support_dir) include_dir = os.path.join(top_dir, 'include', 'fmt') self._ns2doxyxml = {} build_dir = os.path.join(top_dir, 'build') os.makedirs(build_dir, exist_ok=True) self._doxyxml_dir = os.path.join(build_dir, 'doxyxml') p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT) _, _ = p.communicate(input=r''' PROJECT_NAME = fmt GENERATE_XML = YES GENERATE_LATEX = NO GENERATE_HTML = NO INPUT = {0} XML_OUTPUT = {1} QUIET = YES AUTOLINK_SUPPORT = NO MACRO_EXPANSION = YES PREDEFINED = _WIN32=1 \ __linux__=1 \ FMT_ENABLE_IF(...)= \ FMT_USE_USER_LITERALS=1 \ FMT_USE_ALIAS_TEMPLATES=1 \ FMT_USE_NONTYPE_TEMPLATE_ARGS=1 \ FMT_API= \ "FMT_BEGIN_NAMESPACE=namespace fmt {{" \ "FMT_END_NAMESPACE=}}" \ "FMT_DOC=1" '''.format( ' '.join([os.path.join(include_dir, h) for h in headers]), self._doxyxml_dir).encode('utf-8')) if p.returncode != 0: raise CalledProcessError(p.returncode, cmd) # Merge all file-level XMLs into one to simplify search. self._file_doxyxml = None for h in headers: filename = h.replace(".h", "_8h.xml") with open(os.path.join(self._doxyxml_dir, filename)) as f: doxyxml = ElementTree.parse(f) if self._file_doxyxml is None: self._file_doxyxml = doxyxml continue root = self._file_doxyxml.getroot() for node in doxyxml.getroot(): root.append(node) def collect_compound(self, identifier: str, cls: List[ElementTree.Element]) -> Definition: """Collect a compound definition such as a struct.""" path = os.path.join(self._doxyxml_dir, cls[0].get('refid') + '.xml') with open(path) as f: xml = ElementTree.parse(f) node = xml.find('compounddef') d = Definition(identifier, node=node) d.template_params = convert_template_params(node) d.desc = get_description(node) d.members = [] for m in \ node.findall('sectiondef[@kind="public-attrib"]/memberdef') + \ node.findall('sectiondef[@kind="public-func"]/memberdef'): name = m.find('name').text # Doxygen incorrectly classifies members of private unnamed unions as # public members of the containing class. if name.endswith('_'): continue desc = get_description(m) if len(desc) == 0: continue kind = m.get('kind') member = Definition(name if name else '', kind=kind, is_member=True) type_text = m.find('type').text member.type = type_text if type_text else '' if kind == 'function': member.params = convert_params(m) convert_return_type(member, m) member.template_params = None member.desc = desc d.members.append(member) return d def collect(self, identifier: str, _config: Mapping[str, Any]) -> Definition: qual_name = 'fmt::' + identifier param_str = None paren = qual_name.find('(') if paren > 0: qual_name, param_str = qual_name[:paren], qual_name[paren + 1:-1] colons = qual_name.rfind('::') namespace, name = qual_name[:colons], qual_name[colons + 2:] # Load XML. doxyxml = self._ns2doxyxml.get(namespace) if doxyxml is None: path = f'namespace{namespace.replace("::", "_1_1")}.xml' with open(os.path.join(self._doxyxml_dir, path)) as f: doxyxml = ElementTree.parse(f) self._ns2doxyxml[namespace] = doxyxml nodes = doxyxml.findall( f"compounddef/sectiondef/memberdef/name[.='{name}']/..") if len(nodes) == 0: nodes = self._file_doxyxml.findall( f"compounddef/sectiondef/memberdef/name[.='{name}']/..") candidates = [] for node in nodes: # Process a function or a typedef. params = None d = Definition(name, node=node) if d.kind == 'function': params = convert_params(node) node_param_str = ', '.join([p.type for p in params]) if param_str and param_str != node_param_str: candidates.append(f'{name}({node_param_str})') continue elif d.kind == 'define': params = [] for p in node.findall('param'): param = Definition(p.find('defname').text, kind='param') param.type = None params.append(param) d.type = convert_type(node.find('type')) d.template_params = convert_template_params(node) d.params = params convert_return_type(d, node) d.desc = get_description(node) return d cls = doxyxml.findall(f"compounddef/innerclass[.='{qual_name}']") if not cls: raise Exception(f'Cannot find {identifier}. Candidates: {candidates}') return self.collect_compound(identifier, cls) def render(self, d: Definition, config: dict) -> str: if d.id is not None: self.do_heading('', 0, id=d.id) text = '
\n' text += render_decl(d) text += '
\n' text += doxyxml2html(d.desc) if d.members is not None: for m in d.members: text += self.render(m, config) text += '
\n' text += '
\n' return text def get_handler(theme: str, custom_templates: Optional[str] = None, **_config: Any) -> CxxHandler: """Return an instance of `CxxHandler`. Arguments: theme: The theme to use when rendering contents. custom_templates: Directory containing custom templates. **_config: Configuration passed to the handler. """ return CxxHandler(theme=theme, custom_templates=custom_templates) openal-soft-1.24.2/fmt-11.1.1/support/python/mkdocstrings_handlers/cxx/templates/000077500000000000000000000000001474041540300275605ustar00rootroot00000000000000openal-soft-1.24.2/fmt-11.1.1/support/python/mkdocstrings_handlers/cxx/templates/README000066400000000000000000000001001474041540300304270ustar00rootroot00000000000000mkdocsstrings requires a handler to have a templates directory. openal-soft-1.24.2/fmt-11.1.1/support/release.py000077500000000000000000000134241474041540300210510ustar00rootroot00000000000000#!/usr/bin/env python3 """Make a release. Usage: release.py [] For the release command $FMT_TOKEN should contain a GitHub personal access token obtained from https://github.com/settings/tokens. """ from __future__ import print_function import datetime, docopt, errno, fileinput, json, os import re, shutil, sys from subprocess import check_call import urllib.request class Git: def __init__(self, dir): self.dir = dir def call(self, method, args, **kwargs): return check_call(['git', method] + list(args), **kwargs) def add(self, *args): return self.call('add', args, cwd=self.dir) def checkout(self, *args): return self.call('checkout', args, cwd=self.dir) def clean(self, *args): return self.call('clean', args, cwd=self.dir) def clone(self, *args): return self.call('clone', list(args) + [self.dir]) def commit(self, *args): return self.call('commit', args, cwd=self.dir) def pull(self, *args): return self.call('pull', args, cwd=self.dir) def push(self, *args): return self.call('push', args, cwd=self.dir) def reset(self, *args): return self.call('reset', args, cwd=self.dir) def update(self, *args): clone = not os.path.exists(self.dir) if clone: self.clone(*args) return clone def clean_checkout(repo, branch): repo.clean('-f', '-d') repo.reset('--hard') repo.checkout(branch) class Runner: def __init__(self, cwd): self.cwd = cwd def __call__(self, *args, **kwargs): kwargs['cwd'] = kwargs.get('cwd', self.cwd) check_call(args, **kwargs) def create_build_env(): """Create a build environment.""" class Env: pass env = Env() env.fmt_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) env.build_dir = 'build' env.fmt_repo = Git(os.path.join(env.build_dir, 'fmt')) return env if __name__ == '__main__': args = docopt.docopt(__doc__) env = create_build_env() fmt_repo = env.fmt_repo branch = args.get('') if branch is None: branch = 'master' if not fmt_repo.update('-b', branch, 'git@github.com:fmtlib/fmt'): clean_checkout(fmt_repo, branch) # Update the date in the changelog and extract the version and the first # section content. changelog = 'ChangeLog.md' changelog_path = os.path.join(fmt_repo.dir, changelog) is_first_section = True first_section = [] for i, line in enumerate(fileinput.input(changelog_path, inplace=True)): if i == 0: version = re.match(r'# (.*) - TBD', line).group(1) line = '# {} - {}\n'.format( version, datetime.date.today().isoformat()) elif not is_first_section: pass elif line.startswith('#'): is_first_section = False else: first_section.append(line) sys.stdout.write(line) if first_section[0] == '\n': first_section.pop(0) ns_version = None base_h_path = os.path.join(fmt_repo.dir, 'include', 'fmt', 'base.h') for line in fileinput.input(base_h_path): m = re.match(r'\s*inline namespace v(.*) .*', line) if m: ns_version = m.group(1) break major_version = version.split('.')[0] if not ns_version or ns_version != major_version: raise Exception(f'Version mismatch {ns_version} != {major_version}') # Workaround GitHub-flavored Markdown treating newlines as
. changes = '' code_block = False stripped = False for line in first_section: if re.match(r'^\s*```', line): code_block = not code_block changes += line stripped = False continue if code_block: changes += line continue if line == '\n' or re.match(r'^\s*\|.*', line): if stripped: changes += '\n' stripped = False changes += line continue if stripped: line = ' ' + line.lstrip() changes += line.rstrip() stripped = True fmt_repo.checkout('-B', 'release') fmt_repo.add(changelog) fmt_repo.commit('-m', 'Update version') # Build the docs and package. run = Runner(fmt_repo.dir) run('cmake', '.') run('make', 'doc', 'package_source') # Create a release on GitHub. fmt_repo.push('origin', 'release') auth_headers = {'Authorization': 'token ' + os.getenv('FMT_TOKEN')} req = urllib.request.Request( 'https://api.github.com/repos/fmtlib/fmt/releases', data=json.dumps({'tag_name': version, 'target_commitish': 'release', 'body': changes, 'draft': True}).encode('utf-8'), headers=auth_headers, method='POST') with urllib.request.urlopen(req) as response: if response.status != 201: raise Exception(f'Failed to create a release ' + '{response.status} {response.reason}') response_data = json.loads(response.read().decode('utf-8')) id = response_data['id'] # Upload the package. uploads_url = 'https://uploads.github.com/repos/fmtlib/fmt/releases' package = 'fmt-{}.zip'.format(version) req = urllib.request.Request( f'{uploads_url}/{id}/assets?name={package}', headers={'Content-Type': 'application/zip'} | auth_headers, data=open('build/fmt/' + package, 'rb').read(), method='POST') with urllib.request.urlopen(req) as response: if response.status != 201: raise Exception(f'Failed to upload an asset ' '{response.status} {response.reason}') short_version = '.'.join(version.split('.')[:-1]) check_call(['./mkdocs', 'deploy', short_version]) openal-soft-1.24.2/hrtf/000077500000000000000000000000001474041540300147745ustar00rootroot00000000000000openal-soft-1.24.2/hrtf/Default HRTF.mhr000066400000000000000000004701411474041540300176230ustar00rootroot00000000000000MinPHR03€»@x $-8ýÿ— óÔÃÆøCxö¥*ö8Ç®/¢þ«_üú°ý!©ÿ.9ýldþÇöþ¦ý?†ÿÛ.ý“7ýÿÿÚCÿ‰¡ÿ30ÿDŸÿ™ÇþèÿŽÿ ÷þ[Íÿçqÿ1^ÿþÃþ_Ìþßyþzþ!xþÖµþáþ¥|þíþå™ÿî%ƒ ùˆÿd~ÿT$ÿèÿ¨ ÿ˜hþ!‹þõþÒJÿãÜÿ_¶¼(Ö½à÷üL àëûˆc·n¼4û•|ô*ý7l ó)¢øPö„öå©õDå£þaPü. ý¯ºÿÉjýOkþM#ÿ ´ýV§ÿýLýðýÚôþoÿ\ŒÿK'ÿ.’ÿ<Àþàÿþí“ÿ‚ëþ} VÚÿ¼xÿ^mÿóÌþ€Öþ)þktþ@nþ"ªþ°ßþ6zþ¶ðþ®¨ÿT:)öšÿA‚ÿª!ÿ_ÿtÿTdþQ†þçøþ—Sÿùãÿb)$ÉŽüé»U«û}`dûôºÐüýv ÷pj¤ø/?ö_ö¬éœSíþÚqü£ý‰ÇÿÚrý GþÿvœýK¢ÿèrý,ýd%ÿ’Bÿĸÿ‰=ÿõ‰ÿ—¨þÚþ±~ÿûÞþ©Ùÿ«nÿEaÿ »þ Áþµoþiþkþ§þCßþyþÌèþÌ¢ÿN=ù*þ“ÿyÿ%ÿ° ÿ¬ÿw^þÍvþ”éþÖDÿØÿZ¸¦*a=špü¶÷•¡ûF~ÿ[[¬cú„ótøû×D oÁ%0ø²¸õ‰™õ†@Ê“]Óþ·gü}–ý"Óÿ”wý/þ}<ÿ>¤ý¯¯ÿЊý‘+ýå;ÿ×LÿaÆÿOHÿq³ÿMöþ %ÿÿÑÿéÿ,-ªŠÿªvÿý¿þ¶¾þ5jþóhþ¢tþ¨·þêþþŽþDïþ¤ÿp5R"!{ÿÌPÿJõþ:ìþRùþbþ{‚þqúþ8Tÿ;ìÿ_ßu,æP¦’üWʃûß<ÿf¥¼ù)—òXüÝ ïU>ø1äõ¢}õwz-œ²þ7¯ü@®ýÏ}ÿ»-ý4þK&ÿ¼›ýÛ|ÿé9ýJýò7ÿ´<ÿ·»ÿãOÿe¼ÿ{öþ=ÿ²¼ÿg ÿO/jêÿp]ÿäaÿÁ·þ¤»þcjþRhþzþ‘¿þÂÿ9­þ—ÿã¿ÿ´AÖ4š¥ÿrÿûþÄèþtùþ jþ@þõÿ1cÿ% zè-ûÁüÇsÖíûH,ÿaÏNÿøx{òœ-ý\ 3°äö‰^ö)Œöò#e1oýË'ü%þìóÿ$ý¶Ðý®Þþ‡”ý1§ÿýüš«üØüþÓõþ}~ÿŸOÿÅÔÿEÙþlËþÙrÿmãþK<ÏWÿoKÿäžþQšþ|Yþ¹gþáqþ(šþœÍþ7xþ ÿ;àÿ6^$<½˜ÿò‡ÿÅ)ÿÊïþ0çþmOþ͆þàÿ\pÿDÿÿ0`-0¹g*Øù.¾æWûbÿž ¤8ùâíñÊý"þ «ÿ0õ}°ôîÉõ_…%cý¿pû¦ªý|Åÿ…ûüŸåýÐÿRý3ŸÿþÞüó_üØ ÿ4 ÿ–„ÿz,ÿ«ÿßÖþ£åþŒÿ%·þ¿çV`ÿ:dÿOªþ¶¡þFþÅBþ$XþEŒþ“½þ‰?þ™×þ‘Þÿ€z]ZµÿCƒÿ$ÿÿÝÔþô þSþ‘ôþ oÿn #¡C1¯t[úšI;.úJÿS Ó°ùøVñÑBü¬‘ Us4ö·§ó°¯óCï¦ü@\þaVû¥çüŠÿØÊü‰Üý&õþSý«ÿuìü„ü* ÿÉÿ¯¬ÿoÿèÿYŸþ{äþT¶ÿrÞþ­Cðöxÿbiÿ’“þÚ¢þ'Dþ=6þ¶1þ”tþ$ÄþêNþ[àþÅÿ]súW9ŸÿÄÿ<ÿ—ýþñþ¹$þOþÇâþüNÿ£Ýu0„E7ôû@™hoúþ6ÿñ ‘ùñº£úûT ø›©Gø xõ ­òØÔþîö³Kÿÿ,ü|$ý÷Iÿ+ý´þ’Âþƒýï ÿãüÓjüÐòþÁÈþš¬ÿ%~ÿãªÿíëþßÿÓÿùðþ²úÿÒßÿsrÿ†ÿ‰ŸþÚžþ;hþƒ?þUAþÛnþzØþL“þ ÿxçÿì{c.§ÿpZÿ€ßþßþwòþ þÎHþ1ðþu`ÿã|ëT†/L÷ÇûKj¶%ûŒòþÿJ¬ùõò¹qûô ƒÐœ÷P½õÓ"õüòý66Æ–ÿ±ýh˜ýT‰ÿåýiÇý Ôþ4 ýûÿJyýƒýnàþ÷!ÿŠÿ›@ÿÓÿúÝþßäþ\´ÿÄ)ÿ5;ÆÿJÿóqÿþþ–ÂþfFþ2Rþ‹ZþIyþX½þY˜þ™Jÿµ$³r'%ŽÿÀ@ÿÚþ·óþ¸óþ Oþ+qþýêþ†[ÿXÙ‰w,¾DDûíÉœ>ü±þÜãêåùqôó¸ÀûŸ˜»6 =ùíq÷»Ñ÷*-ÿ¯m‚Ëþ Tþ7%ý%àþYýóIþl•ÿHªýîœþƒŽýªtþÎVÿSnÿÈÒ0ÿ‘¬ÿèÝþ9ÿd—ÿÞ<ÿn3e•ÿ/]ÿqÿ“òþÏÿùµþu­þKªþèàþ¬ ÿµòþÆÙÿÛiæ‘Oçnÿ**ÿHûþ1çþþåþR„þÎþIÿÇxÿ †$VÑ)ciOý›”¸éúcëÿÃ,ßùè]ôÑ6ü°h7{ïù¸è÷Oè÷陊YØþƒøý˜UþÝ<ÿeýB¢þÈGÿòþÓ*ÿîýöþ"¦ÿ³oÿ¡ÿ--ÿ4­ÿ ÿ/Tÿ~ƒÿÿg˜¾ÿðgÿéRÿ²ÿ^ÿ¬*ÿ¹ÿ‡òþæXÿÔ{ÿ$ÿctÿV«ÿ'àÿ:çÿRjÿ·Xÿ:'ÿé%ÿí1ÿëÂþåþO7ÿz~ÿìêÿ %Ç'À%íêüâú§û\j¸Åbù#ÓôS ýôq ÚwxùùPøŸÀøþšºQþÍýã þðoÿÉþüäÚþÿøþqÿ¸×üö þhÿ3‰ÿ1³ÿ_@ÿ¬²ÿ¹ ÿ&‚ÿ{‚ÿá2ÿœe§ÔÿÅÿ¼|ÿÁÿDÿ+ÿþúäþ“åþCLÿMÿlßþl$ÿsÿáÿźÿ‚Mÿcÿ*ÿPÿW^ÿ1ßþùÿÁ3ÿÂUÿЪÿ£@oì%pzüüás½BüãØ´°.ÀùÆ^õ0#þx ª#€ù'ñøàÀø”T+Ug1þÒoý£‰þ…¨ÿõ)ýÐÑþãiÿ˜%þgeÿ›ý­ýabÿêyÿ*ÄÿL>ÿ>ËÿZÿópÿ¾ÿÚ@ÿ¿Dð°ÿ¼Uÿ¼PÿeáþoÿµþnÈþÚþ"ÿ ÿ¡¥þ‚õþ|ÿUñÿAÍÿµdÿŒlÿÌ)ÿ™?ÿ­Cÿ”¾þîáþæÿõ>ÿn›ÿ âì$$¿ ï}üÚõùòûz¢ÊÚFúHXöÐ1ÿr/ Ž0Éçù-áøÕ”øµê«uDþwýlœþdóÿnpýÎÿ0ÿ4þ¬Žÿ«?ýVæýsÿwÿ`£ÿ @ÿ/»ÿàþ•Bÿt|ÿ˜ÿŠ ]‘ÿ½`ÿ3Vÿåþtõþ ¨þG¿þWÃþJÿþÿ^¢þ¦ÿn†ÿDêÿ0¾ÿcgÿxÿ~+ÿ37ÿ )ÿ/œþÁÊþ¶ÿÄRÿ½Êÿn;#î ªiü©2(ü­Ô«³4û±÷~¯ÿßH .Ò€ù6{ø‡ˆø»rô#LþRãüýwþëÿÞ|ýL0ÿ%AÿÐBþÂÿ¦YýEâýåUÿZÿNyÿÊ ÿåwÿ§þ25ÿä‹ÿÛÿ±A©ÿÚzÿ¤bÿ8ïþ¬þþ÷±þ¸þ¨þFéþ«õþ}”þ€ÿƒŽÿ•¾åÿµÿp•ÿ<ÿH>ÿýÿzþÂÄþÝÿ_WÿhÑÿ/Ïúk#Ñ.‹üŽLYÜüηѺ}!û9ªöA ÿð Ίˆùîø‰^ø.«@2•-þy÷üwþ;&'Êý÷ÿnÿ$þoàÿ mý|ýQ!ÿ„ ÿY~ÿœ.ÿª£ÿºËþ¤$ÿ†ÿ·çþìùÿ¦¨ÿßlÿÑoÿßóþW ÿk´þ†±þÿ¬þÊêþ|ÿx›þ—ÿ¯¦ÿHÕüÿˆÿ}ÿ(#ÿ£ÿzÿ¬’þMÂþ( ÿ5hÿ>Ûÿ¼k]#;Ó  ü®,èü¾¶«Ò« ûÓkö¦ìþ•ÿ ˜ŽCùTzøsyøÚúÆÿwþÁÜüS?þ·îÿX˜ýÑíþ aÿþøÿ®³ý±Ëý¸DÿÿàMÿ€úþsˆÿÓÊþ· ÿg„ÿáàþøìÿ*‘ÿÍHÿ½UÿÝþ*ùþ,¸þJ¿þ¶ºþíþ´ÿþ½¡þ¥ÿ³¥ÿ¨7ñÿ£oÿÅjÿM&ÿJ(ÿW ÿ²ŽþÚ¬þ­üþh@ÿ!¼ÿaÔÔ$”~Êûme]ü ÿé±þ°úÅÂõà_þ†* º\qhùÇdø×MøD¼òòÆ»þ“1ý±þHB×ýÙÌþÌTÿxñý•Úÿe¾ýáÌýI„ÿY{ÿ3ÜÿrYÿlÿì˜þHØþ¨`ÿEÓþðÿªÿ=[ÿþYÿ|ÑþÉàþF“þXþr¨þÿåþðÿbšþGÿ¢›ÿ‚ šÿÿ×vÿiÿzÿÉÿTÿ÷„þ¬ þrÿvIÿ‰Âÿ6‡%Y†û ÀTüÀCжFú‘ô4Aý.Õ Éß¡íøqà÷¹ý÷Hm™PXöþ Mý´ƒþSt!ûýy»þóžÿÈ þŽ !þ>Öý˜«ÿ¡ŠÿIØÿAøÚJ÷µ|÷-í—÷Ž|þ´ýbþñaÆ×ýqþa°ÿíõý°óÿ/éý†ÀýP²ÿð‡ÿòÿƒgÿ‡æÿ¸7ÿ²jÿ.-ÿ‹>›tÿcƒÿÝÕþù×þh„þÔœþ›¼þdÿHÿÃþÁ ÿƒ ÿ¼wòÿÀCÿTÿÈþ:Øþ8ÿUþ#»þ`%ÿÆjÿêïÿð`©.*a6£uútV³üÑüÿøŒÄøJÕòLßü* ¡e øƒ÷÷¥ê±µ˜_þ‡FýƒIþ£÷œý4kþ÷Tÿ`·ýœ¿ÿ`‘ýè¶ý…¾ÿ‹ÿ~ùvÿÑ÷ÿ$3ÿ‡jÿJ è&ÿþ-ÅÌÿgÿ„›ÿ ÿ&ÿݹþL¯þz¶þ^øþh<ÿ·þÿ®¹ÿ&ÊDwÿwÿܼþÓþ7ÿl¤þNÜþ€:ÿ)yÿÜ|×: +¯»‹÷úËi£ðûäUÿj/Ãæ÷iÀòzýü‘ ­ŠÚ˜øU¯÷¼-÷F›s$¸NþÛÇý €þ˜™ÿ;Oýè{þö|ÿ âý„‡ÿÔFýÇ—ý¥ÿÛ[ÿšàÿ zÿòÿ[2ÿoNÿ=Ýÿº ÿ»AòÊÿü2ÿ¹\ÿ¹Âþ+ÑþJ…þ!—þüÁþÿwÿ*ÿëHÿrÔÿ]%òuŸÿðTÿáËþ~Íþÿi›þÓþ@ÿFˆÿ›$‘VÓ,¨–hûq#\HüÅÆþ¬>°'÷çüò©+þ“&qe4ø ÅøGø 7 ?áýŽËý¶Wÿ—‚)ýÅQþÒ\ÿkþB‘ÿ7Éü0)ýömÿi5ÿ¸¼ÿ‹ÿÎèÿ±ÿã«ÿðÿ¡žÿ¼ýþ¸=ÿ1¿þعþ(…þS¤þ“Ëþ\õþi#ÿñ×þÉOÿóÿ+QÔ!eœÿEÿÌéþÊþùþ’þñéþU\ÿ˜ÿ” B×¥.ºIðäù)š«¯ü)ñþîÄhSöôÀòJ¼ÿ]; ¼7ÿŽúõ4+ùahù_·ðöËÊûíü´ ÿüh‡ýÌ÷ý¬íþÕÈýëÖÿ@µü·Öü`EÿcÃþüaÿ·„ÿº ·êþŵþ5Tÿ4Ùþ¾[Püÿ<"ÿœ1ÿu’þd‹þÚfþHšþ°®þÅ¿þKâþážþŠYÿï Y(! ‚ÿGŠÿÎ.ÿãÍþÓÓþ¡cþ¬ÅþGqÿñÿÕ]+ë<1ýme±öcúýƒÿr W£öh°ñúµS  „ÿ†¤ó^º÷»zùÏ]C¶—û‹üKÿbXµ ýïïýÿÈqýÚ°ÿ*“ü²tü@ÿ–Õþa*ÿ1>ÿÊÏ3ÿÀ×þ|_ÿe’þÀ+­9-ÿ7Qÿ¯œþ ƒþ¬Uþf€þS¸þcÃþzÂþw@þÿ¶5¤„‡Rl™ÿ¡Zÿ»+ÿFßþT¹þrMþè’þ~bÿ˜¶ÿ¨5 ž»3aæíªô±…û‰ïÿØÎ 6Fö³Tñ¸H×x ­›(êñpÜõz.øA¸Éíí ûž]ûÜþ’,Û¹üþTUÿ˜:ýÚÕÿ´büH&ü­ŠÿÀøþàlÿ =ÿjÔÿ¹èþÓáþd€ÿtþ%Õ¯+ÿBcÿ¦þt™þ=þ|Qþ(þ¥þ÷»þú þ~èþB%¢˜£VHµÿ°wÿ&ÿ5ÿܧþ´þý÷_þ $ÿƒ§ÿR(s¬+5h“EHô+{¦?úÓ@v _ÝözðÏÿíïñ ò3ôa¼õ¶O8_/ûÏáúþ.Ïÿ ªüœOþOÿ²ÔüÐßÿ|üÖ@üÛÃÿè)ÿ–’ÿà=ÿD´ÿaœþôÉþ-Žÿù‚þ­C}iOÿ“dÿ–þN þ ;þ>þxRþ þŠºþ¹"þ–Õþ#þÿ¢5[j­ÿÕ„ÿJÿm ÿýÓþìïý°Jþæÿ߀ÿr*>0à®5Åô×oõg1Aù/Q¿Ü z÷ÚÕïþüýæV¿ójó7«ósáÍýüiÅú#ýÀ¢[ü÷þd%ÿTÔüî}üimüYrÿ´!ÿÅÿ²ÿ£ÿ†xþïíþ_Ùÿ¡¼þÀ|Û`ÿ¦eÿ=pþ¦£þ©4þ33þÖ0þþÅÑþH<þ[÷þÚëÿè‘æTž€ÿê~ÿ¥ÿÏøþbïþìþpTþcûþüaÿ!á5ªm5ØÊ’¡÷5|Ï“øëd½Ä Ë©öYïðTü÷«úÇfö¥,ôá—ñ•þ¦¤¯<þ¡ƒûÓ…üb½ÿãÏüv þÅÿ‡5ýf/ PüóVü}%ÿÕÁþ­Ëÿå ÿô©ÿ èþ^ÿC%Àþº\_ujÿ¦nÿÞgþ„¥þ‹?þÅþ¶5þ2þøÿµuþ¢ÿ¤dgávÿ/FÿZÌþDØþñìþ8ôýÌRþV ÿ?kÿ\*˹AQ4¢ª ùzŒd‚ùJÿ@µ ²4÷åBï &úêä Jà)?øÄÃö}¿ðnþ °”1ÿ&iüpiýÃFÿÝKýÙSþ¤þßÝüi Jfüü·?ÿ­|þÚÿVàÿ½ÿ%ÿc_ÿ¯:Ûþßÿ,ÅÿPÿm™ÿr„þäœþUþì:þ,Pþuþ™üþÅþ*4ÿ~6 ¥Ým„ŒÿÅÿþ—þi¾þ´ñþ«÷ýFKþ«ÿ³ÿ4°PÆ3ÂRúž‰Þ—ù2Þþ£{ „”÷Ö¥ïÔ¸ù0 ¹PøÁ÷™oò?Ÿû¾b3ýLþ£0ÿäýÞÏþlþ@Îû^ÿ;`ýülüt[ÿõþ,jÿÏúÿÛßþE_ÿÂûÿ–äþïŸÿ`Wÿ&ÿððÿIæþ×qþ þwXþñKþä|þ#Æþzñþ™¬ÿ¾N5qN!œÿðÿÿyþ€žþ‹èþ¯Wþ*yþ‡ÿ¾~ÿæ5¦Q2ƒzä;ùO–ôßú—þ£ìÒÊ÷Éò…hû ´ ¢÷s÷ÍJöë©û{¡U·œbþô þ¯ÖÿqýçÄýž­þQ¥ü«êþ¢þÅ_ý«ÆþF=ÿ܈ÿ$qÿuqíþëøþ·ÙÿzGÿÜðÿ³ÿAÿY…ÿˆHÿ°ÅþDþóuþ1vþCþ`Ìþ¬àþŽÈÿ‚®|öÿÔoÿËäþúŒþqîþöêþ_þH–þÂÿ¶ÿ TS·ŽŠ/E]>/úäݤSûürþ3•ߢøù‡ópïûD¨ ¶¯PÆ÷ê®ö»÷˜êýǵìOÿ8¥þÔý1ÿäSýEþc#ÿzYý›¡þ4Èý !þš¢þÑCÿ·Í/ÿLøÿ©ÿƒõþzÏÿ ÿÓtÿðCÿl9ÿœ ÿÇ%ÿR_þ›tþ²þWÈþûàþRïþ¤ÛÿP~Q’Úþÿù+ÿïÿžãþQðþ³ñþ6wþ4{þõÿ©ÿxDtML°*aÐ è>û*? ‹büøàýÏ?+àù¼:õeûŸ•²eqžú’3ùúKÓýµWþ“Ïÿ<îÿè£ücžþbeýètþåÝÿ?ÄýSþœ7þU\ÿü&ÿ|¹ÿ2Y¨'ÿâÈÿ«òþ8ÿa—ÿ$Xÿf’_ÿæ[ÿ¦{ÿG&ÿlIÿëèþÐðþçÞþ& ÿ 4ÿ¡Nÿ¶e׺Á˜ T#Vÿ“öþ0öþlÙþL×þÁ²þ²¿þKÿ|œÿyö㋼'> ÍýX5ªúµ¿þKXûú¹›õ¨pûº¾9gàßûYßùÞþù(ÿLÿAˆþ¹ÿÁ‰þŠÝþI'ý’Ìþó¿ÿälþDÐþý½Êþѵÿá›ÿêÌÿÕÿž¦ÿ[;ÿ _ÿ^}ÿ=)ÿâÿÀzÿ*fÿáOÿ[$ÿM¥ÿÅmÿˆ:ÿöPÿÑÿNèÿ—ÿÐòÿÇÿ0ƒÿ«¯ÿ\ÿ´7ÿ„/ÿ&ÿ"9ÿÙ ÿ°&ÿGoÿF½ÿ†Púhù$(ç Ààþ,û P{öïù>2ö¡³ü´H·¿yûê(ú1¿ú‡¿¸Òÿ—eþ÷„ÿ¤ ÿÿNJýY#ÿkUÿ‹{þ¨´þký»ÓþHÒÿwÿ7’ÿdcÿ˜ÓÿJAÿu’ÿpIÿœÿ£@Âÿºiÿ¢aÿþ`ÿ.Æÿ=¨ÿqjÿkJÿMÌÿäËÿÃmÿm ÿpiÿ¡šÿ²ÿä6ÿ @ÿK&ÿ¤Dÿårÿ/"ÿLÿå}ÿДÿòºÿ‰TQò"“ [þ–`ènüDü³Ákù öþptÑ“Á3ûiÄú„rû6/¯@S½þòKÿw=ÿ}$ÿðAý^Nÿã9ÿJqþ´ þ®ý›Ùþ¦Äÿ¤ÿ»µÿYÿïÆÿœXÿ¶ÿfÿ!lÿ*u˜¾ÿü˜ÿ•ÿ™^ÿ~—ÿVÿ],ÿÁHÿÑ»ÿ™ÿ\7ÿSHÿöGÿ œÿBtÿÆ$ÿxRÿl5ÿ?xÿÛšÿSEÿ!hÿ®hÿ&cÿË€ÿ!a!¬òËÑýööüÙqh“ùÿP÷è ÿ¥c'ºQûÝpûOû]OõÍ £þÙþmNÿ6ZÿYHý³gÿúbÿ@—þÛòþ”7ýè¸þñÿ:Žÿ°ÿPeÿ)ôÿqZÿQ²ÿ¸§ÿ:˜ÿ!hWµÿшÿ]ÿ)ÿCGÿôúþ$ÿwBÿê‘ÿ'fÿ…ñþâ ÿyOÿ&³ÿõ{ÿ?ÿïfÿ›?ÿAyÿ–ƒÿä'ÿuJÿWMÿâGÿxiÿFüN.‡Ö &6ýO_;ý=|¬Ëú³è÷MXÿ7×$ HŸûCÊûÇû¥?×_þOtþa5ÿÂÿŶý Šÿ®ÿZqþ¥%ÿéEýònþ^°ÿÈ¡ÿOÄÿpVÿñÿ`ÿjœÿªÿKÿ5 >hÿþ@ÿþQÿ ÿÀ0ÿ+ìþgÿ‚!ÿ!WÿO?ÿƒÓþ”ÿÙpÿƒ±ÿ’‚ÿêIÿÈbÿÌ3ÿÅWÿ—cÿ ÿº ÿµ3ÿv:ÿOÿuÛAY6õýñÀæüsrsãÀúNùøÃŽùŠýØlü9Ÿû ûû¥!ëpþM þErÿ;’àýT ÿ¥6ÿI‚þÞlÿÕ‹ý9£þ‡§ÿРÿ/ ÿlaÿÛÔÿ6ÿ™mÿAcÿq ÿåÿM[ÿ7\ÿPYÿÿ6ÿÝþ­ÿjÿßEÿI-ÿOÔþþÿêfÿW ÿ$uÿEVÿˆwÿ]<ÿ3Wÿ¸Jÿáþš ÿA0ÿdTÿÍ®ÿc¨¨ÛØQòýN‹TUý¨g©ýÇûòÔùkÕ õ›¸ãûÚûøïú0ÿÏ(Þ‰þ–ãýánÿ3íÿÎýé½ÿ†>ÿûÄþ×¢ÿ!°ý³ºþ»¨ÿÁ¤ÿ tÿ&)ÿ|ÿÁþ,]ÿØeÿ¾:ÿÄùÿMuÿúoÿÖSÿIÿ'#ÿ|èþJ ÿ' ÿíCÿ ÿfÃþÍÿ^WÿºÿÌ—ÿtsÿ–ÿíMÿ¹]ÿ5Jÿ%âþ0 ÿ/ÿÕ\ÿk¯ÿUÊo_kaäýq5Gþú§•YpFüw ú«Lh G¯$û¿ûòû(D?~®Jþœ¶ýà:ÿÿ+=,þ.Þÿ›Žÿ7ÛþñÚÿ$ºý„fþÎWÿjNÿgdÿÿ#ÿm€ÿzÊþEUÿ.~ÿÃ.ÿ½èÿ«ÿ$Œÿ"yÿ%ÿu3ÿ^ñþ(÷þ’äþDÿ)ÿ9¾þmÿ&Žÿ^éÿ„Çÿ›ÿªŸÿ¥Fÿ‡Pÿ-,ÿè¼þÿÿ…2ÿgÿÇÏÿ4[¬>.R „öý~N¦þÎÁBeÚûSzù h£RÊ,ý`û'ûŸÁúV⬜¿-þÎæý»TÿÏ^ÍQþuÌÿý¬ÿÙ·þõþÿ½¹ý8þ1ÿ«(ÿ¹jÿ¯Fÿ2´ÿŽéþÙMÿ´nÿèñþuÓÿ¼vÿEiÿrtÿ_(ÿª?ÿ4îþMòþEñþ-ÿ¿&ÿÅþ'/ÿ ™ÿñéÿrÔÿ¼ƒÿ‘}ÿ!-ÿ¶-ÿ -ÿôÏþÿ–Bÿuzÿ"ËÿMqjN° ïýâ; -þkÖÔ3¤šûÙùÄ6ì\)%ñFû+ûÒûªGôO]þÕ¡ýlýþ”8å;þm­ÿF™ÿ*ÍþB/Ë þTþ$ÿJÜþÌ2ÿÊ)ÿ»ÿŸÿÃMÿfÿ’ñþÎÄÿMÿÛ;ÿd]ÿÿ}0ÿbþþÉ ÿ! ÿƒ9ÿáÿ„Íþ¹>ÿø˜ÿ ßÿ·¾ÿôaÿ¾gÿ‘6ÿ[Aÿ×:ÿ·ÏþZëþ¿ÿNLÿ'¯ÿ•g}Vƒ»|¿ü×£>ÝýØÑFÙÆûè ùù.L°úhKìú¬³úu“ú=f“ѰùþÎþÙ(ÿôøÿËçý zÿgÿurþŽ8þ ¤þ¿ÿ1{ÿ`ÿ»åþ˜?ÿY˜þƒ3ÿDuÿCóþFÏÿÕ^ÿ‘Eÿ,Zÿþþ¹ÿ8Ûþóþ“îþ“ÿâ"ÿ¼èþøIÿ‘šÿlìÿoÅÿ…Xÿ[ÿÍÿì5ÿDÿiÏþÝþXÿ¾6ÿèÿ‰”nØ éKü—RÖRýîÄIáû øíkÿÉãáiè ûÖúÚ úFPË}øþØ3þ^RÿZ¥^þRÿ1ŠÿRdþÉøÿˆ3þ€‚þDÆÿö·ÿð͈ÿ¸˜ÿr™þÃÜþL=ÿûÙþÏÿžÿ9VÿZÿæùþÃÿ¶¿þØþBìþˆ&ÿø%ÿbÄþäÿàŠÿÐýÿDÖÿ¥dÿE\ÿÿÿJ+ÿï5ÿE¹þ"Ëþ¼ÿòIÿD¤ÿïÃî ¹R äü[‡C:ý?öOLmEúF¾ö‚7þ(“³þŠ€únú|ú êÈM},ÿº?þLÿº wþ€dÿFÛÿciþA8IqþÒuþ¾ðÿqÏÿcòÿ+mÿ÷ÿzFÿ£XÿÕÿ)½þe˜ÿbwÿfÿƇÿy ÿ9ÿe³þ~Õþäæþ§ÿ|AÿÊÚþ-ÿµ‘ÿàðÿª½ÿ§0ÿŒ)ÿûýþÄÿ­/ÿ¹þwÒþ’ÿð_ÿÖÊÿñy¿ !øM ±¨û& 2+ý ŠÉ’Âpù¶”õ{\ý,œ¢yþ§ùŸ£ùVÝù®LŠ/ÿâCþèOÿÁà;rþ¹+ÿȼoþp<õ¥þIŒþìÿ”¾ÿJõÿ|aÿsýÿ÷^ÿ|—ÿÞ)$nÿO-±¥ÿ¬"ÿm:ÿjöþÓÿ`ÑþBÞþRìþž5ÿdÿ^üþÔ9ÿÚ¢ÿQüÿð¸ÿ:ÿ=Øþf­þÅàþÿpÊþ‡ðþ"CÿÚsÿÞÓÿØ#1â ‡¹úŸ&ýØŠÒ4{øÜ¤ô# ý£Ú'¯Äâø#%ùQù)ìÎíˆþþ<ÿ¦Ë—JþõçþÉ%yVþg#fþ_]þ< ¼ÿ[SŽÿÄ“„ÿ*¯ÿI5wPÿTIø4¡ÿ@–ÿ÷þðþˆ¡þ#Öþì ÿˆ\ÿ“ÿ¬ýþ[(ÿ"˜ÿÚîÿÙËÿjÿV×þ ŸþMÅþLÿ‚Ëþ›öþhMÿ—€ÿ8ñÿW8º+&âÛ ôþù Íý¹1(6÷JÜónaýÖf ,HhføËø”ù‹ k»};þ6þ  ÿî¯RþÀþ'ÇÿOþúÿéþªMþ¸0½ÿ‘8b³ÿ>D„ÿÀ¼ÿ¿GŽHÿ†5_ÐÿÝÿ/ÉÿaÿŸsÿ êþêÊþÈÞþ.=ÿ¥…ÿÙùþù=ÿå¡ÿÃåÿkîÿLDÿîæþ<ªþÑÈþÿ»ßþ ÿdZÿ·…ÿþÿrïT((%Êù óõ¿ü2äÿÃyþqöNUó‚qý„Á•Xùø…ÿø+Žø6¥ƒªlþÒ¨þÛæþÈ ñýJžþoŒÿ`ìýæ±ÿóÎýIþ³´šÿÄ3d«ÿ³(„wÿ Žÿô1NFÿB(«ÿsGÿ¢¥ÿ('ÿ‹@ÿ®ßþ³íþ5ÿZÿ±œÿ<óþª.ÿzÍÿÛ–E€ÿ²ïþ ‡þ´þ ÿÿàþê*ÿ(}ÿ}£ÿ—t³µ«)ïjžŒúà"¨vüŽ*ÿeú†qö[ó ·ý*#èÊù,Çù-ßøš€ïª)É3,§Lõú”5ýmÑýb\÷õVô||(>r%ý%ºøFPü&"ûò.)ë‘û¤aÿËÇ,KýÝ›þiÿžgþ{—ÿX{ü°kýŸ‡ÿ±çþš·ÿµÉÿ™&—ÿqÿì²ÿùÿÀoÿN¸þw.ÿ‹ÎþðªþÖƒþ?Íþmÿ6ÿçIÿˆÿ}ÿŸ I¶äÿ¯–ÿÌÝþå‘þ÷éþ²­þ77ÿ«ÿ ±ÿÖJŸT.’¥pøø£·oXýÞþÌKïêóÖñóI@1úëöqÐü3;üûªh!BIúïWþ« c¿ý‚Tþ€ëþeíý¿×ÿ»tü7ý…ÿ8fþDAÿ ÉÿñYMùþ«þAÿ{Øþgéÿßòþ„'ÿâþ"xþ‡zþ·Îþråþ”ÚþúþçÕþ0¢ÿQAîAŸ ¶|ÿ•ÿç3ÿošþ »þ5‚þ£ÿìÂÿíÀÿRE1'–¹uõŽþþzþN^ôóFåòGò^ kCú’EóÆûÀÊü/’Aiسùt†ýwùD¿ýUeþ?ÿ!fý©§ÿç_üóáü³Ìÿ’dþÿæÿ‹N“BÿHÇþ±Pÿ°ƒþe3í‹ ÿ\PÿΔþIcþv`þ¶Àþ^ÿøÝþNÄþå_þ'aÿ}sgo :_‡ÿ.Xÿ.Fÿ½²þëœþÊyþ"Ôþ¹ÿdÛÿƒoÄ4e| Éáñr=ý†ÿ† ÞKóäñ.Oƒ¯ Ýú#úð/ÆùA8ütC18ìø‰–üZäd= ülþÈgÿHýb´ÿüÏvü\òÿVyþö ÿª†ÿ?-ÿ†ØþôüÓ¥N¦ûüOü®ÿ¶¶þáñÿÖ¿þâ¹ÿ¡ÌþbrÿoV‚þ§¥Ò(cFÿëiÿ?þ¹þÂþ þ2<þ¬–þæ,ÿ»`þùÿ¢9Ú°¯^ANÿ&Fÿm½þ!Òþ6òþîÍý¤bþ1&ÿ+tÿ7HÇ\E89'¦ [õ7È ÷½ F PõóE^í’ûû½Pø3 ÷í‰õ-¤î㜙­ _¬ýêûCxü¼ÿîýÆTþuëþÙ,ýAã‘‘ûxüdÿ2>þG ø^ÿ¬ÇÿÇFÿ1¹ÿE`Þ“þDZy$+Kÿinÿ!6þÁ´þ¬cþ þÆKþ_¬þ"Oÿ"‰þ~(ÿ(…¬Õn&Lÿçøþà}þ›½þ«ïþÙÆý`bþâ1ÿ÷‡ÿj^£[9¿7ýaŸµö½#CPø¾ÊÿN= ÂôÒ…íqeùº¢r;{øÝrøòßí êý áíþ±²üfžý¶ÿEˆýPþçtþð½ü=—©¼û|¢û ¨ÿ³"þNµÿrÿ˜¤ÿô º¼þÍÿɹÿÃÿÿ©ÿHjþþ¢þîÉþÅ'þ3dþ³‚þLÿ}ìþn[ÿË”ÂÌÞrjÿÕþhVþ»Ÿþ-ïþ“ÎýíXþNÿǘÿEV’"u16ÆjÑ—øÉ@ÍÓ÷Ê¡ÿ‘ª ˜§ô+ îšø5 v:%ËøÒÖùÎïjúX„ Ûð£1ý’Cþ‚ÿ‰{ýÕÿ­þi¶û3óÿ­üXèû.ŠÿpƒþîÝÿQØ?ÇÿÿYÙÿ­$K­þHQÿ(ÿsÿÒþckþÑÿþ’VþLþÇqþƒïþÐAÿ–ÌÿVþ›4j½ÿå»þÄþ¾|þÆñþÜþÒwþA;ÿ_ªÿiqjºõ4Z×–NøÕˆ üøÉŸýS] &¡öqïç°ø¬; ÌAÜ÷uEú€Ýò‡Ñ÷1ȯ²:RþêÌþJCÿEý“ÿ[þ¶*û—sÿêíý‘uüAŠÿ ÿ¯"ÿÕ†K-“´þ%£ÿ$ÁþKyÿ&]ÿxäþÂùÿã3ÿ’aþ÷Äþ‚þÓCþ˜Šþ¥ÓþJ!ÿ 6Ö¹n.6†ÿvþØ*þ¥¥þÄþzþÁ¾þBÿh¢ÿz^Ɔ;¶3ku[ñ÷jº¼6ú ÝýO¥Dö©}ó™ú.Ь,õãöDžù^C÷ùü÷ÅBƒÕ@oÿ-jþâ5BUýB©ý4<þRFüµÿ9ÓþRbýòiþÒsÿQÿÁ½ÿoXñþ¼-ÿ«÷ÿ¹@ÿÕ½ÿËwÿŠÿ~ ÿÆ„ÿ­¯þ‹^þŠ£þ¥|þÒŒþºæþ†,ÿ¯?ôéwËØÿ;`ÿtþ„Eþ»ñþéÏþßsþƒ¾þ¶ÿ÷±ÿ±Šxµ*1dÙ îðøKç7ôú¢†ýž‹±÷³ô¦ûä‹àÿ(E÷=Èø¾ù9úoþu;AB×ýÊj­ýiáý¦þ®¿üQðþÿòýd²ýïŒÿû6ÈJÿƒSôÿ°÷þ Z»ÿòÿZSÿ?ÿš5ÿöjÿæ+ÿãCþ«¯þ¼ßþ³»þ*ëþB;ÿU$ôYŠ/¶ÿ9 ÿa°þU‘þ¸ìþ=÷þ‰þåŽþ% ÿÜÿW‹Üx³ã-†` …uúk¸ n-ûJŠýs#“øÈôâôúÝÞopù¦.ø<úštügŠý0†B0ÂüEÿïÓý£dþ†ŠÿèŒýaëýëJþ4-ÿXþ¿ÿªE°&ÿá(¼ÿ ÿÑåÿ¬ÿÁðÿ:=ÿLÿ¾>ÿÓ1ÿyMÿTŠþµþPêþÒýþHÿ—WÿGh§Ç;“qÿ?Èþ•êþ3èþÙþK¤þ7˜þDÿãÿiUîÓ“'çšÊý§% õÿûÔžü!Œ;ùúÒïöÏø£˜•¨’üðúú¼üMûWÓú¥öþY`›û¡þ¡íý¶Oþ©üÿîýpÏý?KÿdÍÿ:“þ_Z›!ÿNõÿæÿ¶9ÿâ›ÿÁGÿ%ÝÿSÿ*bÿè†ÿögÿ;eÿk$ÿ”Dÿºÿ4*ÿbÿ²­ÿWãµüŽ—Äp„Sÿ¶Åþ%ÿ´Åþ«¸þäþHØþ¾&ÿ Äÿ®ûÈ|Ä#Öà ¸­N% xIúfýwvûpƒ÷®îù>äðdEþEüóÞüpÌü)~ûpÝÿ€†˜zþ§þ®Zý¾þƹÒþ‚þjiþ¹NÿmDÿ]îÿXÿ”²ÿìzÿhXÿÒkÿOÿ¯ÿ™[ÿJÿýTÿ²Sÿ¹Ûÿ½¤ÿþlÿ7©ÿ}:'T„ærnÚÿØÿÙ}ÿZÿñ ÿÂ3ÿ$ÿ<ÿ2[ÿ2fÿóºÿ9hq6¢ Çà¶â>–_ûú¹2šS–ú"Aø˜ìúÕåp¨Ðý„füôXýO‹þ“üJÿ»VBÿëþw¦ýß+ÿ©ÂÿSÜþ™Xþ¹áý´uÿgÁÿüŠÿuÿ¨Wÿ÷ýÿœÿ¢ÿæ7ÿ\,ÿ¬ëÿø¯ÿ$kÿJOÿŒŽÿãÞöÿO³ÿ<ÁÿæLˆ=…ñÿö‚hÿq;ÿcŸÿµ.ÿo)ÿ5Dÿ"5ÿ»rÿMlÿ¿ÿϾÿÕÚÿCÉÿƒö¡æNUÓ× ümÇYj0‚ú¡œø.Tü'¬ÇÝ¡•ýÛßüòý¬cÿg#ý{¡ÿR;;Bÿ3¥þêý,_ÿôuÿ:Íþ3þÒ‹ýynÿ±ÿÿŽÿ¥¬ÿ‘„ÿÜÿY¨ÿ€­ÿû#ÿkÿ M#³ÿšyÿù–ÿy·ÿ•{øÿ‹Ÿÿb«ÿ6JÔÜÿˆÁÿÌ!ÿÆOÿKhÿ“ûþà!ÿ•&ÿ_dÿÉ»ÿN¢ÿ@ÃÿwÅÿsŸÿ›ÿÀ´«%’‡¹’nLþVØ„i^¼úÄ ùUÚý(83Úé—ýâ¿ýÜjþR›ÿ»ûýXïáÑyÿ¼þußý@¬ÿ€ÿ´°þúþzªý¤•ÿ-µÿ8¥ÿ›Æÿ¯†ÿ¦ðÿÁÿ:ÄÿÇiÿÏÿ$o%ºÿ¿½ÿbÉÿ±ÿÕ×ÿ÷zÿ¼Tÿ¼ÿl¾àÿšÿ)Tÿ!ÿ6cÿ?&ÿ ÿFJÿ=Jÿ9£ÿ®Õÿì·ÿøÓÿÛ­ÿ„yÿ³Kÿm_ö¬:ÿ²¾íAÃ’þí›Ô)<øúÞàù=ÐþÖ9c[ûýtjþ‘_þ¦Ôÿi¡þ~¼ÿwMË‘ÿ¨óþŽîý,ÂÿžÿõþÌþlãý»iÿsrÿÿà¿ÿ¨™ÿ„“«ÿÈÿ¤¸ÿgôÿ"V³³ÿÎÿvqÿK_ÿ´tÿ+ÿHOÿj©ÿ5êÿ¯ÿ)LÿÞÿå0ÿuÿ1<ÿ)ÿôUÿ6Oÿƒ™ÿj´ÿ:—ÿ°ÿ‹‰ÿTÿ›3ÿü ™ÁŠÌ^ Ü~=Ëþ•uF§ØUûƒú 6ÿ@z$ú^eþkÚþ.Ûý#¯xÿc;ÿ²ßÿ0£ÿÉXÿÀ3þ*ôÿ‰õÿ™ ÿÉþ1Ëýãÿ^‰ÿ¸ÿÔÓÿ®~ÿÜ ºÿ½Õÿ´Äÿa±ÿtÿÿÊOÿÑ@ÿwXÿ/HÿOZÿ¾!ÿSKÿ„ÿ©ÿdƒÿø%ÿ$ÿÚMÿ¹jÿÒFÿŽ>ÿÖTÿ_EÿÞwÿ “ÿ%sÿ…€ÿ®dÿD9ÿy@ÿLE œoÃú¶jo¤þö‡tRüûolû§C›0Û6ÿþìÍþò ýe¨þÿ“4ÿlÿ/ÕÿÆÿ‘®þ>*¯˜ÿ!ðþjÿÍþ¥Aÿ§²ÿ/ºÿD±ÿT›ÿDŽÿã©ÿ yÿM[ÿs­ÿA/ÿ£Pÿ iÿÛRÿ Rÿÿ‰Hÿmnÿ¤ÿ[iÿ¨ÿZ8ÿÌRÿÔOÿP4ÿÝDÿãfÿùOÿ¸tÿe|ÿ$Jÿ÷XÿHÿëHÿ–}ÿÍ ªd}Þ0p” þÄðØlèüS‡üs§Õdâ=ÿi[þB™ý›ý5Ë"ÿð(ÿÊA ìÿ¡”þmùÿÕpÿIÿ7Iÿ«@þ=JÿæµÿIÒÿ­ÿ¶†ÿu­ÿ¼;ÿnuÿ±;ÿëEÿËÿªPÿ{ÿDfÿ~Iÿ ;ÿ¥ÿ­SÿCqÿ„ÿ¤UÿÎÿÿ'ÿ°Lÿi>ÿMhÿ9ˆÿXÿrtÿWnÿm5ÿQQÿ‹Yÿq[ÿvˆÿ$` "*á¬À…™vyÿ4þ%ËP#þ8ý æfÿÅþu×ýlsýkúN¥£Vÿ:ûþêëËÿö«þ™=™œÿ&sÿ)}ÿtpþSfÿ7Çÿ°Æÿ,kÿDÿzgÿ­÷þùoÿáfÿ5tÿÿÿ[aÿÉuÿdÿÃZÿYRÿ<1ÿ)KÿŸSÿ€}ÿöHÿiÿþ¥ ÿÏ3ÿ’xÿoÿ‘‚ÿö£ÿêrÿh‚ÿßlÿÈ4ÿôJÿyFÿ¼aÿ ‘ÿ'˜ _ÊBÏ/¨L?X0YÞˆþäšýéÖ»xþ{LýýèÀý_´ýhRÏ_ýþ~¾þqÖÿôãÿÎc7Ýÿ“™ÿlÆÿËrþzÿ hÿIlÿ#[ÿQDÿSsÿÝÿ=pÿÓtÿŒ[ÿ¡ÿÜcÿP•ÿX‚ÿY_ÿ£dÿØ<ÿT=ÿ™/ÿ Fÿí&ÿÚùþ¸*ÿPpÿyªÿ^Ÿÿó¬ÿ!©ÿ8dÿ¦rÿþEÿ-ÿAÿ™Jÿsÿâ¿ÿ G 6ÀOù¾ïOÀ'W‡(gîýNüüÅ2¤sgþBÒý xý»G3ÌéÄþráþ:ôÿG^¸)ÿùg÷ëÿ^ƒÿàëeþš¯þb,ÿÊ4ÿ Wÿ—rÿR´ÿ &ÿÊxÿ¶^ÿÿØŒÿ‘Oÿxÿ0|ÿôaÿ%nÿ4ÿR:ÿ?ÿdÿ>ÿËüþ¸AÿP€ÿyºÿA´ÿ^—ÿùÿ5Fÿ DÿõAÿiÿê;ÿF^ÿEˆÿ~Âÿ§ ´HåÁä@z²èÿºi*ó=ýäCü{‹c?†Ðþ°Ùýš‡ý~nyí,îþïÁþœ³ÿÄHºCÿ+Zà¶ÿ›ÿTOñ±þêµþ:ØþŸõþUNÿúÿCÃÿ,0ÿ(tÿ¨Mÿ¨"ÿ›ƒÿÿ£HÿuÿŸQÿ‘^ÿ5Cÿ¡Jÿ¬Qÿ/}ÿòGÿÆÿ½fÿÕ‚ÿD¦ÿŽšÿçjÿÄjÿ½?ÿðJÿ¼\ÿ_*ÿ¡2ÿ*@ÿXdÿn©ÿæ: Ÿ¶át |tÎS$Hmý:ü¼Ìİö.Ÿý‰ÁýôˆýòërDKÿw¨þ°ˆÿ‰,Âþ Øÿ™uÿ¹8Qüþa]ÿ{[ÿ?áþ‰ïþÈÿרÿ(<ÿpÿSÿÏ ÿØ…ÿ–$ÿ#ÿ Kÿj6ÿZJÿ–;ÿ¥NÿT[ÿPxÿñ>ÿÚÿ¿^ÿÄ‚ÿ­ÿšÿÂOÿÁUÿÓOÿ<]ÿtZÿ¿ÿÉÿÿî=ÿ&vÿØy ›>ÅNÿ¢ûbÿ7c‡Âfý:îûˆo=¼ú”ýN>ý4êü€ºãQ÷Üÿuÿ¡öÿz#*»þPçÿ˜‡ÿ‰ÿuÒâþiÿˆÕÿ¬•ÿT'ÿ».ÿ¬þ3ÿRUÿÿ¬œÿ08ÿ·=ÿ\ÿÝ.ÿ¢3ÿpÿ10ÿQ-ÿÙRÿGÿ>#ÿþ[ÿzƒÿäÅÿlªÿ¡VÿOPÿ.ÿ%Lÿøaÿtÿ ÿÍÿÌ6ÿFdÿ§× ag¼ú«þÿËÆþ_)²!þZü͹úo£ÿ»~¾âÇýªiý|ý›„,OžÈÿŸ\ÿ]lÞ;(ÿÐÿˆÅÿôÿ•òÿæþÆ;ÿçÿS7ÐÞÿöªÿ]¼þåçþDÿ;øþœÿriÿ¤`ÿWbÿ7ÿ‡-ÿ¯úþÌÿ‰7ÿeiÿèJÿÿ¡#ÿQsÿ8Øÿé³ÿ%_ÿÞOÿm ÿAÿÉVÿoÿþóôþ´ÿFHÿ~~ÿ\6 üÐl©UþiØã˜þKhB&ˆaû©SùüPþ9õvãüŠý¡÷üƒæÂôÿ\ÿ'üÿ¥à²*ÿ§þÿºõþ—EPÿÞ"ÿ¼ ½*Q¨ÿ(-Éÿ iÿ:UÿǶþ¡Uÿå]ÿ’‹ÿ×­ÿSJÿ©ÿÞþxÿ<ÿÀdÿwkÿ ÿ§,ÿwÿÌÿͤÿh+ÿËÿ­ÿ¶/ÿáEÿq÷þIüþŽ+ÿ©bÿ®ÿI³ ÓÅ` GÔý¿m*£þ·Øƒ§ä’úÉøYý+«ÙÜûÞ'ü½kü±îUÿÿ2ÿw;·5ÿš‰ÿWø ÿGN,[ÿÉ=ÿ0 Iÿpž§ÿöÿ05 Oÿø¤ÿ8iÿ,6ÿ \ÿ=TÿÙ[ÿ¿ÿOÿ¬ÿmÿµ’ÿMFÿŠZÿZœÿ8ßÿ¶‰ÿIùþ€×þá·þrôþó?ÿ¹ ÿÛÿ—Kÿñ}ÿ[Òÿ  Ø‹$² þãü®T)~þŒjMù9ìöŠÓüŠ„ú‚ÌúA}ûÔìûRYÆ«Þrÿ6RÿÒçÿî-ÿ~qÿ‰Ž/ìþ3D»Lÿ*ÿ‹%ŸÔ ­”ÿ„2d±ÿñÛÿød‰ÂÿÝ_ éÿ[ÿˆPÿ{ÿ-ÿìþ#ÿŒJÿ´œÿð»ÿJÿ^ÿ†žÿãÿS§ÿIäþÑþ^{þ߸þÿ˜ÿ™2ÿ=oÿMÿÇØÿûWž‘B ¬yû,ošþÜÿ«\Qø¿ØõÆåüßùÅ îù˜ëúA’û¤Ï“!Óþ5ÿj°ÿvàÝäþ0/ÿ™©ºþeüüþ¥æþ<9díÿQRÔÿ:gÇùÿèÿkNB~ÿ(KS޼ÿÿÖÿGWÿ!ÿ4ÀþÿôQÿαÿ#æÿaFÿY@ÿ†ÿðÊÿuÄÿÀÿ™þ–€þžµþz%ÿ„ÿ<*ÿÊuÿì¢ÿnøÿæŸZ•!šj Îú_äþ×HÂå?wö¥)õ|[ý…²Ñÿ•pù³úû˜n5Iq¢þ€(ÿ-”ÿfêÑ’þÇÿØ ‰þQÝ}þ$æþPAÃëÿ\gWñÿB‰P×ÿÇe{.rÿ/XÈÿý‹ÿñðÿõ¯ÿÌ·ÿ½"ÿüüþÿÿÛÌÿh5ÿZZÿÎ’ÿºÿÏèÿ29ÿŪþŸ‹þ²þ©ÿ¿.ÿÆVÿð‚ÿ  ÿ%Ä[7$) úÞÏýÍF0À'Jõ1Ÿô™”ýÇÝS2-ú4±úGúé9Sû‡ôþJñÿé4ÿÓ3å…þêþ²ÿS6þ|²ÿ†>þýêþlG4Ýÿ«qëÌÿßX²ÁÿŸßÿ§jíhÿç”ÿxJÿXÌÿzÿ‚ˆÿ|'ÿ¡>ÿ7]ÿ}™ÿºÖÿ8ÿ‚4ÿœÈÿÈøÿB$åÿò—þDNþk£þvÿÔ6ÿ]wÿl¡ÿ½ÿÖ$gWÀ%TÆ w±ú¹À¾¥ýîÝþë¦õóbôƳýÝó­%_Lû„’û²Gú‡ÿŽ«5íþ3{ÿ†ÿÅhþûÞþôØÿllþ–mÿ,!þzÆþW2tÿ”_’þÿÙGEÆÿRÿÂ; nÿ%;²ÿ$ÿUˆÿ)ÿ²$ÿËêþÐÿÚiÿÃÿ¤7?ŠÿNhÿàÿ(îÿÒ ±ÿ}éþÔcþÉþËûþ[ÿËiÿ†»ÿÍÿ×BYá„'u— Çüña 7ýTüý{Õ”zõJþô©þn…mÿjü³ý±ûÄÿb<¨cþ? øÜÿYMÿ4Nþÿ·ÿ0½þ‚Tÿõ©ý¬þ9Äÿúÿ;'éÐ;[¯ÿs‘ÿ^ÿà¬ÿVÛþªYÿ"ßþ}Úþ,Åþ†ëþGÿ;ÿø*ªÊÿ¶¶ÿá ²öÿÛßÿœ¾ÿJ0ÿn€þö©þÞýþ!ÿ¶cÿÓ¶ÿ©ÐÿË5!N(_ ÂÖü’™hý¢#ýžl³ôÛ¦õáDí~R´üœ5ü:]þHüeoöþö%ý£B̾Ç|ÿ¾)þ‹=ÿ’™ÿŠÏþÿã(ýÏmþš‹ÿWãþËçÿ“`Jc©ÿ²€ÿáÒÿê4ÿ-ÊÿöCÿÖ£þ2ÿ£ÒþnÇþ>îþÕÿaMÿYÿ ¹ÿi²ÿfÎÿynnëÿ‰ÃÿÊmÿ’ˆþ!“þúþ¼üþˆÿøÔÿvâÿþx>‚ )úù Â_ü6> þíý«TüѽÛó/öÏ(šYXùòúìçÿ„jýÜlŒþe3û«x†¿Žÿ»ìý÷>ÿó]ÿ(¨þ`Bÿ»üìþæÿ˜}þ »ÿq 0nCÿ“KÿåÍÿÄ'ÿÐÖÿ8ÿK‹þ¯-ÿÄÝþ þ¡þ ÿ,_ÿ³`ÿÛ‚ÿwcÿè±ÿZ)¹ ó¶ÿ½«ÿ:¼þ¢WþæÞþýÓþ‹…ÿ¤íÿ`Ðÿ_)x¢ßè+ ¶ 6lú‰ †ßý5TüÊÎsò öŒÈ»ö¹?ø{ü€Žþ8”ôÿ ›ù‘·’ýï©ÿ¦µý.ÿ|ÖþWþ…˜ÿÝgüiáý÷”ÿyøý|Xÿ–$MtÿÎ×þëOÿ¥ëþID"¾ÿ&Êþ')ÿ4”þ€gþñŸþDüþëÿüþ.>ÿ¾CÿØßÿa= ¨Ÿÿ¶ÿÿGþz­þ2µþHUÿöjÑÿ‹&¬8 //– ÿÏö¢0 bþ~HüAÜA³ñÔŠõîåotôÖõV¯ÜnÿVËÙê™ø`Àÿý=4—ÿ¼Fý-ÿQýþšqý\gÿê$ü3¤ý¼ÉýGãþèÿDlð.ÿë¿þÅTÿ“¢þ®+3ïÿ;àþšSÿΟþôMþV|þ÷öþ%#ÿmàþ³æþÂþ=»ÿe…´4p#w”ÿuÿG[ÿdþxþ\­þ !ÿ: ãëÿ´*duï2Í ¿eñ o -Bÿùü•-ØñáòS„´èc,õéîñýÿ¦°ÿä çYIøÂ’þQF¹ÿ©ýöþ£bÿJ3ý+XÿœèûOáüË„°þ<”þ¶ÿXp“•ÿÆþêOÿCbþG ˆ$T¸þÍUÿE²þgRþ#|þ Ðþ{/ÿ/îþñÃþ0_þámÿE¿JdÂ4 µÿa6ÿØ[ÿ™þ‚8þ¡þX÷þ“Aðkfó6uä:;îk `šüødþ" Û¾ï“öòØa P© YõÌbïÖý•ÿ¦ob÷oöþýŒ†ÚÿúGüW'ÿ­ÄÿQý¨•ÿ5rû—üøàûýñþFâÿ,WÎ%ÿiÖþ©€ÿ€0þ'öìÿó©þ'oÿÄœþ¸rþ¤rþ­þŽÿêßþ¡¥þþ5Lÿ¢ÚA™€DêÅÿ 4ÿØIÿ‡Ìþüþã,þîÏþfÉÿú<0&Dí"9uîUØëŠn ¹ûñfÿ} ¬ïÿòY ,‚ ™#÷}îìkû±‘ýDs¢cìõ5.ýàøÂÿ‚ü†ÿ ÅÁügüÿM/û…õûèñfþ)ÿ)¥ÿc`úþiÄþ2uÿñýž8<øÿæ¦þ‘ÿA¬þ'xþÜAþ3„þ¿ÔþÁ³þ-£þÖóýÕ"ÿÃÀ¦6×ÿ]ÿº=ÿ9ÿþüÈýœþ ‘ÿ‹”AXYêí; †V@êX" ù|ù•O’Iºî!Èð.- ¥CfÐùðËë¡<ù6ÊúœëÛ¬&Ÿõ!Xü ˆÿã*ü!DÿàbYüì óûüBdö¡þðMÿUÿ«ìÿ’¼þþF`ÿšóýao£ðÿ-Äþ·°ÿ¬þvþ]+þòhþUšþ8˜þ«¦þjóý*ÿþ™ñËå;ZÉÿ¼uÿ“ ÿÝÿ¡Tþ:ý—ƒþxÿðéÿñY6' ±Š=YTüæéW”ßTøGÑÊvuï›gïyàŽÇÝöüóaëïØö„,øOò¾N6ö¯Tû{Eá¦ÿ°üRWÿ‚¥ÿ}Øû䮽dû.üÝnüØþ oÿÖÿ¸Ùÿˆ>þL­þ©‰ÿáþÛ§ÞûþIkÿÒþ¸™þ#þÊRþEvþ\ªþŒ þáÛýò÷þGqTßYA¯ÿwÿŒøþ‰&ÿ ŸþýJbþplÿñÄÿycpî!úW>ï*­ ë_êRö3Låµi—ï°·î‘éµ{ñ­þ³¨ì‡µô3ÙõkpC¦É÷ ¹ú]$ÿ«Pÿ^ûPAÿ÷ÿ üÐËûÇ«ü0Îåþ7ãÿê9ÿ,¿ÿ%îýÌÿ\¹ÿ5$þlAþÿÚ ÿ»EÿVqþxªþfýýÂWþüLþ)¦þºþ îýåÿìG3Ö5ùqÿâ~ÿøëþ°#ÿ2Äþ„ ý;xþ¿Eÿ‚–ÿ\ò‡!>§/ íÉoõDìI]GÄï¿í$O¼”ýºÑîú<ó.öò&>Ö -§ù aúëˆýÔÀ,ûÙƒþ.£ÿ¬)ü CêûXsüÚs=Òþí¾¯þqÞÿÅ7þä8ÿ“YÐHþ] †ðÿÐ-ÿ'eÿ¦þÐÇþÆþÈ"þÁ6þ šþrÿôþî9ÿ^CQ¿áÇÙñï°ÓçcôBú0y‰ïÀí¯÷ÿ Uk=jò >ó8‘ïŒ}0 ¿Sû"Øú¬ü cÆûg%þŽëÿó¶ü«7Æúü\ü¶#¥kþÎ-»{þõÝÿÝþMªÿÙ¿/þêðó*ÿ¥tÿaþåÒþ¬þéçýµNþV£þ^ÿfTþm7ÿv¹¾9Xêÿ3ÿè—þãÅþ5üþTžý"zþœFÿp}ÿo« ù=EÊ  øò$L­fô·j ÷`ðtì¨<ýG¼Ô”qö01õÛ+ì—²ËG öRü:Ùû«ëûŽFÉæüûaþ™ÿø8ý´~·‡ú$ ü•Èÿ³ñýZW_ÿLÝÿ3rÿ ýÿ½žcMþ¯­û+sÿsÿæþ#ÕþLþ ÜýUdþíÄþ?‚ÿÓtþGÿÆÙzäY]!ÿÖÛþÌSþæ°þÖ÷þJ›ýÀ‚þÑKÿ‹ÿ]ŽŒuí‘;-ƒt óǶ„÷¸FÿCò ÅŸòë×MùlX^Þ'øUCùNêv>ÿû¤¨Qý­ü ýêþuôý³,þmÿ Îüÿ:ÄþaPÿ3éè³wÄ8ÿ„™þ`CþGwþðüþ’¯ý)OþÎeÿù”ÿÿxÌÐ>w9ª¸z÷›ÓŠ1ö?\«ã æZò`Hì=÷¡á‡¿Èãùñ¨û´ÈéBKû„£ 9|ÿ·HýŸCþ®þòýnÿ½Ðý…uüÔï ûËûµ*˜úýiQÜ+Øÿvœÿªÿÿä*zŸþ§yÿÛjÿÁ²þèçÿ xþßœþBÿ^#þ–tþ¦‡þãÿã3ÿŽ´ÿÆèÛðo„ZÿŒuþmûýTwþ Õþ=ÀýŽþû{ÿįÿž~ŽRš7Ê%ó§ø4I°4ö„VÿUç zÙò­ìX±ö\Iù•ý‚;쬤öœÁ JA”ýÇÿ‰þ.Êý½´ÿ*‡ý’ûX:ÊüY:û`âÿ°þÞÓÿO€£©ÿ60ÿPDÝ$¼þŠ ÿ¤(ÿÒþJ)!®þücþêrÿQþÇIþ Šþxùþ(tÿÒBî:¶Éul‹ÿÓ?þ ¼ýhiþYÊþ^ þq¯þÆeÿ™Êÿë|¬:ý6ãx¥gø:þ {÷Òœüö˜ mõö{îÈšö‰l AäøiøÊlý˜Iñ@ósÏæ‰þU€ÿqÿŒ3ý‘oÿ&hý¾úÑåÿJ(þÉû ¸ÿ‡Xÿÿj40 »þ€:&­þÈ9ÿHxÿŒÁþaýÿs;ÿÓNþš,ÿ”þ,2þžþ5éþ“\ÿ’€G,áŠKRƒÿ„øýÒýÀ™þ•þÔ}þÐóþFÿ¸Ëÿ.xYO¹4 Æ |Møö@ |ÀøñÛü½߈õéÈó¦”÷sOëýy¿öí­üqÜö*Jò(éñ~ÿ üþ+¥Ïzý&‡ýµ|ý2ÚûMrÿ|‰ÿ¿«üLþ:âÿÔMÿ­ZëmÐþ~µÿoŸÈþŸ…ÿ¸ÿSÙþåÃÿ>¯ÿ—\þ%½þ&ÒþCþ]¤þ¸ÿ \ÿR—;i[sçÿ™zÿsÚý[îýrüþ·‡þ…þtÿ~1ÿ²Úÿ‰¹V÷1AB 9ù%… gúw–ühQß¶õ²Šöø ù SPÕD÷5ûWéúÇðóàüæ‰TR¶ýFPWþ«ý Žýá™üs“ÿQBIýûü˹$˜ÿ¾›„'ÿ&Cÿqøÿ¹ˆÿgÀÿ¥]ÿKÿðjÿk—ÿêþ¿xþÝðþ)¼þ¸þùÿ½‡ÿ‰®2B ‚¯»ÿÿ#%þ?GþmõþJ¾þû—þC·þìDÿ«ø©˜y{.8Ø`ûŸ Þù8cüЈ$Ÿ÷*Uö•ù5Ȉ(Q?ø:0úÎdü=øöFú ú¢!mžü”r†þ=ûý· þ÷ü…¬þ¿Dþ®ýADYV“Bÿy¸ ÿÓÿ†5·Åÿ¿´ÿ"?ÿ­ÿ’(ÿ ’ÿ /ÿ¢tþ6ÿN ÿïñþß$ÿð£ÿ Þ;)ñs'ÆÿÙìþåSþNªþuïþ‡Çþ°¶þ&“þ 6ÿ =©¢Ã6w¶*ŒbŽÐü,Ë ^¦úa0üËI@‚ùöçjøøØUèÎúCåùJ¨ü¨xùð'ú÷ùSTyzûbÿG‚þêTþ¨ÅÿZ¯ý €ýÔGÿW¡ÿëüý )y3ÿyiœ ÿà2ÿ¼V‰ÿ®°ÿq@ÿõGÿ«OÿÙrÿMÿ/¼þ0 ÿ5 ÿZÿXÿ„¹ÿ+ä µ´» ÿæþæêþ*Öþâ³þqÔþ²þð1ÿ:ºcÎã{º°Y¨R»üùÚþú×€*{þ£öøÇó(€ÿm ˜>þµýP2*úöÉ´öeðý¥¸Ýúîçÿñþ® ý˜Éÿ³þYšþÿ†ÿ¾ÿ!ÿwGÿ8©ÿ5âÿ&!-Ù£<Íÿ°þ` ÿižþ«~þ¨ÿ¦Øþ·:ÿýßÿëŠqÁ-¤Í ¼Äè1ä¼ùªôúÒ3…Öý¨’úžQ÷¢³ü2Àt^ÿDYrAùÎiö<]“cG©þ»Çÿ£œýº—ýþæÿÞVÿüFÿ8$`þ& þؽ•]"(ÿ:òÿA­ÿs=ÿjjÿú2ÿf'ÿ‰ÿ’ÿµgÿ&•ÿ7Õÿ1¿ÿq«ÿ—ñÿÐGÇ]¨2ûœ.Óþ-Yÿ’Wÿ­¼þZ>ÿ'9ÿn ÿmÿa€ÿQ7æ–e ­†Ý:¼¼ì<µm `ûú ŸýbÜeý7’ûø™Üü†ñ±ÿñÄÿ-3íûúƒøV[nÑóOÿŠþ÷ñýªœþYçÿô!ÿɳþ¾–ÿ©:ÿú ÿE›fÿmKÿ–PåÎÿšFÿ›xÿ”,ÿF5ÿå­ÿ©ŸÿÖkÿ¯ÿ îÿr‚aÿ¨Ý{«¾2¯ÿßþL…ÿ &ÿÿ-„ÿZ(ÿAÿ¿…ÿq‰ÿm-–TèÉÿ ü °Ýty=ÆÊ Q.ýÎ`à ‡ýð>üÈø1ÛýëÊÿ`>8Ï6 üt¬ù¢ÒWÐÿ{þ*”þK=ÿŸÔÿï³þzFþY-ÿ½lÿ‹+ÿÇZÿœXÿØÿƒsŸàÿ¸gÿÌHÿ…;ÿÃÿŠßÿ+xÿþ“ÿÐ>7»,Ç'ÓdCºŸjÑ_o§Zÿ´;ÿýiÿ ÇþHÿäRÿ§4ÿTÿ,“ÿçôÿ)qiˆÿfÿ r÷³ÝØ@`n|ÿÏØÎrÂýRPü¾ÒùW\þýèG³ÿ8`hÆ6±ü²‹úL!µvíþZþþÿÎUÿ™ÿí¨þÄ þÏãþ½Oÿ?ùþ¡‚ÿMÅÿ@Öÿ‹AkõÿäqÿÇOÿw¯ÿ‡ž¾ÿŒÿ$ÔÿV%çFmæÿñ)HoÃ_ãƒfP!ÿ[?ÿÿš®þaÿGÿÏRÿƒÇÿpîÿ÷CƒjøäÿžZÿÇN Œ¸0jÒ¶Q$Kjºˆn¸*þæ­üyKûL‹þ÷$n6èªýgæûd¶€CÁøþ+¢þc6ÿ]Äÿïƒÿ£ˆþ +þ±ÿŽÿàþ†sÿÉ| å8wÙÿ¿ÿÚÈÿ¥ÆÅÿ0äÿÇ'Ð6½‚ÿA—ÿ£-_ER?pE¡ÿÑ@ÿpHÿaÚþ:àþ+ÿ<5ÿÿåÔÿ<~ckKâÊÿLEÿúü ҆߱ó† ó6y¶QÒc—‚þM@ý.^üõxþ«~«´—E…ø‹ýòòü»v™^¡òþêüþrÿª¸ÿE\ÿÝÆþ¥þò{ÿæ—ÿU¢þsSÿÞm 1D³ÿ— ÿa‘:Õ7qúÿ¬ÑÿµÊÿ‚ÿ5ÿkÿk¬%ÐÿîÿÚjÿvOÿIÿÍõþ^ÿ»1ÿHEÿk‰ÿ‘¶ÿSH¬y¤ÿ¦0ÿkØË• á‹év~½)QÝ‘¿þ}ñýJ"ýOþ¡5@gT3þ(ƒýy“w÷Q\ÿ¥#ÿ0aÿò®ÿÿµÿŠoÿµÿoÿÊ>ÿåyþáiÿÅ#,øÿÎÿºÈÿç1dXŸ![˜ÿÛVÿðsÿÔˆÿj\ÿÃ=ÿÊ“ÿÒÿUÏÿ™öÿƒÌÿœeÿ\Yÿ¢0ÿ•ÿh-ÿQ,ÿ‹Lÿoÿ£‹ÿxâÿ…'êÿ‚ÿþ!ÿqA v|‘æB$p?…³÷ÒþMþxþý—ÿ/•1Ö·im/SÆþæCþAHñƒÿƒUÿ0§ÿl@'7‚–ÿ+ÿ;ÿ–bÿÕðþÿ3fßÿ,Þš &+éãÿ’hÿÅ%ÿ>ÿ7|ÿ½ˆÿxÿZÿÛlÿ‡­ÿ²ÄÿÓÿ‹°ÿ–{ÿsiÿþÿvÿþ5ÿ{?ÿ&Fÿù^ÿ¾ŒÿÖÐÿ¶ÞÿN¯ÿñfÿZ7ÿ¿ø-‘¨›ð@æ÷›H("ƒxÿEÿ#üþNáÿþ¿Ô?»«D+(ÿdàþþ ó©žÿ·ÿ™)ZPáÿ§Ÿÿƒ{ÿ\„ÿûlÿáÿëÿ'îÿ[ùÿ®Viûÿ«Èÿºqÿî%ÿõÿqÿ‰—ÿÈŠÿksÿW[ÿ>zÿ5”ÿ»®ÿ‰Óÿþ³ÿ¡ŠÿwHÿ½òþÍþþõ*ÿqCÿTXÿ‡mÿh“ÿQ°ÿL²ÿ‰Žÿ(vÿÔnÿ•ïkÃÖÄž·íTF5éò¨úÿ$Ft&Ý }N3…¯tèJÿÿá©ÿßõÿX;Û3æÿ4”4¬õÿÉÿ]‡ÿ,„ÿ}[ÿM¥ÿ/ ^±ûÿCÊÿ¢‚ÿocÿöEÿ5ÿã_ÿ[ˆÿ”‘ÿ‹ÿvuÿƒYÿµuÿ˜¨ÿ­®ÿSÅÿJ°ÿÕcÿ=ÿeàþVÿÝJÿ/Zÿ(fÿ>„ÿf™ÿ´Ÿÿ$­ÿÝ­ÿ߀ÿá`ÿ-‹ÈZOµâiíK—¤`ÆÅABØÇïìè@.#lП4ÿVÿÊé8+@ óÿNêÿL<;J”4­ÿÊ¢ÿdÃÿÁ¿ÿòÿŽ‚óÿƒŸÿµPÿXLÿCuÿÿNuÿ*jÿ~ÿ„ÿ܉ÿ|ÿCrÿhÿ(œÿ¾ÿ½Àÿ‘ÿ“JÿHÿ` ÿšÿbLÿ‘|ÿŽÿœÿ ¨ÿгÿ±ÿ"˜ÿmÿzSÿèËÆ7VgXÙŽ'îÚ¾ÝûµÚ(“§ânÅt…•3n¾¬ÞÍÿŠäÿ6õÿÅåÿòÿñÿa*&f-`½”Øÿ_Üÿ(Ëÿ_ôÿêäÿª†ÿ'Yÿ]CÿH[ÿžÿgªÿ……ÿZÿ`Qÿspÿ}ÿ$”ÿ—£ÿæ€ÿÏkÿ“ÿưÿRqÿYKÿJFÿ•ÿÑ>ÿýxÿ<‹ÿ»ÿiÇÿc}ÿŠfÿgÿÿsÿbzÿS}ÿ{YÁaïîg·é¿î^‡…0ÓðÇÐnüO©¼¶îê†}cõÿ‰¤ÿ‘ÿ*äÿ¤C}yï|°ŠÞz¢ýÿJ£ÿ^”ÿ¤ÿP|ÿocÿ[[ÿ…ÿ¸¨ÿºÁÿ²ÿBÿºKÿà€ÿyQÿÎGÿÇÿ¹—ÿ¶”ÿº˜ÿ”ÿx•ÿoÿfDÿ&@ÿçYÿx_ÿ{¢ÿ–Àÿƒ¤ÿ}°ÿ&zÿ!:ÿSNÿHCÿcÿ¶Éÿ"̓ý`ŽD´K=ŠÃ9áXg:w¿ˆÀ'Û‹ ;– êË’Câ¥ÿN•ÿ%Éÿ H“'Д\ˆerâ| •ÿ^Cÿøÿ)ÿJ{ÿ ™ÿãžÿÀÁÿ›™ÿºuÿÖWÿ 0ÿ?ÿõHÿC_ÿÿŸ…ÿÓuÿsÿ¦Éÿ,Æÿ{xÿ×Zÿ:Uÿ·_ÿ°tÿ‰ÿâ²ÿe¥ÿ9XÿÒJÿˆVÿWÿÊ}ÿóŽÿç“ÿ´'dkDŒÓ|Âé‹yjVÕ&m%ÀËÿ£aÃOJbášÍm{%ñ—Ýÿá¿ÿ¶áÿN4R¢SgÒ¢² âÿ÷ÿéþ8ÿvQÿü…ÿlµÿß°ÿ|ÿ}ÿÙ`ÿòÿ/-ÿîAÿM5ÿITÿ^ÿf|ÿ¡ÿÖÿe¹ÿZÿÔxÿs\ÿzÿÿ›ÿ–jÿ1\ÿ…Zÿ¯Pÿ2xÿ4yÿÏ{ÿGsÿaÿþ~ÿÚ WT|÷ңᎃv`ªA ël•/(Ôµ^µu¶‘á¢")ì=3ÐÿâÜÿ1âÿÇúÿ½òÿo$êEŠ,Ãùeÿiàþà ÿ£\ÿ›ÿ´¡ÿÙpÿæUÿ hÿõ8ÿSÿšEÿÀFÿº=ÿFQÿ>[ÿs¢ÿÓÿ¬ÿ”vÿ©¦ÿhÿ$…ÿ²‘ÿ-Hÿ¼8ÿ‹Uÿ£Sÿ×mÿ—‡ÿ${ÿdÿ’ZÿcCÿf¦o0¦ÕÄäeÑh^*`Ëÿ¿QÿÌÿã ÿ΂ÿØyÿ’Fÿ’Eÿ.ÿµGÿ]ÿÚQÿ‚UÿõKÿ5[ÿCƒÿq}ÿ8yÿ¨¦ÿçžÿ'qÿŽÿ§œÿ®jÿsEÿ7ÿ§Lÿ(„ÿ®ˆÿßWÿ KÿûRÿ1CÿPh~›}œˆ ˆúÝoˆÂåGUÍþ®=ÿÓ(ÚÑî[%EÍÿôÿgFü×§iÁr¡xôIÍìÿßÿÂÞÿº‰ÿI¾ÿr7¶v™ ÑHÚÿ¥öþÜþåÿÇXÿèiÿSÿˆ?ÿ¸aÿ¡bÿ]ÿ‡ÿ–]ÿ¥QÿÕ…ÿ ÿ vÿHXÿMiÿoŸÿD¢ÿOnÿrBÿCEÿ4hÿG~ÿÁ_ÿ«6ÿ4;ÿ¡Aÿ£Hÿud B6ƒ3õÌ¥´Xr£þâ ýr×ýÑ7g!{®Ì/Œ ë<)ìE …FÍBøŠÆn¿„þïÿ¦àÿÂc¤ÿ§­ÿ…6ꞘãðEXhÿ7)ÿPðþuôþÇCÿñÿxŠÿÂuÿç‘ÿwÿƒMÿµQÿÈ•ÿ ¹ÿÌ‹ÿŒbÿå,ÿ—Qÿ¶ÿs©ÿŸfÿ‚<ÿÛ'ÿÿJÿˆÿ_dÿ„ÿ°%ÿ"<ÿ`ÿx§®\ · /ž†šašÓ‰wvŸý×øû¬ªüÕ FŠ$ºÿŸVÿ£¦ÿý.Y`e\|¡›˜É½Hˆ|snÏÿæP#½ÿ©sGHÅ_j—E8æÿrÿnùþ| ÿ Aÿµ¥ÿåÿ˜§ÿ¶Hÿ#?ÿu{ÿd™ÿµÿq©ÿËpÿ±Pÿhÿ•¿ÿq¹ÿV4ÿ÷þˆ ÿ£+ÿïSÿÜPÿÇ4ÿÆ6ÿ£YÿêÿÏÞQ:I ¤tM’•šl,3¿ü©ú¶±û·ê©k2Ñþ°Aþ6.ÿH‡ÿO0UZ÷›mÌR©õÿ“ˆ»Îÿ”4ƒ@§êÿUµuNQùÿv"ióÿciŒm“®ÿ*ÿÅgÿâXÿcÿÿ·™ÿ¶sÿ2iÿ8lÿ´Çÿ`ÉÿÓ¤ÿmŽÿ”ÿÐàÿø ÿ› ÿ‡ÚþÆ®þ»Îþu/ÿ Rÿv<ÿÀXÿ]ÿJÒÿ ¶ÕzºpÕ…ÈhsX_%¾žj9ûvZù¬#û¨†å‘výâzý³þcïþ¢ïéÀpu±‘ÎCI3~å›ÿ! )7JëÿŽ#}v×PCÄÿð?Eßÿãìÿêsp·¼jïÿ¶'ÿÔ.ÿÚRÿèRÿEEÿ”aÿ}ÿ$ãÿ äÿ=¹ÿ ±ÿÍ ÿˆñÿ:Ãÿrèþ dþŠgþ³—þÿMLÿPÿòŠÿ‡²ÿìÑÿQ ì”^ÍŠìþ[‚ùa½Hç)|ÒùëCøT€û”üÁZÒRü^ÔüÉþË’þíÙ+Yg<' Íÿ–Žÿê; ¾ÿÍAÕÿM\ÿ`[²k(Ì\•õÿ.È€FÏÿvÃT€¦ÿ­ÿ©ôþ"ïþô[ÿçÿ¹¬Þ’ÿî‡ÿ(°ÿ åÿ~ÄÿðóþþVþ’CþLtþÍÿ|lÿ”bÿÅ}ÿ¥ÿûðÿµ& ` ÀÅü2@çLµ¶þþÃÞÄøÉöaüµìjõû¦ÆûÍ?þÜYþ–?§ÿI/?3WÄêÿm^ÿ Ä{ÿã™ÿŸÔÿ÷3ÿß*ëŒáNͰ<¨kZíÅÿ„9²CÀÎÿ{2ÚðÿegÿHçþV ÿ|“ÿ¯÷ÿË4ížÿãUÿØtÿ’´ÿ éÿ@'ÿkRþ˜aþ\¢þE"ÿâpÿ‡LÿæÿäÏÿÃúÿ)²žkäËìü”¬Rúÿíg«œÝ:öȰöô„üàÛ1<þúf‡ü~AýÿxœŽžÿ²:FÝÿÕþ}?ÿ]HÿÍ``ÿ™õÿv ÿ2[ÿ>CÀ&•{HKËÜ ±Yî¹w®ÿ’(ÌÎÿ“ÿM ·qÕ{ÿG5ÿ`ÿíáÿ Àlÿ‹rÿ݃ÿÿ—ÿ- Pÿ¸oþÐtþe‹þ%ÿù‡ÿb“ÿä©ÿq¿ÿ ŸÏ ŽP¼‘üÙŠÿÿ5pךƒãô@3öIÐü®|êþûi]ü^/üFúþÅNi;58ÿ¬<Æfÿ^#ÿèÿ9œþ0’ÿ>ÙþTÿC=8޹ŽýÿX± "½¦ÔœÿÓä¤ÿJÿ¥Þÿ[¿ÿ½ÿd}ÿ•ÿ_¥ÿÑäÿ¯°2ÿ+)ÿFÀÿ=ßÿ»KˆŸÿJ=þ™%þÑþR ÿõŒÿN²ÿ̽ÿ[áÿ¬-'ŸŸ" ü ‹ÚûS=Õùý1öpßõùõÓ$ý´‚Å8ýgý ü.½ýŽ‘Nûÿ{¦˜Äÿ€Yÿƒÿ¼þùÙÿõþÕ7ÿ|ÿjÿ÷ÿ šÿ*Ãv*T)é ÿ1wÂÿ!êÿHîÿ£Cÿ›ÿ0ÿÖOÿ4Fÿh8ÿÍÿã!Vˆºÿ"=ÿ>æÿ¨ìÿ"JÛÿÁ¡þ1þxlþÁÓþyTÿöµÿNøÿuíÿË8û]1‰!ð§ µÀý¦±÷‹þˆ ýÍ»~éõVUö'ýŽÂÿcKÞÜþ³þù~üQþ*Ûþú ¦žì¤ÿ(Êþ]3ÿù Ü—é#îü‚þÂ!ùЧ$ØòýœtþÙ—ßßþa“ý5šþäßûLÃþÖ(.$ýÿïþçI 1ýþFôþ¿ ÿ'ÿÔÚÿ¼mÿ‰ªþÝ‹ÿ¹ùþ Nþ‰‡þ„éþÿ/çþN\ÿßtÿýìÿH;¡Úÿ™=Xíÿ·ÿ™Aÿiáýóeþ„ôþØlÿF4`Ðÿ 5˜üËÉ-E?°ö‚]GÿýJø áÌ„ðåøìê ª"ÿícõ^/*÷­7†ÿ|Î÷Ýç`ý+òý©Ú„Cÿ\Ñü‰þu×û/þùŽÎ7ýÐÄþ{Úx[ÿ¾ ÿ$¶ÿŸ þè­ÿ­Íÿ²þ1„ÿeÔþ€:þXþ²ÿ;8ÿtÙþzÿTÿþÅÿ/”‰íÿÀ;öøÿ\lÿÙ„ÿðþY þ ùþÄ?ÿL^ú2 › ؾî¢;hÎÜ÷4 ¬Îð4ûòéw hMÃHïÓÙñ÷kŘ¿pY%:Íöw}¼Zgþ+{ý˜üÿ °ÿUÞü¦°þܯû ý CÇ{ýnþÂ']Z0ÖÿÒÍþHeÿuzþ‘¬ÿ¤+egþÍUÿöþl6þñ¥þ/ÛþÈGÿmøþìÈþË’þšyÿóCiå ö%ÿvÿ „þò°ýðÀþ0ÿv5¥d‰ÖÿÜ¥7[_œ·ì#G)Òû`Šü¤› ljìð+õVºcYëî´÷ï…Òßû׫äÿÀ¥ô™k» Dþ'Sü‰AÆ2¹ü'"ÿ¼ïúµý±æ9ý³Íþå_„W+ÿ]Úþ%¤ÿ2 þ÷ÿœÊÿ`þo•ÿ>ªþÑYþ:œþQÓþb,ÿ_Üþ£–þnþ‚„ÿ‘!Æp›4Oùÿnÿ2lÿ«þ$ˆý[þ. ÿà Úo à,“ž:Ø8¼èq§~ûÑdü…ÌíëüTóNý²} ¾ñSìoLK:f‡k¿óê>ÿ1Çú°þ` ü4Îÿ¸¬ã\üí´ÿÿÞú<Êû¬­bÅýcèþÅ)ÿ±þ›ÿÈý¢ö RHþn¢ÿõ³þìWþsqþfªþ… ÿy°þ*þÅðýùNÿFAÍŒšIÿ_Yÿùÿn•ý#Òý"ÔþÊÉÿ)r-Sà"ÞŸ=¤’ü‹ÎçáüÏ÷»ÝBÄ7éÓ=ó?ˆâ» #“òÐêÀmþ îýÈ¥ûïvØòúÔþßÄ—þ]ùû)h…¨0üNSúoFü®W¡ïý ÿuÙÿÂþÿ ÐþÜŸþ‚Sÿñ§ýŸ‚íÉÿOfþéôÿÃÀþQUþÛ,þyþÄÍþ ‰þªœþgøýG+ÿ ô.²i#ùÿ©Xÿv@ÿà(ÿmÐýåŽýXÃþ‡¯ÿ+1ÓT56"×ÿ?nÏÿÖzäpävøƒOQieêøJðq xâ$÷y踔û%ØûV†PI(NòµAý_׉þMüìÁÿ ŽÎvû1E ÛúéÕûÓ£º.þo1ÿðÖÿZÑÿq©þSCþgfÿÅýƒŽTþÿÒþNëÿí¡þSþc:þÉxþlŸþð†þ6”þ­Õý=ÿdÕÃäR%%Úÿ|ÿ ÿË#ÿÎ2þ‡lý/–þù§ÿÙwrg‡$'„A:>þ"Ÿå¬l !öýz Á¦êD¦ï`ã ;¶6ùSèB'ùGçø:2 â¶À óýNüo{ÿk¿û—S‹ÿw&ûÔbB±ú¬ü|€?nþÔ^ÿîÜÿ­×ÿ+ãý)±þBÿ¼ý­ú¿ LÍþ8jÿŒwþ¾›þîþ÷bþƒþͶþ†þuµý©ÿo©õ>)`¹ÿ‹iÿÏâþICÿk‚þ‚YýÍwþL¡ÿƒÞÿêzÓì&ÙB@-üþèoU œÁóÞT|”è·ê?ïõÛûY„ú:Ìé ˜öûŸö ×¹Ôåô„û2-ú&Îús¨ßþh—ûoÍÓ,úLý‚‹«‡þg7Pÿ€Àÿ;œý¡Dÿ—©ÿœÚý…iàÿLÚþnGÿ™sþç§þVÞý(vþ¶Sþ ³þܨþÊý¥@ÿwdí: mÿ’}ÿMÝþN<ÿ ¥þhpý›þPjÿ©ÿw*‡& ”Bd<þéÞM 3ñòf `Ië áíjüA2þËGë±Tô\æó ? ˜a [Æö —ú̃þRx/úª:ÿÚ“ÿƃûe*LúÁ¹üc±³þ]'óþ¿gËý"GÿúIþN„T˜ÿíÿjuÿäìýQÔþçóýs<þ/þs©þpöþlæýògÿ\´Õ–'~ÿÿ±¿þsþþ|êþy†ý(”þ@KÿPŠÿ\~”r%ñ˜BX·ù6ëî˜ Ññ¿Bóîë(Æì^¬1€ø©îý&ó¨ï3Õ Àù W¬øò±ú"Pü 삼ú=þ+;ëûÅ Ðù•iü-ìþwl>4þ+ºˆþ‡‘ÿDo³ý <ŒA2íþÓkÿDÚýq ÿ»Ôý¤åýy^þR’þsÿœ)þ2_ÿÓ¶¯ÝQwüþ@Kÿ„þlÎþI ÿJnýâ˜þ@]ÿ‡ÿ!™MÕ%S4A¹+ë³ïdÎ ‡ðÿeú´=3ëû%쥛÷”uóWMócì–Æ ž– U#úo5ûp ûpÌŒû23þ³Wö·üO±Yù2™ü:Nìîý—¹þËÞRÿÕö­ÐýphL ³Ìþ­ÿ²Õýüíþ¾ßýºýë}þ¶þÞ®ÿúMþXÿƒÛ4Ä•PæþÞÿ&Uþ¢¦þ›ÿƒgý¿›þÖdÿxÿû¤oX$&®?+¾^)óR±ÇÈðB¾8 Èví`"ê—Éü$’Ø«ø¹ŸõlFçk’Ô’ßúHlü0Iûc—!ý¶nþqøþ¶uýÂY‚ùæ÷û¶¡©xñäÁ÷õ¼uþÍz FÑðºíèÚÀ÷\® ¼Œø/^ûÊeåÿš³¹û‘ýü  ý—kþ¢’þ˜ÕýŒjÿ™¿ü€ê“Gúuú!0 Æür” Êk ÿ¥±Jâ2¦{þbp¹LÑòýˆ¾ÿ¥Bþšþ9<ÿ”ýìÂþ£Øþ£"ÿDËþpÿO¡îcyÿñYþd#þÛ1þÿ¡¦ý#Mþ1ÿRÿ³6p ;nÐ Ç•÷è·¶5ôHWþ §Ûñé2ê8ÏóÎì#°­\û¨ þT>ã$û¹p"æýF¡ýÒþÂþÌPþ0ÿéý¶òü¦´‘ù .ú:Cωý»Qªèžÿ;§Ç6m£þÏÿP•ÿ~¸ýbþÿ­þ˜—þçÿ†éý=³þ*·þYúþŸ ÿOçÿ¶ÝŒiž8ÿž.þdóý€?þ­þׯý›­þ'ÿ¥ÿežø‡83 ¼úûOa sòi‰r ¹—ñÞêpˆò´¾× Û©üÖ:û³ãEGö‹{ Wýrÿ'0þöˆþ2”ÿ#Eý ÓûbÀ¢ ûmpúúc"!þþ†5!,ÿu¥ÿÛ›0niþW1ÿw'ÿ`þ %¨þÐŽþ­Ýÿ *þjpþßšþ]ÿèEÿõ J’Ýùz:vÿvúýòý°LþAyþÞý]þþ>yÿ%»ÿ·©øx6‡Ú rû›I Ôô”ÞýØè ÷òBëòµ¹ü ÎAúké›>ï$ÕÊÔ°Hý¶‹¥”ý3þu§ ýTú#LôCýnåùÏb‘®ÿL;ÿ¥ ¥ÿVqÿ\–.þÿ tþ0Ëþÿ”ÿ}_þË¿ÿüþf|þ«öÿ"Zþ8þ¼þ+ ÿvlÿû¬’¦ðq®ÿK©ýÿ]ý~þòPþRþ@%ÿ=•ÿfÙÿ_{ÏÍâÔ4µ~ :ûÙ|`áõ~súÐD XöæîÒ7ò8 >% =ùD¿s6ð ¢ëëj=*JíýµÓŸxÿ-ý³`ÿ#Šü•ûÜ!þ°ú>3B£þø¿BËÏþý¤qÕÿi,þiNÿð ×€þ€‚ÿáWÿ8vþP’ÿZ¥þó#þÏþ®ÿ°^ÿ,˜ê•Jè?g{ÿyuý`€ý¡Ÿþ?1þ¤qþÿMÿÛŠÿÇåÿ/y©A‘83W˜%fû8: Ä÷Èû·Ês/öp®ô?ò¸%¼“ ®+ö~ˆ½Ëög•ê“x ùkþÉ~‡7ÕýIÿü?üXüýf¶ÉÿüVû¢@þÌ Zÿûý`2Ôîþ-{qÿ’QþŒÕÿ®þ §ÿ2ƒÿ×RþeÿäËþÔþCÿôÿBlÿ«ÊZ“Œ¾^4­fÿÕXý;»ýBÙþ*/þËþ‡ÿÜoÿ“) ¬PøÓ0ßE|ü»–mùaªûe¸xõ–zøõ õ$rÇ/ ¬÷ëýýá€ü«ë1’ü[ <ÿþojÿ)ür°ûýÛ/‡ÕŸ±ûB“üÛø_ðÿ‘(–•ó.ÿ ×eÿ¾àþ©7Sºÿùóþq‹ÿcgÿƒ£þ„3ÿ¹ÿŽVþÎÿÊ[ÿuÿë|ÁÝ-ÿUý .þƒåþ <þø»þ;áþoaÿ¡icÔÐy¤,œÕŽÓþA¦*7ùL=û`>ðµ÷ûø»Žôc’‚µ h÷‡~ýá_þîî ù¦W€§²Ëü_3rÿ8=ýsºüX’üêö:~ãßûÔü6`T0þ¯ÿÀòÿ{ÁÿËäÿÅ0ÿc*вÿeºþƒDÿG”ÿ޶þÿÿ™eÿUµþ-0ÿÙhÿÿ¶&&qøWìºÿP£ýú}þçõþ\gþ|Åþ®¬þ\Mÿ-‘üê‘p´(ªj%˜šøÅ®ûÐïäkú0ø-´òXÂ9- •øzÜûq+ï¶ñªÅöÈþ0çú0jÛSÿ¾ìýÞ±þe]ýcßþøéx‚ý~Ïü[šÎÿb|ÿ?R$³þž£ÿ sJ"ÿ¥ÿÄ©ÿÜëþR4ÿ©°ÿGãþ³öþ»kÿeïþTÿ*•ÿªÞÿ¾?ÚH]K4ëÿWåý~âþößþEbþòãþaþ&Rÿ³ž'¨C²*Ç#nC'Ð#Ó(ù™¤úy²åÖü©øÅ’ò”-æû ØüäJü‰ÁfÌôåAöñPSŠÌ<ú_Õþç‹ýºéÿYþlüý`!ߘþã£ý»–Eƒÿ¶·¿ ÿ%Öÿ"¢þVœÿ[Òÿà ÿ gÿ®µÿ(ÿƒ/ÿúŽÿ·èþQ>ÿ=Åÿ1õÿN'14øpu”åXÿp>þ"ìþÒªþêrþÿv½þFÿýC·e"¡’–!¼8à BÍÞð–ÊûV™»øƒÅóÒkô÷ Ž… \øãYD7ûpó«úù^½ŠÔýâùÕÍýŸZûȺýë-ÿz¸û|Qþ—«ÿÔýHÿ`ž¾ÝþÓþþMóêÿ~nÿÖ’ÿfáþûGÿj`†ÿº•ÿ’ûÿÝNÿÍÁþ€)ÿù’ÿ·Mÿ.ÿ[vÿÝÒÿˆiÿÝ›ÿB¨ÿ¥¦ÿnÿ©¡ÿ†ÒÿçŒÿ™ÿÝbÿ1Uÿt°ÿ+á žƒÊpÏ¥qZöç©ö8¶lú› ÷^ ø"”=Ö ßýˆq*ê ÷íŠ÷—H’ÿBPûLÿ8µü÷ ýOü$ üõãüœ9ý·Èü¸æ“ÿ›BÿW½ÿh$¥ÿåXÿÿ mÿIÿìöÿ«ÿ…Âþ*#ŒÿÿÿÆèÿ›ÿçöþøµÿ£ÿÛ…ÿ(×ÿ”hÿãrÿÒ›ÿæÀÿÚÿ“™ÿí¡ÿÑ©ÿc³ÿè­ÿÄ2ócóM(@ ->!Õ÷îù”Ú¿ú™6ùXíù­¢M23ìýq»k)²…øýÂø’Yóþµ®üÙÿÿwKý%VþƒÙãÀüÁ¿ýÂÆ"ýv ý9¼ ÿgÿÀ$Õ†ÿ­ÿNÿ.Éÿ_6×…ÿù<ÿRÅÿÉ>ÿ}Bÿü:–«ÿ5ÿøÑÿìMÿ—Ëþ…˜ÿÞ²ÿ—ÿQÑÿë‡ÿExÿSÿ&®ÿ·ÅÿûÀÿ„ËÿRÑÿëºÿ'œÿÖ0 ¯ª ™PB W ƒú©û|r<ÙûKûÍÚúšsr>þ€  :ú³­ùÅCQMÿádýBÉÿ;ÿlÖÿEà PýØçýÓkDýk8ý6ÿÐuþÿA#òÿY“ÿ§fÿöÿýFæ¾ÿ¡Zÿý¥ÿ£ÿðÊÿ\2ö—ÿþ.ÿj‹ÿºKÿ4 ÿÿ™–ÿî“ÿÙÁÿXfÿ+kÿ{žÿó²ÿ?Ûÿ;ÿÿ(k¸ÿ}ŠÿÄÿó £šM À „Í [Ñü¿ÒüßÑÙý¤’ü>@û«b ®ybþ韤l´û1ú›Ÿÿâ¿ÿéðý‚,°'˜„¥ýyþ_`ÿ[qýçfý(‰þ†mþÿ´óÿ×!ËÿÙ±ÿéÜÿ(._èÿ•’ÿ \úÿ¥ÿççÿˆŠÿKVÿˆ›ÿ°Gÿ·ÿ“ÿãŽÿ¦zÿùŒÿË\ÿßÿ7±ÿÌÔÿÓùÿçÒÿÞµÿ¦–ÿo†ÿî®xCHÞ¨rÇõ $=ÿwóüTÅ”jÿº?ýþû`ÞW<ÿ¿þ·%'Ó‘âü±Åúa7ÿudßNÿGYô§/U‚;þ:çýŽëþW¨ýl™ý Îþ›†þhìþõÄÿ†ÃÿféÿmèË2ÃûÿîÙÿéØÿÿõ¬ÿMžÿ¡}ÿ´¬ÿüZÿÚ ÿ oÿ,Šÿüpÿtÿ[nÿÌ™ÿÙ¥ÿ‘¾ÿ²ÜÿÝáÿ³Üÿ,®ÿr”ÿ”€ÿsCB†x¿¶"o /uÿ^þ0Éÿœ¸ýX«ü~øÿ™¬1ÿŽgeqôýÎûÛþÿoÔ¾V¸r…:VÇÿ[ûÿ‰þðý† ÿÍeþúþÙrþVxþÉÿÂÿÓÿ%þÿ–>štõBüžÿ¤ƒÿÅÄÿ’ÿ¡—ÿpËÿ€“ÿä„ÿ' ÿ>ÿÐýþÔpÿs•ÿƒhÿamÿµOÿ‘aÿÐŽÿ¥œÿJÑÿÕÕÿ$ÅÿjÃÿå‚ÿ)ÿÔ‰8µ Á@ÈB¹8¦ÿZ[ê1¿ÐTþü ýèrÿQå$œÿ„Þ¢p¹ýôìü|”TW“êÛÞ6ŸÿrÌÿx™ÿ)]þÿ¶¤ÿ’pþè þƒ–þüëþñvÿ¿üÿ&<§]SA³ÀÿlXÿñ‡ÿRÎÿ-±ÿÝ®ÿ¾×ÿn£ÿÅwÿS…ÿONÿ IÿvŒÿÉsÿêMÿÖMÿ0SÿÇlÿ8‡ÿ™´ÿ¿Öÿ¢Ýÿ×ÿ²©ÿŠŽÿa®ÿþìH- ¥@‚À•=©&QêûßPîÍþs0ý·ÿÚ““€§õþ‚~éý¶|ýÙYÕöÖ"‘Eÿ+¶8LÿîLÿ‰—ÿX¥þ{=þ$Þþ†ÿôÿòU·*´ÿôÔÿýðÿƒ–ÿ~†ÿm¼ÿؽÿú–ÿ*­ÿ¾ÿGÿeÿõžÿ¸Rÿ¼[ÿøtÿ‘EÿÁJÿ\cÿ\ÿÅ|ÿ–šÿË«ÿ»Ôÿ«ßÿÀÿ;™ÿ’œÿÃÿ/›² Ö£úø’Ê•RØ1¼¯Wÿ«jýüNÿZÏå\övç1rþÿôýýåÕØ|_P«ÿïÿŽa­O€|ÿÿ:_ÿžÿ»ïþ}xÿé¸;!»ÿ3ÿ•hÿ£èÿsüÿ—§ÿÜŽÿ[¡ÿþŒÿ6ÿï…ÿ´›ÿi£ÿÜÿµÿÖ_ÿÅVÿ€`ÿ_Gÿ³]ÿtÿÖiÿÁvÿYŽÿÔ±ÿŒÇÿBÂÿ½³ÿÞ¡ÿŒ´ÿÛÎÿLÚÄásÃAÖæˆÆÕ¸™³‰íÇ-=šNÿò‹ÿ%r]íZ'‰¬½Kÿ¢~þ^½Ä~·4Ùÿn#r7x‡ÿõ~ÿHïÿv¤ÿ—ÿþÿwÍÿ[ÿY-ÿT5ÿöœÿÔÆðÿŽÿ²oÿYwÿbÿ˜‚ÿW…ÿ7ÿ¾Äÿh¤ÿcŠÿ±mÿÚRÿÚhÿYVÿYhÿ;uÿëaÿÇŒÿHÿxšÿ@¶ÿI£ÿ ÅÿFÊÿ˲ÿÎÈÿÅNÔöüE¦Ÿ«éwJø!è ‰z¼b˜ª€V‹8’ìÿªBÿˆgè]ÌucøÿJ@òfi<ñ‹M1ô¹ÿ§ÿ!ÿFÿÒgÿ܆ÿV¤ÿÐÿh²ÿKiÿÿDÿJÿ¢ƒÿùŸÿ5ˆÿݧÿæ«ÿEÿ•ÿbÿ¬XÿÔlÿ¾]ÿ–€ÿˆuÿŽiÿÉ|ÿDwÿ¾§ÿx»ÿz¯ÿaËÿ¹Ëÿæ¹ÿ·¿ÿg&*ä«qn5Éñêbýü[áTïr͊Ѽ^UÚÿæ½ÿ$|5ìÕÏ|n2þÿc«»eþ¯ XM*÷ÿª[ÿïÿO9ÿ9@ÿ]ÿ¬“ÿí€ÿŒ`ÿ1‡ÿ9rÿú;ÿ@MÿH~ÿÕ•ÿSwÿƒÿÉ¡ÿ‹ƒÿµjÿr|ÿ‡ÿûPÿYÿm…ÿdbÿø]ÿthÿÕÿ]‘ÿ·ˆÿ·Áÿ9Óÿ̶ÿ›°ÿD¿ÿ¬Òÿ~B ß.®m£t+àZ8oµV¸GùAM{VJ H4ÚLA4\|S¢—f‡ ì¿§÷¤!háÿfÿÒÿtÿóUÿnÿRZÿÉhÿxYÿÒPÿˆRÿlNÿoIÿÊNÿAkÿŠÿ0zÿ·Vÿ_‚ÿP’ÿVNÿs^ÿŠxÿ&:ÿ0XÿÓŽÿŸWÿNZÿŒ†ÿ$‡ÿæ–ÿIÿ‡ÿ»Êÿ¥Ûÿ‘¨ÿnÅÿôÍÿû <®r}­‚va5‰q×ÖÌo¨C«€ÕÖr£:)ŽLTÙÚÿºIc7~ƒû@2D¡ÿè`ÿ$Pÿ9ÿKÿ'dÿaRÿèNÿLÿà0ÿE/ÿYTÿýSÿ50ÿúYÿÈnÿ•:ÿUÿ(jÿnKÿ¤aÿ`ƒÿ xÿVTÿ=ÿ¶2ÿ2Hÿyÿ†ÿr…ÿû˜ÿÞŒÿ gÿˆsÿ¾¬ÿD×ÿœÆÿجÿûÒÿÞÿSNÅcyO3 Á+˜+î!·X<šû¥…ûÝRý¶½°Xqñÿ Áÿ¨#H¾Røüp³3!Ñ`ãˆÿŸæþ‡ýþÜ7ÿ{NÿŽ}ÿ¾fÿ Pÿ‹mÿQ0ÿvÿ+aÿ×fÿ)ÿ5ÿ´Lÿ%?ÿ”Xÿ÷Oÿ„HÿïtÿjÿOOÿCRÿêkÿkRÿš"ÿ:kÿÈ‹ÿ-tÿ= ÿb£ÿ^yÿ_ÿÿ–ÿíåÿËÿè¹ÿçãÿ;èÿåÓ MÃQ°fßmqj^U‰¬:7uˆÉ€…>SÿæÂÿ™‘a~”ÕtOºÀµAÔx¥Ðÿžâþñàþ <ÿºÿ!ÿ=”ÿ†Šÿ>@ÿˆfÿI5ÿîÿUÿÍMÿ ÿ ,ÿEÿÏÿ©Uÿaiÿk+ÿFEÿñPÿDKÿhÿ/Dÿµ/ÿÈTÿÞPÿdÿ¥ÿ›ÿ)qÿi‹ÿˆ´ÿE´ÿ´­ÿ±ÿ‚çÿÜ =1ÏsÐ&4Ê4þ÷í¿Í!g¬iüFfÙ,3y F¼ÿvjx4CüŠÈ¸­+€bΤª¹2ÿ!ÿÑþ7ßþvzÿÎoÿ£bÿ‰€ÿ»€ÿKVÿRÿ=ÿ_kÿ­Iÿ (ÿ ÿóHÿÕMÿ¦"ÿ :ÿ7Sÿ¬nÿÏ_ÿâÿÕ'ÿj9ÿqDÿScÿ·]ÿòƒÿy«ÿÏtÿó‘ÿêÚÿþ®ÿ>ÂÿÝäÿÀÿæßÿðªáoèlìR^Lmå‰`Yçè!ƒ–©ªwš²\˜n­3œqË/ÌÖþ)ž{=öÿò’þ•´þ© ÿêGÿÞšÿá”ÿë—ÿAfÿòPÿz3ÿ¥ÿ5eÿvÿüpÿF[ÿÇÿÔÿTEÿ˜Lÿ‘Iÿ*3ÿƒ3ÿò0ÿ±7ÿ±2ÿ!ÿu]ÿ&‡ÿ¬ƒÿ‘ÿp¡ÿ§Üÿåòÿ Íÿ·ÇÿÕ¸ÿnªÿO÷“ e·•k j9a5Jäæÿ@&ÿ˜@P,ÚÑ%Á'q"€(4 ˜«½á”ãÿ:®‚>áÌ+o˜&ÿëÃþ³5ÿ~ÿ^~ÿ¢ÿÀ–ÿYÿL\ÿÒxÿŠbÿbpÿltÿ5Pÿiÿ˜bÿh-ÿ?ÿéLÿÇÿ@ÿ¼XÿKBÿÇ'ÿ!BÿèGÿùnÿÿvrÿfÏÿñ%ëÿìÊÿÐÿï¦ÿBµÿù¹Œ³ ‡vž®vÊÏ&büýafý%%JA^BÄ*¾×Éÿ©s 4}Ï@׿žÂÿ4eŽÁhÇ¿Pö@^hÅ…ÿÜÒþ~ÿ«©ÿ4›ÿ´]ÿ8³ÿÀÿ„ÿYCÿ ÿSwÿ«­ÿGÿ½>ÿ5 ÿlÿ=ÿÀ8ÿ&þþ æþVEÿ¬zÿ,^ÿªEÿÆNÿ–ÿÈØÿáÛÿ@Óÿ~Üÿßÿ-ÅÿƯÿ¡¾ È ~|tT/$n—i»Ö’ü¿9ü¼íÿìÛ')Lÿiúÿ4(ÿAŽ­`«ñáñöAûJM#¢FÔc ÜAÞÿ¦3z¯‹…ÿÿdYÿ¾ÿŽÿcÄÿƒøÿS{ÿË…ÿ±›ÿPsÿƉÿ½Wÿø$ÿK=ÿSÿ–mÿ`,ÿÊÛþíþ-%ÿWÿ‹Eÿ¿3ÿßbÿ°–ÿ‡©ÿå£ÿðžÿ©¡ÿBÆÿÖáÿÆÿß±ÿÞ)‰~´’< µ"`QúÁü½Qúü0ÿEë }ùý$…ÿ8°þ‰=ÿÓß®¶ \ß×ü ÿh7?|>ÎZ€Êÿþ®/‚ÁŠPŸ 9úÿ¹4ÿö@ÿjÿ Àÿýùÿ±ÿRÿ²\ÿy.ÿ«IÿwWÿ™Cÿoÿüþ²$ÿvAÿÃ-ÿš"ÿMÿ…{ÿ]”ÿǶÿ´«ÿlžÿÿ—~ÿî—ÿ¸¢ÿÌÒÿÓ¬+û 8wÿÒ‚ª…#ŽÕ¯sú+:ùxÑÿ娢Pý'ÿMÍýù&ÿ €ItŽ8DM‰ÿžY9ÑJÇÿúÞïÌÿÉÿ«[!¨ÿµ÷ÿßËÿÑpEÖÿ—ÈÿkGÿ$VÿÙÿ?‘ÿ˜ÿ Çÿh»ÿ‡\ÿáþ€ÿÙ(ÿw@ÿ|>ÿ=÷þÃ>ÿLkÿ±Gÿpiÿ%…ÿ £ÿ¯½ÿý¹ÿZˆÿ½]ÿáƒÿVŸÿ„ºÿ`« CF$O”ÿz„¿½sÔî¯eã÷ÜáøZœ’î G/ý³þŽüÄÔÿDFLäÿgŸÜÿõÿäò«wÿÛLMÿÝ!ÿz"TA3ˆøÿ0Ä{Ñ냌UÄÿwŒÿÿ7ÿÙ-ÿhó(™ÿ$[ÿÿ;ÿXCÿÿ ÿÈ ÿ–Hÿûpÿ=^ÿ`ÿäZÿ>pÿ–£ÿ¯¢ÿ~ÿäžÿ®ÿ¨²ÿûÿ[€ÿŒ' Þ{ óðýCÛØ[fdUŠ… ö%Iø;½³mª¶ÿ* ý°AþЦû µÿ…MjÇÿ"9”69ÿíÐþ·tq”Jÿ(òÿösþ/ºþ2B/&y’öŒ;¡°ÝïÿýN¾ÿ‚ÿãdÿÏ©ÿ.œÿ¨Pÿ—WÿTÿ7ÿ›íþ¡$ÿÈŒÿÍ}ÿÆaÿãIÿÀLÿ ~ÿ¢©ÿ°ÿÌ™ÿÌ ÿ´ÿw”ÿ°ÿ— Y7Ðd Ì€ü‰ïÞ·¥þã&ÀSõÞöñLè<Ãñÿ^ïü90þGû½¦þ"YmÄÿcB 6ÌþÕžþ‡ÿ±º¢þ0ÿ…ÚþÇþ˜Ôÿ?·ÿíÿGT ²û“œÊz#C®À]óÿö"_õÿ¨Jÿ ÜþDÿeŒÿ#~ÿ'jÿ¾9ÿ¨?ÿIeÿ,BÿÚDÿ%Nÿwÿ°ÿ”¨ÿÞƒÿçmÿ´ÿ»˜ÿþqÿ®wÿ†g <öö¦^üø[ʯ¬üÌÚz3öÏqõù>{ýèàÿå,ýS!þé“ûKÓýXµ ®ÿˆÂŒCÛoþ ™þ\+ÿ ¹NÿlØþø®þ þ.Sÿeüÿ9Åÿ˜˜ÿØ¢¹š9Öÿ·æ’JþÿçŠ*‘ÃŧiášÿÆ.ÿÀøþ{ÿ \ÿJ³ÿŒcÿëAÿ„^ÿ';ÿÞ^ÿxÿzÿã«ÿAÌÿÕ¡ÿŽnÿPÿwJÿ"Wÿfÿh ×ü𸴠þ9 ¾`ãüÌêþó*õ‘÷ |Åäšùþ0¸ýãýá®û`Xþ‘^ÿ˜úÿÉëäòÿ×TþŠþ¥›ÿ§œÿƒ¸þG þ Aþ-+ÿ®ÿ¥ŸÿÜKÿ£¨5ǹ"®¯}ù,u¦‡dB|ÿgxÿ£aÿ+ÿ7ÿ# ÿŽNÿðzÿmpÿTyÿ8[ÿÕYÿ˜¯ÿÞÿª‡ÿù^ÿfRÿÂMÿfGÿãWÿ4ë Ãqend÷]zœÿúïüZwýLúô@“÷ÐÒIÕ ÿE,þEÒü"üx}þ•<þ!4‚ÊUpÿ¬;ÿÑdþíŸþS^Û=ÿþ¥þlþ/çýgqþÿ›{ÿKÛÿ ýo‡úÏÿá™85y®ýÿ.¯ÿóY¿qöÿ%¢ÿÙÆÿ!§ÿP˜ÿÖˆÿæþ! ÿö_ÿ“rÿë”ÿÄyÿ ]ÿŽÿئÿ4uÿjiÿG~ÿÄpÿÜfÿyeÿ‰³Ò ›¾dá­d€þ;ý=ýÕÌô-ø‚š êyÿ‰öþÛ üoüNÆþ‡.ýÛÿx7$¾ÿåáþ£°þp ÿ˜Rÿ™&þ %þ”ÙýÅøýÛ•ÿçÿGžÿ;p“2±œåúÿ¦¹ÿÊÿ’Äÿ^:µ³ÿ«9ÿå°ÿFåÿÂ Èøÿ“fÿFÿgÿp"ÿ>eÿnƒÿ™yÿè…ÿõ£ÿŸÿá„ÿ"wÿ³cÿÎMÿUSÿÝ67²KwÂ0€ nþÏüw±ýó'õ¯vøÖ7$ ÿÚþþØSü ü öþšû6öÿ]ìùþ^ÿPÿÔþ"³€ÿ"ÔýxÊýWEþ˜×ý¯Ûþ‡ÿr ÿ/;¢ß®ÿ÷&ÃÒÿå–ýrÿpsÿ†ÿ݃ÿÃ}ÿuÎÿ@’ÿî±ÿYæºÿs»ÿÔdÿDÿÿØ-ÿomÿ°¼ÿ6Ûÿ!¡ÿ¯ ÿd…ÿ¤AÿÇ'ÿo0ÿ$|a¬ ïÃÈsæ‡ ýFþžyûòþô†õ—ú‹Ðÿû!ñüÍ#ôü“6ü”³]åúcåþú¦qöþ~aÿÍqÿ;%þ„ÃÿtÜÿÀµý1Éý¾½þ*nýûTþ ¯ÿ —ÿö¾X>¥ÿže%ñÿk÷ÿ®¼ÿ@3ÿÁÿÔ‡ÿßPÿëHÿh©ÿÂŒÿ+±ÿÐÿÿ7Yÿ,‚ÿ©€ÿ°Eÿå˜ÿãkÿP–ÿ'ÿ1VÿµÐýîvüªþ?7þ Žý¹½þ9ðþ@QyÿÈ£þ¤÷Dÿ¶ÿ [«¼ÿ¥ÿlÿ#‘þe•þRÅÿ¤ÿNeÿ=qÿL¾þÿ1BÿHLÿgÝÿbûÿ¹šÿe·ÿÜäÿTŒÿ ÞÿÌéÿHÿIÿùÿœ„v+& =aû¿H„¥þáñóåècF÷ƒ%=Ð Uií ˜é…CÝ ú1ìÖ*þƒuû™‡ƒþ#üüÀlÆ”ïþÃý£‹ürþJÎþNTý“sýÎþR<TÿQÏýÌÿ^;ÿÁÝÿƒôƒÂÿþWÿ5.ÿamþ#£þ\ñÿn­ÿ‘Aÿ÷dÿî›þéâþ¯QÿyUÿ˜Úÿc:ÁÿOÁÿ…öÿyÿ¿¯ÿ<ôÿ€xÿ¨ÿ¦üþÆ™òö0ÌA’côa¡ÖÿÇ¿óX´%÷«„O©þuî[寅àNèû@wþz<û-úÏþgœüΆ°;á¸ÆŠþò¬üø¹ü þH"þЕý÷`ýýÿ› ÿ 3ý ³ÿ^°ÿíÿ‘«Mÿ¡)ÿ¨”ÿH‹þUþs¿ÿ$³ÿPeÿaeÿAkþ7Äþ[Aÿ}=ÿ­Çÿ|.nÙÿ6Äÿ;adÿŽ•ÿ<÷ÿÓÿfÿáÛþˆ>ä¹56sað 84äýõ M+õ5¬§Ÿî ábÆÿc6Ó¢ý…\S/ýú¬ûýþõdþØZý®Ìÿm^yáþVkü™üê»ýWøý>Œþh¡ýáþeÏÅ­þ²lýÓÈÿÜ^ÿZ‡ÿw\ä°ÿ´PÿámÿóKþ~PþúáÿUNÿYÿ¦sþNþþÿ±ÿCÈÿü=Wðÿí£ÿÛÎÿDÿ¾®ÿ"Лÿi ÿÆÏþñ±I‰;"1¡¸è9IQ5þùZøÁú Lëñ~‚ÿû °ÈðÆà`þdYñ™ývXkýiäúh GþpFý ØþSW—þ~ÄûØMûýa.þZÿQÎý/{þý÷þnXý°?ÿŒ·þž|ÿg°9¥ÿîÿA*ÿ¥Lþ™‚þcóÿU^ÿš’ÿ‘ÿþ—¬þÿi(ÿ”üÿò+ÿ²ÿñ|ÿÏÿq0ÿ‚wÿ”ÿNÿŒìþm‹#›½@T¬ÿAäÙ›ôoüSUýv… „¼í~œŽoò{Öá¼üjlq©ý–,S²þ´áù-ýÿ"iþëŒýsoþ¢(ý~`¥ýnòûùú/*ý½Ôþ­æÿþÅþˆ®ôþ¬ÉüZIÿÃþ ¦ÿž™Æ€ÿã4ÿ<.ÿöˆþ¡|þ£±ÿÿ÷æÿp¢ÿÓØý°™þ³þþ !ÿ'¼D±ÿåtÿÚÈÿ*1ÿ$fÿ`÷ÿyÿ< ÿ ôþ¬°'xÉDcü ©á°J ¾Ùùœ±-ø §êçnƒ¾ô9FãæÅú6Öÿöüf á>ÿF÷÷âÿAŽþ2wýÜ·ýwž1G‹ýVsüèùù‘ üuÿú(þ$ÿ9µþeþÉüŸ™ÿõUþn”ÿݘ,²ÿ\|ÿ»%ÿÜþø,þDŸÿwÚÿ&õÿÛbÿÝÄý¬þµèþ›ÿ“7U@º•ÿ„ÿÎÿ1ÿ‰_ÿºÒÿºjÿåÿƒ ÿ×Q)u‚HERü÷lÞE Åøé²…? "öéï/èºùD\äŒøINÿ–û ¬„àõ aÁÐþºÉüÝüÎÖ|ÝZýƒýçîø½(ü ANí<ÿ ÿx@kþåý"hÿ“îýŠÑÿfÆÙÄÿ%$ÿ”lþ„Õý/¯ÿìÿú¿ÿ®fÿÅýÜþÞäþÿ F7Lú‡ÿ{mÿ*ÑÿÛ8ÿåRÿZåÿ°{ÿùøþãÿ"^(<KË›WìÚL”¨Áø^ª>y ^êæÿœÜXR7Yærô+ÿ²øÞ> ¦lôuxO'ÿ׸û8ü}t¼‘ üÉÜýj—øžBûÑÅN<˜ÿÖ­þÏǃ¬þÂÎüêGÿÝýý æÿÞ‰×þL\þ5ãýWƒÿ±ÿc¦ÿ0wÿr˜ýÝWþëÿ:TÿâJn1„ÿ­]ÿKÈÿt:ÿ1AÿšïÿÜ~ÿÿóÿΞ)MAM€ú#ÛÞY}¿öp€èÅ Lú騏þ®hÎSâ•èpöñ’µýw÷PÔ ÓÝ¥ïóOXˆûþ]ûΉûñÓùSÇ/üŽÏþGxøŽûMü™)±Jÿå‚þ3¯ýBþ³+ý†…ÿ¼êýK÷$ëõÜÿW¶þÀ¬þ ÞýÚAÿ.ŸÿGµÿO1ÿR†ý”gþ‡ÿäÿÊ\‡ÿ3Vÿ½ÿüEÿ¨9ÿââÿ|vÿ®ÿþ¦5ÿb³,M¤ÑýÃÞ\Nœ¶òk™H‘ ;\臈þ>_”>MêŽòäÚúÚçõ(® ‹µ§õ%Nÿ†xþ-ü eúªý¦iÿÞŒümoÿ”`ø¢¦û"6}ßÿ\ÎþÔMþÑ­uëýaíýÿgÿVìýº¬¹óc®$Øÿ´ïþÞxþ×ýèWÿ¼Yÿÿ ÿ•œýF”þyÿ*ÿªQÏ jÿ­iÿÎÿ Aÿ¾ÿëÃÿGbÿ]ÿLÿ¡ˆ)éMWî½.߈Ojñ­ÿ"W‘çæpÝù†ð*¿ DRîÒòhùcëóm_ g•±÷þøîýXýØù Äÿ³:)aüÀv‡FùyFú¾¾ÿjžÿ¯nþŒ[þ7ºïýsáý × þÚv­Œu©Ù'²þ}žþ7 þZ&ÿÿCÿi2ÿuÊýœæþ•ÿcdÿp1Oâÿ'ƒÿâ‘ÿbæÿ >ÿÿmŸÿ®KÿÚÿ³_ÿ>.'ûLrs uhâÇÒ+ïGþ$eUåI™õ;”í “cò-•ôûÏ÷ëð?¿ŒnmøúÀýxøüLBýxMú†'ÿ ðxñü@¨õuùëÄùråþÁáþupÿŽÔþ¼Ø,`þÑ þˆqÍýÍ+¸¨47·<dÝþ'ÀþQ(þã ÿUÿÜ\ÿ~ÿ¹%þ0"ÿt3ÿ0ÿF#jöÿ…ÿL·ÿ¾1)ÿüÝþ¤‰ÿyIÿÙ*ÿ†ÿÛ(·ÜJ¡Ê|§é©à›êéÃÏ“JñâY×ôm‘SUQôÖeùF?öËín© îi?ù{4þ"ôû¢ü™7û'ÞÍ.ýÔ×øøÂù›¾þÉëþØîÿæmÿ•Îòjþbþ‰+Køýýåÿ)>€FC¢þäÿ«iþ ÿSðþynÿá«ÿ‚ƒþ®RÿÐÞþ]ÿ_çÜÿ ÿžÅÿãÞÿŠÿŠÜþV“ÿ½)ÿ®FÿÔ¬ÿð€';H«Ó giïAá³è’Pb/ ¢¾ä±1ñž—Ÿc9ö_óüåøûÝçljk …0ø!Sÿ[ëû*rûgxü‘ý¯îþQµý¿ƒk ÷¬ú^žÿ&þÎþ5B@}MþaÝþÃGVsý‹ÿ. õ<ìûÿ\ÏþVMÿÛ‹þŒÿ< ÿ†;ÿ4èÿ-ºþÎ#ÿÿ÷CÿYbÌÿÿ"¼ÿŽçÿÿ×2ÿ}Êþ×¼ÿwó"Љ;¸¬ ûfÿ¶æ äLßö7 ‹VïK;ìùÄÿåÉý-))é¬Öíhw •‘ýæ¤úÏþž^øñúëêÁݦû§…ÿþŒÆ÷äì»ñý²îóÃÿF÷ýK"ÿÉøÿ{Üý +þ¸øÿääÿÿ]$Ïÿ¿­ÿ’™ÿ.úþr ÿ@Ûþ5QÿrŸÿ¦ÿ?[ÿ!ÿæÿ»ñÿLûÿCéÿ³ìþðAÿë ÿßñþªåÿÔû!œ`6vY¿4Oö ¹µã­Dø‚ˆ añP@ìmBüUÄ4 o3ÿiv Ö²í[òëe^S×ùöÏýMùIøúí<IBý¹uƒüàõäßL…ùnýÄýÿÅÿ$BþßÍÿ°žÿiný†žþ8Z”ÂÿÓž»ÿsÿtÿÜâþúþ2ÿMsÿ&ÅÿzEÿ‚ÿàÁÿ¾ÍÐÿŠÿÄãþmPÿ~#ÿSëþmŸÿD¦ ýä2"¨Üãê?è/³öCS¾ð»ìÙù@8µ ü»kÊöµªëÆR±Î5üùKürlù+÷_Üx‘6zþµ…Ø3ýªUõ5êy|ý˜@ÿàìÿôžþô"V:ÿNý(.ÿ=YYÿ£Ñl¡~ÿÈnÿOÿcæþ¤ÿ‘Óþš]ÿOÂÿ×CÿEIÿ Üÿ“çÿBåÿ¥9´ÿÖÿp ÿÿÀöþ iÿ€SYY.Ýo^Ddõ\é§k÷ç,lžóò—íëÏõJ{„bFuø'}»ÿ`Wì-ýL]ØùÀÀù?ùûº:øÛÿÛ6Rvý1òŒÿR ÷?“þ¡{ÂñþA^ÿnÿÿ¾þÚ±ÿ݇ÿðjþu ÿ÷ò…ÿNæÿè-`ÿ‹ÿ/ÿNïþ*Ùþ¨°þElÿdÆÿª?ÿmuÿcôÿ‰ðÿ C÷ÿË)±ÿWóþýÿÿìþlaÿ776ž)eþ£(¨ë]ù}¨Ѽõ:–ïOÒó³ÁûÊ ƒcö\ÐÁÒWî÷˜û>Þ:?ú¨Åø 8ýþQù8Vþ ÿb…ûK½æoÈùè;þêÇ7¨þýõÿ9ÚþCàþ·rÿZ4ÿ2‰ÿ±ÿ"Dÿj­ÿx3ÜÓÿ&ÿÆþÙ ÿÿ¤Âþ‡:ÿ+½ÿ­™ÿ4Ÿÿ'ËÿBäÿÞsËÿaÜÿñ‹ÿÈÿþ·OÿMÿšùþf\ÿwgÊj%«Sÿ³Q ¥ì웑ûŪµ·öðëñ»ôŠ ÷Š·o#Þð‘Óû³²ûdmù#qýÿüù°]þ!Âþ&;û% C&øû²rÿpSÇÓý¾Òÿ9½5ÿjåþ‡žÿ';ÿ…|ÿÒ±ÿ»0ÿÝÏÿk9hÿ˜Ýþjáþ¸#ÿ}&ÿ‡ÿ§_ÿ“×ÿ`¶ÿ˜ŸÿMØÿäÈÿ7¹ÿ‰zÿu™ÿ¨ÿ¬gÿ}ÿ PÿV*ÿzŠÿ•žSÈ'Üñüq%þH>÷¢ºñöñ#ˆú5åô/dôò `m q5ñ2{R[Û÷7îþ±ÿÂòiû ŸQü3 ý¹àüKÙö_žüþÿœÈû˜úüíÿÀ‰ÿ‰óÿî©Þûþ}™ÿó'N<þæK±à þ…ãÿ\†ñÿQdÿ•\ÿQÿþcŠÿƒoÿû[ÿ"¥ÿÞeÿôäþ)vÿu˜ÿê>ÿÔÍÿiÎÿ¤©ÿÜÏÿCŠÿWÿ5tÿ¿OÿìËj#`¢!ü ëAÔþÏîìÚ†ü&—õ½Þö9¢ç ƒøµâþtLþ ùM›ü2xR‘üUŸÿXý&&ü;Çþ”ùNÛû™»ÿ^ÑûYýêžÿYÿN'ÿÈÍ$ºQÿ¹­eûþIŠþ’ÿÿŠ’ÿ@ÿÅÝÿ°ÿæÿScÿuRÿ2žÿžcÿ²Tÿg£ÿfÿ¸ÿ,bÿb¸ÿéVÿ¬wÿõ³ÿ_´ÿ¾Ÿÿ}™ÿó—ÿö;ÿiyÿ#>s¸ñÔUÑZº&2û Zõë Ó}û»ö4\÷¸¾¸ õQùûäsýùωý`¢¤LþüH£ÿñLýcýçqþñÌú®ýMFÿ“ÁüêLýh{ÿ™nÿ$Ûþß+„•ÿº‰ÿ8h‰ÿŒ®þˆ¿ÿç’ÿBzÿ1Ùÿ§mÿbÿŽŽÿkŒÿ¯xÿE:ÿ:ZÿM—ÿÕzÿk#ÿDvÿ5ŒÿƒRÿòÿk°ÿ÷§ÿiªÿäÃÿç€ÿ'Oÿ{ÿpË 9GÑ•o!aÝûUÌø.£Ÿ¯ûùö ¤øê¹¨ "<û°Rè-uû!¶üz÷{@ýiÿˤýéyý,²þŒ=üŒòþü½ÿëüL‚ýÚ–ÿu`ÿ± þʆÿ!ÿT!ÿÃŽPÿÁÿ¿žÿ1TÿÀÿÝ[‹Ÿÿa<ÿ‰`ÿt|ÿ“vÿO:ÿögÿ}®ÿf€ÿI;ÿÿú‚ÿ—Xÿ÷•ÿIÃÿVÖÿì®ÿ¿—ÿ |ÿ2lÿ–ÿñ déG\Çi)ôþVúY<ò¥üé-øØeù«ë0¶€üJoÿ£&½­üv4üèþ<#‘šý¸.ÿ|þÇÁýž‡ÿºÎýö0ÿO¸ÿ€ŠýrÙý؆ÿ‡ÿN"þÅ>ÿµÿRÔþà€ÿW=ÿI}ÿõþÿ¹ÿX¼ÿ“Q•ÿf&ÿ˜,ÿˆQÿëÿFdÿ™kÿœ²ÿj†ÿ‹\ÿswÿ‹pÿ‚cÿ»žÿúÓÿpÆÿ.œÿ5–ÿÌyÿ}vÿ3yÿtn O­õq:¸ÕƒÚRüµm½ýo’úóúô"O5l ýnÿcóç]ý©-ýeGG{ þéEÿ›„þÃ#ÿÙH±Zþ¨ïþH¢ÿí]þÓ)þs ÿá„þÖGþÆ{ÿóãþí{þ|hÿÊ}ÿéÿŠ|Ú³ÿÁQÿ¬ÿAmÿ¼:ÿ `ÿÜŒÿè~ÿë~ÿÈ”ÿœžÿŸ›ÿJYÿ‚xÿÊ…ÿEuÿù´ÿGÂÿ1µÿ<–ÿËÿÏÿ5YÿÌ^ÿærgâŸÄ‰V‹ yýŽû8Áýàãûd û?åòÔ±ýܼþþ‰©ýyRüÿþ"öÿ2–ÿ3YÿFUÖþS¢þyŠÿGšþ þ«þ°Ÿþ[¨þìYÿHÜþL–þ’ÿËØÿ^Äÿ°>uÿ/úþøeÿU›ÿµ‰ÿbsÿù{ÿKÿÖgÿìÿé²ÿš¦ÿz\ÿïoÿÌŠÿfÿé˜ÿV¢ÿ5ÿ`ˆÿë‚ÿ>kÿ¡ZÿEhÿËÁ˜ŸRz ž>]ÿdYçqþð;ý™ü†qu |Oþˆ ÿ&$\¹þ?þÜøJVP¶ÿÎŽÿD¡C[TþzþÙTÿްþègþƒüþÐþõÙþ9„ÿ$ÿUÿ‡ÆÿGQÿÅ:ÿРÿ‡'ÿó;ÿ;Æÿ¡ÿr€ÿU|ÿ®hÿµlÿÿ£˜ÿCµÿð°ÿ§VÿFjÿ¦ÿ¥{ÿõÿ*~ÿ>—ÿÌŸÿx‡ÿ®rÿaÿ9sÿCçhW ´¶¾d Ë ñÄA&@Í”/ÿ,¿ýÅ›üð ¿´NþÏ*ÿM{ÿ&ˆþ[ïVk†ÿ‡$ 7@c˜ÿ&Õýw}þñªÿk"ÿÛ¤þ(ýþ ÿù`ÿÈÎÿjJÿ ÿ¢ÿ ÓþÈ"ÿSjÿ˜Sÿ ÿÁÿ pÿ¥Wÿ‹ÿvÿò~ÿI ÿB˜ÿͳÿ—wÿ×DÿKmÿ)^ÿcoÿe‚ÿjˆÿÿC—ÿ߉ÿ²`ÿldÿzwÿéñ²¿ (Äî$7 &5°ÿïgª,‚}þ"ý‡<ÿŒI·òþøþ1øHy¢þùƒwÈÜÿ],GJ§ÿ$÷þ×-þ4ÿéÿ—0ÿ%Õþ ÿ¿“ÿ„Äÿl‘ÿ)¸þª{þuìþTæþ‹ÿ±tÿ/Wÿ>kÿä¬ÿ`iÿ¼MÿÍÿûŽÿ›ÿÔ‹ÿ¶nÿ*“ÿ$mÿD>ÿèRÿ\ÿŒqÿ¡}ÿK“ÿ‘ÿ§mÿ¶ƒÿ vÿ›_ÿp^ÿÀ$¶ÿ yŽ%rþ­^¸=_’viJ”þæÚüÜîÿEgÿ‡a¶#¾6ÿÁºþLÔý[.nËoÿU_ÿ=ÿï"ÿ¼ÿ>ÕÿDJÿ³-ÿk´ÿP}ÿ=ÿ!ãþiþIÑþ SÿÆ ÿ)üþ[EÿÑcÿýqÿ…ÿ>qÿtÿy›ÿ—ÿ*ˆÿtÿzkÿxÿMÿ}:ÿ\aÿÏkÿ8dÿroÿ؈ÿÃ…ÿO€ÿÑ{ÿ=eÿ‘[ÿnHÿØ”: ä²Ю04‰Zôr¡þëBý' Þ,Žm#S¬©1ÿw¶þ{ÿqÇÛÿÍâÿÏçÿîÌÿJ·ÿRÿåéÿÚ7C|ÿ~ôþV,ÿÒûþ¸òþŽûþcîþq/ÿ .ÿèÝþgöþ›ZÿŽUÿrÿ/¥ÿrÿ6’ÿ°²ÿJ†ÿ¦}ÿaÿÈYÿÏeÿ{CÿÉPÿpiÿ“_ÿÙ]ÿ hÿ´–ÿµ›ÿ¶vÿthÿNSÿXÿ{fÿpx!R ç "û/Ù¦½&Wè=ç‡oUÿŸÏþ€xµ}úó=Vø³5ÿBþòtC-¥{Íf°ïÿÈÿ20J¤I ¶ÿ@ˆþ’oþâ(ÿÈBÿGRÿ0ÿ…ÂþZÿ¼AÿìÈþ“úþ¾jÿ Wÿ©„ÿšªÿƒ†ÿÍ•ÿ_™ÿoÿ^VÿƒVÿ²PÿÑUÿöWÿ¯Bÿò\ÿFnÿhUÿ¿}ÿ šÿ‡}ÿQkÿ³Wÿîfÿ¢xÿ£lÿ÷BcïëÂþŒ ‰Úè‘ \WhI¶ÐþùnUàAZmÒþBÿzý¯iP.#2@ ÀÜÝÀÄÿ¤ÿ%Ûþ=¬þWòþ)€ÿÎvÿYÿ>êþÝáþµ#ÿcAÿ—öþž#ÿ-{ÿÆhÿ~„ÿ2›ÿ܆ÿvŠÿÙnÿÙUÿFSÿoKÿRÿîTÿQÿÓUÿUjÿxvÿÿ`ÿ|xÿù‰ÿFdÿoÿ(wÿÒvÿƃÿ=pÿ`ç¤p50dI5bá¨^rØZÿ’ÿ1"î :¢üÿx…ÿ™-«Í·¥Mæ²XÏx"˜ÿyÄþÂÿiýþG3ÿÎ…ÿˆ>ÿ0ÿä÷þœõþ8SÿÍrÿT3ÿÄ4ÿoÿÞkÿ%jÿšÿR~ÿ@]ÿ‡nÿWPÿQRÿ-[ÿ•EÿÔ[ÿbÿmÿÖ„ÿKrÿðdÿEbÿŽiÿF€ÿœƒÿò‰ÿ”ÿåŒÿjƒÿYöü`MÓxÌd ³õj²ÄÀUEŸÿ¯Uÿi”ÿ Èÿ¿¯tsÐ+U,:›nøEn2/n(`ÿnÿ$ÿÎÿdýþkÿýlÿ uÿÊ+ÿ¯ìþGÿypÿn`ÿ6:ÿ“Pÿˆwÿ‰tÿ×pÿŸAÿ½*ÿõYÿåFÿû[ÿ¿„ÿ[ÿœRÿÑdÿf[ÿåUÿõwÿqÿeQÿxÿÌ~ÿ´rÿ†¤ÿƒÿx…ÿó”ÿúvÿº A%´ìŽIòwúåR† €7)eÿ̃ÿo"Úz–¯K›4|8|˜}úÿf¨ÿ?‘ÿénÿã;ÿ¸)ÿ4BÿíÿÙáþÚ)ÿÃsÿômÿ­^ÿÒ]ÿŸBÿ‚IÿGNÿÝMÿ7^ÿñFÿ¼NÿâHÿ?ÿ™Pÿ@4ÿGMÿ‹ÿcoÿPÿ¯tÿáuÿðZÿ?dÿ!tÿ+vÿevÿz‚ÿ7}ÿ̘ÿy±ÿ¸ÿ–|ÿÆÿv¸vú(#ÌC‘ÀÕ®¶a 9ÉþÜÿD}…¥üŒË$ÅB{iË]´10/^ÿŽ€ÿ±¨ÿŸÿ“ÿLVÿ, ÿN ÿJ ÿÀHÿ±ƒÿz5ÿ±BÿÙ‘ÿ¸ÿµbÿMÿ¡=ÿJÿ‡5ÿúÿ÷9ÿûiÿÆHÿL$ÿ®Bÿe{ÿütÿÕXÿáiÿЉÿÿXÿ€mÿw’ÿ·~ÿ iÿokÿU“ÿªÿ‘ÿÿ³™ÿì—ÿεàWöŽ,¶rEA>Üຬ-Bõÿê¤{vDr…ÝvÖúª >öÇÿYcÿ(vÿ­aÿW™ÿŪÿVÿ³Aÿ’àþÊïþøJÿÆtÿÕfÿ•xÿ‚rÿ¤[ÿ¤hÿiMÿDDÿˆ=ÿÝÿØ$ÿ=ÿ^.ÿ§ÿ¯3ÿ‰Tÿakÿ¥~ÿƒdÿ|aÿCsÿ·†ÿ¨’ÿâ{ÿ¦~ÿÁqÿQÿç_ÿ¹¡ÿN»ÿ5‹ÿëÿh¢ÿgÿZk×ÿ³ÏãÀ½Ùå±µÕëÊ<Ø ÂëÿÄÆßô"{ ´d|@ZÍÕcÒèþÛßþ˜”ÿ)Ðÿ €ÿpTÿB|ÿ /ÿ5þþìÿ:'ÿeŒÿé¦ÿŠ}ÿ2Hÿfÿõ7ÿôkÿCÿõÿ&3ÿL ÿžÿg&ÿyÿ77ÿ}=ÿåUÿæ”ÿs‚ÿ!_ÿýnÿ œÿì‹ÿXÿ©\ÿ8Yÿæuÿ‰ ÿ«œÿõ¡ÿß‘ÿ™wÿŒhÿßêàš­× ½e¡áF¶Ñ‹°F¡rÕnÿÿWd‘¨wÍ‘—’1:;2O/ÿŒ×þö^ÿÌÿ—ãÿö‘ÿ ÿÇ,ÿ7ÿ“ ÿÑAÿÚÿµÄÿÔÿÝ ÿÐ$ÿ{oÿ8gÿ’>ÿ2!ÿÙ%ÿóÿŠÿîÿ”8ÿŽEÿ ZÿdqÿpLÿ hÿ¥ÿ„ÿÜlÿ€ÿx‘ÿ‡ÿfÿÝÿŽ£ÿ”ÿoxÿŒÿ{uÿfE KŒë7ÊÕMcJkËÃmUŸë·,ü;Ç¡ƒ€&Ÿz1@iÑWY~êU¼@‚ÿMÿZ³þŸŸÿõ?wÿH9ÿª]ÿ”]ÿwÿŒXÿ·§ÿ Žÿ­ÿ?9ÿfÿq”ÿ“fÿ¨>ÿ¹_ÿY;ÿ1èþÉÿ _ÿ:VÿzMÿìRÿñZÿUVÿ}ÿRÿø|ÿt„ÿª¡ÿªÿ¯•ÿMŠÿ “ÿj}ÿ&…ÿ!ˆÿ¤Fÿ—) Q3˜´’oúª'§¯o’±ÿ-Qÿœc¶gÈ_˜ö /rKÙX¹§(2o&²µ±’ÿÂ=ÿ)GÿMèþlÿ–6·žÿáCÿ¶pÿ-FÿüNÿ|Œÿœÿ»pÿŽ–ÿŒiÿEÿc”ÿUƒÿÆ*ÿ ÿ ,ÿÏuÿ oÿ¢3ÿÞ7ÿcÿ„bÿøNÿ1lÿ¼‰ÿRŸÿÖªÿk£ÿ¡¢ÿœÿõ¤ÿJ¥ÿ‡{ÿƒTÿ Rÿn³£J L  V³E+*—æÝOÙSþ‡XþGdrèÂk&óS~kV‹ïhÅ÷íj|ëË2 ãr&åÿÂéþ ÿjÃþc.ÿäÝÿÝÿ¿€ÿ’GÿrRÿ"ÿGÿ†ÿÊ^ÿáÿ`{ÿëOÿtuÿ•\ÿÝ1ÿGÿuVÿã5ÿÝ,ÿÝ^ÿlJÿ+/ÿÝ<ÿXCÿ xÿŠ„ÿ Šÿï¥ÿ¤’ÿz›ÿæ™ÿÀ“ÿ‚ÿ[Fÿ/IÿÛK+ë V%`8”¦[”¯ý0~ýÍdB¢YÄÿ¾ ÞjÎ{U>Q¡—GbuaÄúÅÉJœUÿþªVÿÊMÿàÿǺÿ!èÿˆÿ"ÿ„4ÿV3ÿèLÿ!ŸÿŒ\ÿÓkÿ"¥ÿ;sÿwxÿu^ÿ$3ÿÎ<ÿèMÿ1`ÿÿJ~ÿ%áÿˆ}ÿ{gÿ!µÿ†¡ÿ.«ÿàdÿç…ÿˆÿÖCÿÜ‹ÿÀyÿ³Sÿ(Gÿ>ÿvnÿsÿcÿ¤_ÿWsÿWuÿ¿_ÿ]ÿ&Zÿµdÿ÷vÿ% ±çÏP)-ÿÝQy–S(‚nÿó†ø,àüVõX×Îýþryþ›hþdDÓ&Ö‡HÝÿíÿ4l§1îÿüÆÿhÌ¿ß))‰*PWÇÿ0¦þ[Ôþìcÿ+0ÿFÚÿ¶·ÿ ±ÿ5F€ÿqÿ,pÿËmÿö•ÿ7´ÿ|¥ÿæIÿñXÿ¦hÿ­jÿªvÿZ]ÿÑnÿIwÿ®iÿrNÿKJÿÄfÿN„ÿ|ÿžNÿ ã ßT|ñXéþ0\N3§ZïþìT÷ô/þÇYµŒ³ýKþ‹ïý\?þ8Apcÿ¨Dnù‡~ÿF`ÿöÿçFmzÛÿ¢fÿ½ÿþÓ¾f`GÁ0e1)ïÿocÿ¥%ÿj.ÿ·Œÿ|zÿÐàÿüBŸäÿ—Iÿóéþ µÿ4ëÿVÿëŠÿ_ÿo—ÿÚ˜ÿÄdÿqcÿýoÿ•wÿ#`ÿ!fÿöRÿ-[ÿuÿê~ÿ~Zÿ^TÿHÿ oá·1þiû£KÿVýwköç>ÿ}ûššuü¶ûý6–ýßWþn—þ½ÈAIXÿvMÿí¾ÿݰÿÒùÿ>ÓÿÑ ÿN»þ˜!È×ÿÅýÿ7xá8Ôˆ <×ÿì# 5ÿ"˜ÿAwÿMÅÿÜ!ãdÿ–ÿ+–ÿ]ÿÖvÿÁ{ÿoµÿ žÿY´ÿBxÿŒiÿ$•ÿ¶eÿYEÿÍKÿ±nÿU{ÿi‰ÿ^…ÿJgÿ¥Uÿ[ÿú °?¤ e¸ü­7ýÕáü öˆôý1ì¸ÆßòûÁ›ý—ý¨5þøÓ˜þ”íþêq]£ÿÿîþûÞ?ÿµ¡ÿ±ÿÄîþXzþ ïÿV»ÿþøG™Ì6Í>'òÿiu"-_xëÿËmÿʧÿØŸÿÕ”ÿº]ÿç›ÿ<ªÿ¢€ÿÅÌÿ¬œÿA’ÿ¬}ÿVŽÿÏ¢ÿ½DÿÜPÿÊbÿ±ÿ/ÿY}ÿˆ¡ÿD„ÿ‘cÿ=ÿJ£ o  Üý×!#btü©øúË1õ¢òÿQŽ °b² û «ýŒ·üÕõþÿλýÜnÿ¤êÍÿB$ÿºØ7ÿØÿ_ÿƒ…þA¡þâNÿ_Úþ \ÿ <°ÕÿG?2`Ôÿ†Sá=kÙ!Z&O.Äxÿ¹(ÿ õþIÿ;÷ÿftÙÿuGÿszÿç…ÿönÿ¥ÿ·pÿKwÿÐvÿxÿC{ÿ= ÿ¸–ÿ:€ÿyÿ‡:ÿ¥£ /©ÖàõýÚŒµöü®ù õQ²ÿÓ° ÀutoûäŒý ü9ÿÑ/}ý®ÿ:«0Àþ5Âþøÿs8ÿ_ÿªÿïý„¤ýÝ„ÿ´1ÿ5åþ( &ôÿèÿšÖÿ¥ïÿ2ZÕÿ·sw&±Ížë³ÿÿª ÿäŠÿ4¸ÿ2Éÿ`ûÿŒÿÜ[ÿ½7ÿ»pÿåœÿOsÿ6lÿkÿÒ¹ÿ¬–ÿý‚ÿ˜ÿQÿJoÿ¿Vÿ§w áû2à·/U4åAùYüstø©&õýpþ´ç…o Pûø`ý£¾ûógþæjpý³,ÿÃÁ.ÀþuÿêNÿ°”ÿ"îþçVýxþ÷Zÿ!±þêþ·ÿ+õÿ(/WÿÿM—ÿé†ÿbèÿ B[Bw\v[»)íòÿž‹ÿLOÿL\ÿÆÿ£½ÿÖ]ÿ×|ÿEMÿùRÿ2ÿµ€ÿŒ†ÿD‰ÿžŸÿàœÿɰÿÞ¯ÿÿ»yÿ-Vÿá- ‹I?Xë–¸^Ö¥¼üï¼÷-¾ôç ÿ–ΜÅí»û¼<ýñeû9xþVìý™oÿ¶ä¨þukþ3WÿƒÜþã¦þ™@ÿfÕýÿBÞ¹ÿÄý4Êü#˜ùT´ýq+]Bûzÿ;"„‘ýÓ öKÿRWýRàþ+Øþ–sý>›ýßþ¿ý„QþÁÿ&Ùÿ­ŸA­ÿÂ5ÿlîÿW¯ÿL¿ÿ)²ÿºßÿ åÿœ™ÿ—ÊÿAwÿK\ÿõzÿJ¸ÿ3Õÿ \ÿ+Yÿfÿ|ˆÿ+oÿCÿæ‰ÿ1¼ÿíÖ.W2Ãÿ®ÿMÿ½8ÿµÚ‚6 :Ióxû ñ›:û§Îøð…ö©cb; ü"±ÿƒ”ýMÞø¨ÓüRéßýúf‘þoá@ôý\µGÿ~SýÿÏ•þ³ý¨žýÿƒý»üýj“ôoÿ>êÿhºÿ`xÿåð§ÿ<µÿ rÿM ZãÿS!ÿ,gÿÁ%ÿXÿvÿ†˜ÿŠªÿF]ÿ[mÿa%ÿÙ²ÿóâÿ•ˆÿĹÿá©ÿxÏÿ.Ìÿ%ùÿ2ôÿã³ÿñÿÿ8Wö|" ™ ¥>V{Ò×ú]ú¤%÷€l.˜þùµ÷´À…ÿû%ø|¢üšœô÷ø¥þäþçoüJ€i‚¡8üghÿàëþŠýÉýƒnþìJý^Pþjt¸ÿ¾ÿ©ÁÿQÿ+Ãlÿ³Êÿí¸ÿ$ØÿÀÕÿÚ½þÜAÿÕ>ÿÿ®ÿ7lÿâÿJKÿM‘ÿ¼qÿ’¥ÿ"¥°ÿÛÿ?½ÿû«ÿì¾ÿáÚÿÝÿªÃÿ¿ŒÿEÿ“¢ %èt ­h¡ 4~Ö÷Ó7ý døùédCÿ¦AòÔõÿñ½ùOûKÿCyøßNþÓ¬xöû'ýb“„ÐûÕÿ¥Sÿ_Óý±ý–~þ¦)ý×òýšÄ-ÿfGÿzÿ ÿgV~ÿÉÿ ¤ÿ «ÿ?Õÿ±ŠþZúþ/ÿKÿ7ÿ)ÿ¤ÿIÿz”ÿ€”ÿÏÿguÿž¯ÿ^èÿ1åÿpÓÿ{¼ÿ¼ÿ[µÿS™ÿÙ@ÿ&Ùˆ(#- XýiÌD jAõÔ½þ8³ú{îmk¬íCÚü™öû6±ù‘þ[ø®Ðü}vF¢ûŽ@^:¶iû“þÌÿ¦xýêþæþ9ý!vý$æ`ÿS™þñSÿäžþÔ$Мÿr;ÿæ³ÿ·ÿÆÔÿÕ.þˆþLpÿÿFÿ¿ÿ—•ÿ”MÿpsÿK”ÿ9–ÿ|ïÿgtÿž•ÿ‚ÿÿïÿëÿ0†øÿ/³ÿÉ:ÿ‰öþp–¾.#ð¤Þöh†å.!°ö±nÿ.ŒûR± bò‘ëßœùªôœüýÿøhKþÛ1÷Ÿqý«¬û:©§ýüSÒþ_&ÿ5hýÎ-þ¸­ÿ÷=ýz@ýÉ!R]ÿC³þ†Áþ–þˆ&Gÿj_ÿ>€ÿÀ¦ÿ;¤ÿKþ#þâaÿ¾~ÿT1ÿ5ÿ ›ÿµ/ÿniÿ3Rÿ\†ÿéøÿËuÿXbÿL½ÿ+ñÿ…*ön2'Õ¾ÿ[Cÿ_öþUˆ^¼3Ú4í8ÆFÛÉÕ÷K^¢hûã6 T/&êºÂôz§UÿÉù¶\ý×$÷ÕÏüRsûô\D<üA-þÄÒþ¼oýË`þçÿryýò ý›À2ëÿ¥ÍþVþR÷ýç¢ÿN,ÿÛ&ÿEYÿ¥¢ÿ,"þnQþa“ÿP¿ÿÎ8ÿV7ÿNxÿµ!ÿã:ÿç4ÿŒŸÿÖõÿrUÿ5ÿ›¦ÿ, ŽúÿsQTNV-…ÿñàþ ^¯49Ì¥éÿäŽ ­ `úçf†¼ú¢«É,Åê©–ðüý…Û(TúZšüØìöýáüõ ¿û|)9cµüBçýº°þBƒý‡Xþ^ÓÿÀxýYsýA©áÛÜþÅ_ýÃÝý>-×—ÿ)ÿÆÿLÿ•pÿJZþLIþ·oÿÐåeÿ ÿ+ÿðþˆÿÂ\ÿ…ÿB¼ÿ@ÿa7ÿ9–ÿ÷ÿ‚áÿ'7§@( {®ÿIúþóä!Ô÷=“Øþ •àÚ¨£x 4Œþæq}éø4 ó]Õé¤ýíšAq¶2¯ûnÕü)çõ þŽO³ú±Ì36ü]$þ£’þ÷HýæÇýcôÿ¢eýŒêýŸ“ÿŠ ÿšýmØý©¡<ÿØ7ÿ×ôþË‹ÿbUÿ”þ cþDWÿóHÛwÿLÿùíþÐÓþ–’ÿš?ÿŠ¡ÿÍÿ¶,ÿÿ¼ÿ'üÿ«ðÿº.D 2Ùÿ ˆÿí ÿ­#÷qBü†þ›&Ûòüù sè¬è“ö2 '4 Ê}èŠEêéÄãß„ü2$ÿ´…õÖ²ü{B4úg¢°ß•ûñíþ8…þË×üÌýevÍ»ý2ýÇÙžlÿ£ÿèüü¤bý5ÚÿWmÿ&ÿÆ|ÿ’dÿ$þûeþlÿ‹sÕÿðþ½:ÿÄþÉFÿŸÿ!Àÿ Ò&ÿPÿ@pÿµáóÿ urËÿ;dÿ¤àþÒ×'7ÀE2$ù¼wÙ”°ËØL*wFøóÏ?”‡þtä€wéíó;ÏåþiY½8ô)ü·Ê΂úû7}ƒ„Áû‚Œÿruþìßû\üŵ1¥ýì…ý³Fƒÿ¼wÿõ®üý{òÿòþ;±ÿ1ÿÈÿ#šÿ þó'þt#ÿ0•ôvÿ¿ÿêBÿ>§þ–ÿìîþæÌÿòúÿ ÿ6ÿ¹—ÿÓÙÿ'æÿôÿøÆÿÒXÿ6Ðþß'õCIÒeü{ôÔEžTsZ'…âñ‘d^ñ[UâG0è„×X ÿx‚=“ôG©ù-vE’ú–[Í 8üDàÿ…þûÓÖûZ–˜:þî#ý¤‘Ižÿ&:ÿR>ýwEýÎvÿ.(ÿÿfÿ×Áÿ‰àÿUÏýçþÜaÿìdX‹ÿ/ÿˆ:ÿAxþ5ÿîþ”ÿåöÿÿÍGÿ–¦ÿ¢îÿðÓÿíÿôòÿ]¬ÿbÿbÉþT,¹wKõÖÖ›£X BšˆÊgï†ñ§!cJàRˆêâ(ŽÑÿ–¨ý¨‘]ô#¤ù]}ú Ôà@JýÖôÿJþtúYÛûÜ¿Hþw ýT^:ãÿ`PÿmýF*ýuQÿŠÿnÄÿ'eÿYüÿ½ÖÿïÀý~<þ)„ÿÀ+l­ÿXŒÿJöþIeþ™1ÿ*ÔþÓ|ÿŠÑÿ‚!ÿ_[ÿÜ¥ÿÕÙÿ$½ÿƒ Ãúÿ{Ëÿ°cÿG´þq9.yËM–£óßUÖ7ËáIÿ]Žzà›íÆ¿ü4€áðIëþ¢Mÿº‹ëCAõ¶Šøs6X—ùáÄþ‡YoWþ©ÈÿÒ5ýtnúÿ†ûG¹þä<ü[²^~åþüüýœ@ÿàþ÷BS‡ÿÍÿ.˜äý1,þÛ<ÿ«=•ðÿæ„ÿSóþ„Xþßÿ]¨þ$\ÿÚíÿ‹(ÿNLÿœÿ#²ÿH´ÿÞ @DñÿÀ\ÿà¿þKµ,ñ Pöwø&âÒ¾jgÿe;$YE,묖™¬¼äQ ë"Îú{ ÿ‚5Ij÷ ^ö™fËø2Süœ#Òãþæ´ÿÅÊü”ú–åú&Yþþ ,û¯oìRFþy¤ü+Ÿü4Fÿ'ÿ£›$_ÿDÐÿ(À ý_êý…Iÿu^ˆøÿeÿÒ>ÿ{=þùÙþê‘þÌ>ÿu&Ð-ÿó3ÿ\ŠÿÒ‘ÿ•ªÿìÊ5#þÿÿ1þ:Èþ4‹þbJÿ@5—/ÿç8ÿ×~ÿ;ÿ…¥ÿ‚'ëGNúÿltÿ»þší-¼}S8êú³ Óø ô®ü!îó¼ç?í!ˆ.ãìv‘ë«A÷Ÿýv¥x \ùÒŽõÉ~8÷ΰúº«gMÿgÿÓÃü —ûÙvùŠU!ëÿ»»ú| ÀUÿ~àý¢Ìüü/ýš¾ÿK÷þ†½Û†ÿ¿²O›ý×”þw ÿçÿ†­˜ÿÿÑþ'ÌþߘþVÿr,bIÿb4ÿ¼{ÿ!’ÿʱÿJ(Â3×åÿœxÿFÌþ¦-õS5¨þ›éÓ Ã¢ûöîþšŒÚJåÑ)Ÿq"þð‘wëâ}÷óÐûѶõh óeú¡=öXähìö5Áú'A€ÿ8ûþp”ýßöûBÕø³ ˆÎÿŸèú“¬v×þS9þ•ý¦·ýAjÿÎÿ>ìäôþ¾¾ÿX¬wþkcþÜþ;Ðÿ¹Ùÿåžÿ¢ÿß þ-éþ»³þúNÿ@/LÿIFÿ¦ÿk§ÿüÂÿ]  NÈÿB}ÿWôþ^%+8‡SOò‡ÅÕŽôl•úl7ý#|ô+â“Û/#1õxXëËÎøHúÇ*æØ ÿrü“÷e&ºÙöA4ûS#T,þâþÿþß!ü…hø:L ²ÿÂûÖ/':ÿDäþ‰þö"þø…ÿ3êþï0ðÞþ{ƒ3þ:zþΠþå„ÿ‹½ÿd¨ÿnÿ–!þ«ÿ<ÊþrÿùyXÿZyÿ ÇÿOÛÿÀ©ÿ–åÿ0õÿâžÿ žÿÛÿGl'úÕQè ¯¨Øp.Õ ú¬¾ú+d]ßtPÐ(×Vú^2ëöúQùvý· ‡­ÿ‰#÷@}ÿ t÷è'ûö¥ý¿Õý~4ÿ|†üö£÷˜±ÿÑfÿˆòúRúD™ÿ»+ÿÎñþ)‰þÉ7ÿ•þG‡ ÿ\½ÿ<Ç dþ …þj?þtdÿ¨¯ÿ¦XÿƒAÿ þ4 ÿ}ÿodÿv:ÅjÿÖ˜ÿqèÿWÑÿž¥ÿÐÿ˜ßÿœ‘ÿ¡ÿ¼ÿÔš&Ë´O¨1˜ÞÈñ›ö›ü7(È)ÜïTw)}üþà‹ìä£ünKùdvù\Î ðøNG÷) ÿ“÷ÄVû>;ý¡³üÍÁþ~‚ý†4÷ûµþ«žÿ0œûÈ¥I ¾ÕÿÎuÿÐ4þ¡ÑþâNþ bSšþçÏÿ{ï/þŒŒþ|Uþ[ÿmcÿì.ÿ_ÿJ9þ^8ÿVÿŒÿ&RÿôÔÿ ÈÿYÂÿǾÿQ»ÿ‹óÿP“ÿ2¦ÿY*ÿE&]zMïUä Z@ôòúüËjT%Ûý™þáî&z·Ðî1Qý¶éú ƒõI Y« íö;1ÿ4 øÁkúÿ+«ýkáúb9þ~þÝÁöÿeàÿÔ£û"³ñÁÿUéþ·tþÿ'þzaSþ´ÿ“„Þý­þÛpþ&ÿ1Iÿæþnÿ#€þÊ`ÿ§Zÿ\cÿKvùÿ:ÿãÿç¬ÿúÄÿÖØÿÖøÿÙ«ÿ‹”ÿñ/ÿ2C#n8K~Ûæ‰ª>ösoøÇ½lÞOÞ÷ÚL"L› ?µñ_Ëû þõñ~ & n–öšïþ}ùŠøˆÛÿɵþ!ú7ŽûÿÄøs`þ|Êcˆû© ÊVÿYîþ¡lþ^dÿ þpâÿ²wþízÿœ¤®ý™þ –þ›þLÿë×þH™ÿ¸æþ{_ÿÿTÿ#fXZÿgÿ7ª§ÿ£ÖÿlÝÿÀ‹¶ÿÞcÿžAÿ7õ ÷¸HaÍÌ'è{}‹2ù¾Æóc¾ÿB«âÅaóQ–.kÍôå˜úQï·ub¶ ú÷¦`ÿŸúMõõÍÊþkÿ#SùÒ%úÆfÞÁø(9þõšúú*SþWì_þÃÿ<—þïîÿe¤ý-_ÿE4ÿ@ÿ×ùí¿ýêUþãþþ‹ þ{*ÿ¬ÿ–Çÿ±sÿœ7ÿ–­ÿ€2ÿq9Zÿ "ÿãN±ÕÿÈØÿ=÷ÿqµ¤ÿî[ÿ&|ÿ_Ä#¬G ‡’ìw‰ Ç–÷Øcò_‹ûË壊ó¤–>8mQô#pû‹ýíëm}¾ ψ÷m®ÿÆdù=ö!ÿü‰ þXÐøèšú.–5Áø5zþ5òüÂûRŒòƒ‚.þ…‰ÿ®Pÿ‰eÿN@ýþÈÿ‡1ÿ÷Úþ‰ÏKºý‘§þ­ïþfý¦Sÿlÿ¹AªÿF<ÿýÿ‡õþ¢éÿŒ6ÿ¾Jÿ‡€¾Úÿ#âÿNÊÿ|öÿ¯ÍÿLÿX}ÿâÎ °jE7”êyà üÂûVí\:ûßòësÖð¾ÏÔ?Ãéù—*ôê õî4{þ½Oç ø·ÊûÙ³ý,õRŸú_ƒýFIù¶Åùù´;öú³+ûÕGlýëDÆä%þ0`ÿÀÍÿÕÿÛdýZ¼ÿ²¶ÿtþëº|þ28þM9ÿÛ¼ýo‚ÿ×ÿðžÿqªÿ¶ÿ¯“ÿŒîþÌoÿó ÿCdÿ(j\éÿR¯ÿÂíÿï 7åÿöbÿÉlÿ&{!ôÁC Ú ©ìôâ=ûˆµë¯vúˆÞî\_ñE „MÝýÿ„ðP }DóSûraÚý³ÇøSçýæt÷üœøü†ýÞÊøY$ù}Šá‘û6¾úæ}ËÃýæiàDÝþ¾Ýþ‰ÝÿŸÿDƒýEyÿ¿”ÿ}þýf\cçþ'þŒ[ÿênþ ŽÿdËÿ¯mÿ˜wÿÈ2ÿ"KÿÍþ_®ÿ̹ÿá{ÿÃEÒãÿŠãÿ ôÿ¨ùÿË·ÿ wÿï ÿ5Ù â&A35 &åí¹½TKú+ëNEüy¬ï¢¢ðß›´aŽŽ8ùí8Ç7§öi/úÎÑ7€ÿYø†aý‚OùÛEø°.ý¿Üù»ö.LRý·ÅúÐ(¯ýXbÏò—CÿËÿP *ÆÿŸ(ýâ1ÿÀåÿÈý<>ÜKÿ.>þp¸ÿó¬þYmÿÌÿKÿ’ÿðþTÿ©ÿ²¹ÿ,ÍÿX;ÿ¥E¤4ûãÿZdºÿä§ÿÝ•ÿ»†ÿl Òñ>ô~ñï7±øð\ꦛaòVqî?QØlµ€:êÌvÖcûÒ$÷nc=I<øßýølú8øªðýµ@ûÞ\ôA¯þê€þüm;°hþÍ‹þùåÂŽòÿAþÿç7ÿ‹Îü”†ÿD®ýÁl»ÿçJþ…½ÿìÿq<ÿÃÿ)ÿœGÿÿ ÿÚ(ÿÈÿ¥ÿL\ÿeL¥ºåÿê¾ÿ?ƒÿjeÿÄeÿå8ù =äu±ìí¶È­6ù3ûæâä.ögímü䪚 |èQcú`˜dôùÏ´mùzû™»üñ®÷EÛýÒBþoòièúŠfÿü‡C 0§sý¢ŽÿB‚eU•9ÿlÿ‹´üK{ÿ…,±Öý§7ÿËýÿ©þ& ÿ­lÿƒ5ÿ4kÿÿrfÿðÿHÿ¢.ÿÿÁÝÿÎÿŠó JÞÿ¿ܤÿoÿwMÿUSÿ"sÎ`:Í¡Ý/ë®UÄ{ŪãÃyÉÞû÷?ï³õ‹‹‹ˆžÌèïAÿ®}óÓò•9Ñ- «.ýêwûÎ ý—Nù~PüùœÿHóøWÿR‹ûßÄŒOýÉý—„þhÑÓÊÌÿ´sÿÚüA!ÿc·UfþO¶þÄSÿô‘ÿ©›ÿ¶%ÿgÿ}Cÿ!KÿÜ5ÿ]ÿØNÿhªÿÅóÿ,xÿ»óÿä# ÊÿÊõÿ¦˜ÿ;eÿænÿ’fÿ@Yqô6‘3 ï[øè¦üé4æ¼LÖû½œð˜¿ô?¤èêÎu::*óÐÑ^w VÿÒºüþ`Wù³Îü<$þzâó­öùÕý€ßú.¶¼U ÿíþóvŽüÿæujiü[®ÿšP¬þKÿ VÆþÒ™ÿÌêÿÎ×þÁŒÿ¿[ÿ:ÿohÿŽKÿþ7ÿ°ÿ¨òÿ5cÿgÍÿ1ýÿÅÿ¾úÿ9¨ÿ˜Xÿ—UÿîTÿÑîL2ÿàìóDÇhàùòÎé”3ÐÍùññKèôW­c­ïë àÆyðÎóÍ™Ál ºT×ÚûéQyaúø•û€"þ9Lô-¦úU ÿUNú‰ŒþÏÀ²ÇÿðÊÿØœ!=ÿº»ÿH§»ý“õÿå––Çý)­ÿù£9³þP§ÿõÝÿÄËþ[ÿÅ;ÿŒ0ÿ`‘ÿÞ†ÿâ"ÿ+‚ÿP¿ÿŽ)ÿïÝÿDé½ÿróÿzžÿÐiÿCPÿ3&ÿµ†w-·š‚™ôU¸H/þÙÏé?E”8ûpÞòõ3œ ôTUï{ÿ4N 0ö)aþ6U ©~ðútdpý7Êú´Øýœ!öã>úÄìÿÉOû©´üßZ*J+½ÿ¨,´ÿœþ«ˆÊ”þ?ÍT7öý1qÿJ¶äfÿóBÿÄ–ÿÈòþµYÿ%Oÿ,ÿyÇÿØ ÿWÿ¤Nÿ”ƒÿ ?ÿžÔÿ s×ÿÎÿƒÿ8lÿtdÿýHÿkVZ*ÊFþL6õô”#[Øš2ä­V,ÿö ö¯}øI" )ð$êM Qè÷ ¸×•ØÿgJþE4CRÿû?œàÍ0úõkþw‰Öaûžaýý«þPüäü";ÿ«Zþ1ûý= ÿ«>ÿÐw$Îÿîýÿ@äÿ¼ ÿõìÿ)åÿÈiÿ×¶ÿ"F†ÿ‘YÿP^ÿÕæþ÷åþ¾'ÿ%6ÿ€þþ·\ÿajÿŠÿkËÿ²„ÿÔÿ–Èÿ#žÿwS¡»$Ñyþ‹ ú‡E¼ ÿÑê`z´5þ«÷VùpÓj݉ò º|LU!ùÂW•D?6)Aþ¹Cfÿ¹~Ø,úÌ ÿ¹¬Êûˆ$þ"Wÿ,ðüøüªØþ0þUˆþ¤þa±þl½ÿi‚ÿk(½òÿBYÿ(Ÿÿ2àÿlÇÿG‡ÿ§˜ÿ#ÿ \ÿžiÿ•Íþ=íþÆ)ÿ‹4ÿS.ÿƒÿÝvÿÿš±ÿnÿ޽ÿÃÿ‰ÿ>}0H Mµ© û{Ëç»á"ï³þdsþ–>ùIÍùDWL VòôæÚHËßúÈ\ÿÄ|r$Õ‡þf¬ÿÒcr]û‡aþ§¸¯üêdþÙõþ¨7ýýþ•cþßþ äþ1FþæHÿòaÿÈêÿv,Xºÿ­ƒÿŽnÿjƒÿ0Sÿ™tÿÒ3ÿ¢Vÿ“iÿçìþðóþ,ÿKPÿqiÿˆÿ¦ÿH¢ÿJ¾ÿ–ÿ]±ÿ1­ÿÀ‘ÿAX¤$£Â©/ýCÍ¿;ŒóYÝÿôÅþ¨ú@ÔùtFâªöÄáÿR{_üv6ÿ.ºu‰ëþaJ@ÿñD'Øèèû lþ*вý¹µþž½ÿ*£þ)þôÿ8oþBþíþ8˜þ ÿ" ÿؤÿ¶æzÿÿü0ÿû#ÿ´\ÿгÿ²<ÿ–6ÿ’Gÿ ÿúþêÿrPÿfqÿÜ´ÿÿ¯¯ÿàÿ¢¢ÿ´®ÿ:¨ÿó{ÿ¤‚ -H'ºýF7edðJö?fÿ˜äÿ¬‡ûú·ö8¡NøŽ’þ¥é¬ÂýXÿÏææ^ šÿ-¡ûGÿi8V±eÄüäþ—yÓ"þQ›þBÐÿ¦ ÿP~þ“,ÿ±þâÍþ&úþ¶ìþ'Bÿ3Áþ>ÿµbÿí)ÿˆÿ‘PÿoXÿ [ÿ)­ÿâ@ÿ…Eÿ’GÿðþˆÿÃ-ÿ[ÿ#”ÿ[ºÿ]Îÿ~½ÿÂÕÿ·ÿó¿ÿ¯¨ÿtÿûM :&¬Ej{þKœòûy“ø­PÿýÏ)üpPúÝ÷­ùÅäý$—~ÈþÅÕþoeòÑ!–ÿ¤ ïÀÿÖA˜Âc±ý¨ ÿך/‚þ¥£þEŸÿ˜ ÿ|´þøoÿ@!ÿsÿÚ?ÿ$üþÈãþ]þíþŒÿÍÿ`ÿk‡ÿDÿìHÿÛÿ;KÿBÿ‹&ÿ×ÿŸ ÿupÿ÷Àÿÿºÿ†­ÿÕÁÿòïÿê§ÿÔ½ÿΩÿ2wÿˆ_ åZš»PZ§|-ÆNû•u/͆µû$Àú¸¦ Î]ïùÆÿin ÿWÿ%œþè+mÿ]ŽÂóÿœì3÷ÿþ˜o1%Jþm«þ3tÿ!äþI@ÿ'ËÿêYÿm%ÿ$ÀþžœþÄ™þ·bþ¬½þù#ÿÿÓÿ£Zÿ*ÿ´QÿLkÿï=ÿ"Mÿ^?ÿ–9ÿÚUÿ5”ÿ‰¨ÿ*Žÿ›ÿU¨ÿ-Éÿ¬Üÿ ³ÿSžÿ˜ÿ)dÿÏ5 5<¡~뤤 véÉùü™7Tï^ûûÀPû×J!Œ¹úœ/ïŒ ÿK€ÿõJxQñ#.¿ÿ¾Ô¹= þªºÿi¼ÿ#sþͺþ –ÿ†bÿ³‚ÿò ÿMòþïÊþê¼þ ÃþÈÀþöŸþÍÌþL»þ¸Ùþñ6ÿŽ&ÿI5ÿ¿_ÿ¥GÿÇ]ÿ†{ÿ@pÿAÿ'OÿÏŽÿzÿßžÿ«¸ÿªÿ Èÿä©ÿ+œÿ•ÿ†oÿŒhÿ6ט>ìÞ6S8 '“¯[þeû‚¿¦ìü”û‚ä3t3üÎ ²f¨ÿâ X˜/¿·ºÎÿVÚ¥zþƒSÿÂŽÿ;Éþ ÿïÚÿšOÿOæþÿSãþÑÿ¼ÿ»âþ¸¯þg~þ?£þä¸þCÆþÿ[3ÿy?ÿMuÿ¢YÿS`ÿlzÿÇbÿµMÿ‡FÿsÿŒÿùœÿ°ÿ“£ÿâžÿ‹ÿÏÿo„ÿFrÿÑfÿ#„5@ ¬„® ?F´Ðÿ2p¯jÝýù<üp¬o ú.ýÑÿì˜éÿíçW…qÛaÿ:—‰Õÿ¼¡\Ãÿ_sþ9zÿXÚ#ÿ‚üþ&LÿʪþâåþWÿ_3ÿ×Sÿþÿ[³þ°•þ)ˆþš£þ°¼þõþ[#ÿ§>ÿ4eÿÌkÿ’^ÿZ]ÿo[ÿ¶mÿo]ÿ¨Eÿ9kÿÕ†ÿêŸÿ¢›ÿ¾ÿ˜ÿZuÿ oÿ‡ÿ(ƒÿ´~ÿ Þà »ÝuÞ`L M¢KCªÿ€Ñoòþ¬—üä¤dïè¯þˆ¥TüÊ\(68­Ä&G1ÁÿK<ïOÿs¬ÿÎ|ÿyþñ¯þB=ÿÿ*AÿøeÿèGÿ6ÿ²Úþ Áþ©žþŒ“þëÎþØÿûþŠ2ÿ«sÿMVÿ›VÿN^ÿ„Oÿk_ÿ*lÿfRÿéLÿìlÿ˜ˆÿúœÿ’ÿ“‡ÿ*Œÿåyÿ·vÿ°“ÿ„”ÿÃ}ÿEÑ*Š 0ˆ× Mã-òöÕòÿwâ’þêýâÐ>V©ß¿UUMW; é,:»ÿ;B¯÷<úþÁºþáÖþpÎþÍÿ¤_ÿêNÿÃ=ÿn%ÿ.ÿÿõÓþ'ÀþÀ×þ+ÍþøÝþÿÁ%ÿàGÿTÿ @ÿÏPÿûTÿßDÿ$PÿdXÿVWÿ_]ÿ=yÿ”ÿÿˆÿ9‡ÿ£€ÿ˜|ÿuÿ[ÿ…ÿ€{ÿühñêî’B²)$ž…èbIÿøæXžÿ µþöÚW€p1 ;ó ~ÐÙî¼—v»ìÿäÿ°†þ®õþÕGÿLÿƒ ÿ…7ÿ€ÿÝÿ‹?ÿ,ÿúþPèþ¦õþÔþëþÿ³ÿ¼'ÿ4,ÿt3ÿ§Jÿ0DÿÄOÿ,?ÿ?ÿÅhÿ'kÿfxÿG‚ÿÕ~ÿw}ÿKpÿHyÿ*xÿßtÿ’{ÿ{ÿœ‚ÿêÿD­Õ*Ûªi¨Ž|kÖˆG5k˜“B4ÿ¯b¶T¢xò-€÷ÿ Óƒ yÁÿÖÿ,nÿdÿšÿ›%ÿ Nÿ™üþ$Øþñÿ3=ÿ)ÿ¶HÿžZÿ¹ÿìöþ* ÿFÿ‰ÿ^ÿcÿB(ÿ5ÿ§;ÿ7ÿóOÿ{Nÿ½AÿèhÿÀ…ÿÃ}ÿ’lÿQÿ `ÿD~ÿãxÿ4‡ÿJ†ÿkpÿ¹rÿ–yÿ?€ÿ‚ÿWº}i8»]+…ÜÞïÏ>@a nrÿÏš „b²hR˜v?¥¿Ûéâ4. ÿ&ÿúÏÿeÿùÖþúþßúþ0ÐþoîþJ+ÿ—Qÿ fÿ¦Wÿþ.ÿ˜!ÿ­ÿ„úþÇòþ®þþ5ÿ¾ÿ”%ÿR4ÿAÿÿ½ðþûøþJøþ†ÿ3.ÿx6ÿHÿºHÿTÿNfÿçJÿÅ>ÿÿMÿTÿUÿÿ\ÿBtÿ)„ÿø~ÿqÿ^`ÿBlÿ#vÿKqÿÐ|ÿÀ|ÿ…‡ÿ{Œô>†öàÝø[0bQ XÀRS·,G¥r¶ÖqÁ1ñcÿ£»ÿ>§ÿèì2 Óÿ:«ÿÑIÿ¥"ÿwÿ*ÇþÚþ,ÿÞCÿWAÿÂLÿäÿ…vÿæ>ÿÏ2ÿ ÿøÿ¥ùþ&ÿ#ÿˆÿÅAÿQÿÃ<ÿsYÿ1Cÿó6ÿíJÿIÿWÿüRÿmÿ›ÿ­\ÿàwÿ…vÿ#Rÿ~eÿKnÿ†ÿ‰ÿIˆÿ’Ÿÿ­}ÿEŸEN4ž³‚àag#½Tèè£ëuí¤p?À?ÝþTëþðêÿs1³k‚&|•ÿ BÿLÿ‚]ÿ×ÿWÏþÁÿ3ÿ›0ÿ±:ÿ÷pÿ¬˜ÿD~ÿ›Yÿê5ÿ×ÿÿ–ÿÿy)ÿš3ÿ4ÿN2ÿlMÿÏ?ÿ<ÿÒ[ÿ NÿXGÿÎcÿÖ|ÿ[]ÿòKÿ…sÿoÿ¾rÿ€zÿ6dÿƒÿ-žÿb‘ÿ’•ÿ¤ÿçžÿfQ[–êG•J“ÏI}ûâ‚’¹ôœQ*½Þ!ˆ-íSÿçiÿ«1.mœþÿHoÿ‚ˆÿpÿ)Bÿ(ÿLõþÉÿQ;ÿâÿ'ÿånÿ§žÿ4xÿýiÿ’Tÿ£6ÿa-ÿÿN#ÿÿÿ£ÿ®)ÿuÿd#ÿóDÿù]ÿêcÿV5ÿ=dÿwÿ83ÿèAÿ•lÿv€ÿrRÿœ\ÿÚ´ÿÏ€ÿ¹Zÿâ“ÿ°½ÿY²ÿ1dÿ”ŒÿÖuÌâæšGäâ²Gmf Бp¨×ÛxvJJ¡íÞUCÿ}™ÿl¹ÿ*Ÿÿj#ËÿàIÿ¯ ÿaGÿ`xÿƒ#ÿ0-ÿ+wÿ;nÿ„OÿæJÿ¬lÿ+{ÿMÿÂ7ÿ™Dÿ`6ÿ%6ÿïeÿÚCÿõþF&ÿ«7ÿÔÿ›5ÿ_ÿAÿ=ÿó[ÿÂyÿX=ÿ—ÿ–ÿ™ÿ•Aÿµ‡ÿPÈÿ~lÿÐDÿeÆÿ_ÞÿÜdÿµ[ÿ®»ÿÖë,êõð¬’OÔ1™ªž¡QpyÕ2Ilº: o«øaÿ×,þäÿÜÏÿ2Èÿêäÿ40ÿ<ÿ¨bÿ£XÿeÿÃvÿfsÿïPÿÚ‡ÿ¥Dÿì1ÿp‡ÿÆ6ÿ;ÿrÿåZÿJfÿ¥BÿKÿ×ÿgùþNÿƒ>ÿÆBÿé%ÿ•Oÿx™ÿâgÿò,ÿéXÿ#ŽÿnjÿLÿÕŠÿ½•ÿÕ^ÿ7yÿ×ÿzÂÿyÿ˜ÿM¼ÿXªŠ¢È½ø·}YËÆqÏ!Ýü¶³6ÿõ´O-Ë:›á]¬ €{þåðþºsÿ®ÿSåÿÄ«ÿl&ÿ*/ÿÉ™ÿxÿ»8ÿ׆ÿ­¡ÿNÿ…3ÿ.BÿVÿ?Sÿ85ÿwÿä€ÿÿÐDÿž\ÿÃ!ÿ#ÿQÿ.ÿ‚/ÿê>ÿÿ[ÿ-Dÿ}+ÿÕLÿ´ÿï‚ÿÖ<ÿÔ†ÿã·ÿènÿ?{ÿyëÿ·ÓÿãTÿnuÿeôÿ+Ë'ìYíÕŸ@/ЭØýv÷,qŒñéŠws®D„ª…+@Ò IõÍìèþYŒþã,ÿ?¿ÿ܉ÿÓQÿnGÿ‚FÿüYÿ|ÿêÍÿ9rÿHÿkuÿçMÿY/ÿ_aÿã®ÿ-Mÿz ÿøaÿÿOÿy0ÿhÿi3ÿ)JÿGûþÎ3ÿv_ÿ:ÿT÷þÆ^ÿ/Áÿ,aÿ˜eÿpÅÿ×´ÿ\ÿ†tÿ:áÿŽžÿBcÿB“ÿ1ÁÿãL±P´â܇œSK£€ž’ÿ¦ —Fè’3ÀëD?ßòÓëÍ Uð¬oÿSÇþ’—þ‰Òþ¬ÿ8ÓÿÐ_ÿÜ"ÿeÿ¾ÿ}Žÿàaÿgÿ©Xÿ§LÿÉ~ÿ¤[ÿDÿ´@ÿ Cÿ˜Uÿ9ÿà&ÿÒMÿcDÿ5ûþ$ÿ"nÿlÿÃÿ®eÿú†ÿõjÿ’lÿÞÀÿ%Ãÿ{QÿtÿÌàÿÔ³ÿvoÿÓšÿ_¨ÿ#„a [@ñ‚Ú&cQˆS‰ýìÅÿÕ]m|lÿýëÿ¨°D¯r„÷tŒH¨û(i´€bÿ}þD]þ&ÿz~ÿ’ÿ “ÿ#qÿcPÿäoÿqžÿèdÿiGÿ³vÿ³gÿ¼3ÿß6ÿ0Oÿß@ÿ„!ÿ‰7ÿ€Uÿáÿoÿi<ÿUBÿV-ÿÝ)ÿQÿ°†ÿÆmÿ^yÿ˜ªÿ_†ÿ6jÿvšÿ!Ìÿ ±ÿU€ÿdÿ—ÿF0âo Yn4Î÷e^¤åæüÐSÿfXl“qÿèÛÿÓ…>ÂneÅ ;XPM¿óp2ÉÏA/[ÿ·²þ”‹þ|ÿG›ÿ»ìÿ©ÿKÿ‡7ÿ^~ÿ|‰ÿ`ÿRCÿÝdÿ aÿr"ÿ˜ÿC`ÿ~Vÿ§ ÿÒÿ4(ÿú-ÿ-\ÿ5ÿ46ÿEoÿ9nÿ“|ÿu‰ÿ˜ÿµ‘ÿ’ÿ¢›ÿë°ÿ»ÿ3˜ÿç˜ÿþ—ÿU«× dQ ¬öÈ´¥„ÿgôûTØÿ,wâšÛýozÿ&:ŠñÛ~#¼ÿ(7Õ†dÞö)¥ïz.û8Æ:“.ÿ²×þ¨ÑþmŽÿ„µÿµJÿðpÿÛ1ÿÕ<ÿY“ÿ'RÿñGÿåKÿi:ÿSeÿ†Gÿgõþ;ÿ÷1ÿÙ(ÿZ{ÿ_eÿ¬ÿêEÿ·iÿM€ÿ—PÿÚcÿh¨ÿÔ ÿ§ªÿ›ÿ´™ÿ2‡ÿ/”ÿª¹ÿ݆ÿ%åž3\„¡˜ÿÚ™ë®+ƒeþéöú„=7V¾[˜~ý&Oÿõ—ÿz®œíŠÿjéÿ_þÄøÿÌ]}è‡Åpû'P›?ÃÛ7-Eÿ¡Xÿ—7ÿÐöþ¦YÿÃ]ÿécÿ'Rÿãÿ¹xÿ¹ˆÿËFÿe0ÿH/ÿp1ÿÃôþô*ÿþsÿÆdÿ`wÿ RÿQiÿIyÿMAÿÝQÿptÿ ÿ‚¯ÿÖ¤ÿV™ÿ&¬ÿz¨ÿ»„ÿ–ÿ©ÿ/ã¯lÌþj~zÖ»™÷ýI³ú´FV^/ÿ&Lý¶«ÿ…Xÿ¨œ‹hÿàäÿ1ùØwÿ9Çÿ.þÿ úÿZx l¸ÔuÉ6“……zÿû¹þÿÓûþÌÿ‚‰ÿ]ÿrJÿ’|ÿ²Cÿh7ÿ¾<ÿb ÿl1ÿ1VÿbFÿF†ÿƒ”ÿÄeÿ,mÿ”Xÿ¶lÿ›ŽÿÆ„ÿævÿKÿ\ ÿ¼œÿ³¹ÿž¤ÿ—ÿ\¾ÿï”ÿ¦— gÊpw2ÿBÁ†õ÷þ]û‡µú(ã*ûPý_rýÀ£ÿ´ÿ´ ðüiþ9F}ÀùôþϺÿ0¢ÿ[QÿŒ[íÿŸkÿ£¬ƒñ ©|RÀ¥Ñ‹ÿPÿ¸Îþ"ÃþÀ%ÿÚ?ÿ…kÿr`ÿý.ÿë=ÿìnÿVHÿöÿ`Wÿ#zÿœÿ…ÿº,ÿ¶qÿtªÿ¡šÿ•…ÿÿ‹ÿâÿ#Žÿ8sÿM¡ÿ¶Ðÿ=Åÿ´ÿ˜ÿ± ï.p'ÿÖ›/Ú@¯ý/Qù=cû³æY Êû¦ùýp®ÿsáþ5~*|Áãý½U¬Š#°þø¸ÿõ}ÿF ÿWÿ8ÿòqç ”PÎIh™èEùVÇsÿ×ÿ¶ÿ»¤þ›øþÞÿÃTÿ³Gÿ¥[ÿõBÿŒ‚ÿ}qÿÑ\ÿ‘ÿøVÿ¸Zÿµÿ7 ÿzÿìÿ¦ÿS¤ÿK¤ÿÒxÿœƒÿ(Øÿ-°ÿ*Ÿÿ_¶ÿY•ÿ3 YÊ” æ¦þ½‰‘Àd—üvÕ÷A|û, ¾Ü7Tú ñý‰œþg“µ‹ZƒýÊ'{©Rþ©kÿ^ÿÊþh!ÈBÿÃUþFN-Òÿ¶³3>åíÿîclOe­ÿi7ÿ¶3ÿ-6ÿn/ÿŠúþ)-ÿ…¦ÿ<}ÿ?Xÿ˜_ÿV?ÿ^€ÿ ½ÿÍ€ÿzbÿðÿ5|ÿŒÿXÂÿp›ÿ÷’ÿ’±ÿ§ÿøÎÿ¢¤ÿ7~ÿ —ÿì}ÿ…ô Õg5 ?Nþä¢;îûPŒö¶øûSß;d\ùE¬ý)œ£þ·‚¢xRýÔÏÿqY!þKÿ„ÿýˆþ½ìÿx#ÿQþU¶ÿô®ÿ®ÿXÜÿŠ‚ÿäãÿ±ÿ´ÿ=0¬ÿ¸ÿ˜×ÿÒ ÿØ ÿ†eÿ“_ÿXÿgÿÅPÿ† Ú¿ÿP(ÿFÿ³•ÿn©ÿxÿ zÿµÿ™ÐÿͼÿH¢ÿ.Ìÿ¯ÿW†ÿK€ÿ&CÿÈ tÉ:²¯ªþ\füÖ—\üÛ×õ)Àú¼GŠ$pù.ü2hž»þLô¸;;ªýK«þA£ù–þoŽþMÿ) þJ[ÿFYÿ­þìaÿ9¾ÿè[ÿtR(! ÿ¶»þ\vÿB«ÿ«lÿ³ðÿ=Ûr$=Lÿs•ÿQçþ¹¥þ-rÿ<¤ÿá•ÿoxÿ½Uÿúbÿ+žÿã˜ÿ®^ÿ½ŸÿxÂÿMªÿF¬ÿ ¢ÿÿÛÿ•ÒÿîvÿûcÿþWÿȘ §7Q2ÿÀ =Ñú¿õAüߺ–ù˜8øKÎüŒðÿŸ•þýaUæ—^ý>ÛþOMì†þ]¾þk§þVÈý4;ÿîÛþüÙý;Xÿ zÿlËÿ±uöçþ@¬þIÿBüþxGÿ…sÿ­Oÿ ôÿ˜ëÖµúÿ¶¢ÿYÿ©ÿMÿžÒþxhÿ)žÿîLÿ³CÿèuÿqÂÿ£ÿŠÿãÿð¤ÿý¨ÿh§ÿ4Èÿñ¸ÿìvÿÿkÿeaÿš] Ðz¯Þÿ¸†s~ü±Àõ Áú ­ú¡6ðøb=ûä'¬¤þÊB Ié%þ~ûý(Ñÿµ ÿË•þ@nþ»ý_ìþƒàþYýEÃþùëÿ4Hÿ‚ÿ?•ÿ\Üþwþ| ÿAEÿë)ÿˆÿ7¿ÿm^î]@ëÿåK'*=ªÿn~ÿ@ÿ°Eÿ7jÿc(ÿÀÿUyÿµœÿ‡ÿŒ‡ÿÿͪÿ¤šÿ­™ÿG´ÿl¸ÿé{ÿÏQÿëoÿ­ o$h„¥7ÿMNö—šýc8ö{¯ùr|­4ŽmùSÀû$˜ÿå2þeA¥.ÑRþ0Vþõúþ3þ-ÿ×Åþ•—ýæqþÿÍþBñý;þEþÂþF':ÿþžÿ—ÆþC$ÿÍXÿ‚Ÿÿ”ìÿ!ÊÿDåÿô-D;"Íÿ#ÕÿØÉÒÿýµÿ {ÿÓ/ÿî$ÿÖEÿû|ÿ*sÿ4zÿ¢ÿKÿ“ÿ‹¥ÿgÑÿÔ°ÿÔ‹ÿ•wÿ1eÿé» pK/З bî <Á'þPÈö>øÓÅÞ#rú4üyþBþ2€õ€sþ7`þ lþÍíýç¹ÿÏÃþ(0ýBZþÿ«Wþºåý®1þ Åþà…ÿXqÿ&âþ6¢þ?Íþèbÿ@±ÿŒXÿîWÿˆ Þ=ìøÿ ”ÿQkÿ¯Èÿò)ú}çÿà ¼¬ÿ):ÿ3BÿMÿZVÿ–Xÿ\Tÿ`ÿ ~ÿC‘ÿ¿ÿÐÐÿ¢±ÿ”ÿ’Vÿ³+¿—ù"2t¾ óŠþ‡kö¥›øŽ_¼M·@û= þRýÝ þdÔÉÂ0þ<ÿ•Üýtý/çÿRùýmýÄ=ÿµþùþýÞðþû“ý¯%þëíÿR2ÿ=«þY¿þãþë§ÿ·nÿÿbÿÿ <ŽˆÿdÇÿ:¸ÿüÿªÿ…tÿÉ¥ÿG×QƒãÿЫÿs€ÿº[ÿÍLÿ*2ÿg3ÿ‹Tÿ9kÿšÿÓÿä§ÿL‘ÿ¤zÿ×YÿÒz‘ >; ¶Oaûbê÷,Nù:þIÉþ’ üCû‹¦û| ýCâ—ÿ®Yþünÿ,ýûšüð·ÿ]5þú8ý;xþ žýàÕþéíþ}OýNþKÿ9ÿ5-ÿ(íþ £þâ±þ ƒÿfÎÿ‚÷ÿ~Ðÿcÿ˜òÿ9šÿZNÿÿ‡ÿŠpÿ.{ÿL—ÿ(< ÙÍÿÂÿÊ—ÿóŒÿù*ÿ41ÿƒ[ÿ’oÿ¡•ÿŸœÿû’ÿ Œÿ‘ÿ_ÿ2ÊE °ï j8Vßõ(‡Á÷^ú&—üø¤ú,ÉûÔjÞû0Xûƒj‰ÿþ±oþvb]ý,¬ûK^’Âý ~ûqlÿ›õý(ýTçÿ¼›ý}êýXÁÿþþ¢ÿ€ÿþÞÛýcçþ–èÿ²eÿ¸¢ÿÿA¢Cÿï ÁÿÉ­þ¦ÿ:qÿ¥Aÿ_ÿŽÿ²ÕÿÇÌÿŠØÿâ{ÿG¤ÿ©·ÿ-wÿhÿ¸EÿßvÿÅÿ÷˜ÿ’ÿY¦ÿUÿ®¶g#Î6Ž¥ÿN€¬!|fô‘bûnÓ²rëóe¹ù{Þ NÛý>¬ùuõIrþöý°uAýT2ûäòÿÃTü{—û¶ÿ´2ý êýúWýNcþ+vuÿÌþ9ôþ¦þ¼ûþMÿ%ÿ¬Íÿ‚0!.ÿwéÿW¡ÿµþk‡ÿCVÿÿ-Cÿû†ÿq¸ÿ<ÁÿÒ·ÿˆÿo²ÿÇŠÿ\‹ÿ¥ÿ߀ÿ³¢ÿyªÿÈwÿmÿ­âÿFÿ/üø!&üj(®û ® Réñ@cû ¢}¾29îBö{Ï ¶…«6ùV±ÿäÿýü0ý½b`ýýÎèùE.ÿe£üëvúPÿNþg|ýÂÏÿZÎý£¡þ8B–¨þºþF˜ÿb(ÿ ¥þ$*ÿº?ÿÏ ÿˆVÈêþÉXÿÞðÿ™Äþ"UÿöRÿYæþl&ÿfhÿxÿÔÄÿJðÿºlÿ¡Uÿ€ÿ–‘ÿ—´ÿ ³ÿØÿkåÿÏŽÿg‹ÿ¾ÝÿŒŽÿâ],L*Î2Bô\• ž+oñ:8ùT ÓmíšëÉéð°Ö š->`ú‘üýã<ýroüÇ(=Oþ-6øÜÉÿí üü®ø§Wÿä·þ¡žýsßÿ×¶ýCÀþ|eÙ»þWþ/¨ÿ0ÿ>¦þŒ4ÿÚ"ÿ¡ÿâ9ù—þ ÿ"ºÿ¦©þãDÿÜ9ÿÍÅþ+ÿàmÿyjÿõ©ÿÜÿàPÿ^SÿÊ]ÿµ~ÿ¯ÿt™ÿ îÿ%'Ñÿßpÿ©—ÿÚ‡ÿßaéz.ë}ís·ö³’òÉG÷™ cÉ ú7êX³ë|ý ô( rªýmˆüíËû 3ü\HÂ]þæ÷´ÉÿYªü«<øiXþUÿa€þ\¤ÿ‹Ýý÷HþíaMPÿ»þý,¯ÿ·ûþ'âþÏ'ÿP4ÿlÁÿ:Çÿ˜þþ»þ?ÿµ‰þh"ÿnÿ¥ŽþÄ$ÿœˆÿÞ=ÿnŽÿ^Ñÿ4Kÿ{8ÿÂzÿM}ÿEŠÿñžÿµçÿªÿ bÿ­Ýÿ®Íÿ$„"3|Áý§éÎnKÙ'Yô¡÷¬… ‚C æÇîëMÛ âI ¡&ѾûoúÜ-ü—K¸üÊo÷Ê qûˆø zþ\Ãþ‘ÿÍÿi‡ýs þ¶®ÈŠÿg þNÿ˜çþ ÿ‡bÿ*ÿÿ-„ÿ#^þI”þR;ÿÛ¶þ8ÿàBÿ®°þÐ+ÿiJÿf.ÿÆÿLÉÿé6ÿ)ÿˆÿ²ÿ „ÿ+{ÿU½ÿ°ýÿešÿ˜nÿ}Yïÿ»Æm8ùäú²_âø]9y¢÷M9÷—3 Ç ºãåˆê3c» ÛÓ+ïû˜kø&-û%Ý$’ü‘ö>ƒÎ-û³wø€Àþ°þŒ¹ÿ„×ÿ‘ÿüøãýØøÿa”ýü[ÿîìþ½WÿâÍÿĬþ§7ÿ…Pÿ’&þD~þÿÿ7äþòIÿ6ÿ|œþèÿQÿüçþJÀÿÁãÿ %ÿ~Dÿ|ÿ¥‡ÿÚdÿ·^ÿï¢ÿEתÿ—Qÿ˜éÿÛÜÿ°I$ëq=AÃõŽPÝ$GÈ(È—ú7ø X O¬ NÐåIK鉒š5 tÛù~ýàÊööƒúdÒ@—û@üõóy‘Ÿú.ÆøþÿƒÌýxûÿs¶ÿ8Çü×Mþ_?h}ÿO\ý»¦ÿuñþ¸ÿðäÿyþÅÿ¥dÿ›òý^nþ†/ÿÎÿÅ\ÿTÿÁþéÿø0ÿÜþrÕÿq×ÿœ,ÿ[ÿEmÿ¤hÿ¦TÿOdÿµ¯ÿR.¯ÿÆRÿéÔÿo§ÿ\ú&¬Bó;󵨸b!L¯ûÛÇùä ‹åæ!烠åv%„ „Ì gƒÿ߯õ€õù¢BÔú\|öJzýùü3ø4žÿPáýgVÿ§ÿÿöü-ŸþrW·–þ+Xý¤ÿÿ¯ÃþÄÎÿ{ÿ7Ÿý4ÿ·wÿ¢—ý2jþ¨zÿ ×þI|ÿ——ÿæ¼þ©ÿh(ÿWÚþî°ÿW¾ÿ\%ÿ‡Kÿ&HÿEBÿQqÿò‡ÿ]©ÿu%}ªÿUÿÚÐÿAÿFM+†`E΀íNÆÖF’ .pwû9)û.˜ j¯3†çBÍáÿ|î €0 B´˜$ö7úù†ãù ›øàV–Ðø‰sø­Ýÿ>Íý\¦þfÆÿ¸^ýªïþ¯F¯”ý\™ý\_JPþ‘žÿdéþ6ý‰ÿcÿKšý¬«þâeÿÓþñµÿ¯ÿ$„þ•Oÿž[ÿ‰žþÀ™ÿ¯“ÿáÿ7AÿQÿ×@ÿ†Œÿzšÿ?±ÿc- ¨ÿUAÿÕÄÿÚÿ™½*ÂðÏ&Òž ª·ø ý´¨Á›–îëfrÚ3Oz wÔ `:íZø¢ù½xkÂûæËøÐý:ù1¬÷ç¦Ïý—ÄýX òýµÜþfìê„ýÿ%ý¨Õ$òý‚ƒþ¾ÿÔ„ýƒxÿ¬Jÿû½ý«—þK`ÿ¼$ÿõ’ÿ÷™ÿTŸþjÿ­„ÿ+6þxÿÝ»ÿCÙþè#ÿìÿXÿ6Šÿ‚ÿ?Âÿ_2—Ôÿü.ÿr«ÿ£ÿAÉ.¸¶Jœ¾ê;ÕQ<£ –(÷GÞûó0 ³‰ 9ërŸ×ŠŠ7 w ÝæÙûXâ÷;<ý”ãøÞ ¦»øâ2øJYš›û…kýƒÑÂ`þ8Gÿ<2;€ý;ýT¡ŽEý×ý”aÿ› ýUËÿ¿Øÿvý+Âþî¨ÿHÿÕÿ­ÿÆ(ÿ£=ÿ9ÿþŸ™ÿm­ÿ[­þT/ÿHÿï`ÿ¨uÿvÿÑÿÄMQÛÿŸBÿ}´ÿzÿ·½0çÕKv é°R×q a‘Êõ¿.ùùå ô/&É„ë |Öû.ÿhƒ ᪛—~þ*ÆöíÕþ‘ÿÇù÷‡òãZù3Íør€N9úrý^üNìþÖdÿDiMQþ€²ü¡£ÿ—0ý±ýÏOÿýKuõïÿlvý|fÿ{„ÿ¯úþ¶ÿÖ Ö2ÿPÇþ/:ÿõ$þ¶mÿ¶ƒÿw½þG`ÿ—2ÿgTÿ jÿûqÿµ×ÿüa­ãÿéXÿ¬ÿeMÿ¼Õ1¬µMÝféy×$†ìWxô0kõÊs -Ô*p`í7ºÖsÂüÆú.I ÉDCؼö–ýªFH÷[ BúnSù4 Ãkùd…ýUå3Iÿ!cÿ¥IÇŸþD~û+ÿNkýýtÿgµýEÕÜÿ0sþõvÿ;¹þT8ÿ‰ÿ¿¼ÿ+Îþóþ—^ÿîýs#ÿÜuÿ,ëþcsÿÁ'ÿäVÿæuÿ›`ÿŒÚÿ+}s÷ÿ/Zÿ$Œÿ<ÿ€71¬6O6¡ìQ±Ö¿ïÛ#{óP.ò¼q -¬úñ ØËzù‡ñí? hqPòø¿ü‚µ‘m÷éOÛ&ûe¡ùáK^Gùd\ýE‰h…ÿ©ÿÛÒÿÃÚýÍ`ûµþùý-šý3¶ÿÞ¬ý,inxðþjÖþÚëþ’œÿ¯&ÿ^ÿ}üþúÿ®ÿÔØýÿT‰ÿ9ÿMÿ5ÿâWÿó|ÿ§rÿ¦éÿÞƒÞÓÿmUÿbwÿ 2ÿ˜?0~åO •ð'îÖËä/z ¼ñØÉïõÊ -á@ø!†Ú5æõJCâ š/VµâõùrDýÊ})ø· sû¯ºù.œÿD9ùý$ŸpšÿYóþ×Bÿq’ýLnûbþŸQý&OþÈ(ÿ¸îýÊØÊ*¥Êþ³HÿßFÿíôþ`íþ®ÿ|âþ$ìþyÐþèý¬ÿ“ÿ†ÿ0ÿ@ÿ–Wÿe¤ÿŽÿ§ÔÿoÅÿ³ZÿPmÿJ9ÿZÚ.&þO9Nõbظhçv,ñï î'èþ€)+#ÙÿÝÎóDCÿWe uCI¼ÿ^7ü”ÛþD*þ´ùB˜è û_ÜùX™þjù¹Rý)…¡‰þ-òý ýÿ-ñüè‘û0lÿžßüŠvþgÿS+þ™› ŸzÿX(ÿÿ ãþŒ6ÿ8ªÿÇTþX'ÿÎâþ½ãýñÿ eÿM8ÿš1ÿTÿ*wÿÅœÿ-—ÿ‹±ÿY1ÛÿÊUÿxyÿ3>ÿÙ,ºœOt ûöfÙ´‡Ió‘òn îýÃ÷8»'õ¬Ïßq©ñáÃýœEEá:ÿ“ôýÞ'müª­úí¸yú©—ù/ÌýSúù ýLöþEýýyœý\YŠ/ýƒûûþËÿï;üd:ÿ&ëÿyEýqÜ¿&ÿ’šþ±dÿ—3ÿíõþ@yÿÀ,þ¹lÿ·ÿn·ý]ÿöIÿ¨4ÿgÿ‰ÿoü1äÉûþŒÄÿ®fùÚVûpIùoü¥ÑùòLý=   þ#ÄÕ¾û·dý‰5ÿÓ[ýl\•`þªRÿN1ïÿJÏÿ‚§þyÕÿÔtþ7{ÿNôÿ¬ŒþeŸÿ¦Æþx´þ=!ÿ ‹þLÿ’˜ÿ6€ÿÜGÿÇoÿ'¿ÿvëÿ£Œÿw*ÿOÿ„“!€dDp mê+®S²ÿ2þë_Žó:Ãéªÿ}‰7 éëlÓþÉàê;óí9 ÷ùÝ̇ðüRÁûø)âü{yûãIúâü5Èø*„ûY]Œëý­%¤þÇ—þù ÿ]ÿPNÿ#\ÿç“ÿ=•ÿkqÿZÑÿ>ÔÿÆØÿbÿ‹CÿVÿ ¬"™C y°è–®5ŽÿÑ}ä¶;÷2ñŸüÙu° fò·žös^ mi÷P=þ$ß fïúTÿ›EÃ6‡®I"ÿ–¤ýæø0-þ÷ûßfø„Ïü¹@ýÊÔÿ–hýüCý¥wþ\Úý¼P…Óÿ'Mÿf{ÿ˜Uæ¬2ÿ>?ÿ~;ÿ!G;ÿA?ÿr ÿ#­þzñþ¶(ÿ2ÿ?eÿx¬ÿÜÿ|”ÿÚÿ ëÿ|¨ÿˆ„ÿAMÿ iÿA¤!3­B´ËvÀ䣇 £)ÔÝŠAù):ø;÷øÜ=<´ßøœñü $×û¡zù Í ïJü­yýµ Z zá,tÓý`LùWþä²û宸m{ü fû€\ýäPþKGÿ ½þ\ëüPÿ5‰©€°<ÿÌÐÿéöÿrqÿ–ÈéþüµþÅ?T‰ÿ®Zÿh.ÿËÉþ×ÿ.0ÿÜëþpJÿò¯ÿD˜ÿA†ÿQøÿ›ãÿ/zÿ,¦ÿ°Jÿcÿ_l"?Aê‰üÎØäk…'ˆ®ÃÝØ¡°ü”_ü1øô®eÍ $_þˆZðÙn ©ßü½›÷OB ÷zý#“ûiõ&±(iþ¶ì÷ìªÿ0gûÌAøñ`þÜ,úV‡ú~Tþã_˜Gÿ ’ýŦþ°Uÿ§)¥ýþõáÿ0žÿ’ÂQÿݺþ’ÂÿÕÿ“ÿ5;ÿÊ"ÿèÿ`ÿŸÿÔVÿçcÿ=ÿ+ÿöÿHÔÿg}ÿT§ÿA|ÿ¸qÿu¡#Ýñ=G"õë é6ˆ-§büÊ‚ØK „Jþ¶\ö)Åýàó•-;«ííœ n¿þ’@öc £ß[¬ûÑvXÎ_^løß~†.øØÃþÇ®üòNøÝùý1 ü~ëøxEü³m¡ÿ¿·þbÌþ€]þ)7GêùÃÿ&`ÿtYÿ&CƯÿ}$ÿpTÿ&óÿß ÿ“jÿp‰ÿ$ÿ1ÿK!ÿJ_ÿXAÿHiÿûeÿH“ÿòæÿlÿ­ÿZ¸ÿõrÿYÃ!V^9Æïó)8í32.áùù4KÛÿ9ïüÂòô0ºüÑ ‹2²$ìÆó ³»ZVô’?P§Gü¨Sÿ¿Õ¨{µ’‰Éø½Åþ\Èý&hø¨]ýÀcýç¡ùeûð’ÿÝ–ÿ̧ÿ=:ÿaBþ§¢ƒ‡[Áþÿ‚Óþå¹ÿŒåÿPJÿ,ˆÿ÷Èÿäqÿ¶ÙÿUÈÿP)ÿ ÿþ+ÿ1=ÿ4íþGÿUIÿå¢ÿ¾Ûÿ¢Wÿ}ÄÿØ™ÿ”‚ÿ†c`-4ž3öyçðn™+¯Ôúáßô·°ü&ûõ„›ú+/íò{ììÄäv(kõÇîfɯÁýÙÿÇûЬþïÏÔOWø4:Ýþí#øuÍýdÛýÑTú¥{ûU–þ¦þ}…ÿj×ÿ‘Åþuqr!0Taÿ»zÿù§ÿ‹Uÿç´ÿíÿ«–ÿ1Öÿ¢ÕÿÉKÿÑëþùþ§ÿPóþD8ÿï.ÿ«—ÿnáÿQiÿ¿ÿŵÿâÿmž…/`žùtó¨+(¸€ýÖZá ̵þ!ö.høË‘ +@6’íˆÒÕôö„¹'£ÿ"ÿã*…–þ¶V4+Uù=ÿGQÌùI1ýÂ’þeû–û^*ÿèþ…–þÜ’ÿ7ÿ¶dàÿpÜÿ4þÿcfÿ_¨ÿ}“ÿŽoÿSÿ4ÆÐÿV”ÿ œÿÏÿÐÜþŸ ÿ¬#ÿÙòþ9ÿ¦BÿP€ÿMÈÿþ‘ÿ‡ÚÿÒ±ÿ´Œÿ8Ï%%°—ùèÿ‹:&îøØæåµ‚ø3ÿ±øIfý ù4–ýé~ùl2 uÍÿžžøâ6¿» ¸þF=ýD’2TAÿ%Ñÿ½ÈÿÃ]  "œËƒþm¦ý¸þ6üý‚<ü»ýç4þËuý‚ìÿpÿôÂýðþ;¤ÿšpÿxÿÚ|ÿ¤$ÿÓdÿeøÿÑÿÉ|ÿWšÿs‚ÿQÿJÿ~ ÿ…5ÿŠNÿ´9ÿþ[ÿ QÿÆqÿ™yÿ…ÿ¨óá> ÛÚúÓtƒ¸ ²eøy$ì÷‡:þ¸-ùAøý¥ÓDuýïúŒn‡ÿË8úáî7«ÿBšýŒ$,üÿ(T~‹cÿsGKzîÿxdþ³þ…þEEþýsŸþÀ’þÒáýÃDÿAÿî\þ¤‘þøþ#4ÿ,“ÿÿU6ÿk1ÿÕÿ®¸ÿ]ÿÚ3ÿx9ÿ9ÿ-Sÿü ÿ)ÿvTÿÑkÿåvÿ7sÿ¼tÿmeÿ}ˆÿ‚‰¤‰­þüT‹TD¸ÀúÆêï‘zˆþJ;ús:þcr0þ;«ûŠãLâ^ûu¼ÿD†nÿàý­2,q»`ñ‹ÿàŽ u‘·þ€þ¡þ©½þLþ–9ÿÿÿü5þRÿA5ÿÍþ8–þwƒþíÀþ¬:ÿ%·ÿÿ­ÿ„!ÿ»fÿ`CÿeûþS ÿ ,ÿ÷EÿG^ÿ¢aÿôaÿö‹ÿŸÿ,{ÿzkÿgÿ_zÿŽ8‰»¬þ˜†>üéüs~ò×á4÷þ—û:„þˆü9ÏþtÑûùW±Û.Ãû^_ÿvRt¶ÿ:9þQ@<Ÿ=ŒÏÿhíÿÈgc&Ùf†Ïc&ÿ?þéþîÿ pþ©’ÿMÿÄnþ :ÿWVÿî{þEbþ™þ͈þŽÿìyÿ–Eÿºÿ‚ÿâ/ÿ8ÿÒ ÿÔõþ¹ ÿËzÿHqÿk‡ÿH¡ÿ`vÿ²ƒÿÿ…ÿnÿêxÿ'ÿ°Q¸Ý°žÿñŠ?³Tþœûô"Rÿµ6ür~þözí=ÿµîû›oŠôsEüIIÿ[cëúÿp{þŸÀM ÏÖÿs‚!~¿Üæÿ’þj ÿ¹9ÿcšþŽ˜ÿ#ÉÿwÆþLÿ_ûþõBþ¿xþSÈþújþ£þºðþþþÅ?ÿ·Nÿjÿ6Õþœ ÿÆÿ¼ ÿ¿kÿ¨sÿ׋ÿcœÿÿR†ÿ:}ÿiÿ\šÿ<Žÿ•M ù2…®ÿŒ|\Mpâþ ó÷” ßÅÿvåüõ®þjWVÄþ>„ü¢ö‘µ(Òü÷gÿJ¯&¾ ÿ(œ|x¦WÚÿã:`Ÿú«Õ?Àÿ^ùþ;Uÿ¶^ÿGÐþ‡ÿ,Oÿj“þþûþÓþWvþÉŽþZþ)_þTiþ_Ùþ)"ÿÄAÿ±Nÿ¦ÿ9âþCðþÿaQÿ®AÿO[ÿU©ÿÓ¶ÿM­ÿOŸÿΓÿÿ…Œÿ‰‹ÿô e‚ õÿm#’EõÿÞù>Ï*H£„ýåþp­¼œþ§@ý3[ªr‡…ý9é”Û§€ÿArWXÖŠòÿ˜ºT»D²ìÓmåÿNRÿóÐÿX:ÿ0PþÞöþòþn›þJÿOéþnXþ´pþ†©þüwþÊŸþ ôþ°ÿo'ÿý(ÿ±ÿvîþùïþ›ÿ{EÿŸbÿÑyÿ ¨ÿkÇÿݾÿƧÿš‘ÿÑsÿûtÿ£ÿv• Õ´Û˜„×[Œü!û鉧1þs–þ…uZÿ^Ký©¬áΡAþmíÿø[È¥°³ÿTž¸Ì¢Á,µE9‰Ô„¢Û*BÄ_ÿ©3ÿ£¥þÏþ,Æþyäþ,rþ?Îþmûþ¼vþr•þeèþ«±þoÑþbéþ6æþNùþ±ÿß)ÿáýþÿeÿIGÿ¹ÿ6„ÿ ©ÿmÀÿN¬ÿ™ÿ]rÿö~ÿÔÿU‰ÿîW–ÁRú€úà «n¯üÔ°«é€šþ±yþÓIÙd†xýÐS1Íîþ&·„¢W¥d½)Ýs™ÓŒa’)êV½áªÜ“ÿxèþ:çþ”¨þh>þxwþ‡þÓbþØûþåMÿMÌþ‰ÂþóþþÙþ êþÿÜþ!¿þýþJ-ÿ ÿTÿ#ÿÔ+ÿkfÿ™ƒÿ©€ÿž—ÿ*¢ÿy­ÿ’ÿ´}ÿX‡ÿ<€ÿªÿ ¤¾a €7ˆ 7y µ"SýtÖøºqþÇþßÞ2©gý:Œñ×éÆÿ}Š)ÝGI3j 4ÞÒ³^µwÄñ%P3þÿÞ_;ÿ†Üþ—Îþ[þŒþ¡[þ0—þÌÃþ•KÿîTÿâÓþ]êþÊÿtâþåþ"Óþ´ÒþYÿ&ÿ%*ÿŒ*ÿÖ8ÿ¿Qÿ4[ÿÆmÿuÿ‰ÿˆ¡ÿ÷‘ÿ°ÿCvÿÅeÿ vÿ$xÿQîP· ‰º˜Œv P,2…þ™‡üè=þ2öþš€­ëÿA9þË6? &7ßžºf߆'Ãå L{çþQÆ¢ÿ©çÿäçÿÂÿÿ”¶ÿýþ6LþÊ\þ5›þ[PþLœþìæþpîþ²Yÿ,Lÿ)ðþÿÂÿ3üþ0ÜþÄÛþ¤ÿÿ½9ÿ€Jÿã/ÿö;ÿVHÿleÿëtÿøaÿÿÙŠÿT~ÿàrÿ,dÿµzÿÃ|ÿÊÿÌL( "²<¢R >~>ÿ(å—¡þÄŽÿ¡ÊÚkYÿS‚ÈŸ个$¢C`2“n½©\Õéÿ‰Aÿ„Oÿ¹Êÿèüÿ¼ÿÿ•´þoþÕÄþ÷Æþ.EþzÁþýÿÖÿêEÿØ0ÿ~ÿLÿ,ÿ¤ ÿ£Õþžôþ<ÿ3/ÿêSÿ­5ÿ3ÿ¥EÿøGÿTbÿ Zÿ>TÿÑfÿ+lÿE}ÿ¶sÿÌwÿ „ÿ„sÿÏŠÿ³Çå® ñ“@ e ÊÛ…^ÿ§x§íRÿSÔ}æ?ãÿÚU¾‰=‰xƒs"{ö<Èÿâ«ÿ^dÿušÿã`ÿÓ"ÿ¥ÉÿÚ¬ÿ—0ÿs(ÿ›îþ5¶þ:ãþE¹þÛqþ°îþ#JÿL2ÿ¨OÿmAÿÞÿá/ÿaCÿÿÝæþñÿY-ÿõ<ÿéXÿ›Gÿý=ÿü:ÿ‰7ÿ NÿWÿ$\ÿw^ÿkÿË|ÿoƒÿF‘ÿ Œÿñ‡ÿˆÿ³Õ—Vvw©Ó 8¸zÁ¢ïÉj^(ö!,î-9É@ÝŠ£-¿TSÙá&yÿÿW±ÿ†cÿU>ÿßñþdìþ’ÈÿýºÿðIÿ!ÿdÕþKÒþzïþöšþ…þ6$ÿÔaÿ9:ÿâ@ÿ¸Bÿ!ÿì7ÿï;ÿFûþYÿÀ)ÿø'ÿÙDÿ×Iÿ^;ÿ`5ÿÍ%ÿm.ÿ¸Fÿ:VÿVXÿI]ÿ.wÿúƒÿ ‹ÿÇ”ÿ Šÿ|ˆÿöÿNÞoËÜœ®ãìÀî^ªûHáÙQèSíB?ÀàÚ™ûó$–”ÿê ¤ÿ’PÿÞÿÔžþ¾ÿÐZÿ©¤ÿÃÿÍ?ÿ£ÿê;ÿ£)ÿšÊþŦþ0Êþ×ùþ_VÿÛ]ÿ/&ÿbHÿÀVÿ„/ÿ)=ÿú&ÿ×ÿ¼*ÿÆ#ÿ½>ÿs@ÿ!,ÿd+ÿM%ÿÛ<ÿ·Aÿ!EÿøQÿÏVÿùrÿaÿ9‹ÿ-–ÿŽ‹ÿŽÿ§‰ÿÁwÿO™- È®­îû-}=Ÿæg,ž~Þ?b”¨ ¥ ¤ÐY^ÿ]ÿ¡£ÿ‚ÿäÍþ÷õþÿÏEÿ«‚ÿI¡ÿ¶ÿ¥ÿìÿ!#ÿsòþ‚êþ+îþÛïþÿÖ]ÿ WÿI#ÿKÿÓHÿÎ)ÿÀAÿ$/ÿ²ÿ&ÿ‹ÿ-ÿë'ÿú.ÿÁ.ÿï-ÿ8<ÿZ;ÿzNÿ>]ÿÅfÿÆ{ÿU€ÿå‰ÿ©ŒÿÿÿêuÿãxÿÆÿÒÊ6ñtÒžUÈdÖǽqBÍåðçHh¥†l>¢†?ÿQàþ´.ÿBíþPÏþo'ÿ:ÿÔaÿfwÿáFÿ»,ÿžÿž3ÿ<ÿ²ÿÚþ|áþÿDÿ-<ÿ>ÿ"9ÿÃ=ÿ Kÿ¹7ÿÍ.ÿè&ÿHÿ› ÿpÿ7(ÿ(ÿÌÿÈ=ÿ”;ÿ\8ÿ¥Tÿ FÿeYÿ=qÿCiÿÍÿÆ{ÿÿtÿÛzÿ|ÿ‹€ÿ1€ÿ^ç4$ Þ­jKœ¦œ¶¹sVN ˜"ÿKÙ¡9 ¯ÿáñÿŒ~ÿBþò‚ÿZˆÿ¨ºþ`ÿ«UÿØAÿ²gÿö`ÿ@ÿÃÿ×.ÿÔÿä1ÿPÿòþ‘ÿñDÿkJÿ#>ÿt:ÿ§\ÿÙNÿ® ÿDÿ†!ÿé1ÿrÿyÿi;ÿ+)ÿ¢/ÿšEÿíKÿêQÿwGÿpPÿ×_ÿ›uÿï^ÿõOÿ˜ƒÿÿuÿã“ÿˆÿNÿãÐ} ôâ +!ñÓ@@ÿÞ¯ñ‚»ÉR¿Šÿ$€ÿ_?ÁÿÆþþ]ÿ7Xÿà&ÿ6!ÿ^Wÿ¸ÿ¿?ÿéPÿÈ ÿm;ÿÐ$ÿ¯ÿ8"ÿæÿç'ÿôÿäGÿï?ÿ\3ÿÔEÿÃ3ÿ½PÿÐ7ÿ5ÿ!ÿÞÿí ÿ¤ ÿTÿKÿª<ÿù9ÿÃEÿ¿Lÿ5KÿŒ2ÿòCÿÂNÿüZÿU{ÿ­uÿ*{ÿ †ÿ’–ÿ>¬ÿEƒÿžfÿ™¿  22Í݉ÐדIwÐÑ]½(’øÕ(ÿ«ÿ?]ÿ% ¤5FÝþ®•þª‡ÿ\Îÿr/ÿs7ÿWÿAÿ=ÿ5ÿÍÿ{Kÿr8ÿA<ÿ$:ÿq=ÿ)\ÿË\ÿŠUÿ€ÿW5ÿ¬fÿ9#ÿ7ÿ¥ÿéÿÂ5ÿ;.ÿ?ÿCÿGGÿˆNÿ .ÿz0ÿ%7ÿ.4ÿ£WÿlÿrÿÀÿDkÿÐ}ÿ šÿY‹ÿ^ÿ¶~ÿö‹ÿC9¤Î ÈâúLFGœmÑ"^c70¹cb’:<ÿ¨ÜÿôÕÿÝ:ÿàEÿèÈÿl|ÿÿc—ÿT£ÿòÿÒÖþ`*ÿ¸Bÿ¹2ÿ¯Xÿaÿ÷gÿyeÿÛDÿ}OÿöMÿ16ÿw9ÿÑAÿ€$ÿM9ÿ5ÿ¤üþ…Nÿ©NÿÉÿ­Fÿ"ÿ©)ÿ Aÿ<1ÿŠ6ÿ²0ÿGTÿ sÿ]kÿ)`ÿ-^ÿ\bÿãÿÛ—ÿ'}ÿÀ“ÿ«ÿm”ÿù£è' àK¬&?Vȵ‡G~WŠç$±XŒ€¦†}9O9ÿ*Ñþ6žÿnÛÿ±DÿjÅþ©+ÿpÿ ÿÏÿ¶iÿ>ÿ݇ÿpsÿŠ8ÿÜ-ÿÛ(ÿíFÿ'ÿrÿ¿ÿG{ÿý ÿ{eÿœëÿÀ¬ÿ’Sÿû9ÿy&ÿîaÿ¨_ÿ!ÿ¬6ÿ$pÿlÿ‘<ÿ«Aÿà{ÿ]ÿI4ÿÄRÿaNÿt,ÿ$ÿIÿ'Zÿ(ÿö3ÿéLÿˆÿÿÿ^xÿR’ÿXÿš]ÿÈ—ÿÏ“ÿéuÿ8‚ÿ8Ÿÿgžj·¨[-ÿ1ðΊ QîÐÿPÿÍ5yÏËO!5>šî»\àÐT 8Ór#2Ç›C±ÿÑÿÑÿzßþŠ6ÿxÿˆ—ÿœ[ÿUÿ2Îÿ6}ÿåþÅ2ÿ´ŽÿYÿIÿ^ÿ”_ÿNÿ'ÿi0ÿÄ*ÿÏBÿÅlÿ†Lÿ_=ÿEÿgeÿMLÿ$ ÿ®ÿ ÿvÿ8]ÿ2gÿ wÿvÿ®ƒÿ™ÿ`oÿ…uÿå„ÿË›ÿœqÂÚ ÐS;ù0ê Æ˜Çþ³þfÑʰ–ÿá€Ës›¡*ÙÈ+|~¨‘–¢eâ·jÿ•ŒÿöxÿÝÌþ^ÿ“Ëÿùÿ$@ÿºÿ«Yÿ×fÿú<ÿKÿ*|ÿÉŽÿþgÿ>6ÿå ÿóÿÕÿÂÿœIÿ[<ÿ5cÿawÿdbÿìMÿSÿ ÿ0ÿ ÿ8JÿdÿÅ{ÿtÿ‡ÿЊÿ¤_ÿœiÿ£ÿÓ´ÿèÝ›h übÕŸ¸áâdÔ“.þá¡þ aA[ä»þîãyªo8§QWÿ‚ܤž GEU¥í)žúÙÿÝ€ÿ4ÉÿDˆÿ 7ÿì|ÿ%JÿüÛþsÿ-tÿÒžÿ~tÿ¡_ÿ“šÿíWÿÜûþ— ÿ`"ÿ2ÿ7ÿu2ÿhÿOXÿ\ÿLUÿá/ÿ€CÿmCÿé1ÿÖXÿ0_ÿ1bÿGŠÿ}ÿ(Iÿ1dÿЍÿ¨ÿÄ®ÿ¹°íÄ |¹O¦•mÁ¹¼§ýÿ€;Ü´êþ¿ÿÿr»^:9e,˜E ÿúJ ¹9éÿ g(Íoë´4]ôпOWuÙÿ¿SÿúÎþçóþ>ÿc!ÿ@ÿX¹ÿª ÿ}@ÿ¾DÿÂ:ÿ-ÿD!ÿbõþl ÿYGÿÂÿÿÿ<ÿ)Gÿ®aÿŸnÿ eÿhÿ¼lÿxÿ’mÿÇVÿ[ÿ;`ÿµvÿ~™ÿe±ÿ^±ÿ ¨ÐbßòH¦"úCžüQˆÿ]XýÔnÇý!³šÿÿR5£pìàþ¯-*pÛdÿÆÿ1°ÿÀÿj¯B²<ëšð [ #Îÿé`ÿ@Êþ'ÙþAÿsÿ¨iÿ¼(ÿMÿûhÿT&ÿH,ÿÉ!ÿø2ÿy8ÿêþ ÿ×ÿœÿëVÿUgÿŸnÿÖÿŽ‹ÿ\‡ÿwÿ5gÿZfÿ¶zÿðmÿaÿÞ‹ÿ–«ÿ,»ÿ>,r]˜Ež*ò}rCÿΛûI(§—êÿƒ.ý[g_ñ­ÿñEð’§þÙæÿ7e,Fÿt‡ÿÚLÿ­1ÿâ¿ÿà›ÿrg{a¦#[›»ÿ®mÿWÿâ%ÿÖúþ…ÿ.{ÿ£ÿ!øþä*ÿMÿOÿ³7ÿšùþ¿ÿï ÿæûþáAÿfWÿRSÿ¥mÿö“ÿ¢ÿ,uÿJkÿ.œÿÆ©ÿà‹ÿÒwÿþuÿ­Œÿö¦ÿ}«ÿMm N¶p˜q8Õ"d)žýmãúðÙ¾G«HþÛýକ4n‚ÿGýxpþëÏÿQÁÿøTÿZùþÌâþJmÿ‰"ÿT¶ÿœ·§ÑÓ½AÊ2¦Ð­Àÿ7•ÿlÿµ”ÿs8ÿÓÿ2ÿjäþl"ÿÿôÿ=ÿªÙþêÿ>ÿ¤Gÿ‡_ÿdÿ7ˆÿähÿ›?ÿ§kÿÞ©ÿ!ÃÿÒ¢ÿ9 ÿŒ—ÿ¦ÿt—ÿˆ’ÿP¡ÿfT ~¼«÷³Âgª{ñ^ü|Þùëa”&±ü>›ü !:(_ÿ8¿½m’þ݆ÿ]mþ=ÿíÿ‚¾þìÎþXAÿŸÔþLvÿ´Dç0(KòIŒ>68XÅ!ÎÿvͯÿúÿSÿõþªàþÐñþcõþ àþ+ÿ¬5ÿp0ÿr_ÿ™ÿâˆÿhLÿ >ÿófÿG’ÿ“Œÿ»˜ÿa½ÿIÂÿF¾ÿ’…ÿ&ÿO¹ÿÖºÿæ‚ ±Vð„ W±|¦%×ûK¼øuôKR’ûº üh§þÿ¦9ÿ—|ŒhdÌþ"6ÿiAfÿíûþ§˜þ™êþIÿ €þ!5ÿ”?’ "´ÿ\”ÿ!´ÿº8mG2=q3×ÌEÿצþ¯ðþyþþ‰ ÿëÿHÃþ{)ÿ“aÿ5…ÿ †ÿ…Jÿ*[ÿ´—ÿØ~ÿãNÿi‡ÿ)¿ÿéÎÿ?Áÿ’ÿ2‰ÿ¨¬ÿSÊÿkÇÿšÇ ÒˆRð z[ÿÄÅncëú&ÞøRÉ‚=ËVûlnûÿ²ðÿèþl5à—§ ÿê¯þâÿeÿbîþÓvþ¿Óþ±òþR9þê4ÿËÀÿÙëÿåvÿ³ ÿÛ‡ÿNŸÿF­ÿbÚÿã“£'Âÿ˜ÿ†|ÿ/(ÿ“ÿûvxúOI]>“þŒЪø-ÿ&;þ7¢ÿk¯ÿFÿ•0þôYþwõþ*Uþüÿ2úÿß’ÿÿ„ÿ”pÿLÿJ+ÿ`SÿWÿšÿ#ëÿ§»ÿ4±ÄŽÿùÏÿšÿ€ÿèÿ¯6ÿ *ÿEÜþ3"ÿ°ÿwªÿanÿ¸9ÿ¿bÿî€ÿ²Ÿÿö©ÿ”žÿ+Ãÿè³ÿ°‘ÿŠ˜ÿð±ÿÒÉÿ« ½çjׇÿô×rtùÖ6øXÿa>ük(ù–ê§Æu|þ”±ÿ…gz›ÿÔþ¯ÿVæÿ$ãþjÀýþ}óþŒY ïý–’ü!‰—üüÎû; ýV×übÿYÌþÔzý,hÿ>´ÿ:þ„þÞCþhký þ†Žéþ€úþŠ›ÿENÿ¾éÿb7ÿèÿ–Nÿïaÿ‰ÿxÛþ|«ÿÇïÿv›ÿ©ÿI¬ÿ/áÿƒÿ¸ÿ»¼ÿÛ†ÿ)¸ÿòƒÿ•cÿ¥™ÿ—´ÿ-’ÿ­ü‹ –ÜøBõÉKñkÏüŽJ PÇü ï±ÞA\ø7Lüè §ÿÍCþ ZÕþÄüðæüÛ¤û6 ý‡*üï-ÿ¿þv"ýö%ÿA£ÿ¾¦þ¶þÝcþzãýc›þvZ"þ¹þSÜÿbSÿåöÿ1ÿ],ÿ~FÿÄSÿá˜ÿ½¿þÖ„ÿ™Ëÿ®†ÿ¨Œÿ fÿ2šÿ*~ÿ@ÄÿÊÆÿ©´ÿÄïÿ Ãÿb’ÿ¨ÿ@šÿäˆÿN+w#‘¥ïçûÓó«q ðþNüˇë»û6Úêû) ;yø8€ù@èŽ;gþ§NàÿpÓû–ÝÿK¢ý„—ûüG¿ûwzþ!Yþ«&ý[$ÿj;ÿƸþ•[þª þ¬fþE@þ\ÇÁþwþZãÿSÿ±½ÿá/ÿwRÿš]ÿx,ÿ½€ÿ¾¦þŠÿ{¹ÿ$ ÿSJÿåSÿ”„ÿGCÿØÿ:Éÿb¾ÿ)gåÿh¥ÿù ÿæ³ÿÞÿgÆ™…&„ÌbD÷k˜àÉšsð¿ü{l’_ûf;æI\"4:bú'~÷v{þáRÙXÿâCZ]¬üÇ0ÿ<ïýÄÇûCÐûàÂû¨Àýøéý nýMçþ¬Lÿ¯yþ¸þé$ÿÌKþþìâÿÙ ÿvqþ*ÄÿÍ6ÿ+xÿÅpÿ\ÿ×\ÿ†ÿRPÿRºþz‘ÿ¡›ÿ1Ñþµÿ5ÿ9mÿTÿ’ÿ‰¦ÿ~¬ÿÕëÿ¿¿ÿ˜ÿÆÿÈïÿÀ°ÿª¡¢f)wÚýæ£ôå•CÓ;wñPËýÂ!ë´ùP—ânZÑ5!Ýü¤åöz¤û=‚>Owø¶TŒüö/ÿžÿüÏCüd…û›?ûµý?·ý<-ýÔßþ¢åÿ@þu‡þ̇ÿŒTþ_þ¥¯ÿ#ÿè¦þ¼—ÿdïþJmÿЪÿ¤{ÿ½7ÿ ÿáDÿS´þ‡ÿ^“ÿ‘ÇþÑæþ§ÿýbÿ+[ÿÁžÿxšÿ_tÿ×¥ÿá¬ÿ˜ÿˆÅÿw†éÿC“Ül,}¨úŒWòý»_<¶0òA­yì÷jœß}Ó :Cÿ6Z÷hùŠËÓÛAZ˜ûÿù»ü´ ÿ˜ûµýÍûwµú Eþ`Åüï'ýl€ÿ ä+þ·þ¿rÿo”þß{þBrÿŽþdÂþo€ÿ²êþÃZÿ‰ÿM»ÿr,ÿEôþqÿl|þö›ÿµ›ÿaåþF¾þëþ`ÿsUÿ,™ÿ9\ÿØJÿ°–ÿË“ÿ „ÿ ÒÿQ»ãÿ-Db0¾_ù¿Óî¶7Á ò²Ý½È§<ø–ÜnIQŽÎåñøg ÷ïd±èWçõñ¬üAöþ±úskýOûôpúŽWþrüÙšýÑÕÿšVO*þ4ýVÿh ÿ#áþ+ÿîýä‰þ¬·ÿÐÿHÿ^tÿY :ÿáþyÿÒJþt¬ÿ“¶ÿ.éþÑþºÖþ±Gÿ©Eÿû•ÿ69ÿ³5ÿxÿž…ÿÓ”ÿŠÌÿœ¨ÌÿÞÝ!°É3Êóì—RöÞ ”òF·P¿ö( Ü*Èl@4]CùˆK÷¸7ÿ ‡,;ûMçþ Ëúzýp*ûr¼úØåý7{üÖý‰ºÿÌÏÿ´ý_©ü€¶ÿ¤‘ÿÊ ÿ¿þÕfýQuþk¸ÿŽäþTùþ#šÿ‚5!ÿ²îþ¹ÿ^þµÿî˜ÿ#åþÿÇþÔþ1ÿ:ÿèœÿ’2ÿ<ÿ}jÿgoÿ2—ÿáÿeüÿõ©ÿPU#-¸8 ó¸¸åÝ×Á *òñ˜ððÉÐ úy¶Ú¿}7]Ðù· ø1UþÝžþ‹ãùùzÿû÷ÔûÐûú³ú¸­ý ýQÛý!œÿqàÿˆýaü‰ÑÿǸþY“þIý[$þ²‘ÿ©þ–ÿÅ´ÿÏ^ÿVäþø"ÿs‚þ”ÈÿZÿy¬þ¿ÂþÖ¥þþÿ˜:ÿç®ÿ—Nÿ·'ÿSkÿbZÿq‘ÿÏÿ9øÿ™¨ÿ2…(÷z;þ¯ìÍgåV‰ 5ŒFíñ+2à¥ú§BÚtA&²ÿPùÎú«¤ýž¢üL£ _#xø" ôû·ûP8üµ1û ý3ˆýðþë‡ÿž¼z ýŒü§­àiÿKëýNÿr0ýº´ýýbÿ=”þ§>ÿ’­ÿ/äXÿÿDÿá¦þxèÿ\Jÿ¿¤þà®þŠcþhöþgÿÌÿèVÿ )ÿêOÿ;mÿ`‚ÿºÀÿñãÿÆšÿ>E*ް>&뤾â+Ò ´p¶ð/UË þ*Ùv ýséL ÿ™@ùH?ûM:ý°žúèP ¦ˆ¬0øùØxüy×ú/‘üÂAû(Ïü¡¾ý¯@þQ7ÿ¿düü”ªüš¸œÒþ ŒýäKÿgýrmýw@ÿ_gþ9ÿ¦Üÿº0ÇCÿÍ:ÿ@ÿˆ—þñÿ”`ÿ%°þ@mþÁ7þ¡åþâkÿšÔÿQ]ÿ<=ÿ;GÿP_ÿ«pÿ¶¹ÿ¶ßÿq‰ÿé½+–A'Cë­xá$} $A5ïóéJ5ß2$ØÖ¾û[‚l±þ?úbˆûôüH=ùA¹Ÿèþø³ÇÿáÍü³Æû2üÇûÎ-ý‚Œý*‰þÑÈþƒàÿaý³ü²þ+»ýèþRCýžý!ÀþçiþvWÿ#ËÿŒÈUÿ¨™ÿ—…ÿéMþ ÃÿÈÿlœþë+þ-þWçþíZÿ ÓÿANÿ‘XÿÏLÿ¹:ÿ€rÿA°ÿåÿšÿP+`C‘aît—ßVHUˆaí·Ô‘½JØjù)z+ þájü£öúohü¿øâˆA™úû}ÿÚÃüÈ“ýBÑûž`ú Hþ©ý‚–þCgþ`ÿw¶ýÅÎûæÅÿøžþ¿ý ÿ^tý¨¤ý³Uþó«þMÿçzÿiiŒ–ÿG}ÿL`ÿÛeþ›Öÿm‹ÿ3›þç%þz"þGÞþ XÿTÚÿÇ>ÿûIÿrXÿ6?ÿkgÿ£ÿ‚üÿ¶¡ÿ4P*qDBèñ¢ïßýÔÑ ‹êÑûýÄïñS ¥=Ù)÷4A #þþá6ÿÍ+ú‘ û‹&ù÷åíÁüÐYÊû]³ÿý;ü‘9ùÀˆÿ^~üY(þehþ’#þ’ý [üòrÿèÞýóþykÿÞý×öýÊ"þIiþYPÿj¯ÿ²‘hÿšhÿð|ÿ¿|þkªÿ“ÿ?¾þ¡þ»þÙæþvbÿGÈÿ};ÿŒ6ÿŸ^ÿ~:ÿvbÿa¸ÿQåÿI­ÿX0)ö¬DöØõ^Káà!Ÿ{è‡]ú†ÿa9yBܤûôå Å`ÿÛAzùeù Múec¿ \ÀþÆÍ* ú¥Àý÷ýCø6ÛÿŠòü.ýŒ'ýõ9ý¹"þ¸ük?ÿv¸ýÆþé–Þü¸ýžÌý-þ®èÿf£ÿé §¯ÿÚŸÿÕ=ÿ¶{þ¥ºÿÄŠÿñ³þ&þ»2þÿ`DÿvŒÿ\ÿ&Hÿº>ÿI5ÿƒpÿp¦ÿgäÿ¤¾ÿüK'8ëC±•úã+ä¥Ì´4 hæ¸ ÷bžå\;rà/ò(-hÊÿIÚБùoï÷§gû„búü?âÿA‚WúÑ:d€Dñ÷%ÿ:Žýéûª/üñoýÛ‰ý“Wý¿=ýEÞþßÚ(¤üÂÊüþ™þe`ÿÕ¢ÿþ¾Èÿ0ÀÿF"ÿ/¢þ°ƒÿCvÿªÂþEþ‚uþÕÓþ™Bÿø°ÿ;Xÿ½Gÿ8ÿ&7ÿýaÿЪÿ´çÿk¤ÿÜR&èÖBu¶ý/Ñçä!×ÓyüæA9ó˜ùµúð$å/ðÛÁ÷ˆÿ`+H-ûÄ^÷¸Äû‹)¬ŒÿƒI1ËûZcÿ>ÊýtùyyýP°üsûwû|»ü4ÀýÍþ¾§ÿü¿Kþ”çOôüʹüÿµýOpþäÿê,×X£5ÿO ÜjÿܪþœSÿÍMÿåÖþ"Qþ¯®þvØþŠIÿ_°ÿÓDÿò„ÿiUÿ£ÿq[ÿÊ—ÿôÒÿ,®ÿãô%öôAYÿ/ëÑ d¤#è³ÉðyRé/VªêsQPÿi.k"ýP3ø®zûܶ¦þ¹áÿ¾#7«üJ ÿ5ëoú•Eü¢rü·üáhú†üÔý{ØþÌÇüM}ýEƒù÷ý²¶üíiü'ÉþßXÿÖáÿ _¦(ÿ3‹ÿ%ÃþG6ÿ+6ÿÔþL>þïþ­âþ+ÿ¬¿ÿ–_ÿ<¥ÿpuÿÍÿq,ÿB~ÿÈ»ÿò¥ÿÌ$c(AðµŒíßÝæÎþ•Néµ0ð&»Æþ¥‹ñdì ‘ï6™!„þk¬ú¦úí×3¬þÐ¥ýâjˆtÿ7óýV>ÄÐúƒCüJcüý •ú¸@ûpþcþ€_øñýÁóü°²ÿqþ^:ý1=üîwþ¸Šÿ° ÿ®hÅ@ÿUs¾ÿüþ ÿ) ÿÿobþs¿þgëþÝ?ÿ™ÂÿIˆÿÍÿwÿå#ÿœÿ[ÿ ¤ÿ×¼ÿÿù"ÿ“@ÔFÇîçzíþì{èå”ñ{©ðÝþú—úꄯ™öÕxVþïpþEÎúÿÁP ûÀ¤´ÓyÂüÿJ\±ü•Õü‚Èücþ±éûkÙú–&ý4ýOr“dÿ/KüfmþÕrþj þ[xüYþÓ‰ÿŽvÿ i¸}ÿÈÄÿ¦áÿKEÿã'ÿÞ(ÿ¨4ÿM‹þòÑþñþ, ÿzÉÿ½ôÿ^çÿxlÿ<ÿùIÿ3SÿR•ÿã“ÿ«S?rö >ïÔ’<ÙÿΞè”òútúÕœ f êc+ý{ÀŽÞüyåž*ü`ùû ôëùØ•Ý 1þµÿª0þÕ€þ>MýÿŽ ýCöùwür˜û<©ˆCêüZý!Åýæ2ÿl%ý“¦ýbÿoôþ¡ÒÔÿŒ=ÿQ¼ÿù„ÿœlÿ¯6ÿÌaÿôåþEÚþJõþYäþ­µÿµhÀÿ×”ÿmÿã3ÿuÿ¢†ÿºwÿ—ä3=8ò -9ò닇þþGë.?ñíFõ ¢Œ¹ÍJëk„ûËHË&ÚüEûo6ûé+ûÝ÷Û%þ–èû—ÿÑðÿ<)éýÅ7ÿ4JþNÏùòû7øùÞ“ý¿ 7%þ VýræüÎ"ÿ¨-þ½¾ýÛ1ÿŠ‚þ—i§1Üæþ‘ZÿìªÿÇ™ÿí[ÿ¢Ýÿü"ÿ!µþ¢!ÿ0ÿ™ÿ3Çÿ ¢ÿZÔÿÙ~ÿ *ÿéRÿÿÿlÉ#p;ŸÅóZ¾Û‡ÿ4pìó†¸ïDL -:í²5øð2 ‡ÀÙµúµ%ýþ¹ø¡Í­hø×û¼`XÖþªs—Àáþpÿ÷›³ËþÂöùþ­ûÂùNÑú^ÔÿÙaÿ…ðþ’îüŽœýû«þwáþ’kÿêý‚¬ÿàqyþþ÷Uÿ–jÿâ¬ÿH&v$]ÿtéþ ,ÿ =ÿ^hÿ ÓÿcƒÿªœÿI§ÿ…%ÿsÿ‚{ÿÄŒÿó$i:9« ÇöwhhGúsÆî[wórèí >ÅR mð7Ö÷§2 Ý;£ö÷ð+ý³¥ø&gã%ùQSû¿ŸLÿ¾“3(YÓåýÕøÆþ‘ýø÷ü½Wùª–ù èýWfÿ´§ÿ,Bþ®ÑýÊþüÖàÿô`¯jý·ýþжÿÊ’ÿ;Ãÿ†ÿþŽÿ\¡Ö}€RÿËÿ©<ÿÁIÿb˜ÿ ÿê~ÿîÿâ\ÿ_.ÿŒeÿ…tÿõšÿyoG: %,ìô3òaúÐ¥éÅÝöôX¦ Ž! ÆZôÐ;÷[ P‹õÂÉÍ„ ¨÷jÌt3üÀfü¡8Éžþdm­±J´TýKymB…÷‘rûåbüÀ™øñgû©‹þº ÿ;|ÿ2„ÿoÀü$þò7R<þ²ºýH¬ÿBÅÿÏ¢ÿ˜°ÿÍ´ÿ+RùÃÊÉÿÿoÿœÿ3eÿ@°ÿðÿólÿý_ÿlÿÙ4ÿrUÿx«ÿµô’:Óˆ)ñè1%·íúì¢ßOyüXåôèDE  #Ùù ÷ >ˆð ÄNõ—kü©îÒøÖ¸þµ þEƒÏü°,¹ pÔÞvÙÿÑŒüÍùr^úU üy:úpú¢Œü­ÿ‹ÿâGO¢þúiü¯`ÿÿ’ÿ´zþµ÷þ™{ÿVoÿcêi „Fýþÿ–ÿwÿ4©ÿñEÿòiÿ¯¤ÿ ?ÿÇXÿ7ÿæÿ~Rÿ»zÿ]ß!é9U÷W3ôôý+ ôAÞÏ `òöÖ$™7OýäøŽ•0ï ›ôË#û€cÊ>û£Tÿs÷ý•Ü„ ¨üîþ`w«è‡ëì @ôúð-ûŸÖúe¼û›û­àúT9ýÀÿ¶—çøÿ,ü—þ£éþxš5ÿlþE‰ÿ"[þ¯ßeqáÿÇÍÿŠ$}Ôÿ€jÿ^2ÿÃ_ÿ³oÿHÿTÿ¼ÛþÀÿø[ÿÁdÿ·ô"ÉÌ6%ZñÔ»ögE0ôïÓÜQiûŸý°wk­gßùyWüÒ ~I÷žëùs¢¶–ýá„ýÿCô@Tþ•`þê0ÿg¤¢ÍëQr0‰¨ûy4úï üœüôxûï°ûLGüˆÎý(?å¹ýéý–…þM«ÿ´µÿ¢¹þYÿc] ³Ä ëùÿC ÿx0‹Ûÿtÿä&ÿ{Dÿ¤?ÿ'ÿT`ÿÒöþ0ÿ¶Rÿògÿ¹l,›3^ós ö¨M1`ÐòkbÚY kþ^ÔøíÇ< [þú£œøÏØ V#û˜k÷ÖÙäþqFÿ~µþÙÎÿËÿ‘Dÿa@ÿþi0(Mw…VíÚü¾ºú{¸ü^ý?›ú ñûö•ýø1ü …gry‹þo¢þÀÝþþëþxÿþРÿéUÿDÿ“ßü åçÿ„Òÿmåÿô ÿUÿe@ÿÕÿ=ÿÑÿË>ÿ $ÿÒAÿ´QÿT_ÿä”6c.£ÊôßËùÞ+/Iæó¼OÝa«þG¤ø€ñþv lüJaøÄB § ýæ÷sâIeÿZÿÄþXœÅÿ¦Ôþh™ÿ9ÿæËžNì%âgöýê¨ü“gýRý‡'û<çû5°ýgïü=¨ÿKc´þ¥XÿPzÿËÒþ+Áþ¼sÿƒÿÊÿ[k~jgæÿ¦ÿÍÿù‚ÿÅ’ÿhLÿ¸ÿ[*ÿ¿ÿ¦Oÿü0ÿ DÿÝ]ÿqlÿÁYÈ)<øÅmûX +ô÷3GàÊ¡(hÿ÷¢OýÒ…šœýYJøÍ Emÿjº÷);ð&»Òþ ký¶‰s^6‹þPxÿpÝÿ3”°Ûç^¨óÁ£ýÇý'˜þ¸týÑCû%Úü™ÁýœKý¦æÿ©|ÿóþ8Rÿ=ÄÿA>ÿk ÿà/ÿGÿ|ùÿÊîÿÔÀÿ ÿºÁÿÔrÿ_gÿ6ÿxÿþNÿBÿLRÿî=ÿ]Hÿ®rÿJlÿ Ѿ&x ûxÿ =æäóÏÏë7¾–pü6éýúßÔcþ{Ný[Üž€LüÙ°ü>‡ íþþuüc>&ÄâÿЂs u½†ÛÅ»Ÿ{Gÿ‡=ÇÿŒÿ ‘ÿÎ }9ÿq\þAMþKýöý‚Lÿü]ÿÎKÿfðþTÅþýþ ^ÿ¡3ÿ9ÜþZÛþ4Öþžÿ6Oÿ¦Wÿÿ>¢øÿ®dÿÐcÿõSÿâXÿN\ÿ°ƒÿTëÔ!œÿê-só2÷[ví}6yWüÊáý<°â*þ‹˜ý £ ­=Óü 4ü­†ÃÿÑ@ÿÝËüÔÿøôå€ÿY²'[J˜9ìoÇseJÿhvÇÿFÑþ®sÿ ^wÿQ²þX=ÿx9þEaþÝuÿÁÀþ¾»þ¡@ÿ÷›þ‘Lþ— ÿíPÿ¢2ÿ” ÿó¾þ ÈþP:ÿ {ÿ°Dÿš¹ÿ’Óÿ RÿxÿYÿaÿE|ÿyÿYø=Eå$Ó/ÐWü’¸ïD+64ü†?þÈØ¿ý´¸ýS„Hg½þý‚üˆJI¹ÿ/yÿ{!ýo¤ÿ» Š=ÿÕ˜ƒdàR%döµ6ÿ¬ÿp ¸ÿªqÿ4ÉÿÞ ²ÿßÿ…ÿX ÿà§þ¤íþ)’þ²¶þž0ÿMÂþiþ|‹þ+ÿÅAÿ>ÿnÄþŒœþ)ÿè]ÿÓ'ÿfZÿO—ÿÈiÿŒÿ²rÿƒPÿpÿЖÿT ‰žG³Ó?ŒT§ý¾˜óV~å7ü$`ÿ•þeùý¯‘­wº‘þÜÜüÃÿÜÿlÿ_~ýÔÿµËvÿ•^Ù…Û_‰ýDÁm¨N`|ÿ•Nl€ðÌÿ‚eÿÇ?ÿF]ÿö-ÿÆØþ.tþs‹þ/ÿÕÿ°„þ¿@þ+jþLÏþKÿîÿ·ÏþgÍþ_þþ8ÿ¢ÿÁLÿVxÿvÿþŽÿÞuÿ™Wÿ²`ÿÇŸÿ7Z Q°å÷ž1Øôÿ[æô0þÿòÌýLFÿ åx÷þºåþ¹]d‹Ðÿ1DýÿÎþ} œ þîýV¥ÿÐYÅÿ",*ÃB–0gõ-ÿ¢PÕ/ÎéõmZ€ÿÝDÿÛþEÿ¼Iÿ§tþ‚þ™ÿªRÿ=éþ`þ"eþvfþÚ‘þ®þyÁþ¤úþâÿñþ.õþö)ÿpÿ"uÿæXÿ yÿ¤dÿjeÿe€ÿ¶’ÿO- §ÈÔ/3ë 9¶`­)ö(ŒÙÉþMÇþƳpëÿ°xÿ®âÿ&ØË~%6ýeìþÄaG×Aþ™ºÿ˜Ì¢ÿ]I4ˆ8¶­¸¹}hâuˆ‘Ì¿¿®ÿ[5ÿø4ÿš¬þÿ=ÿwÿ‡;þo§þ"›ÿXpÿ©´þïzþ`;þeþúŠþWþÚºþ­ ÿ9ÿúÿÿ (ÿëRÿŠwÿR@ÿuHÿ˜rÿ9rÿsÿ˳ÿsß â OVôud,g%ÿù}5&„þCbÿvŽ®s1ÿ«ÿG®[¿èý24ÿ6c®öÿì†þ‹Ji¯ìôÿŸf#<ÐZý¬}pV¼T8¦dìÿH(ÿOTÿÒãþýÌþ*ÿ„þ!Rþ0ÿ<¬ÿBÿd¢þfþ'+þºsþ%zþ’Šþ_íþ>6ÿ~Hÿ/ÿa!ÿvÿŒGÿQÿh9ÿLZÿÁtÿ©ÿí¨ÿ¿ÿ)õ »æuÕÛ#v' —Bÿrvú}`Wûþhñÿ»N‡Q#ÿïÿ"›ÓÓÿ+Hþ´ŠÿÆ_=çÿWÿNÍLË@·2£J×וÕl°ßÿm{›ÿà7ÿn&ÿÑþÆÈþµØþUyþË“þ=ÿØ_ÿÄÿ¾ªþWZþQsþ ¡þŸþ·Ãþ…þþ5ÿ <ÿô<ÿŠÿ” ÿñPÿoQÿ^Rÿ…\ÿN}ÿ¹ÿ¬ÿÛ±ÿðÄb94+;7 xOÊÔúFªq­ÿ¥#ÿ–;ÌI­ÿà~ÿYù†Žþ9¦ÿ°D…ªõÿ}³X\”ÍÃëÜ{HO }Œ¥Ê.çÿi¢ÿEçÿךÿ ÿtÄþ åþïÆþa–þ’~þaŸþÿÅ]ÿØÿ‘¨þ³©þnËþЮþ·¼þ?íþŽþþ³'ÿã5ÿÑ*ÿù6ÿˆ1ÿ@ÿçVÿ.`ÿ–iÿƒÿ†®ÿ´«ÿ#ªÿc6g®j8±é  ×=ç@ýRjÝyÿ,JÌVHž±Qÿº4UÎ"ÇXÿûÿÛë0¤ï’Œ ?Ó öÚJzoYê“÷sÒÿrÿ°uÿ{`ÿÉþÕ·þzæþN„þ¥NþÊŸþÝåþ‚+ÿþeÿ@ÿìÊþýëþ›äþÁþÞþ0øþ±ÿ3ÿ­2ÿô3ÿžDÿ­5ÿ«NÿScÿh]ÿþlÿ€ÿx¦ÿÆ ÿ#ˆÿ†{… %†s~™b MP¼¯þÞŸaÿj9})vž;ÿexTöñw®Ëÿ_ÖÏá#ì5ÑÞ³üd=Òÿf*˜9=#5…çPVÿÓTÿoÿÿNwþ·þ¯Äþ·Yþxþ¬ÎþT ÿ-iÿt^ÿøëþ+úþ_ÿ™äþûçþõêþŒñþlÿ8ÿÃ.ÿ¸7ÿrLÿ?AÿFÿx\ÿ gÿivÿJ}ÿosÿúvÿ¢tÿ§—xÉ ¿òdO\é¼/§ÿ'H]cÿÒŠLy%ä‚ïÿž¾ýu'–;¿iq¸(§­’Ò !éÿ“ýÿ:çÿq%ÎÍÿÍ;ÿ‘<ÿ®1ÿ·ºþì{þÂÙþZ¯þ`oþFöþi ÿõÿ)oÿNÿ×ýþ!ÿI5ÿ8ÿþØçþÔÿüÿ¬ÿðJÿ>ÿÌBÿOÿÝEÿÜSÿ—cÿCfÿUÿ¶Zÿapÿóvÿ(†ÿqâ_ `âȺÓüJ•‘ÿ$­ÂÔÿ䘡!ú¯PmäxT%‘YRLrÛ˲¹øóÿOE{#üÿìñÿ¨gÿvÿîÍÿ„ÀÿÓÓÿ€Bÿÿþž÷þ¨´þ`»þ'÷þçÕþm þ¹÷þj6ÿU=ÿÎWÿö;ÿv ÿºAÿä<ÿWÿ­ ÿÅÿ ÿÁ<ÿùIÿËEÿ!>ÿ'Hÿ>Cÿ‚Mÿ-XÿDÿGJÿxcÿµÿOƒÿŸ‡ÿéø|¹ ¿?¶7^‘Âè¶7–òÿwXÒŸ6²B”|õ¿ueæÿrª*÷Õf³Yÿ¢Yÿësÿ›pÿPÿÿZ€ÿê4ÿŒ"ÿ?ÿ>ßþz×þñäþ±Ïþmòþuúþ»ÿ'ÿI7ÿgÿ<@ÿ=ÿOLÿ¥ÿOÿ4ÿÿ«0ÿ<9ÿôRÿUBÿá2ÿz;ÿó-ÿìGÿIUÿÁNÿÚlÿéqÿ#zÿ§ŠÿŠÿ¼Ùâ.”‡7ŸS™ 7Ô!þË¥ŠñÜNÄ𰉌ï&H嬅ۛ¢€ÿÍUÿÆÓÿ ^ÿªÆþª#ÿ}_ÿärÿêbÿECÿÊiÿó.ÿ=ÿpúþ¸Óþ6ôþÐñþ’øþ½4ÿtCÿšIÿ(NÿàMÿºXÿÔLÿü(ÿÆÿÏÿL>ÿ‡-ÿ3ÿ„Kÿ²2ÿ´.ÿd8ÿ#8ÿõCÿçTÿÕrÿA~ÿ€sÿkÿprÿž™ÿp)k ÜÒfÝÔ»~\ß}÷žS\ŽPY,èÿÄJ”¥öªÌ¯Õÿ8Ëÿ­DI˜ÿÞÿ=#ÿ°¦þØ·þwÿsŠÿÌGÿpaÿ™™ÿM}ÿ¹ÿ¿Öþ¡ÿéþþAöþ?ÿ|ÿ@?ÿ¤aÿ°bÿŸVÿ¡Dÿ@CÿË<ÿv&ÿóÿúÿQÿ¶>ÿ_ÿ?(ÿÖAÿj;ÿ (ÿüXÿO€ÿŸOÿ(Qÿy–ÿFŒÿ6Lÿ·|ÿ¿ÿ‚µ”  äú Ýu´ªÖFzNNºúÿÑLR<‘ýhŒV’ˆgš[ÿ ¶ÿ{íÿ òþÖþ¬Ùþ€þíïþWªÿ…²ÿßbÿÏSÿ¯zÿ`jÿeÿ¾êþþìþÓãþÝÿþ”(ÿOÿ¤RÿPÿ–bÿsLÿ$8ÿŠ-ÿÛ2ÿû6ÿÞÿh ÿê:ÿŸ-ÿ´ÿ¹)ÿ_]ÿnJÿû3ÿljÿzÿLÿØlÿ‹ªÿõeÿç5ÿªÿ°œÿQTÇÅ Ûg ê ÁÀ$V 8+6¡Àÿß ’f‹^d®ÿzµÿq DaŒOAÿCDÿìUÿž‘þ³¹þÞÝþ† ÿËKÿ¤Rÿkbÿ3ÿV§ÿoÿŒÿKõþ äþçüþ´ðþ¯ÿþMÿ¦nÿNÿÛ>ÿ]GÿK0ÿÔ8ÿø:ÿ %ÿ (ÿØÿwÿ ÿè4ÿB9ÿè4ÿxnÿILÿ'"ÿ‘ÿzÿw+ÿ\Hÿ‡ºÿgÿ“ôþ[{ÿ¼ÿ}‚±êó©e… ­³¯—æ—­ÿ†þïäÿCüòÁÿ†…ÿ 9²dÿPÐþýþ0½þ(ÿÿÀáþcÿßDÿÊÿ¾wÿrƒÿ„ÿCÿsôþ~àþšæþ‡6ÿÀ0ÿ(1ÿ¤9ÿIZÿtfÿÏÿ_%ÿ;Nÿž)ÿ8ÿ/ ÿ-ÿÿ*ÿ¥PÿÍ8ÿˆ-ÿavÿ kÿä5ÿqÿ‘ŒÿŠGÿ¸)ÿtÿÌvÿ²ÿÏLÿ¶«ÿêÊÐh % €w+LôX> ‡€¢·þ »ÿykBÿJnÿƒO÷Kÿ˜–ÿIoÿœÎþ#hÿÿcÜþw!ÿøÿbÃþUÿi£ÿdÿ{Xÿ2rÿ¬)ÿŸ ÿ5ÿeìþøÿi@ÿö8ÿÿGÿÁ_ÿ-*ÿõÿT1ÿD/ÿ¿ÿF ÿÇÿ?ÿ£%ÿb.ÿn5ÿîOÿÍcÿàqÿ,WÿkKÿ‹_ÿnNÿ;ÿxKÿ¸aÿÀUÿÿZÿä~ÿ\äœþzǾYÏò·‹ôJ+ŸDNɵÏÄÿ»Jÿ¸ÿ ýÿ¡§ÿFØþ5ÿmÿ®jÿ ÿ0 ÿ¦Íþ2;ÿ@)ÿmíþœ7ÿùeÿ\+ÿ¼BÿË—ÿcWÿ ùþŽÿBÿ@Sÿp ÿå1ÿ€Vÿ(ÿ€=ÿO9ÿ"ÿïÿÓ"ÿŒ'ÿ±ÿdÿØÿ2*ÿTUÿ¦Tÿ6Yÿ…fÿªiÿxEÿq5ÿ_ÿ÷Yÿ˜Bÿœiÿ uÿhMÿ¶ôDZ÷f{µ…ƒLˆïªS Uþ¶ä¯Ì߫檣Ü<ÿ·=ÿÅsÿ‰ûþa%ÿ(ÆÿÕOÿ:'ÿȘÿM9ÿ˜ÿ7Sÿ6ÿýõþ¥õþ Pÿ8`ÿKÿ²iÿ ^ÿ&*ÿóAÿ iÿMÿ„ ÿCMÿ¸hÿ?<ÿÿ5ÿyÿ5ÿ§2ÿ[ÿ?:ÿ?=ÿ2ÿþ3ÿ…;ÿ@‰ÿ³qÿ­?ÿÜbÿ6BÿdJÿzÿ_nÿòeÿà÷ue°Q:¸ºCYìs­r¤´àXI·M1ŽTj, É]O-’}ÿÖvÿY]ÿàLÿæ)ÿéÿ—Lÿº¥ÿ>xÿá0ÿª^ÿ=Bÿ>çþõ'ÿ16ÿRùþC;ÿÿæyÿìRÿl\ÿç[ÿ13ÿ}Lÿð0ÿÄÿ­Eÿ"0ÿ­ÿFÿ…*ÿç/ÿ2ÿ­WÿO1ÿCÿJÿ?Tÿ•TÿPIÿjCÿXAÿµBÿÿó”ÿE\ÿyjÿs.s9ßF ê7d?þ6QgØhnšXþ ãÁB:gV?“1’„ŒY×»ÿLÿ±Žÿäÿjæþk‘ÿ2ÿcÿ1ÙÿIÿ”ýþ:ÿ¿0ÿ‹èþö)ÿJJÿß*ÿ¸xÿº–ÿÑMÿAÿŽuÿéhÿ3ÿ’Fÿ¯8ÿ_ÿ­(ÿB2ÿLÿAÿpÿÇCÿ¶"ÿç>ÿNÿù.ÿ¯Cÿ;Xÿ~ÿ%VÿáœÿÆÿQ‡ÿ³‰ÿU8S c‘»ºë%mã\T1(õõF–KµÓxÛ&Uêl?ÙÞ7–\|·8À:6QQLÿÌÿõÿg%ÿ)8ÿÛÿ9¡ÿBIÿÜfÿG7ÿ«öþ–!ÿeIÿêÿu:ÿûÿƒuÿìRÿ[dÿ©QÿáSÿ*aÿ Lÿ=ÿºíþ®,ÿHEÿ‘IÿçzÿWMÿ-=ÿSÿF8ÿ&%ÿd+ÿFÿ*6ÿÛSÿ?‘ÿ5ÿ%Žÿ6¨ÿezZð `0Å¢iÁ‡ ÑþoÃ,¾Xç ¯Ö aøõkb NHÒÈ•ïF§^$çÿ[|ÿÅUÿ%aÿsÿ`rÿDfÿJjÿ_hÿ«<ÿnÿ}-ÿl2ÿ¬1ÿŸgÿ€{ÿ«QÿRÿ uÿÁKÿ =ÿ™]ÿÁÿÿñþÜ5ÿ~4ÿzHÿx‚ÿniÿ*OÿÓTÿ²:ÿãÿ©*ÿ/Oÿ8>ÿyeÿ2ƒÿÌbÿ´‹ÿ¾ÿÀt¸) SV7ŒÊBB·óÿ2þR¸¯1½%÷¿ÿB(Þ }­Ÿ¢–]öÿAlùe±—zMßÌ—bDÕÉÿh‹ÿ§›ÿK‰ÿX2ÿçÿ²Gÿdÿ›Bÿ>ÿõ'ÿb<ÿ„Pÿ#`ÿ0Eÿ'8ÿäTÿ‚bÿGAÿÖÿöþîþÕ*ÿ²JÿCÿÔmÿÀ}ÿA@ÿ!:ÿl=ÿPÿ':ÿQ~ÿËHÿ°Pÿ[ÿysÿ~tÿóŒÿ°¡ A q|,Q¯¹¯(¼ÿÜmýíỆ%ÿ1Vÿ—PÔJ`m!mŸÿÁÿü%7=OÉ «bW tL§G3ôºÿ{yÿ»Mÿ-ÿrÿÒYÿ˜Cÿ˜)ÿGtÿfÿåEÿ=ÿD+ÿ½Vÿ3Qÿ¨2ÿSÿôþáÿŽÿ .ÿÈiÿŠÿ¼Kÿ-8ÿlEÿÑÿ).ÿ wÿ–uÿÀqÿõtÿ¥jÿ0wÿÏiÿWÿÓî×Ó PÀ·˜ÑJ_ê¾²ÿÂ)ýPæRˆ<›þ°íþ„¬.ö"(ùÿŽKTÿ³mÿZÙÿ{»ÿñÍÿ»·ÿ¹|)O ðÍÏŽÀG*©ÿª.ÿ’-ÿë@ÿòÿÓ/ÿ(ŒÿßbÿyRÿ~WÿÑ!ÿˆ-ÿ¸ÿÿ> ÿµ ÿ‰ÿ7ýþªKÿ´eÿ¢4ÿÉ9ÿ >ÿv ÿpÿÐfÿº—ÿ›—ÿÇÿš‡ÿwÿ Pÿ÷Mÿ’eÿÀ[_Š9 ·®Xp ½ÿ…¼üt²€=è%þ>¨ýZÞÎc”>Yéÿ}ùÿ†yÿ08ÿŸËÿ  ÿ¥ÿæ<ÿèrÿ¿$8FäÇ_¬Çq9J·ÿzYÿÎRÿtRÿ®Gÿ½YÿÝbÿûXÿouÿ,MÿB ÿ÷þÝÿþñ ÿ1ùþhöþJ ÿÐGÿjFÿd5ÿÇ9ÿ½ëþˆÿþ¸Mÿ2wÿXžÿ…Ÿÿ ¥ÿ†’ÿ¥qÿâ\ÿ¹]ÿbnÿ9túbÑ* ûXåg—H>“þlµüy1%#±üèŒý>7\b(ÆÿdôÿÛjÿT(ÿ ¥ÿ‘¬ÿ[ÿPÿÒRÿ“³ÿ4¨ÿjøÿŒA8¶Ùã|–ª&åÿLòÿyšÿÐOÿ_ŠÿÆŽÿà\ÿHÿ ,ÿdçþa6ÿuAÿ´üþÙþ‡ ÿ† ÿGÿdTÿ,Eÿô ÿöÿsÿù@ÿuÿò¸ÿoÀÿbŽÿ|jÿ'„ÿ•ÿQ„ÿölÿ|³±sY ³eÊx{[ký3“üŠëq>áûq¶üŽE3} >ë’ÿÇÿÝ]ÿþÔþžÿ†Áÿlžÿ6Îþ$7ÿ¦kÿO'ÿ/ñÆÿĉÿ.:I1rUÓ)\çÿoøÿ+ßÿA~ÿ#›ÿ6GÿØãþ».ÿª6ÿ .ÿ‡ÿ[ÿvÿþ6´þV ÿHBÿ*ÿŸ(ÿˆ-ÿz8ÿãIÿœ‰ÿ[Ãÿ©ˆÿÊzÿ—€ÿÿešÿ={ÿQƒÿ9 …,Ž  r²=h}>£žûEVýVÐÚ(]úûŸý3;+Àÿ‚ÃÏþ rþÿTàÿAœÿ²þ‚5ÿIÿÕÿÙÿÈEÿÎsÿÅaÿRÆÿ•!æCÑ-ý^U{ÕÅÿCjÿGSÿ^(ÿÕ/ÿúÿ¨Eÿ†.ÿ„Ðþ¢ãþŸûþEÿŠÿ@ÿŠ/ÿr ÿ–=ÿ&xÿ˜˜ÿÅ…ÿuxÿ^‰ÿ»‹ÿ#„ÿµ†ÿ¬‘ÿû©ÿD÷ yËú ñ¦ºØ3B·úb5ýC =zCù2žýK7óÛ&ùzÿõ mÿžþ'ÿq…ÿükþ%ÿzHÿϽþ7©ÿ4#ÿmkÿtÿÌóþcÿ|Ûÿ„#úª‡õÞÿ÷êÿÔ¹ÿ*PÿÙgÿ4ÿ¸#ÿ†üþÈÿ ÿ¸§þ©9ÿ ‰ÿvUÿ–#ÿ,ÿXÿzBÿ9aÿ ²ÿëµÿ†ÿÉWÿ~qÿ ªÿ{¹ÿr ÿÃq ƒLj“ =3ãú+§ù4Áý‡Ý`ÅcøÞÝý åÙ­ãÿ[ÿó6§ÿaìý¹Úþ^5¶Nÿ¿þq”ÿlXÿ%ÑþËÃÿk–ÿ9Wÿv&ÿ·´þK¼þs'ÿ.‡ÿdÿòÿU¡”ýßÿõÆÿÏÿñÈÿ ÿwÿ1ÿ—ÿƒåþöþ]qÿÔ‰ÿeÿ"Tÿç2ÿ)"ÿn(ÿrÿ¾ÿ{™ÿøkÿ rÿW~ÿÓžÿË”ÿ¡ÿ“ ’©5 %Ò¢îªËÿ 'ÿ¡6ÿž"ÿ*ÿä¯ÿrxÿ½Iÿ2+ÿg0ÿUÿ0Šÿ—ÿžÿµxÿÉiÿsÿˆÿ3¶ÿ®©ÿ' ‡Ö¹ÞÑE»±îpùù0ûÛBQ]‚føö¦üX”´Âÿ«ÿ@nWPÿqèý" þ¯©ÿJ%þD‘þ&ÿ"ÿíÇHÿwÿæPÿ†þXvþ±ßþöÚþàõþÜŸÿBöÿ}Yÿ<ÿ‹PÿI+ÿ(Ðÿ(×ÿ©nÿñjÿáÿq–ÿDfÿÅŸÿ»ÿŸuÿ8Eÿ+ÿ©`ÿdÿ¡hÿb¬ÿ¹ÿ˜—ÿN[ÿ;Mÿ”ÿ‚Ÿÿ²“ÿ_ÿ š ÿ-›;;œ¸·ú¦Kù;^I)ùÛ«û"M!G4 ˆ¼þt$Õÿ&ßýåýÁÿ¦z<2þ'9þ“Lÿ±¡þÒÿL‚ÿ:½þÌ3ÿêÚþe/þåeþ§ ÿXýþ‚\ÿKæÿ (ÿ2ÖþÐþ¡þíiÿ̨ÿª—ÿʳÿgÒÿ0ãÿ‰ÿ Ûÿ¾ÿ]gÿÂ~ÿÑEÿ]ÿâ<ÿ»uÿ3Ìÿ;Ãÿ=¦ÿçNÿjÿò€ÿ£zÿ—ÿ]ç 力HMÑÄõ}o2úà~ùvÅNMxéøjýñpª >cù=þ 3¾´IþœtýÇæþGªè þSþÿâ¢þ>®ÿÍhÿÙãþöÿzþ¯ÿýýpþ»YÿRÿ,ðþûæÿ² ÿÄ<þ)ÏþZxþOüþ{ÿ2’ÿWðÿðÿ·àÿT–ÿJñÿãÿã­ÿÁÿÅ%ÿË@ÿƒÿa‰ÿ’Ðÿ†ºÿwÿÔƒÿÅ]ÿtpÿz¨ÿˆ˜ÿÕ JB/¤QØd ÅUÌWùä«ù‘ìüºùHÔýo2¸æþ_ï£ÝýÓÿ°n¼þZýˆàýÆ >›þìý ¾þú±þà³ÿŠÿMÿñÇþ®þVèýj·þó[ÿë´þ#ëþ²xÿúþmVþ–bþbþÐõþïwÿ+}ÿãÿÂæÿÕ ÿ¨JÿÙçÿ[ÆÑÿ¤œÿioÿ®fÿwtÿ£ÿÿÄÿú¿ÿPžÿF\ÿbÿ}ÿË«ÿú«ÿÈ™ûB ¾‰®» Ù%Ã’÷åXùoïhˆ~÷tøBôÿ)‘ý»±¡;þ}Âÿ½ÛÿH%½ÇüDýhÔËyþ´þ®\þJ¬þ•òÿk½þ)ÿí”þ’õýøýˆŽþ8>ÿwžþ›ÃþüþBâþg¶þf5þêOþbÿêŒÿ5©ÿl³ÿa¾ÿä_ÿ|ÿçÿ<,ìÿ!½ÿ‹ÿ·Šÿ8œÿW»ÿÛ©ÿíÀÿÖœÿ±eÿùgÿÿ}ÿ¥±ÿ¤£ÿ»èzpÆ© ºª5#ýÝô“û*ä JçÿN˜õ‰ ñîæû >CþgÝXÿ×É‚¨ýà±ûÊߌÿžãý“gþ„þ…ÝÿÆkþÏÿP þhKþ Êýó£ý\œÿ ‰þ=þZÿ£`þ{ÙþÂþ·1þ;ÿ¹ÿ§ÿÃiÿ6ÍÿynÿKÕþ‡ãÿ\æÿ½ÿ-ÎÿlŒÿyÍÿ2“ÿ6±ÿMÐÿ‘¹ÿX¤ÿiÿdrÿ7lÿ5¢ÿÇÿ‰ç«i¹gª+´Ï³›ñÊ]üΑ רüÊ;óG…à 7úyÿ1,þw°¥þL‘—Sÿd»úÇ "$ÜýUQþê•þÜÿi(þޏþ™¶þ–EþÓæýÔüÙ þn«ýÏþ(Õýßòþñ‚ÿeªýNþþÈ"ÿ³üÿ¼;ÿÓ9ÿq[ÿ,ÿÿÇ­ÿ-çþ*(Åÿ¨ÿp$ÿCYÿ>‡ÿr0ÿîuÿ1VÿEÿ€eÿcÿ²‘ÿ·¼ÿ¹éÿîæÿ¯‚ç¼*‡4jËñ³Ex|õmJý2ŒZõZléH ž {@ûÐXú4übIwFý(ütÓÿ ;þÿMÿ{ýW.äDÿé7ÿÍ;ü¦ þh¹ý2 üåÀý]Ôý”þ„þ²ëþmÿîQý”ÞýΔþ^äþ~TTÿeÿ'bÿÓ½ÿµÖÿŽûþœCjÐÿ¸ÿMÿšôþ‚ÿ<ÿqgÿÿLÿíÿKÿEAÿÃ…ÿÖÈÿŒæÿJÝÿ ¡8“-K‹ÿlð'=ЯèöXÄr`½nð”ïéŒy ?>~Üü¡YùªÃû^®ˆ¥üý¢9þÍ€þÈ?ý…v™pÏÿ§Ùÿ|ü\fþp„üÓüñoýú¸ýV/ÿàþ 4ÿvþ…§üÁøý_½þ;ÅþZ±ÿsÿõ¨ÿþaÿ¿ÿ¼£ÿ$,ÿŠoD­ÿËÿ íþø»þ1RÿBMÿ£†ÿ3ÿãÿX"ÿ1:ÿÝ‘ÿ¯¦ÿ×Äÿ¤ÔÿC>&1plO0ìÒÁ&\(Ö÷›.,TŽïb¡ç< B; úü(úF‡úYc<ý¼ÝüòÀý-fý´ñw–ýÙwCåÑ´ÿ£wÿDêüEtþríûúìûnêü§ýýäÿsªÿR‡ÿÄ‘ýnòûNþÿ`”þñHÿX°ÿãËÿT…ÿ!£ÿzÿŠUÿË–û¸ÿÖÿ ×þšþ0Cÿ˜Rÿÿz@ÿE ÿaÿ9ÿå–ÿ“ÿެÿÁÀÿˆzSÄ3¸£ûŠë¡?… ß÷u; ŸM¯Ýê¢輡 Љ1ýÖôù±4ûÁ·üÈþ›€üÕúü v¢þR½I}šÿÏiÿ“XýÏ÷ý´¸û=ÜûA"ü@ñý³4R„ãÿ 6üIûΔþ{:ÿŒCþà*ÿMØÿSãÿ=•ÿâkÿD"ÿå¥ÿؼ8žÿÓêþ”þ“þÑ4ÿ÷\ÿýŠÿ/<ÿYEÿ½ýþÿtÿj†ÿ'µÿm¤ÿpM 37eSü0 è?üŒ #ùõA¢ n:luê‚DåÞ tn|8ü°ûj(û¶tü¶ÿeüß»ûÆBÿÜÓ$p7þiæfýÓ?ýükü`ûˆšûÍþš¶¿%BT‚ûéú†ôþ¶„ÿ¦Ýý?FÿqÚÿËìÿõÏÿ*öþfÿ«¸ÿÍÈÞÒÿ@Òþ˜zþÕ‚þ¾8ÿTÿÔ—ÿdÿ,ÿ¥íþÿSQÿ~vÿrÍÿˆ ÿ`o!²!:DÓüí\åí*Æ 5Íó&† ¶¼‘ê–Æâ¡Ôe€~û ü+9ûHGæ¼ûŠ©IüÆõú¨X¦þP´amAiüúfDÖýÁHür7ýŒHû­›ú©þ™·ÿŽ4ÅûiÍù÷rÿèÁÿ,•ýDrÿP¥ÿbÐÿª'á–þj·þN·ÿ9ÑáÆÚþ¯kþíeþBÿp:ÿǨÿ¯ÿýþYðþVîþ’0ÿ¢Œÿ_ÆÿÏ«ÿ($N!<|ñúšåØà ®ñ¡Ó b‡‹éhƒâ«®ÐH´üŸü 4û¯/ÿ-ýú'›û€rûëmÿ×»ýð´`Ì”cû¦ |éþŠ¡ûþš û0,Tûûü^æþz–üµ’ Úd7ûNà Pî³û¡üÂ/ü­6ø¼ýW÷þG3ÿ¼).àû0›øÁF¢8 ý(çþƒÅÿÿãôÝþp¬ýBÛÿ#WÈÿS7ÿiVþÑNþïEÿŸ&ÿ~„ÿè‘ÿÍ%ÿ,Ïþ\Ãþ) ÿŸ£ÿkÔÿu¯ÿ§g'p™?…Où³å‰/T)3îÈù 2Woìúðã¡  Olý¬rÿÉÇø?[ýßìûï7 û°Íü™æþ:û^I Î\*vü0AW"ûü0û‹üûÒBø}úûHbþŒhþPÀmýmŠø!“ÿŒµh~ý cþÝ­ÿ˜¨þ±²ÿ‚¢ÿhý£ŠÿÊ]ÃÉÿ|EÿcxþìMþÆÿÖFÿ5bÿ°dÿöoÿ$Óþ<Äþ“8ÿ®™ÿêÒÿnµÿ4Ä&–@A²ü¤~å$&=>Íìd£ nõùð°Àäôµ Òq'lþåu¸Æ÷´ü Šüí¿¢ú8åü úÿô†ùŸA£¾ÿƒç?)8ÁþØ(ù›ÕûŽù7.úLþÅ•ýt:ßcÁùzým¬èÿzŽýÃÈþz±þøaÿº4*XýÅÿ—9Ôÿ²Yÿ‚¬þìKþ Éþ°7ÿÛeÿª8ÿµ°ÿ,êþc»þkÿ;tÿèÈÿ)ÌÿyÙ$¸Š@„¸Š]æëì 0ø£ë¢–Éœzõoçä~‰ ÿï^þs­ã´÷¸ûy…ýc Ž;úpýóÓ½øeŒŒÓú‚Xýÿ[’ÿüŒ‘ùEÜú¨àù‰ùdÒýå ýÊuC~CÚúÖÐû0ÿ(9ÎZý·õýÜþcÖþQ®þŸuþ3hT­lÿhÙþubþ#Vþ&ÿ¸|ÿQ?ÿ Àÿ¥ÿ¾Ñþ~`ÿh†ÿS¾ÿ»Ìÿ\ñ"¶c@/„cV踉 _5”ëöƒþÃ+GŽü˜+åý˜qÐ{üju\¶øÜiú&…þ6Xúøü­Ü‘°ønW}Õ3!š¼ý\tÁtú.žù Úúù‘ù“³ý-µü@`uhÇý Îû ÎüGÓüþn`ýoþïdþR\îþìþj‡yœ0 ¤½þí<þ/aþ‘Éþ~ºÿeÿ:›ÿÇSÿuÍþ}`ÿ1˜ÿ¿ÿ.êÿÅÚ ?»@nÕ ¼éÌ 8è†êrßýð‹zJ;èœ\ªþéú°í¦úYØùÈþŒ-õVüˆû `úLcÿÏ)t ‹ÁÁý÷ÿ™ý™Þøû!;ûz%ýyoû™“ÿ~>-†ÿ‡Hüþkûüûþ›jªµýâàý.Xþ_¿ÿJoÿó_þµÑÿD˜¥n‡Ùþþ8qþ}×þ4Ðÿ ÿ¾sÿ€_ÿ×ûþ,@ÿR®ÿZÝÿÝÿÈÞ!\â@à†#Âì\JBûÕ‰è*kKRÕ,«íOý Ïü;üAGúÑ û^Üý\z–¨þ”¾úÈ@lüEü¿fðKc‹ÿLiÿ«Fþ™úÛhûW!ûxíüâ<ûdþ+ÝF­1þRû%ý;œ„÷þâýÞ¹ýµrÿˆzÿð¨þÉÿ& àVñÿ²þ¯þ€ ÿÆÎÿöÌÿN_ÿäVÿrÿ2NÿO·ÿ<¸ÿ¾øÿ–•"dT@ªwöïNþ½áö{ç9Ž&u Ü„Çò§oúT©þäÿyX(ÐùvöüÄáü(Zÿ–Ù½(únõcŸþÔúÏpÿ¦ÌWDÉ1aÇþ˜ñû.YûÝúâ¬ý7Iû¦½ü… ]­(½ÿVvûà/ü¤Íÿ±Ìÿ¬sþ°býºÿJ ÿ;ÿXûÿ’¥ÿéÿå.ÿ™þÜÝþMIÿÁÊÿ»Àÿ”‘ÿ 9ÿc5ÿýkÿk…ÿПÿFúÿMÈ"1?‡’ ‰Àð±Î)ø²åZˆÿCr±H÷§}÷îŸþ´—eÑùýSýK*þõ|ý¬ßóúL˜ýHÿG6üÄ×ý?>WUŠ·ŠJnØýªÂû˜Nþ0úŠ{ü£³üHÁûy ÿ5ÿX˜ý=Ùûpjþ™ÿ|)|¹ý:Ûý2âþ¢ ÿ°^7GÿßtÿuVÿÝÿt`ÿè7ÿ*Íÿ¸ÿÈÉÿòsÿ®ÿ‹qÿßcÿOŽÿKùÿ»˜›=_Å.Ìñ>þØúãRä~Ðü÷Sq„ÿû{õ±‹þ ´ݶvúx þ7xþi!ü£óú•ûù¥û¡œÿ£fþ„ þÛÝýzIäŠÑùÕxáøúæèþ‘~ûNAûÖþS^û üýædýßå–ùüûšýB’ýãqêuÿ´Èüù…þªðþY¦ÿÐÿþ|ÿiÿ íÿ03ÿ}£ÿ  ×ÿªÿ÷ÿô9ÿÆ{ÿŽÿ`Ûÿ/ U÷;4€ „bö§±Tùõ¯ç> úgãþþô3EþíIô÷ý|–%n*dùkÿ8@ý@^üU´xšúyyýY±þ%£þ<®1þ’(K‚Ù»¸9zúeÄýyÓý€åûÙ¯üc*üîïýùüF{þ Ù†‰þñ$þ»¿üÔûþù,„äýZ¬ýÜ‘þù'ÄÓÿ¡jÿO|ÿiÿ‡ûÿÝ“ÿn¶ÿ0<Êòÿpÿ¢-ÿ‡9ÿWcÿ÷Šÿë¾ÿ[vÚO9Y”¿ÇøÛj_!÷Bë{ö³ù5( 3ò>ƒñ´þ¡Òoùˆÿ¡hûÄÀüÉ…Àø«Tþáb*3ý„'×Ù@ ôdäÂ%`LýfëýAý‹™ýÕÓü‡aûKþ£¦ükü¸àþ[|ÿh1KYýv"ýâ?ÿXÿWnþ©ýŸ:ÿÿ1°ýt¬ý˜tÿg&ÿ-ŸþâÀÿü` ¥·ÿ)¦ÿÐÿL$ðrúÿ°‹ÿ‚8ÿá×þ!ÿ¡ÿ•Âÿn#v7Í~\øŒ ïÒðygë´ëý›¹óaF ê´]ó‚ü1ŠŽÒ½gõ†iýr‰þV¢þñ5‰øRaÿ¼Öh,ÿ×”3l~ªÚûþvþÿŒd…?‰=ìýMÉýÿX?ý€?üRhýûIûúcû?ÿ¹É$qZ?ÿ+ý‘ývªÿY-œ%ÿBœÿ¢íÿ1ºÿ0äÿ Éÿh¨ÿ`\Â~ ]ÿ“ÿ&"ÿÝ/ÿt‰ÿ›Áÿ£’5–…ýÂÕø¢´ ‡öï4âåùMdåø´’»›Ê»õ¡5ýh`„ùC‚õîÏüqÎA/þðBÛ4únãÿW«Îáþ–núRº3í=ÿßV%šCÿÐP&„)mþWkÿ›þ¯äý?µüªEüã üYûCÈý%‡ëò Iÿ9þ†~ýp ÿ†AÚ¸ÿ!áþ‰™ÿOÈÿ­¦ÿ„Ùÿ5dÁalÿÿáÿÀÿ>@ÿµÿZÏNá1*ù"ûBj$MXí$䀑>|û·lÔ÷ø9¯ûXþ·SðöCý1¤yLþÃÿ9Âû5ÞÔp þLÅÅotíÿÓÿ&Žm/¸ÿ•äÀlöôþs/>ÿ£€ýžfý|­üõ9ûüâJþÀ‹ÿ—¡Y¯ÅþþA[þØÿNÿ{€ÿZIÿ-ÿå_ÿ‰¨ÿ™×ÿ5©ÿÖk:Qr_ÿŠ…ÿ.ÿ&ÿDÿjxÿö¨< /wâùæëú\Ê"j<ðÜúæV'JÐùUŽg(@úò¨û‰…¯‡ø¤¦ü Ÿ5þkÿwaü‡=èÑ"ÿK¸²ö÷‹Rìþ]pV†ƒ7ÿ„Cu=¥ÿß@†ÿ¿ þH9ý?ýE¤û¾õû;!ÿK¦ÿ¡¹ÿ¬Cgÿ()ÿýXÿ(Üþ¦þØDÿ-fÿâÿäcÿÓÿ=«ÿ—P;ª^ÿK‚ÿDÿuÿÖ.ÿ²‰ÿG6«¾+-lûýû‡ 4ùó¡¹è®„A4û‰ÿ¢)Zrü(Úü4ðúó¬ü/dÿ¬ÿ7²û{áÿ¿Âî—ÿ º¤IȽ½šÿ¿y LÿRéÿ^zÿöÑÿ¸òAÿ´Ôþâèý®ý„œü¥üF'ÿ+þÿcÿ;ÿÄ8ÿ—^ÿÀqÿ×ýþ܈þÅÆþ¶cÿß4ÿ¦ÿÞŸÿЬÿ>ˆ0 kÿAcÿòOÿý3ÿ£<ÿ¾Žÿx£­ô$&//IN8 iÁó# ú›/þ>aùÕ´V‰ú”ÿR ùHíúðÿDýÝšïRÿC ûC°àÿ°?þÂÔ0ŒEÕ`þM0þ–8þ¤ŒÿBGþÅuÿž#8hÿ¿°l¾@³UÿÆ÷þ.Âý’>þo ÿ(þXTþCeÿànÿ]äþQáþÍþL†þ²£þø°þìþ¬ÿ˜šÿ{ÿá%ÿMDÿ€†ÿJ•ÿ`¦ÿÝú"Ìmþ“¯ #9úUönH~ú [—þ&û5ÿv{ª€tÂû77þéKþýŽÿ"ýÿóEû޽y‘ýè¦]YùÄÓrGÔþãìþŸ¬ÿ þבÿ*2¨(­6Ýr¾/lùÿÄXÿSìþ 0þ_þÿ›‹þc€þúÌþÌ2ÿìþõ†þþª‚þÃÖþ¢ÔþÖÐþFÿ…ÿÛ<ÿ ðþÎÿ[TÿßxÿCÿQÍÇ™èb ŸdÿF9 ¢äû°öîœ×ûL´§Œ5ü²{ÿò1MðüÜhþšþrnÿ³•ÿ®^üîA=®þ{ªDƒ"›vˆGÿöHÈÿ7þÚÍÿ³[r f²{r’pÿ ñþKÿˆ–þ÷)ÿo9ÿ`þ<Öþ@åþëÀþþÿ“þ<þ|•þÔÜþGÕþ¨ ÿ®<ÿÇ9ÿî,ÿ5 ÿîþŠ=ÿÏmÿl_ÿÏ[ >å„ !1 ºƒûû§ø¿¹¾Ìû’uŒ–½ü’úÿÐå»VyýKÔþ[ëþ Gÿ¹Lÿö%ý¯Ëæÿ5­þLÈY+AÞôC(™ 6 ÏÝÿ2ÃþÁëÿ±cs¯ÁîeM^ûÿñÿ*»þÂâþTÿòÿ¡ÿ{dþ7èþ]ÿEæþ°Éþynþobþ} þƒ—þÝÓþs.ÿÖ3ÿž0ÿ“ÿ…õþ”ÿ³ÿÜGÿioÿ÷8 ñìÆu ¿¦öv ß„ýË´úc'þ¡û©2ƒ&ÈæüH¸ÿz=’ãá þñ>ÿ°ÿˆÁþ½ÿßÉýðÿq™óþ¼%ôf+x´L|)ïsï N÷ÿ½àÿüK½1ˆgêÿ5„ÿé{þ€þÔoÿ¦¶ÿ<Øþ‹eþäÿÏ:ÿËAÿF¬þë.þ6­þƒ„þnþÃÉþ#ÿ×Iÿ$'ÿ†ôþöòþÿJÿ1$ÿ$wÿõ ±g÷ îú\/ý—|þþ÷üoÅ<=G7ý¥Æÿ9™u ÿ[ÿV%þÙ×ÿ³èÿúëý6±ÿÿÒÿ³^ÿG‘~;ÎÈP©µžv­§âÿZÿ–OvÉbxÉr^"Ý©Aÿ3þn½þ“ÿx\ÿ¾šþÕÈþª;ÿ±cÿ JÿÅrþ³]þã»þ-xþ YþyŸþÝÿ³Lÿ|BÿûèþÖÝþ ÿ\&ÿN8ÿ\nÿ¨I7¯1 ”ümÏMþÛ|ÿCþì»üšO()“ùý âÿA5UjÁÿa5ÿÅþUt ŠÇý”ÿ8Ë´ÿÒ¾”Båf‰hfÁ£t4Nÿ¾Aÿö, [Œt˜3Bûÿ+ ³äþrLþÔïþHÿüþD®þÿ:dÿ`ÿrÿþ¯oþv¤þ†¯þø^þ@þ;•þê"ÿ}Kÿo+ÿìþ]çþ5 ÿÀBÿgDÿ:TÿMöpë/ ÛÏ™vPâþŸGY>ÿƒžü¯ÌçÄo·þ¦ÿÚ¾¸Kn'ÿ{Oþ_µñý‹ôÿ CÀ©œ¦’wè{TY”Öÿ@4ÿMNÿÒISšÔúÿ•Äÿ0ìëÿ¨þ gþû&ÿ ÿkÃþlýþMÿg}ÿd<ÿ¬¹þ8ÆþLëþ1 þ<[þ†`þhÊþFÿ¤3ÿK5ÿßýþ?ÿý0ÿ[IÿZÿ\Yÿíý£ë ¸ìgð‘kM%ÿnt$!ÿȇý5yV¬þ+³ÿ™ÜôÏÄTââþ/¤þQÄîÿê,þ]{OÞØýþw5Âõêuûÿ‘ÿMŸÿОÿ¸ïÿ5ùÿÕ¹ÿùùÿ9ÿäpþ”æþ—ÿ¸”þʼþÛ$ÿÜMÿ^ÿÉûþ]µþ“þþ®ÿ6þH_þ¥uþD®þ@ ÿû7ÿFÿæÿý3ÿF0ÿøHÿ^ÿžbÿ TÂp \¼G0ªä”ÿgöBGÿx>þ÷Á Uÿ·D‚OzØ©ÿêNÿàÙ„‘ÿl²4 1faÒÙFZ„¼sF°ÿÍtÿÏwÿûìÿûßÿnÿ›Ðÿéxÿ¡ÔþÜþ™èþ«þ¤þ\óþp-ÿ¹JÿØKÿßûþðþ¡7ÿìÑþ0gþPŠþ{‘þÀÌþ2"ÿ-7ÿ$6ÿ¶Gÿ·JÿC'ÿüLÿênÿöZÿº4F ¦ä¶ü,[r¶ZÌÿtˆþf ãT!8ÿVüÖ–SéNž*sáÿXŒÝQí+8Ï™™78‘-íç ¯ Íÿsÿ<ÿpÿ3Óÿߤÿb_ÿyšÿžÿðþ‹ÿ­þ5‰þ&¶þ¾ÿ%Kÿ+`ÿÜLÿeÿu ÿîÿÿ³þá™þM¯þ­«þ—êþW3ÿ7Hÿ|PÿT?ÿ“Dÿb=ÿ¹JÿÉmÿöfÿrõØv  ¿—UØ“(d·Áûÿª†ÿà.'ã]:ÿ¾ &jï¢(@ٽƼšgÒÊÿ”í‹p×ÿš bàÜ© Y"oB·ÿÿ|ÿ7wÿòcÿ=¥ÿþcÿJNÿˆCÿ²ýþÚÿßþ3þk´þŽ ÿòBÿ"\ÿ¤uÿ£5ÿøáþ}öþÌýþ‚Õþ±ÉþÕÐþáþŒÿ"7ÿxEÿJÿžLÿUÿ^FÿXÿ¨oÿVwÿìZ¶ …‡&E-¡(6f¦5i}†ÿÆÇ‰^ãÿó¡ÈÓg˜Z¤­÷!@¢ù1Kmÿ·TÍgtÇÿ3©~þmÄZqâÿíqÿídÿu\ÿôuÿRÿÿ]!ÿæÿNÿÿó¥þ´þ¸ÿ>ÿ°Kÿqpÿ(ÿÃÝþºóþ¨ÿjýþ¸ùþ@õþûàþWüþµÿ5ÿ)HÿIÿlÿœMÿË>ÿáwÿVmÿ XÿÛòä (9yÚá­'@‡š3!1þU7MñÀ‰ŒR~:ž[+ÿ“¨~·ÿCrÔÿ´¢ÿ©>5kAbÎÿ»œÿgÿŽGÿJÿÌBÿIþþ?ÿãÿ› ÿå!ÿ¶&ÿàþ±ÖþŸÿ7+ÿwJÿØ)ÿq ÿYÿé ÿ(ÿ°)ÿpÿ}ûþ&üþÔÿÿ‚Eÿ0[ÿ‚Zÿñ^ÿIeÿNYÿÑUÿü[ÿÁMÿ2¡@C >áÏhåàÌD2ŠuVr#»%4*p;°¿û \ÿ›Oëÿ“lÿ./þñÿ¨àÿkø(N´ÿ \ÿuªÿQQÿŠPÿ¼kÿ êþvúþpÿ/ÿAJÿ\EÿŽ,ÿXÍþ*ãþÏ ÿðÿ,BÿÀÿb1ÿV-ÿfÿ“<ÿ-ÿ´ÿ.ÿÍÿèÿF-ÿ2RÿÞ^ÿèdÿ¸yÿìOÿÌ8ÿ`ÿöKÿû8ÿkÇå2 éÏ”rP±v3>ôã ËC7†¸x6¿ªÿ¢M.jÿ7€ÿlçÿÊÖÿª SïÿE¸ÿl–ÿfÓÿI…ÿåÿ3uÿ®Zÿt>ÿ¬LÿÆÿìþã0ÿsÿcbÿk&ÿ‚Ùþ&¾þoôþŽ ÿ0-ÿË>ÿý;ÿî=ÿ¹ÿæ3ÿÈ1ÿ !ÿ²!ÿeÿ~ÿ!ÿ?ÿeÿÆbÿiÿ³Uÿ#:ÿûGÿáCÿ$DÿW\ÿÕ›Yä L¸²»Q@w—FD}U’Eå! ,ÿÅgÿ¯ªOýÈÿ•9ÿêÖÿúlÿ°ÿÂÿÍÿËãÿ`~ÿõÿ‘ÿï_ÿÿþB'ÿ`eÿ*=ÿ nÿ=3ÿ$âþ¨/ÿOÿGÿô%ÿÜñþÂþµþ8ÿ…0ÿ7ÿ90ÿgEÿy6ÿÓ"ÿ†-ÿO(ÿ©ÿßÿƒÿ,ÿÖ1ÿ¨YÿÁUÿ¼XÿAQÿÃ=ÿŒ0ÿ\@ÿëVÿZGÿÔKÿ½Ã_ 9w ’>?ÿðëÜÓ‚ œåÿ?1éÿ|¸KÍÿËzÿ¨,;ÿ9™ÿ7G‹ÿmÿÅÿÒ‘ÿ*ÿ‹ÿ¢ÿ°Oÿ¥—ÿ0sÿ¤kÿÒLÿÝÿ/KÿNÿÌ"ÿÆ ÿÿÖ÷þËéþÛ'ÿS6ÿÇ%ÿïHÿYVÿ Jÿ7.ÿü6ÿ:4ÿ¤ÿDÿ76ÿIÿK?ÿMÿ¿jÿœRÿ‘4ÿd=ÿ[ÿ¹\ÿ¾GÿeRÿéZÿ†ÕfÆ X¸ \\Q‘;@+V9­•¦žÔîþš±ÿX²Jáº+ÿ4Dÿ”úÿPZÿÙcÿ¦Æÿ­áÿ3ÿí?ÿ¥ ÿVÿþfOÿ£sÿ“ŠÿNŒÿäeÿ[>ÿ~0ÿÆ5ÿi#ÿI&ÿû8ÿžÿQûþ`ÿÿ ÿó ÿÌ]ÿcQÿ>ÿõLÿCÿf*ÿ2!ÿqÿÒ7ÿ”XÿÁ@ÿZAÿŸUÿ$EÿŸFÿæQÿCOÿœ[ÿïaÿEKÿKCÿ%=ÿN o– ;¿¼5fòLƒ¢ëaˆøÿv<ÿ £Ë3ÒçÿÒ–”¼ÿX‚ÿ¦Pÿùÿ—pÿƒ±ÿíØÿ({ÿšbÿÒÿîùþò€ÿ³hÿ¿^ÿifÿŒVÿtUÿJ*ÿ1ÿú(ÿùWÿxSÿLÿ·ÿÕÿ(ùþ†"ÿ ^ÿ Qÿ(<ÿÂ>ÿ7ÿ=ÿ£-ÿO(ÿË6ÿ;Lÿ÷MÿÀ2ÿ@CÿÆIÿIÿÏ`ÿK^ÿAYÿ´Nÿú>ÿT@ÿ;ÿníFI õ¢ kÿÐ=s•xS°³O<ý˜Téÿ+Ð §Š/6êÿlüÿF5ÿN#ÿ¡_ÿB8ÿ¼’ÿQæÿ¥ÿÿ»]ÿ§ÿç;ÿ]Fÿ'5ÿ,<ÿ¯?ÿ¼,ÿFJÿg,ÿSÿð{ÿ [ÿì*ÿ†ÿ¡ÿzÿÕÿ mÿÐYÿSÿ‚'ÿþeÿFÿÛ ÿ©Aÿ]ÿ-ÿlÿ‘`ÿeUÿd0ÿðdÿH‡ÿžSÿÞ'ÿÿNÿAWÿ%ÿE­‰Æ l< ²–ÕÌ_JºKËWÁòÁ¸K}q‹ûÀƒ©Þ)ÄÀÿOÿRýþÐ]ÿå˜ÿ,aÿ«ÿM·ÿ0¨ÿ¤ÿ(‘ÿ±vÿŽÿ[ÿþëÿ2ÿmMÿ×5ÿ°sÿ,“ÿ1sÿiÿ\Eÿx2ÿjÿkÿâFÿ^Mÿ9ÿ‚#ÿ¥Gÿ—YÿºDÿKYÿöQÿú<ÿØ2ÿ7ÿF]ÿzbÿ3fÿätÿsXÿÎ;ÿLJÿ¢Yÿš/ÿ¦8„ü ] *y4ã;MåÓb]¨p™WhßÜ:kÃ{FsÝŠ®§ÿ8íÿã½ÿ™ñþ8"ÿ#©ÿD·ÿYÿµÿqUÅÿ#nÿÃÿGÿ¹àþ!ôþ¨ÿ¾Pÿnÿqÿ;„ÿ)ŸÿlÿRmÿîoÿµ+ÿ -ÿp6ÿÊ+ÿF3ÿeQÿX=ÿ6ÿâ~ÿQXÿÑ@ÿAQÿâ1ÿ‰`ÿHsÿ-kÿqjÿUÿôHÿHÿ'cÿ#CÿÑÏ$ <ÃܦdöcI Ûˆ)жo’ß"=<ú?9¿£$¡ó.W8¿ÿ°ÿ÷Šÿ¸-ÿç\ÿ,›ÿYÇÿ[Íÿõæÿ jÿ´*ÿ :ÿ­üþÙÃþ²ÿKfÿdÿRQÿïnÿ€ÿÉ—ÿ/˜ÿJ|ÿ»aÿ¼*ÿ)ÿ]ÿs(ÿÝGÿ¸8ÿ%\ÿWSÿ2@ÿeLÿFÿ+{ÿáwÿbÿ _ÿàCÿŸKÿÐGÿ–Oÿ¡Lÿ3õ’, †¡â‹ŒbñZºò!«ÉxúÔÿ :/ì^5üŸp²FžÖ†¥–NëUlxÿ,­ÿC~aÿVÿšbµÿE–ÿ”¬ÿPbÿJÿ`ÿ5ÿ¿ÿ<`ÿÆKÿ0ÿ¡Pÿ|—ÿV¦ÿü¢ÿBµÿðfÿ <ÿ2ÿ[îþ.ÿ—cÿIQÿÿ:ÿc>ÿD7ÿ4]ÿ—ÿÿ‹ÿf|ÿ,UÿZ4ÿ=Pÿ:JÿÖ>ÿ{SÿÔôÚ ì—Éü:)nB'¸ÿ¡ÒÿãVp‘^%ÿàÔÿC#— G?†ŸŠÑ‰Œ£Ï¾½«o¼ ?–üÿSÌÿEÂÿyàÿû­ÿYTÿIIÿ’yÿcPÿ&ÿ\ÿÈIÿ™ÿ!ÿk2ÿÔQÿkqÿ–ÿ »ÿ^šÿúyÿ Nÿ‹ ÿ” ÿ5GÿÇCÿyÿ}%ÿ>ÿÏ[ÿÉÿö§ÿ"zÿCÿ9XÿÃ=ÿ3ÿÚYÿmJÿ•ëœ Dd Dî¶ßfuÁÿe}ÿ!°»Ÿÿ£oÿ?K~UdÙ ½ìUk¯TšT JŽýÿÝ—3±¿9W§]Ñÿ¾_ÿqIÿW<ÿN7ÿ?:ÿ¢^ÿô ÿðˆÿl'ÿÿEÿ:5ÿd[ÿÌfÿý‹ÿ\›ÿnÿ¢FÿŸAÿCIÿ2Nÿ*5ÿdùþhÿM4ÿ'SÿÁ•ÿ>œÿëWÿLHÿîXÿO3ÿwBÿ¯eÿò[ÿÇœß Z :ò8Yj  ÿuªÿi¯d5˜þ™vÿŸS#9a“~/É6&ÿ\áÿ »}‡“ ÄŒêÖ²Íü⽟²oáÿÿì)ÿ‘×þæ ÿSÿè8ÿKzÿÇÿQˆÿTIÿó ÿ~ ÿÅdÿNŽÿ—{ÿ‡QÿÎCÿR'ÿã/ÿ:~ÿÊ{ÿŽEÿÿ' ÿ#(ÿ{^ÿÿcÿ|Uÿ2\ÿY,ÿDEÿÇfÿùiÿ‘ŠÿýW–Ð U™ È•Ì\k Êþí¿ÿ¹A;`þY™ÿ Hî2zb¨7èýþìÿ×ÿ0õÓÿ¦Íâi…ëê£Wº¨ÿš&ÿÿÿÎ'ÿ]…ÿEžÿ.kÿö}ÿLVÿ%)ÿ#nÿïÿ#‰ÿAPÿ ôþêþÙ$ÿeHÿëdÿ´fÿv6ÿi=ÿAMÿMÿçNÿ+MÿË^ÿd2ÿÖ ÿ Xÿ4hÿm‚ÿÙµÿ µ  dS ýƒV/[/RšþÞ€ÿ¡ ŠêlËýiÿzi—Ø{ÿÿ·ûÿŒìg‘‡&ÿƒ»þ…sÿ¡—ÿæ+ÿ¼—ÿ/YVÞ#{ „ÿożÊ„ÿ`hÿm2ÿÏÿˆRÿ ‡ÿwÿ#Rÿ¹&ÿp\ÿ&ÍÿUËÿtŒÿ'"ÿéþÿþ1òþ`ÿÜ8ÿSIÿYMÿFoÿ.nÿ¡,ÿÀ>ÿ‡jÿ±/ÿ ÿ\6ÿÈ?ÿöxÿ†•ÿ·ÿzNò(;û <ò]ô5€l„þìKÿÔ/¨Ø›zý®ŽÿÚ]à—NüÿˆâÿbŸË¤¢Yÿ¨½þÜ@ÿ¸Lÿžÿ¹ÿë—ÿz†‡ùðтچòK8̳ÿðÐÿÏ€ÿ´[ÿG ÿ,•ÿÏ<ÿ83ÿ¶2ÿ¹Fÿ>Âÿmûÿ!ÝÿzMÿ«Ôþàþ5ëþˆÿK#ÿú&ÿF\ÿŸ[ÿdÿ8ÿšÿXÿ˜#ÿ/Bÿ[=ÿµIÿHxÿ’¤ÿ†µÿ/cÔ¬ P«Æsb þŒÿ™Œ/ý:Õÿ¢xSXw€ÿ\ÂÿržÖ‹¯nÿµºþµÿ7ÿÐ÷þÞíþ QÿוEŽh ŸÊ;‚ÁÿhÛÿaªÿÕ¸ÿûÿÍÀÿÏKÿS6ÿi5ÿj.ÿ´žÿÑêÿlÊÿotÿÃÿOßþöÉþB ÿYBÿöfÿ8ÿÒþg ÿûTÿýLÿÒhÿéoÿ´Sÿ°GÿOÿƒzÿ±œÿ[Œÿë§â[š= q¦ŽÔ§Ã°Áý¦ÿxä”°§ü¼èÿhœ<°BÿBwÿa{oæŠÿªñþB'ÿ&þþ,Îþ ÿÚQÿïÏÿÃ.lxF]#Ó ¶PÿSÂÿÈÿÿDÒÿ½àþÿÿ¸ÿ3lÿöTÿo8ÿn}ÿÒÙÿZ­ÿgbÿF"ÿS ÿÒÿO5ÿl`ÿŒ3ÿöôþOÿÿíöþÕ*ÿ²’ÿ ¡ÿ8tÿßSÿ¯Yÿ(nÿ'rÿ°’ÿ€’f™Ðù 9g“@RWý!/ÿsr+,Çõû2Ûÿ+éÞ.Qÿ;ÿ1K‹`ïÿ½ýþX6ÿÿ™þ…øþÞbÿ4âÿž¿ÿˆ7Ôr#œÿŒÿ¾)ÿ…=ÿ°Ãÿ9Ìÿ»±þÒÿñ¼ÿÿVÿõ¦ÿÚÝÿóuÿC-ÿ9ÿÿ3ÿ ÿxÿSèþ7ÿ!-ÿïôþ&üþãEÿá™ÿU¯ÿMsÿDQÿ€JÿsFÿ8ƒÿÂÿ6«ä~­êÕüÉÄÓÝüº¥þ/rM[Aû]Ðÿ8yÿ¡þ××ÿ°T¡Šÿ»òþ‹#ÿbýþ,…þ½ìþ=KÿÂ,¼ÿó©ÿ¿Žð»ÿ*ÿÿ÷þÝóþs$ÿ÷}ÿ ®xñÿ¶”ÿ"qÿ ­ÿHÍÿ°Ãÿ±~ÿo`ÿF-ÿÝùþ,:ÿã\ÿ'-ÿ=RÿOMÿ3çþ—ëþ[/ÿ:xÿ ¬ÿ[®ÿ-kÿo#ÿÿ›Tÿ‚¸ÿ‹ªÿÔ;û“9P±|‚¢ý‚ýês–c¨úü|ÿ®‡òÿŠÿˆ‘þÊtÿrøÿð–ÿ…ÿ^ ÿô#ÿ‚Zþ¥½þÝ;ÿÞ9(G_5ÿ?à =ÿú±þ|ÂþùÄþäþ®™AÿU—ÿ–~ÿ9wÿ:ºÿ‘ÿÿº—ÿ’ÿÅÿ4ÿýÐþîìþn‘ÿ‚ÿàÿò ÿ8ÿ|Xÿ‰ƒÿŒÓÿnºÿæQÿÿú"ÿÕ†ÿ‹ÿ?“ÿÜÐ ¿`>Ukÿ{l’/VûöüþL;EäÿWÀú8ßÞù¹ÿÁ¦þ\˜þãÿ¡4ÿliÿÚ!ÿñGÿøìþ$þ±¯þ{*ÿãŠlH»eÿ ¢ÿ6§ÿ€ÿµvþ¤þç¦þ”þ&âÿ. ZTÿj9ÿ²`ÿú6ÿ?{ÿÞðÿ²ÿ•ÔÿF¨ÿµÈþÐÚþR„ÿìÿ24ÿ¨ÿ£-ÿËaÿG†ÿÞµÿ‹Êÿî‰ÿÊ?ÿ@ÿ¼OÿtIÿŠlÿ#›ÿ4« ì½üöþÄB‚|úøõÿ¼0ɼþ}ûÌ› ŒÖ”ÿoZþâ¢þóÇÿbpþÑÿ;kÿ_SÿžþþÞ þ>sþzQÿ¸ÈÿßÑÿe ÿ@ÿ–¨þ‰vþ,þ%oþù—ÿæ Pÿ§îþnÿtÿôÿSÏÿÌâÿ2žÿiJÿ+öþÞUÿd¹ÿb‘ÿhWÿå2ÿM0ÿ¶Tÿ&˜ÿn³ÿç©ÿP¬ÿÇ}ÿ$NÿO;ÿfLÿ»~ÿß ÿͨ gZ«a4ÿøã÷Ò3ú+åÿñò~›þD!û3×¥k•Ìÿð þ^=þ:4Èþà‹þ}}ÿ aÿ[<ÿ*þÎEþdÿµÐG'4ºÿˆCäÒþÿÈþ¿ÀþÌkþôoþwþ/aÿdÂÿÛhÿ†âþ*ÕþzËþùþùÂÿhkÿ…ÿ?VÿKÿVÿ åÿKèÿj¤ÿ|\ÿnLÿF?ÿ£^ÿÆ–ÿ ¼ÿ›Þÿ'¢ÿ5Qÿ2>ÿÎdÿI«ÿ<¬ÿ& öF¼SÎÿ£`äm,úYÿð™ÿÚú$÷KhÙÿ¹wþœ‹ýlI]*þhóýæ ÿNƒÿ«ÿ+þXþ$§þ!н!7ÿ•N†8ÿî„þŠþ,þniþÇŒþd2ÿ Uÿíkÿ7ÿæOþù©þAFÿè'ÿÈþåÅþ´tÿÌSÿ$@ÿ5%Ûlö(dÿ¯5ÿÄ>ÿ¼Eÿ! ÿ{ËÿÑÿ”ÿ±Rÿ“_ÿŒŽÿ/±ÿú™ÿL ‰ò}y*G¼lé`T°ù_àýp²ÿ;úVÿufPÿÈÿu9ýØãÿ‡QþpÃý1dþå+ÿql»&þSþì¨þØS C»8ÿ¼ÿœÿËþW$þeèýoþ}þÝþ^!ÿ/Fÿ+8ÿõsþ•mþÿ¥Çþë†þääþ4ÿGÿs;ÿ&Ly­W,C~ÿ‚ÿD6ÿ×XÿŒËÿ÷Ïÿ‚žÿÌÿ(_ÿQnÿ’‡ÿ#ªÿn§ÿÖ¡E¿šˆb ˜õ ä÷"þØŽþÂÙù'>+ÿ  ÿ šýä¾ÿ» ýS-þáòý•’þ•ÐÑtþƒNþlÒþÅbÊ碙ÿÕ±ÿKÿ ÿÑôýŸâýS‰þÈQþÓ›þ”üþ~ÿŸïþÎÖþç/þãTþ)ôþ^Èþ¶ÆþÄÌþMÿÖÿBEGŠw“ÿž0ÿJAÿ†{ÿ›Êÿáêÿ¤ÿˆoÿ¶lÿøtÿ«ÿ¤™ÿB­ÿMÂj H DèaÇ ¢ÚõSôþ 4Ð ýéìøš`ˆ„¤þ§ãþÅÙý''ý&)þ4÷ý8¦ýGÐð`ÿÐTþ ‹þ»ÕfX²ÿàL‹™þ7ÿ…þ¿ý;þÒVþ¼½þk^þ2ýþòûþbnþ¿'þ¯(þò,ÿ÷þOmþÞÏþ¤Uÿ‚Õÿ›$x6Ä9•ÿï@ÿCÿЕÿoåÿÓÎÿ²®ÿÈ‚ÿXtÿGtÿ¶iÿB ÿÑ®ÿ¸’œ# OÈ Ýz1¦r^/ òŸ3 C ~Èú8àöœ¡&¾ýÜ„þ‰Ýý]ÕÕ ý•´ýÂ>þ ­ü7G‡ãþàûý>+ãÜRÿ4 [mþò¨þ×ÿ«¢ýXïýnþ3çþ£¢ýĽþ™Fÿa®ýÁþçiþ€3ÿÿÿ[þü÷þPÿ‹ÕÿR]Œ´ÿfÿ»ƒÿ^ÿ³lÿâÜÿìÑÿE¾ÿg„ÿUfÿÊ}ÿˆ}ÿº“ÿ“˜ÿ`W‹"hD „šþ˜"­«hçð³ÿ?Y ±ùBõÊ*LHèÍüÁ$þ(þDOˆü{Fý¹Õþ6:üÿŽ<Ù÷ÿ•ýývÏkž<ÿ½þtSþb7ÿ6™ýx¤ýxWþ*úþ“výr þ?ÿ²ˆýÊËýƒŸþÁÿ¼ÿ›¡þzÿX%ÿ8íÿ÷ ØõÿRÿØPÿÿàZÿ(RÿœØÿò½ÿšÿ^tÿë\ÿS‚ÿûŒÿèƒÿËÿòùË$ûF ~ãúç;=+ Í”ñˆ‘ýù„ ù7>ôKýÿ»º,üIbý†¯þ¿íÖàûõæüã.ÿÔrü5#þFuÿ!̃¯þÓõÿÆŒ¦ÎÿÃúþ™™þ¤ðþ–}ýÂyý)þcòþ§ý×UýÅzþ¸åý]Àý€«þqÿÓþ½¿þ™(ÿ,KÿïöÿI±Véÿü:ÿ¬:ÿvÿMqÿhfÿ?Îÿm ÿkeÿx]ÿ%jÿA|ÿçxÿŠÿxƒÿîfä¦'û ¬÷ôClá àHó?~ýb‚ ðøICôÅ­ãŠÉûñ]ü2QÿÑKP'û(ËüŠÿÑàüÄ¡ýÚ^þ£§ÿéÌÿs+ô³±¦;þþyBÿÊgþ¢,ýÿ„ýôþ$µþñ¬ýÁùüW×ýœ5þŒñý¬·þ3ÿÎfþ£Àþ*pÿévÿ…ôÿ‡®Èêÿ¿@ÿI"ÿÇ:ÿíŠÿüÿOÀÿakÿ\IÿGMÿÊNÿ_ÿ/eÿø’ÿ'}ÿ†(‹*âÜ“ó4¸ö9Ïü. Àrø%žó •Áª›û†ûŸDÿ*èÖîúÞlü ÿä6ýÁÆý´ýeÒs¹Oøÿĺn9¿6+ ÿ¸ÿÙþ ÇüÞ–ýdÄý§‡þò’ý½ÏüËgýkOþ½~þwÍþIÄþwôý:ÆþÇÃÿ{–ÿ¨Ûÿä—fì^ÿ;ÿCÿh„ÿéÈÿ'×ÿÚSÿ #ÿ;%ÿ0ÿ›^ÿBMÿ„Œÿ{ÿÜØ\-±¾ñȰH Øpøýýto…ö¤§ôKˆøÕš$üÍŽútpÿ Iiú¸Îü—mþ ·ýöûý]qüÜPãÙ¹òrNÚbùý’7ÿíÞÿô°ý"¸üzwýe\ý>]þKýø²ü]GýåWþ­ÿ¼ùþ9Cþݨý:Óþ8öÿªÿ{Îÿ”z˜ˆÿ#éþiàþé‚ÿißÿEîÿÆIÿ#ÿ×éþ© ÿVÿŽ]ÿhÿQiÿeŒ0ÚfºíæÊ /Ázú¿ÿ¥› 6õüÖô÷Ù³dü‹~ú¥vþ $þ‚ú<ÁüÕ þsÓý>“þ–ü~ÿJqZÀ!€cføÿøÿÿ®ýr²üãQýÑüþ¶ßü@±ü†ýBþ‚„ÿq:ÿÓåýÉqý%ÜþŠßÿ•›ÿ—ÔÿDo‹4ý“ÿ‘ºþ|Êþ$Ÿÿ/ôÿwíÿª:ÿìþÕþÚÚþ$5ÿÖtÿ0ÿ—_ÿf7›ó2qk ?êNß Tšņû¾Ùö¼^ô¯)ôB… vÿü*ºûDÏüÓèû–‚ü þ+pý_³ÿ©Åûëìþˆƒ¦ah÷¿ç˜ßÿ¬+ÿk7þ/|üN:ýÈeü¢ý1jüR†ü»#þƒcþV­ÿäRÿp¹ýó†ýÊÚþ¡ÿtWÿãÈÿ²CSØqÿ×þ]¢þÁßÿE&Öÿ%ÿëÕþùÕþ8¨þk0ÿÐpÿªÿ=wÿ1§’=6p…òèH š> !ûnH}ˆ‚Zñuö¼ ÌÝÿîümüÂ@üñ·Uüú÷7ý{wý«ÜýW—¬û¯ÖþÿV§adoU³ÿŽþÑrþù}üc›üo‰ü|ýµûЙüˆþU5ÿúâÿÞ·þ´Šýv)þEâþ,úþ~ ÿÇÿ Idqÿaþ¶¿þø”!ײÿòúþÒþ±¦þ’þÅDÿÄ‚ÿS¿ÿgvÿÑ:2R9柟Žçù2¸MþÊú†Â_t«™ð`>÷c —þž¹ûµÊý ©úF/‡ûüòüýýíÂýÚhâ*ü|lþ#ÿ(‚}G–уmV­ÿÂŒý…çþ¤`ü·ùûEØüdíü ûsü”Ùþø&þzŽý5ºþØÕþÚjþ'Ãþ”ÀÿÆ%V‡ÔþÁVþ)óþ­rØ ÿHóþ÷Áþ*sþV~þ5aÿî¤ÿ.Îÿã†ÿx:"¶Â;:ÿo‘çø´÷íÎ’ú~ è…Œð?aøV° WAý?'ü _þÅ\ùz{òü!ý=þÐäýµ„ÈÏüšGþ‚£þʇðìσÆÿΦü ôþ=rü¡ûg3ýŽý[}úVXüEÿ¸|‚ÁtÎý TýÄüþAàþ€EþŸ|þGÿ'Øš»þ€bþ;ÿžÜÿ!óÿÁ»ÿÙèþ«þ ]þ´rþÁtÿßÖÿÊæÿÞ•ÿ>#ªÌ=Ö0ÿàüæ«óJkù²í :lûoñuø÷— /Ëüú5üÛõþçø9ÊÿVøüæýQ þ¦ýýÏÔæý{þ*þ+ÑüK9ªO¤æfÙû³Tþî±üóû ýi™ý=úZ·ûì§þU3Û Fþ÷oüíþÞ8ÿ¼Jþãþ¾5ÿÅèϰ7æþsIþeLÿVªÿ‘Êÿé×ÿ®Ñþ†’þ†_þLfþ*oÿ1Í÷ÿþ–ÿW‡!‹g?äÈ~åiý °AõÊöz6 ásÙóãº÷÷é9ýý‘û@úöö Âþê}þ|˜üÃý(õýÄlòâüÙîþÿhþ‚J…mþú@vÕ¯ûˆuýˆÿüz¼üØÏüŽ>þ¦)ûmeú©=þð‰ÿ|ì®ÿ’«ûøƒþHÿjþ©ìýÃÙþ…J‡b‹ÿr=þÌÍþ@¦ÿ§®ÿ¬ýÿ3çþrLýNL¶B¾?ýÀýÔ™üzÄýp’ýóþ™ûÖìù²ØýY\þôåYå¢:üØ^ý?.ÿBÒþ?!þœ†þK#‚#Ë@†þþ)rÿUÃÿb1d-ÿ×Wþ~nþ1ƒþÔwÿgèÿóûÿá!Ëå?§zéþŽ ü¿“ôbÓ 0ß·Oö›_÷Ø&%Šûw?üdzöõ ý¶Àÿ’üq¬ü¤qþ4Œ@+üdH¥¿ÿ–ŒýÊôåüã7¶¼úðÿŠ.ý¨DüF=ÿš£ýö7ýî²üùú Ïü¢ý6$%ͪ›ýµWüGþš ÿó°þÇtþu­ÿ¥²ÿeÚþÜOý·ÿ ïÿ j×~ÿ eþlþ¤þÕÿOÌÿ™b&½ü :?. ö-ì*Ù’Æù­hó¤´%Í@'÷$7÷Êß‚ÿùmýñMõ×Lý¡ÿñ¹üe‹üÚ-þqøVü™Õ¥Cün.»ý¾Ñþ§{Éq-×þ²;üF#ÿ»«ý~YýäwýÉù‹æû0ÁüU³ÿ…6gõÿ­?üðÑü´þlKÿ­þV]ÿž$ÿ D<ÿ"ôü¯šþç,xÙÿ¢þþzþæ·þrÿiÀÿG˜,v>C| ·îBKøF2ñ(9G°HøìQ÷ ]~úù]øýþË õ¦Eüâÿ)†ü¤3ýÈ1þ › Zü:ê<D-ûwÀŒ _ý^”¾O ÆûGÿPÑþÙûüÌKýˆú¾µûBpü—2þ$LSz±ýLµû¹ýÒhÿ¾:ÿ{WÿÇÒþ`ÿ1Mÿÿ}aý$jþäüÍÅõRýËþ‹Dü{üÁq„ôêþÈÿqæNÍ]ýrøþ°“þjý9äý÷úÕûT{üá_ýf'ÇÔïg©µû†ü@×þ-Mÿüïÿ¬–þ§þºÿ1þ³UþC¬ÿ…«®E±ºÿ—ÿþI¶þæ?ÿœ ÿfÎÿ Îÿó È <=â BõZOäóÆî¦&°!ù2-úÐ]Ò£ù“΀ãôpÝûÒIÃý+ý¬´ü÷zÄmý½ÿ£øoýIà[&u9¿ ýM,ÙŠÍÎþ+þ ÿÅýIþóû[ü±ëüjðüxAÿ²¬6w|^ýuÍû‡ýaàþ1œþÛý¡þÁÄþnÆþáQÿª·“3¬1ÿ2yþ¶.ÿX‰ÿñ¥ÿ‚²ÿX¾ rf:¿¸ Iù$Íäµñð%í׫äXûýû¹ƒ-ùúþPž´õð—ýSYþ>¿ýÿ¹ýW8ûzlùÿØTÿXÀÿrÿkm­ÑþýD{•¿SÿÅŒÿ õýg©þÀìû­`üLâü’ýhþe‚ÿµ¥ Üÿ–VüÉ>üUâý2«43ÿðºýû&þgßþiÿŽwÿ9n=à¸v9Qÿ¯oþMâþ}yÿ˜ÿ¸ÿ™¯9ø› ë÷úŸ² àñLDëŇÿœ£ÇýŠü¦±åû½ŸÛífö:Tý"¸ý¥?ý×òüæúÞáÏêþ 9ÿÔslÚÿ¸6îÅñ}-ûH%Žwvå¢DÅmþÆ:ÿÌÌüŒŠüe×üNýRWþ÷§ý…(AÂ0þóüƒvü9äÿÉÖÿ¥„þÌý’UþúÐÿ~Dv~³[nuÿ¤þ¬›þ$Pÿ‡±ÿì¿ÿþ­L¨7ù§dhüÈû p.ðÍ.ê ºýúöÏÑdýN»þ#ùýC¾a÷R þ †ûšaü™þ{ŠúÄÿ&i.¥ÙÈí*ÿB9Ì}³øÿÝÿ[ÅüX½ý ñþñûÇî…ÿÖšìü‘ý3ý7üü%æþlÒünÒþйTÒÿºýíÀû ¬þšÆÿ¿¯ÿM;þ lýb¾ÿ¹xõì9~äéÿ{ÿöÕþqÛþ`ÿô–ÿ¹ëÿEË^²5K× ·uþ î–îøtê"Ëü(N…° ¼þiýþ`þÆ7üâ:ÐöG€ý /ú¶•ýÿ÷ëø[—Ÿð—þY„V|Õ~u†6ÿ§Óý‰Îý3ßûÜI;0Ê~ ýƒ¯üâ5þÉØüñÐþ)÷üELýœíþ‚ÜÿF¯ÿóšüG9þ•3ÿí“ÿ–6ÿ:^ýã(ÿ†°ñXyزÿ PÿÔÝþï-ÿ+ ÿ·mÿ#:ƒŒ2zÏ ŽóC› ¹YíŠãî,ú­ùýJ ÿ`–¬÷PÄ‹ ¦ÿ ÞöÓýxù:þ5‚ÿÎù_ó|©UOþldí ÊÿüÑ*ü¤~þ’‡ýí¼þdûÂÈþ½ aeÿç€þÒèûË¡þ‰ûýå¢ý*ýÅPý~]ýGŠþ+íÄþXMÿP¿þ@þBÿ'Áþ´ìþ Œÿ…d”««0£ÿjþÇ)ÿ”ÿI¤ÿ½ÐÿœNVÊ1%¸ wPÁ(Nï§=óÉføyëûÙ¼ †~ߌöåŠd»óÿ»â÷R¬ý™øW…ïø®ÓT—c˜þ&pÌÿÐÿÌM®ÿ·þÒký/mþ­³ûmqþcdmp÷rº2štüN+þ÷IÿÑKý­úû×ýYýúšýSRÿµÂÿÑ kÿîgý þ­pÿDŸÿÛËþÕÛÿÏ‹TEÉCÿ”†þÚÿŸÀÿR ¨¹ÿrüÛ1½‘eþÓ Ú îS$öR™û¼^ù … ÑE ÝõUâÿ ¦XþœøVþŸ„ú WŒëÿoìù ùÌ¥ôýž±ߪ!îÐÿqÃý¶ÂýJXü²ÿ‹W¸¾°ü€ÂÃý˜)þ[²þ hþÕÑûÕü)<þ°·ýtÄþÑ}/EòÿµþcýuŒþ åÿ’ÿYoÿ8;„ÝÿHÿèþìòþ;Îÿ;âÇÿ•öÙ/’†þão h+î *ö¼‚ýŠûÀƒ ã†ÿø²÷9Æ»QþèEø‡Øÿ/£û¿ÿ+€ÿõNú_ú}DÌýV#ueetÀ™ðgžÆþZøýÖïýè¿ükàÿæ;&5E—ÁyØþ†óþ™>þ¶*þpý øüÅý9þÆ®þ§±ÿIè þ¨þóÝýéïýœ ÿzšÿ±gÿƒºÿ~Èÿcÿ2ÿ¾Eÿð¼ÿ]8ôÿɾY¤-´Ý;ýÛÐ2dïk9ó0/ùÈúUùÆÈl'ùÑÿ’o¢ÿWÙøý«¾üÕ ÿÀÿÊû2(ªÏÿâ ý3¨ºÊz¬%6F1þ_þCŸþ)ýu×ÿ ZÅÉR 'àÿ¡aÿNÿ×ýÔ|ý;þK˜ý3 þIùþ„2ÿ²” Ø-ÿcOþÌþ1eþ/ ÿ,Àÿà’ÿÊvÿÛ-ÿé$ÿuÿ§±ÿ˜Ðÿ¦×ÿê;´)Åy÷þÊàÀñ«.ö­Ñ׉øçÖÔ“ÖÀø;¬ÿÇ:º¬(ÆùaÿÉ\ýd3ÿá!ûb\%þ)Ý‚‡y jè÷Z:þóÝýñ$ÿX þ´kÿ´/b8ÁàZ$¤M¿/ÿJžþ qý)AþÛ•þήý_µþ vÿ/rÿ †ÿ_8ÿª¤þQþønþ„¤þîPÿ:íÿ_\ÿµöþXaÿ»[ÿ{™ÿÀÿò£ÿ¾*ù9%‘9‡¯C«ðʲûÑùZ­ü(œ×ÀüDMü¾ÆsüVþË"û›ü:Ïû¶Sÿ?^:„û›1|Ùÿ/©ýë` IˆEÿ:¢ÎêÿÝÿ”#JÀýsÝþÄ TõþüÔÿ÷MWÐGxŸ 8W"Åæþá†ýÒÍý“¯ý‹„þzÃþ*ÿ¦@ÿ±wþÿ}þ¥þ&=ÿ Qÿ+Ãþ¯kþ¨þÍ)ÿRkÿ°iÿ iÿ’k ¾ -j ”!Üáô-âýù»ÛFüôX»£ý®$ü!Ìqƒÿù§ûr¬üùÿg8e-üYê ©ÿˆþóLôŠÿv8yþ ÀÕÿc¥ÿóëý÷Ôÿ‚öÿï6{eSùQMfá­rÿÆÿUSÿ?"þš~þÝþ gþòSÿãCÿõ–þŠþŒ®þ^{þ0ýþ><ÿîÜþB«þSÛþàøþXOÿ8[ÿ.*ÿÍÌ ãCcü ¤ _/úþáüþÒ½zûUºv†ÿ5ÑûqÍÿh+ü9ü¦õ1þúKþÿÃìüé2¨êÿÐæýCM"Ä¢ÑÿjF}ñFa× ÅÃÿžGþ(0µøˆÿʰÿ¢0C"Ïc ]B¼ÿa2ÿ=õþØÇþ©7ÿm`þËþÞ4ÿ.{ÿVÛþF<þ‹Œþãšþ”¥þôþS÷þ¨ÿjÜþ€Âþ&ÿ·=ÿî)ÿLz ÅÌ ›=°ÐKû2 þ·Üÿ¬QýNãÅþ3ãü¯Aäè ‚fýV»‹þ#ÿ¼xèý¡ýÿŠrÿkÛþ½9G8qàÿ<è°†´±û|}ŽÿKiÿ<Í„]±6ÿHÑUe$Á'äüÿ‚wÿ+5ÿ—Ûþ×ÿ¨aÿÅHþÇuþ¨LÿQÿÁÿrþ'PþœŒþ{Æþ»ìþ:èþ*óþŒôþg·þ§Óþ{ ÿ‹%ÿf I›Õ 8.y—fû–?þUбþ¦`^þpþ§cD9ÁªÿÇþYÿHbþ;ÿeùÿêµþÝ×ÿ{Kÿp<ÿúüÿ¡þÿ¨p0:¨øOîRÙ«ÿDYlùžÿ^ÿ·œDïm >™ÿÿº%³²äÿÑH¡Çÿ)@ÿe"ÿÃDÿ?gÿŽÿõ˜þ¡™þeIÿN^ÿJCÿüþ“§þžÃþZ þ"‘þ<›þ÷ÅþKïþ8îþhèþOËþaòþ»ÿ)ÔbÆ& ¾zôm¹´þX#þxÚnZ7ÿ¾qÿBvÿ°jÐíÿwÅžÿÛÀÿìóx£ÿ«>ÉôóÿôØfoì¥|¸zÁµÿcøÿ;|ÿƒ>ÿüŽÿíÿ áÿâ!rbÿ0@ÿ/8ÿ>ÿ„sÿ© ÿ;þÈþYWÿœZÿýÿOøþ«þ%µþõ¸þš®þÅÎþ;êþ@îþüþ«ãþ4Ïþ1ÿ~!ÿ þ s ʯ¯à•ÔšJþ¯ÌþZ1Îc(>ÿQÿÿ†DÏX ÿ’¡äÿ×no­¥,–½9_ïÿ©p¢¡±¬9çC‡Öÿ"âÿs>ÿ¬fÿæ³ÿŒÿ@¿ÿÆE¯Òÿ^&ÿ4)ÿ %ÿJxÿ¤}ÿàÖþÇ•þÑûþ$Rÿ5ÿF ÿöÀþÒÄþOüþdèþèÛþ€ßþ²ñþAñþVíþ¶öþÉöþÿ 9ÿݸy Ú(”‘lè¶þŽšÿm‰ŽÎLñÿKÿ¤“ÿ=ïÿfzð>†…ÄTšyxæ&m…q@Ãÿëpÿ1öÿ€Îl¸w5‹•/!Ïfpÿ\@ÿžXÿý\ÿ¬ÿ£²ôÿg3ÿO ÿoÿS[ÿlTÿÑ ÿGÃþdÏþ¥ÿnÿ6ýþÿ”öþ7ýþE ÿ<÷þ¸Ùþ êþ¥íþ?ýþDÿ ÿô ÿÍ$ÿÝ[ÿ»u!s X 8(è¤þX6ÑñÑÿ»Ðáÿ8Îÿ×#N¥ðƒôÿÇ\©C¥ÿ­Nÿé§ÿ™šÂÿ8öÿA_àl„\(5áÿüÑÿò@ÿS*ÿ.NÿÞRÿ8¼ÿ-ÏÿØqÿ–3ÿ aÿØUÿÊÿûÿ)çþ÷ÀþoÕþ üþíÿ&ÿ].ÿJÿHÿÿåþåÓþ úþ'ÿëÿÿÿª,ÿ÷8ÿŽQÿ™+Á) ‡aLïþܽ‹­¼‰ÿ 8Í™ýƒ†:Bú¯rWLR±äÿ›òÿ-˜ÿ&XÿAŸÿ4ÎÿŠÿUMÔî·ÿ¯NéÆÿYÿ¼:ÿX2ÿ5ÿèaÿóÀÿ%€ÿe\ÿó;ÿ<:ÿjÿ‰áþ±öþ•ÎþAÒþ‚ìþ:õþ·(ÿ15ÿ® ÿ‚ ÿ ÿ)ÿÚ÷þÍýþ®ÿ ÿ,ÿ ÿô)ÿµ6ÿV(ÿ£Aÿ¨M´ 1þùêò<Ýœÿºp“ÕÁÀ…ùFùkâØ…Í“ëÿšËýÿð ÞÃÿò7ÿ1ÿ…RU«sÿ(Õÿúÿ/{ÿº#ˆDžÿ/ÿÄ ÿÆPÿ ÿ rÿÎÛÿÔKÿÝüþ0#ÿJ*ÿ£Úþ”ÎþvÿÊÛþA¾þÕÿ‡cÿÿÚþ6Eÿí7ÿŸßþSÿÜDÿ' ÿøþì;ÿ™+ÿ9ÿ,,ÿfFÿ&ÿ¾ÿx£~# _³ ¢y.EÜP¢rc©m=°%ò¢kX$ÝQGZ ôÿèéøÿúIÿãÿ¢’ÿtäÿ½GäÿŒ/ÿ¹nÿñìÿ¸ŸÿwîÿóOxÿ·åþ28ÿ²‡ÿmTÿëgÿïŒÿº ÿèÚþªÿ3ÿ ÿ›úþ—äþAüþ#ÿ?ÿ‡6ÿÏÿ ÿï(ÿïEÿ£ ÿC ÿÇNÿÿèÿ)ÿÛ/ÿ˜&ÿ& ÿÎ?ÿ ÿÿÇ{­Ý N¼qv(å‹“rH —Ê‚JŽüÿu6”¹R ÿ:è­ÿ_ÿ“‹bOMwÿÍWÿ„ŸÿÚaÿv1ÿã‚ÿZÛÿçü¹ÿúvÿ‚hÿìWÿNiÿ<7ÿ†Uÿ‡2ÿŒÏþÍùþc$ÿ÷%ÿðÿ'ÿÿþRÿM;ÿÎ1ÿr9ÿ%ÿ9ÿ9*ÿ>ÿ”2ÿÐ;ÿlBÿX6ÿ<%ÿ¼ ÿv+ÿ,ÿÎ(ÿ(#ÿ ÿ¹%ÿ” ; ÿ; /™­Ò.MaŠÖOAæÿG»ÿv’ýÿÿ¾&2£Åÿ3#~ZÉ:U•ÿ†–ÿZ—ÿÉnÿY ÿ÷@ÿf§ÿƒ±ÿ¨|êÈÿk†ÿV6ÿÈÿy>ÿàgÿ­ÿÙþä<ÿX1ÿÏÿûAÿB<ÿiÿœ ÿö\ÿQbÿŽ/ÿCÿÿ§/ÿyKÿ[ÿRGÿ-HÿJÿ'ÿ¸!ÿí:ÿð5ÿiÿi#ÿºÿkÿ/ò³bF çW•NVûýµ ˆ¨ÿ¯ªÿº™$L<̤ÿL ¯ÃÿÉ[ÿ#¡b0ÿ4"ÿ%ÿójÿŽÿãºÿOr®LkÆÿo3ÿ¿ ÿ”<ÿ ÿ–CÿyNÿ• ÿ6"ÿh8ÿðHÿC_ÿè>ÿ±ÿñ3ÿµYÿáUÿ—1ÿ¼"ÿˆ,ÿÌ@ÿßcÿˆWÿ˜Hÿ `ÿóHÿ³4ÿ¦;ÿã2ÿH7ÿ##ÿÕÿM*ÿ÷>ÿøv} ¿Ä­TwTaØ¿µ&?ÿŒ±y¢¸úÿ"f³jl6ØË±ÿ¬Øÿ ÷ÿËÂÿ£Rÿë<ÿ=ÿÛÿR’ÿ$ ®d~X¦Äÿdÿpÿ™ÿ›1ÿöÿ0[ÿ¤Zÿ@ÿ9ÿ¦4ÿpgÿ˜wÿ¬[ÿ-?ÿy9ÿÛ>ÿ*9ÿ$>ÿHÿ®Iÿ Oÿ\ÿ­VÿÖYÿâcÿ®ZÿÿEÿ4-ÿv5ÿ¡,ÿfÿd>ÿ±Bÿ T¡Í » ^Ë„réFàÇx¥ðãG5Fÿ™B+I2]òÿäÁŠÁHF5ÁÿÈìÿË!C™ÿ©(ÿ4ÿ~lÿ[ÿü·ÿWU×ùÿä€ÿJÿq4ÿ ÿ´ ÿ(6ÿ3Fÿª\ÿ|ÿˆPÿ ,ÿAsÿŸŽÿ!‚ÿ7]ÿî ÿ/ÿøIÿ_ÿñOÿ×Lÿ¢IÿKÿÞhÿ‰jÿdnÿaÿ¸BÿÂ6ÿFÿAÿTÿ±/ÿ}•U c} üfÌŠ#h¦+å{òA1}Nÿõ“Õ°ÞÏf\„rCÒúý• ðÿÉ>}¤ÿ¯pÿU³ÿakÿ=ÿ–ˆÿq=»J<ÖÿP|ÿ ƒÿUŒÿš)ÿÐ/ÿ$CÿÒÿˆ,ÿ›dÿuÿ?[ÿõoÿ|ÿgyÿOÿseÿ‡ÿq÷þ·Mÿ©jÿ‡7ÿ³Kÿ,KÿôAÿukÿ\ŠÿÜtÿ{GÿMÿA=ÿXBÿ‘SÿI-ÿ#Ít& bÉ ï¶¿§s$‚Âë ¾«ÅþýT¬ÏÐ  ΄ÑX½7Áœ|;‚:äÿ{®ÿ‘¿ÿ¥›ÿ°ÿ>Ãÿ;Øÿ¾.r©Vÿ˜8ÿdÿmÿ9£ÿâUÿÜÿ†6ÿ˜Mÿ:ÿ`Dÿ¨‚ÿp²ÿ0“ÿhÿ¡jÿdFÿt ÿï5ÿ°Cÿ2ÿ@1ÿ(QÿICÿ£Jÿ\ÿñ‚ÿVTÿTRÿA_ÿÜ[ÿ7Dÿã5ÿ¾£y é` Áš{³ÐiÎG•‡ÂÆþÂýÿÝX³‹ðÿxw´;½Á“ÔëR '¦TBßÿ¨îÿ€óÿêùÿºd/Ù[qÿ™#ÿéÿäFÿÍ…ÿ•ÿ,nÿGÿÌLÿ‚ÿð,ÿídÿ‰}ÿJ­ÿæ«ÿe\ÿÉÿìDÿÌNÿªAÿí9ÿþ ÿo$ÿ3Dÿ‘Zÿtÿ2gÿ‡cÿòkÿÀ~ÿ¸Vÿ?ÿNÿ “•J 0Q Ðò áȶK¸§%ȇ£yþS¢yŒÿʱÿÅæ—˜|‡Ù&ÀŸ_##(×ÿ#‰“PˆX¨ÞQЏ®ÿÀ<ÿµÿ" ÿAGÿ»uÿuÿ:Œÿ–‹ÿïMÿ6.ÿšUÿi€ÿüŽÿ†ÿôrÿ$Uÿ{1ÿi-ÿ»Sÿ]NÿÊ%ÿ½ÿGÿ¦:ÿ¥ÿdzÿjEÿÛoÿ)|ÿRÿ“WÿI=ÿ~ÝÎ Ä( ™ØæhòWás•lî]l©TþHÓÿÏ61Ëy¹ÿç1ÿ Rªÿ–7ÿ^š×>ÿõÿí :8üaÿ±ÿʬÿÕ4ŠkôÐÀ†þE}êjâÿ+{ÿ`ÿˆ1ÿ”MÿŽÿ ,ÿÉKÿFYÿ@}ÿø—ÿ!Áÿ¢ÕÿfšÿFÿ_ÿYÿÂÿó5ÿA4ÿÑÿß ÿï,ÿ VÿׂÿBÿú^ÿ ÿøÿ-^ÿ¸Dÿµ[ÿÕz^Kb z·}òhðÿQÙÒÿaÀ®ýJÓÿ]{ˆ×¿ÿ]ÿð·ÿãÔÿi7ÿè^ÿ³² ’´ÿþ ÿ.­ÿ¢%„<\øÿLnD vŽzüÿJ>1åÿQeÿš‚ÿ¿[ÿ!ÿWÿ©JÿØNÿ¿.ÿüwÿüâÿY£Þÿ:‡ÿSÿKëþ»ÿÒÿEÿ?ÿÿ•%ÿ¯gÿÅÿ±ÿÈÿ}ÿÿ×ÿÈiÿ4dÿ¶Å¹]zf ¢øÖ(³ÿ&<E'¯ÿpýUG÷QmD;ƒÿƒÿIÞÿGvÿÿù<ÿN^O®~Rÿކþ MÿB)¼ÿÿâèÿÓEòž8|‡> záÿoºÿâµÿ™­ÿÖsÿa ÿ… ÿ2ÿd~ÿÔ‘ÿ‚ÿ‹Þÿû/ªÿ½ÿ¨ ÿ¾Oÿ_ÿùôþ6ÒþcêþVÿÀ¤ÿù™ÿMÿ¢Rÿz3ÿ×ÿBÿ£<ÿTÿ2ˆš­ ì ßw^ôïëTÿ”¼‘Ýþ»Wý¥ßSQäÿ„(ÿl¥ÿŸèÿ‹(ÿÃþµ(ÿ{-Í|³ÿ‘tþ´òþÃ8îÍÿ·›ÿ°4·ATÿÿÉ÷ÿ4Óÿ™”ÿJ•ÿÝÿ>ÅÿÆ^ÿXÿ¹%ÿÝ ÿðÃÿ«ÿ:ºÿ]ÀÿxKÿaÿªZÿ…[ÿN$ÿgðþPÞþú$ÿ8jÿ{_ÿ›Hÿfÿ CÿÓ-ÿrIÿU@ÿ´Yÿ¬€ÿ•4 ±‹'Û ¥K»tô4àþvça«Õý©ØüsZY{ïyÿQºþªŸÿ›Ôþâ\þÝñþPüÿ,Iè·ÿg®þŸþ.ÿÿ”©Zÿ%çÿ³NbÿÓµÿ ‘ÿDrÿrÿW‘ÿ¢ÿxÿ.9ÿÿ€¼ÿËûÿ¬Íÿ±ÿ0gÿx'ÿjûþn1ÿ©;ÿÿ–ÿ‡(ÿ QÿKÿÐ*ÿÌqÿRAÿ–/ÿ,ÿ‡5ÿµvÿi‰ÿøŽÿÌ  x[_Aˆ4zþDäò†ý×ü3æŸ ßDÿUnþüvÿr[ŒËþ½þоþMµÿÍJ éÿiÿ#¾þѯÿ‡èÕÿ¶ÿòÔÿÂmÿÿ øÿ…Òÿ HÿÄÿ–„ÿ4Zÿ=ÿÐJÿÆ*ÿ›ÒÿzlÙöÿ±®ÿwqÿŸ/ÿ™ ÿ.ÿ[.ÿ…ÿ¡cÿN@ÿNÿT7ÿ„ÿýŠÿ—JÿÒ6ÿìDÿìkÿ‰…ÿ ƒÿ:…ÿP J"ï;%èÿ§þù¢ôéý€@†X?üb˜ûI³UJ ôþŒíýEvÿ­l½žþíÕýzgþ€cÿ>)‘¾ÿÇÿÇÜÿƧÿ#=Vÿ^@ÿ‘ÿeeÿœšÿb~ÿŽ?ÿž‚ÿ·@ÿ•®þ05ÿU%ÿƒˆÿÝ_Z1®¸ÿ‚Lÿ#8ÿªÿx:ÿd[ÿ=Iÿ¿;ÿiÑþ§óþlÿ£›ÿ;‘ÿÅ_ÿ¸wÿ¼xÿ"tÿÎ~ÿíaÿ×\ÿ÷ó ®ˆØ¹ìÿ&r†žýSçô_úûoÐú´úêïãþç|ýi?ÿ<–Šþ®ýAþæÉþ!àÿœwDÿþÐÖþ~ç ÿ=¬ÿê‘çlÿ'¸þ‹ÿž]ÿg1ÿ#—ÿMbÿêûþòOÿ0Îþ Úþ ÿi9ÿÏúÿîYÂÿZÿc(ÿþ ÿ«Œÿ ðÿç)ÿBØþ†ùþÌÿ‹>ÿ¹‘ÿsÀÿ@…ÿWµÿ ›ÿ«[ÿU~ÿ¢ZÿZƒÿ’M kw⸢¨;ÿú(¢ý G/ÍÄüýùÐpÏš¤=ÿ7ýJ×þ™Qœþ#qý‡9þ¹{þ¾úþކ6áÿÿpþÑ[Ãwò7ÿ?b=ª~þ¶oþ¢lÿ±!ÿ®]ÿšµÿÌ«þìÅþ21ÿûÙþ Åþüÿ÷~ÿºÿ¤«ÿg;ÿÅäþG4ÿËÎÿ³üÿVHÿüþZ1ÿ~ÿD7ÿÚÿí·ÿÖÿºÿáŠÿDÿœdÿ,µÿž§ÿ'g \³+ëÌ£gþ½‡ÿ³ým´íëœçýKzùëgL’½ÿË'ýÓWþ‡?Áþð ý þ…þu5þJÄÿV§œÿpŸÿÆÕ ÜÿÑÿ…náaÿÀýkÚþšWÿÿl¹ÿŸÿzKþž°þc5ÿn£þ¢ªþbwÿ@ÿž5ÿ9!ÿ9âþDÿÜvÿÒÏÿuÍÿðtÿ\ÿó*ÿ(VÿÛ†ÿÆìÿïÄÿ•vÿÌtÿìEÿ*‚ÿSàÿ! ÿ· 5©VO¶´q@ÀŸ}üsáî®móü±Þù9ûXx}íÿnýå$þóÞ;þ˜íü8þ–[þª9þ ÿ³›òÿúÕÿcI_-êÿ¡Òÿ_ØÿÈþ·&þ=ÿ¨Kÿa”ÿº#ÿdšþ "þOÁþÖÛþªþ¶GÿFõþ&êþQöþ¿ïþûÿ§ÿþ·ÓÿÊ(’ÿëbÿ¥Oÿ4vÿþ×ÿôÿ|¨ÿàaÿ1Pÿ÷Rÿˆ“ÿaÓÿ¹ÿªõ yÉ ì—êþ|”6Q” úr¿xYËeüïrùÒ>-Ù 2ýœýKRöýQËüÄþÖýÁuþÿ~þ²úÿ‹[©T,È £¡úwÿå´ÿúÍþNÉý%”þž.ÿ ±ÿU$ÿc×þœèýtÚý ÿýÃþJ¾þÀÔþ¢Ëþ ÿW·þY»þü4ÿ¼®ÿêÆÿ´µÿP¤ÿìmÿ ”ÿïÿèÿa¼ÿlÿç3ÿF[ÿãÿxºÿ{ÃÿžÈB{"ü ´þZ¢‚;öáÑûú)•ø:Ž’ÿt ýÿ«ý!enýsÉü "þâbý:iþïQþÒ`ÿX|•Æub±ÿüú|{ÿ+cÿ€#ÿ‹þQKþ¢•þ;°ÿbÿžþ»ÏýŸ_ýä¨þóåþ1—þtþÏþ³üþ´‰þÞþ[ÿcÿþ•ÿ¦áÿ«¦ÿÊcÿÏžÿÍïÿ(÷ÿ'´ÿPqÿ‘WÿáQÿ3iÿ ±ÿ„ªÿ| ÉV#øÙ ³¶JµFÿR÷òþ_’ìöØ÷øúÁ­8œ‰þÓü>„þÜ*—üO%ý]LþÅ.ýþŠbþ·4ÿu|ñq×Z-0MeÿEÿ:(ÿËþÑ^þQþ3œÿQGÿ{¤þöýÅ(ýÔáýÇéþ[ãþ9 þM¾þêöþ¥þºÿÓ+ÿœrÿ¹ÿN¶ÿðÿê~ÿ%Ãÿþ×ÿ\¼ÿ†´ÿ §ÿiÿÙCÿk„ÿ2©ÿ {ÿÒ Ày&ùP ì%ü=)YW´ñA ý©qø=÷1 |nDýÚý»ªþê‹U"üòÙü~ŽþñýGþÇþÜWÿˆpÿY•vûÐÿ5ºÌÿT7ÿÿçÏþ Ëþ þŒ—ÿ¹–þ„Ýý}þ+ýÕýé©þk@ÿ5þ„`þ ÿ Ìþ¾ ÿþþ«]ÿ¹èÿ»´ÿ½[ÿ uÿnàÿ²ãÿñÿ„gÿ“ÿ}ÿ`bÿÓ}ÿð„ÿ~ÿÐø?þ)ðÖ l¶÷W£‹àAòâjMú5 ù“÷®c‹x¼-üýnîþ}\5Zûöäüʨþ‚ÿü~þމýËÿ³MÿÀ‹žã¤-¢ÐgæÿsÿµHÿN²þŒÿyyþA¥ÿBºý¾ýÙªþvýúæüždþ^6ÿlþVþ: ÿMéþ>ÿ(Øþ]HÿvéÿæÅÿÆhÿ±\ÿîïÿ™#|ÿœ6ÿà_ÿ¤WÿÍ]ÿ®ÿ ƒÿ»†ÿ¹8³-û²ÈôÈ™Mï{@ôþ<Mþžù{d÷€ÞK×—ûvÜüÝþåN°núvý´™þèýHÿˆðüˆ6³wÿ¯ÿYæí×—æ zÿ“íÿ —ÿqqþ³ˆÿÖáþíYÿÙ2ýozü-&þ.ý÷$ýÉþ?ÿ©·þµŒþÃÿ½ÉþîÇþÏûþ,Rÿ†³ÿ…¸ÿ”pÿzÿ€ðÿ… gÿ¾ÿÿ¤ÿ¾_ÿ®“ÿ§xÿQ„ÿR?™;1ä ˜ƒï‚*p[ »ö-ÿ¹Ó ìúf ÷j_gÐÓû ýÆýu:Uú¹AüMWÿ4Þü¹¨ÿÚçüê¾ÿ\Œàþ;”‹4ÿ7Df£þÇ$ÿBÅþÒcý‡ðûç>ýMiý´•ý±ÍýgÍþæÿÍìþè#ÿ9sþ+|þIAÿhÿ‹dÿÖ‰ÿŽZÿ §ÿ’/þÿÒ®ÿÿw×þhÃþsMÿ!–ÿ+jÿ•kÿê½°4üû JíÕP¼ 9!ùÆpÿPgáû ãø„yMvvûhùüE¡ü+Œ ÞùàFül™ÿÿýzÚÿ­ý#ÃÿNzS2ÿ7Y½Çç#[þÞ• ^&ÞýP_ :ÿ܃þR;ý¿íû¡ü¨Gýïýs²ýe®þýdÿOQÿÿÀþE^þ›zÿÿ¨)ÿe3ÿ98ÿ•Ôÿc,–÷ÿô±ÿÈ ÿĪþßþ";ÿ!{ÿÂeÿ`ÿä'8plMÖéí®Ý›ú*¤é!ÿðÏû¥ÏùYnš8MûÜ.ýaûqSçiú0¯ûPêÎü ò˜ý…:ÿOñá–ÿΔÇTöÚìðý‡Êÿ'Wè«ýûD Øÿîuþ6ýÆ0üZ¦ü~üöý%ñýà þÌ“ÿ^mÿÀ.ÿ'íýŒ&þÝ­ÿ²¿ÿMÿÝæþÿÌÿœW€òÿ$°ÿ¬5ÿŽþØþ`+ÿQdÿ‹Zÿ"Yÿ{;8;YR¶ è, û³>¨ýÙ$üÀ~úb¾öæ"%ûÁMýY“ú­èÿàûÀûÏS´ü—x¯çýŠ ÿî:Œ”¸h£Ñ‡ìýY—þ>ÁbýýV¹o)uþHQý+ü&¬ü§ÚûÛÇý–gþÎWþm§ÿoÆÿY8ÿ"ÒýÑúýÉÿëÿaÿ3¢þYÁþ³ÿNW n•ÿ¥*ÿ—­þˆþˆÿöYÿíOÿ–`ÿW }=ýÒ•±æ´I nÌÆ©ûÃã>ý]üÂúW&ŠkmöúûØýºùÆ{ÿwüæxûpÅü…Õ 3þÄÕþ€Á1Åÿ·—Ÿ‚þe ýƒVÿ—™žÑÿ‹àýÄJüÙ½üK¶ûSý5•þ›òý¿«ÿZY/ÿý¸ýÄæý/®ÿrÂHÿChþ¤?þEÿc_1ëÿµmÿ–ÿOèþÊ©þ} ÿYLÿRBÿ–ÿ{„!×S?þ$(Ìæ}# «ô”ûY„mÄü îûíû,˜ uÿómûŒÉþðÖøOWÿÉý:+û+†ÿnëümi`/þsØþ‘F¢?Ùþr´pãþ{NüÕ­b®ÿ,°°ÿyæý—=üñ,ý4ûÞ–üÔêþàåý&'ÿ&´ò£ÿFÊýr´ýÐsÿ蟤ÿ˜Nþ°ýŠÿúb\ªÿîJÿÿ@ÿ«ìþ¹ ÿ¤9ÿáfÿ!ÄÿT!ª„@#,K…ç{ KþŒûˆ)>ü¾ñûê.û€™œÖþÂ5üìCÕÄ÷’ ÿ§þ[Pú›kÿ.ýfkΗþCÿ®Aà Àº¨<þ^g×®ÿ‘tüê©ÿÂtÿÎE¤jt”ÿ+Šþ»ü.Åü?Iûa”ü Dÿ‰ÂýÇZþ×}*oÍaþQý ÿîÿ”Íÿ‘”þTFýƒ!ÿ;T©‰ÿÎ3ÿ"&ÿ>:ÿ9ÿ*,ÿ ZÿO…ÿ%áÿ9G |Aíã uè(–Æû»Žùh’-¨û«±ûRû‡{ˆÍþðýËÛâËöæ`þ¨éþ6|ùnÉÿœáü¥jÿ…*þR¼¬ÿ«|þ þ±ºÿrlGLüíÎÿ¦KÿªÝWhSý>ÔþF‘ü?zü£Âûà|ü¦ÿþ“ÀýDÿ#¢Ç†ÿÑŠý 3þ^sÿ¤ÃÿPïþÀFýòtþúÿðMÿA1ÿ:ÿNÿáKÿäœÿ®›ÿ¹æÿÛÕ!ÿ§@4¨ Téìn©eÀõVú³iûß–û±Gû¹:`´ýá!ÿøüÇiõ=³þ%¸þ;ÌùÃÿÿKýTZˆþÛÊþ«ž·ÿ7Ã]þŽwÿõ•ºývÜÿX$þ|þÿ_Ì2ãþàüw:ý~û‚üŽdÿYaþiÕýŠþòÔÿ«Rë“þîèýî¬þØvÿoÿ§ý—þ@—ÿj½ÿÆ™ÿAgÿ ÿ`ÿ–sÿ!äÿhÆÿDÇÿªì",˜?™) -Nñ…òHñ*,ú=‡²û%ûà ücû/‡üŒR8ôÅþè›þ½cûUÿïü"Y°;þ‘bþv* qÿ#Shþ C€õW?ýK¾ÿØþÿàp¤%ññ³ü™ýuü<[ü£ÿ‡­þÓIþß°ýÓPþ¸1×ÿéÁþû·ý×þÉîþ½ýÍ9þºäþYËÿÖ>HtÿÖÞþl ÿ´¨ÿÄ!ßÓÿZ‰ÿÓµ!~ÿ=È R¾ôêÝÈæî È÷á]pUüVûwüÈ•yü5ð¿Ïeó®]ÿ*E9Hútþ]ü~#.þ©íþËêÚ`ÿ ¥ÓÿÛ³ÿäÇ·þRTa¿ý–ÑþqY}É?<þßZýßýÍ@ü–¯ÿ¥_ÿkŠþžEý?ÿüýþ±ý“Íý–‘ý#ÏþÊý`Œþ²Úþ gÿ+”Ôÿ¶ÝþÜíþ âÿ¬6¶ÿ*pÿ/Q!6ô;¸® {øÂª Çì$Sõ^¹hÿ˜û$ÁûT´Ùü%òB!õb5ÿõþ NûÔLýÉšû,^IqþAøþ¼~ x@—þk¿½hº„þ-pUoý25ÿ ËþkÃÍòý_ôüîýïÙÿ`|ÿWàþ¼ý¾Mü2¾ý/Bÿ¿vû+ÿÍ0ý“Êý—ôýüþ¶ÿw3ÿcÒÿ÷ ¨ÿ}øþ›‹ÿü÷ÿf¢ÿ(ÿ.¨!”à9I¡ ¾ÿûEÄ¢Ìé ó´ÁkªªÃúœ.ýôû+ûÎk“îó‘ )ý.QúþÒý0üµ}ÿ ªþ°m0êRM’Ãõÿ™_wçþ¬þl÷þ#ýþ´þÊ3טñHøãü”*þävÿ<‰ÿ»ýü4ýžæý\ï€Á^þ¤ýp«ý½NÿHjÿ ÿcÿä ÍBSÿRÿÔpÿb¬ÿíÝÿ‹V ‚ã78« mZþåiGç^Fò;ÓEQ˜ý;ü!ˆ ý­VÝvšôÿÿ0ýû±»ûåþëƒú˜îÿò<&™ÿ×6ÿàx±$ÿÖ‰ÿ¹Ùþëÿëý„ý=–BÿT%þIä²-Âéþm¸þ™CÉ¡þH.·Âý´]û' ý„5ýMÿfe2%îþMFý”$ÿãþWPÿjYÿMÿ·" lÿ‰ ÿô/ÿá¨ÿ›îÿq<=Ü4@S aØ8Òö»æ=ºòP#2Ös„þÀoý˜ÍZhülJ Ñ–hó4,ƒû`Äü%þÆTûÚZNËÿ…8ÿHh]«©ÒTÿª¹ vÿ~ÿ×q}þʧýS“ÿðÿ‚H³7Ðý|–ü—ËÿçB¹ün÷ö2Màúq³ÿy¨ÿúíù Ïk?íýO-ÿŒ\³uàÿã'__ÿnp_L.¼ýsâýÏÍÿ^\ÿÞÿíþkýþ>=:¯èúÿë>û?þFÙüU•üžaüìÄþIþï¯þÛ—ÿ! ÿª©ý®pþœÿUÙÿ…Èþþê>ÿÆÿN¦ÿsÆÿ…ûûŠ-ØÆn:T“Û£ë¡*ûpa‚ý <ïû{úFæ2®Í:üñ÷~û-ñþ[LúÿQ‰ÿ¾‚ÿæ_‘)zžïB?‹þébü“6ýÑ#ýÝáý¸oþÖ-ÿÏÈÿùiÿíbÿsþ«‹þU/ÿåÿˆèþÄþ2Òþ€…ÿ]Äÿ¥±ÿ?[T)Û bN8!bÐí]û;VC«þ.ðÿ¯úü üÀdÊ4ü}Eúy)‚õúÄ‹ÿêîÿõú¹Øÿƒuý=˜™R~ÿ øÿ ŒGžÿ´ÑÿÔä=àýþŒþÿöUÿ¹XÿhÞ.Ö‹ISüB<ðͲþêèü9+ýÏým=þH-þû’ÿÃÿ]’þÏÿ<Áþãßþ7ÿÍéþ/‚þ¼þ‘4ÿ¿Dÿ{ÿüÅÿ[e‘B*[ Ù˜y»ø—]ä×9þ j s;Eá¯øùÖùnmüƒŸ“ðþ: ü3nšü–ÿúMšý–þb:þ/ÿþÿþ`þÓläÊ÷ýT5(´-ÿþ/¡c¬{þoÛÿ«9I5¶ ¶{’9l Mc_ÿ·ÿ½þü†þ4ûýGþ„[þÏþ)ÿâ¼þ)ÿ›âþúýþ5Âÿ.­ÿÜþ·iþ·®þ¹äþú敤%ÄJ 1ØúªçH¤ú›üëÿ ;7’üBàùPPû`×àzÿÔKþ“z°þüh/ËÚ³%þmê±Ñ4îþ+ÿî9ÿ îýÖàÿ(úýá„o‡Äœÿ^ÄR³›þÁßÿwùÿ cøQ¬©ÿÚBw•4’þ8·þb)ÿä5ÿBLþ þ4hþ˜ÿkÿÍþ$ÿ:Éþ[…þtEÿ:èÿÍGÿ+zþ þoÍþ_‘*‡!éÀ 5¸XêüQ–êiGú^ ÖTRVù÷ýÒú5kûÈÕní¿ÿ4kT³ÿá—þ%úþ›ÿ^wÿt1ÿo5þ«]þ˜AÿŒqÿ¡ ÿïÕþôŠþÀ¸þ{ôþåÿo0ÿöþžþýcþô k4#¯?×õp…»ò&¿øS•ÝçÖ,§ÿ8ýŒ`ü*G-ÕCþ›4ÀÁÿàãþ\åvËÿçàÿ2åÌU3Wÿ”ÿ‹ÿ9¬ÿ}ŒÆÿ`ä(€§]Ú9Юÿ8ÿјÿÃÝÿ²«ÿ?è_^ß–ÿ`O·¥ÿU’þï.ÿFMÿ©@ÿññþCpþÑ¥þÎNÿ¥‚ÿ[üþÍðþ´¹þ¥—þÚÿþ‹ÿlýþðáþȯþó‡þ!" $ÜNfóQôô¥TúZ'B8‚èþÿ)ùý,¶üÏ­‘çyþYE¶ÿn|ÿ;‹g'% ûÌ‚²Iñÿæÿ½Èÿ`ÿ@¡ÿGñÿ¸™&¨JË&“´ÿXÿi„ÿi·ÿE¸ÿÏ;ïŠõÑÿùðÿgy*ÿƒžþò@ÿ“^ÿò ÿGþ¤þ™ ÿÛjÿYKÿ@ÿÆ ÿaÃþ½¸þmáþUÖþÒÕþÓþ-Íþä³þŠ ¡O+¸…ÎödpöÐæú’³jM-~íKÅ\þÛ{ýÿ»&5[ÿô¦:Ùÿ“Üÿ¡ŠùC¶Ÿ ¹Sìÿ1°ÿ™èþ0JÿÔ*•q hR‡‰‚ÿÚ¥ÿü¸žÿ…ÿ DX^ðÿü?`Eèÿ&•þ¿<ÿú]ÿ#ÿÿ¨þ‚«þTEÿÓuÿüWÿ 1ÿƒÿðÞþ¶þ;ÌþuÍþ¥âþ½ÿbçþ•Åþ‚ù€Nc xÀm7ø÷Z¸úþºG´é…¼ßþÎçý\þÿãˆSÿ¹’/TK™ÿÑô1Çã&ƒ*³¶¹ÿ"¡ÿù2ÿåˆþu„ÿA:÷þÿæ_„=ªÿH«”ÿFNÿ¦dÿÇ@Ñ2’ëÿ¶muÑÿNþ*ŠþÂ@ÿ:fÿäÿ½™þ€®þ´;ÿkÿ,iÿ`5ÿ™çþˆ·þ ±þKÓþÍíþÿð÷þKæþ…Úþ?ÔyÉ®¥ ˜Íg‰øì¾ûu«µÒ†´È;ÿÅiþ|:Æ<Ç€ÿWÎË,Ðø3Ú=¯‡ò) Ë ÿFŽÿ66ÿ“þÎßÿ°‡aÿ<-] ·döÿrØÿ5fÿ‚QÿGÿ¾ðÿH:`B}ÖÿTÿQ{þ5äþ`ÿ¢@ÿÕôþP¦þúØþÃ9ÿÂmÿldÿèÿ“¾þ¿Ôþeÿ>ñþãâþ‚ÿüþ†ùþµÕþžîtÄ× É6_¢Nù\åü<ï2ŠqduÃÍÿ°Vÿ…™ÿ&ÿ÷þr ÿãµÿ!3Iˆ}Üëÿé$ó>UÿÔ'ÿ.Øÿî,ìùÿmÿ€ëþ%Àþy$ÿåJÿ4ÿûúþGÆþjÿÆ1ÿñIÿ¨Lÿ;ÿ3ÿ! ÿÿnîþýþI#ÿW ÿ6îþ÷ÄþD†(¬ é9Uÿúåuþÿ‰¯TЋ^ÚÅ›ÿ§DGPî…êa¢V2å¯Cùaö^¤Âÿ«Fÿûdÿ=ÿ9ÿÛbÝûÿ—xÿ 0úç$ùQ?¥ÿWpÿ…^ÿ'Žÿð‡I¨ÿ+ÿ[èþæ&ÿèÿ"ÿÉAÿ;,ÿÐøþÒþ¸ ÿ«yÿДÿåIÿðöþxÿ¸#ÿ©õþTÿˆ&ÿ_íþçãþ2áþcî2Ñk ¼qæÿoãúòÏþ Tˆ}œw¤¡_zÃDõVäÖ†H«wo“ƒh…Y.&®¥ÿO2ÿ|kÿ¿ýÿžîÿHŽÿEfÿ µÿ0þƒ_Ùÿ:ÿ°VÿI×ÿ ñÿÞJÿÛþ¢ÿ«:ÿ2ÿ’Aÿ™qÿ1ÿÐæþ¤éþT8ÿå¨ÿ$†ÿ/;ÿŒÿ3.ÿ§(ÿÿºÿ} ÿ|ÿ/üþ‚íþœ  ]Æý€Xœá¥ºŽµ‡ozmÿŒÿùóÿ#ïÿxø&`róÿ«K7æéÿg-ÅÀ:‹ÿ£ÿßjÿuÿhVÿdÿÑÞÿp×ÿY0ÞÛÿ«£ÿdÿ×{ÿWtÿu-ÿ’Õþô ÿqxÿVÿÍÿŽwÿ_tÿ05ÿÙåþ±ÿzŠÿë¸ÿ:yÿÿèÿ ÿlÿÏÿJ&ÿÎ8ÿÿÌúþju*8'Ý øW¨–J8ÿlÙ噞bº ·Ž,…œÿÅ‘ÿÔßÿ¿ªÿ âïcó€ÿƒŽ¦¿Ì5€>m¯A½þì’ÿ%aÿ¸ÿðIÿ?›ÿÖÏÿAJÿëõÿ ¶‹HBÊÿR9ÿÊaÿ‡ÿùVÿÈEÿìþ“÷þÙÿ<-ÿ°sÿÚ•ÿ™ƒÿÖÿ­óþb,ÿ¥…ÿ.Ãÿjÿ³,ÿÒÿåÿ( ÿçÿ°YÿÏBÿr ÿÿ)ÊÎE4j ¿E ø-ÿ'9Éà7dCY@zz?ÿ÷tÿk(²ÿ}K¯ŒÃÙlÕÀ(i»ûÿ\ÿääÿFtÿC¡þÿ¡ÿ€ÿ)ÿ¹ÿ2†DȺ¿,ÿåaÿ•ÿŠÿC‰ÿž=ÿ¦æþŸþ õþkÿ¤¨ÿ¤ÿÊcÿC7ÿ' ÿ‡1ÿ¿“ÿî«ÿøsÿx1ÿíÿ-ùþ&ÿö:ÿžbÿoKÿQÿùûþxKGH ôjÉmþXI|ÆÍÓaf P!ÿ2ÿ8þÿtã‹Eàkt!cèÈ wDÿˤÿBßÿB?ÿŠ´þ> ÿ¸|ÿ2öþ‚wÿ†m ‹J†‡ÿèMÿîsÿ‡®ÿ©ïÿ˜‰ÿ†Iÿ{äþ ‡þ1ìþ·dÿ^ÿé~ÿ{ÿ^uÿõ(ÿ,ÿ‡hÿÃÿ9”ÿ8 ÿôþÄüþæ&ÿËKÿ aÿ¾Aÿÿeÿ&UÄüè ŸP®ÿ«×ýFÿ 4×»‹Còׯ¼²èþ4-ÿË0O­sfŸëˆ×ÿU´%~…¡b)ÿ3ÃÇÿ´ÿ îþYÿmÿ¥þ@¥ÿ›Á‰ÓN´ÿµóþcÿ‘¦ÿ‹ØÿxíÿW¥ÿà`ÿ ÿVÙþYàþEÿ"aÿàgÿ9jÿ£pÿäeÿ&LÿPÿ[€ÿñ›ÿØ]ÿmõþóþ"-ÿ¬Pÿƒ_ÿG7ÿdÿ€ÿ{ÛÐNø ^!œÿ0Rýä#ÿ6[RÃ\Ìh+ ˆZcÿÝaÿ ÇB^†OâI‡,vèJß9 Sÿ"Æÿåÿ¡&ÿÇ ÿ_mÿ[ýþ½þžÿ„tÈ&Îÿò×þ¶ÿw’ÿøhþÿÅ{ÿÎfÿZMÿÿ8ÿø$ÿ!ÿœ?ÿ`ÿ3ÿq(ÿy|ÿ)šÿãpÿaÿ-JÿÁ5ÿí"ÿx ÿÑGÿ(JÿD7ÿ ÿ ÿ³†´¼H°^èyÿb¦üþï]3bôbÕ…ÿÿ ÿ.Gÿàþþø´íCLaUŠ4åÇÿXâÃ4›³ÿ|jÿ¡(‡œÿT1ÿîsÿ(<ÿIäþ*…ÿ‚z5ÇàÿÂÙþýæþ9pÿ£[(­ÿÒQÿ³Zÿ7nÿ…Wÿõ]ÿSÿMÿ¹XÿòÿÝþ/ÿrÿ¿ÿŒ’ÿ—>ÿ1ÿ×Aÿ¶oÿµrÿLÿ…!ÿÔ ÿƒ%ÿº/poÑ\ªÌÏÿžü­þþb@Ñ”°6^ÿ*Pÿc!ÿ„þå,ÿ]ƒuoÃÊ:ûQvîÿ4.ó‡\ÿ»ÿ×Ëÿ”ÿtÃÿÆFÿäÙþ:}ÿÔŽ–³ûöÿÿ™‹þxÿ“+=ø ÿ,ÿiÿ'„ÿilÿ.vÿ`ÿP‚ÿ“ÿ|ÿÛ·þÊÛþ]8ÿwÿÃÿ…tÿ,ÿé'ÿ aÿ5•ÿkÿ$/ÿÂóþo$ÿµ¬Iˈ¨§Õþ¹pû ÷ýsæ 2ÂW8ÿe ÿ Fÿk‘þÞÆþ æÿ›":úôcËÇÃÖp9ÿ ÿð™ÿâÿfD£ÿ ÿ?‡ÿF”;¤+­[ÿŽþ%Ëþöÿg²¤ÿÃÿ8ÿ œÿò¹ÿàpÿþtÿV¨ÿ§¸ÿSTÿ?ºþAÔþÞ&ÿX?ÿ(IÿIBÿaDÿªAÿjÿ=°ÿâ™ÿuSÿ¥.ÿ?+ÿs÷ýgZTî7rþkµúrýe,G¡ŒØ ÂþÇøþç\ÿí—þúÌþáÁÿ»§«\OŸÿÝöÿæÀ¥áix»]éÿþYÒþ-”ÿ2® cKÿúÿǹg$¡¿ÿýÙþ!¿þžyÿ2Pxaÿ€ñþ‡bÿ×ÏÿàÄÿ›}ÿ'¹ÿ#Þÿ rÿáþÏþ‘"ÿ”UÿIÿ¾ ÿÜþ›ÿ6ŒÿŽÁÿPÁÿç†ÿü.ÿ ÿAÇH¼ù¾[ijý7úÚ°ýâ#H‰9.þ×èþžWÿ°þÈãþ8¯ÿbŽ>À…ÿCPÿ¹bSíç%Ôšù¡Uÿ EþÏÿú =óWÿ̇ÿ“ÍBœ7ü*ÿ°þ xÿD4!ëÿŽ/ÿ&ðþ”-ÿ•ÿÿ¼ÿÐÿFÈÿ×Âÿ¾€ÿ™çþ}Øþ°ÿTÿ§hÿsÿPºþàäþÓbÿk¿ÿãÿùŽÿ±ÿ± ÿÚ¼†O̳¹`KüCù!mý-õ°RýÖþ“gÿÇÉþlåþ”Ëÿ1I°vnÿÿX‡µ@¹j,Ìék”ÿpXþÕÇþ·¤ÿoÕsÿÕUÿ—Z_*¥W‹¬ÿ6@ÿüZÿ|òÿ§òÿø\ÿ|ÿ!ñþJhÿÍËÿ´ÑÿAáÿÕÿIdÿÌþCßþËGÿ}oÿàQÿúüþCØþHÿ©vÿÝÿ¨ÿì$ÿÁíþ¿5ÿÁ` YGfÕwû¬ÇøÛëü‡®}ú¦|ü¢þ¶”ÿÀÈþ£Áþ»îÿ«T§Éÿü^ÿ‹ÛþÎôÿÔž([ ÏÙÛTòÿwoþˆ€þª¡ÿÕ :€ÿt<ÿféiÿàK.yÿ(¢ÿÃÓÿRaÿ¥-ÿ“ÿƒEÿÃ¥ÿ…äÿ]úÿí·ÿ‰)ÿ¯Êþ-ÿ~Jÿ¶TÿÑHÿJÿÙþE$ÿ ¶ÿ¿öÿ¦qÿèþSíþ<ÿa]$¨•}XŠ7*úsì÷LMü¥æ£!¯mSûXþ”Éÿ³Æþ\¨þCËÿ¶Š!Žÿ¼ÿÿÜŸÿRnIJ¡E%£RªþÄbþêMÿŒÁÿÉÿä–ÿ>ÏÿTÿ‚bÿQÖôÍÿÚýÿuâÿ‡ÿD?ÿ:ÿKcÿǨÿ‡àÿiâÿΘÿMùþsºþx/ÿ?ÿj1ÿX!ÿôþ¶ÿÃtÿsÍÿÖ¢ÿBRÿž=ÿ¶>ÿØ4ÿZ¸¦i^ôY‘ùßööÝü¨}òÑ™¡«7úÑþŽ?ºªþB‘þ>¿ÿô¼vÿ½Åþüÿ •ÿ˜aù(`ãÿUsg„²òþ²yþ<ÿÍŠÿk¢ÿK²ÿkÁÿ€ÑþYùþaWÿð;W µ÷ÿ»öÿ³`ÿ kÿ(Bÿ‹€ÿ&Ñÿ{ìÿÁÐÿÎ2ÿÌÿÿ9 ÿÿƒ ÿ#ÿÒÿ“@ÿ†”ÿð“ÿÈuÿ­|ÿ±“ÿ•[ÿ†ÿ¢£¥LÚîïéî÷ÌàõåXûÐ ÖQù¾kýJÚ¸¾þ?þ²£ÿ¬ò"šÿ†WþŒÿþ+ŸÿèW#+Ù‰ÿ½­/[ÿz—þ§YÿíWÿˆÿƦÿc€òþÉÝþ×þbÊÿs[ʼÿ8ëÿâyÿe?ÿž]ÿE“ÿû zÈÿËýþB=ÿM`ÿvÚþçþ¤ÿ*ÿÂ\ÿ¯XÿXÿYtÿ™ÿ£ÿâ’ÿ!pÿu'ÿµi ¢Ñ`ª§¾Ùfö£LôîúÀ) ê(^ü÷ ÿü/W¡Äþ ÇýðZÿ´öÜ«ÿÖýý4¦þ[zÿ¢-y0Ÿ%ÿgTÿÍ…ôŽÿÙÏþmƒÿ¬Eÿq1ÿ3ƒÿÅ1¸1±þ’vþP¦þ;eÿÙ:b¸ÿëJÿ¸\ÿ‰Fÿ±<ÿÅeÿ( ((ÿóþùFÿ³Hÿºôþ_ÉþŒþþ—5ÿ~Nÿ×ÿoÿ˜‘ÿ¬ÉÿB ÿÃkÿ,[ÿÛ,ÿ—2 ÆÉT ÏË‘áõËóøƒúåä6£ ä£ÿJ÷àüÿË&ÿÍjý^ÿbзÿÓõý4´þ {ÿ&`úÿnùþúYËÝÿá2ÿ´ÿû­ÿNÿÄ‹ÿIHÀÿüÿÌPþ;lþ\mÿ#ž»ùþù ÿš~ÿ½JÿÕuÿ–ÿNÏÿ³VÿÈÿm:ÿ/9ÿ-NÿNÿV:ÿikÿºÿ£ëþ.8ÿ¶ÿ®Ðÿ­¶ÿÏlÿÁIÿ×hÿg( °ú s  À| öô¢ñ„ù0 Þ ,ÔþÅ¡öóúûôuþ°ÿ@ôü¡ÿ Xm¤ÿÜ®ý‹Äþ nÿƒìÿq\îÿœþ‘ŸÿZ*ËÒÿÜŸÿKÿõXÿ^€‹ÿçþ9Uþ(þºKÿlúÿÛ6B%ÿ†œþ|kÿ4rÿ”Dÿô ÿŽ[ÿ¯|ÿ¯äþ˹þƒ+ÿgÅÿD°ÿ¹oÿ Lÿƒÿ¯ÿVÿ—˜ÿ×Êÿv¢ÿdÿEjÿü‚ÿ ¡ ìt$?¸>d öœùð·2ús» Î ')ýóö1üos@óÿ@®üäÛþ “ü_ÿQxýZéþÛcÿÜÿÌ8¡ÿk­þûÚþ2ûÿ´¤“£ÿ[1¤¤ÿÝ5ÿ䌭¨ÿ¢þRþ=þ×ÿýóÿø&R%ÿ§»þm=ÿRTÿÄÿôáþµÿ9ÿCÕþÊ„þ–ÿ}íÿÔ/ï¶ÿsÿöÿ¥;ÿ‡vÿ¥ÿ'ŒÿŠÿäÿÃlÿ¡`ÿ WR(,#w&÷K•ñª(ú}\ !„üçûÞêö$üÙÞˆÈüd þ3«Rÿ¢WýJ÷þì`ÿ¯Úÿ§ÓÿqÿßáþMþSRÿ¸R UÖÿLnGTÿ©üÿ++gƒþf¬ý…÷ý@¾þíÎÿ¾ ‰,ÿeèþrÿVíþþ÷þ; ÿÜšþ„Qþ²Úþ$²þãÓþAíÿ«WùÌÿÊ=ÿ`ÿÄ^ÿ5fÿ³Žÿþ„ÿ¥«ÿ¡šÿ÷Gÿ7ÿu_Ê–(t]¨b’ù»ó~Qõèr [¶ âû#™ö'ûisÄÉcý”ôüÛ+,"3ïü ÿVžÿ¨£ÿLšÿ:ÿþäÿ|Sþ”€þØîv‡„ÿpâe¯1ÿs\=ÿ TýÖýúxþ\_ÿáö©ÿ ÿʘþR¥þ;!ÿÖÿ,Tþšöý’ªþœêþ²ÙþÕÀÿT7ÈæÿyaÿŸ‚ÿc”ÿÝ5ÿ·xÿ«ÿÄÿÊÿ.ÿÁùþ—G‘+p“8™ý7ý¥þó>qóq ­ ¡xúÂöõIÊûL†—ÀzýHºü1ÀÄÿó­üzˆÿyóÿô{ÿT ÿuÿÜ.ÿ–þSþ¢Šr»ÓÿË'Ë·4ÿ<ÿÿÈEÿ$^ýc°ýÓWþ€'ÿ½¬ÿûÿVXÿßõý‚nþíDÿØÊþÔ+þÑôý¤vþ„ÒþÆ@ÿ+ÌÿŠæÿ ãÿ~{ÿœÿ?uÿøÿªÿ†Ðÿ¾ÊÿyÿÃÿûþ¥Ø0#^Éû÷ùÿs÷rúò£ £k W¬ùm¿õÜ üu¼âÿ Íý:Qü?‹MÉÿ<\üÀ÷ÿK=¤ÿ~–þœÚþŽOÿëÌý‰¥þ™ùÿ‘‘Ïòÿí'V†ÿçžÿÃÿ¾ýn°ýþ¬HÿÈzÿ3¾ÿB_ÿ™òýß<þH¶þb¾þøVþùáýÿQþ[Æþÿ£ÿãêÿu ÿÕÿPŸÿj‰ÿ?8ÿ ÿ¨ƒÿ‹ÙÿÚïÿ§Eÿ’äþ3$ÿ¹› 5­Š} óO¬Gt÷a¡õ7 nYKùçZõÛ4ý„×.Nÿ ÏýÇáû¨ÜŸøþ­˜üµJI˜öÿ¸úý÷þ&Bÿì“ýEÿżÿÕéJG@â,Šÿ¼ÉÿÁ—þh þe0þâÆýsÿ9™ÿÀEÿ1 ÿÓDþóýÌ:þ€þþ>HþÙýþçÄþ+æÿë1xÿºÿ»ÿ;qÿÙ ÿDÿ©vÿµÇÿÚÞÿY*ÿÞþôFÿû¡ø880UYìö“r'ýŒ_õ§ ùT©.ú™2õûŒ‰þÝ þ“ûx­ÿðÿíýûS^Âp? åþGôýu%Aý ¢ÿÃ޶²êîwt×öÿ þÿ ÐþIJýZ?ÿ;¯ýæ`ÿùËÿVtþ×!ÿwþ ÁýJ»ýÁðþIœþ¡²ýÊ¡þŽÑþÑÿUhçhÿ€|ÿæºÿã]ÿ“ÿˆÿoVÿþ„ÿ“¨ÿ¿0ÿSÝþyQÿ–kcý<†8³mç|A»ÿ8•÷eØE>û•ôSòú£±âý;,þòZûjYÿ$JüvßÁ<_Ncçýý—£LÅüê =¨Uµe,ÖYí­VþDq1ÿ0ýܽÿ½þ*¶ÿ“}ÿhìýøÿlþ\Ùýæfýš¿þÆÑþ °ý¿ÇþÃæþ"Ñÿî}Ógÿ‚jÿ>ÿŠFÿÌÿ™0ÿ‚Xÿ\Cÿ6cÿÚ+ÿ&áþaVÿå(PºA­‘7ãïhÝÙþw†ûRç¾ücñòßfû'}%?ýŹþ»Öúòßÿ0©ÿݦüG; °ÿ(ôŠHý1Íý4ÄÉtüU'DÑ>ÿ£…Nâ„›ãý¤ˆž™ÿÃþ¾Öÿ1­þÄ4ÅÛþëÏýèÂþÝDþ:6þ1#ý–„þQ¾þbâýYñþÿÿÿ3DÒoÿ´uÿÂlÿ Lÿ_íþ #ÿ}zÿ  ÿ!ÿn ÿÞæþF{ÿqi!è EãR 8ïßyòþjíþñ˜ý…ò<Àühóñ’àú›*Sý4}ÿÓÁú…úÿÇàÿ¡üã+äyÿ©e”Mý¯ ý©ì¨üÁãz¿þÑÐÿ¼KX†Çþ½_ ‡xþáÞþIc¹øþ£Åýµ4þêþF¶þ¶ýÒþçþ<þÂóþ(ÿ³ tÿi}ÿoXÿEÿpÙþñþþ€oÿÿèþ¯èþáôþûÿ” #dâG m®Ýœ0ýŸáýÒ$ÿ3 ßÿÍüô;ñíùŠª‰?ýºÇ²útæJ*ümº“ÿ5î lýÏqý£X†¦üFQ…\ÎjþTÿHÚÿd>Czþu.†\|ÿÝ$¼þ‘ßÌKÿåœýYçýÛÚýÄ&ÿÂOýE˜ý@—þ¥7þ6ïþ??ÿ;+ìýÿÂoÿZÿ"6ÿ¼(ÿ8íþ!ËþsUÿÜ2ÿäÕþiáþÿŸÿs %E«J¸j AhÜ™-ûå»ú%Ê*Ü P©þÛüXð6¸øj @LþÐXjLú³ŠóFTPûhhLÀÿF7VÙý4¸ýGØ'õûÞa¿ÿGþ®!ÿГÿc–ý ÿˆsÐP8ÿ¥\ëÿ”þµsÿMïý©ýªƒý×tÿå©ý”^ýqXþ—?þTÒþÔCÿ/yÿñhÿÇøþÃÿ:ÿc°þ/)ÿDRÿÁøþáþŠúþÿ“ÿœ—&è^LîÚ u¢Ü„RùóöÈ.ÁJ Úàý¢Äü%‹ïT;ø³Q|ÿúšáùp¼ ·ÿúKûüìßrÿoÝøTþˆý_âÿ¸æûíz2ÿwþë_ÿÛ+ÿWšèÝÿmœ‹ÿnLÿòí âþUßÜß)þ+`ýøaýw”ÿÃ#þJWýiþ”þ»¨þ†2ÿ¦õ[Oœÿ ÿ;Áþ8åþªÿΪþûÿ[tÿÿïìþˆÏþw}ÿøœ&vNM‡ï ¹Ý¦šö…ò€9Cò<ýñJü!ð7à÷Ì Ì0þøm>²-bIûPpÖÿr»¼ý¤fü§CÿŸVûZI¤ˆÿ:Þþ¬¢ÿ¼ ÿŒ™Èûÿ/§ŽÇÿÔ#ÿÚ¼‡ìþÃðyjþ–Uýœmý2¹ÿ––þò¡ýyÎýfÌý eþýÖþ1¯‘Š€ÿˆ¶þ\WþvèþîZÿÂþúÿ3ŽÿJÿhËþ¡þð}ÿO^#VMιÞÙêó_ïBlþµ()ýjùû:Äòóòõÿ*ŒnçŠù9íþá]$ûoÔZÿ;ôŠÿgVûz¾þ$öûE1†ùÿ¿¹þé€Õ¹þ­hvïVïÿzþ»9Qÿû8óç²ÿ¨ý>Cý‚éÿÞÿþïýhþÏHýb%þh|þrùÿyËtMÿ£•þ¦þÒ÷þ˜°ÿBÐþ¥ ÿzÿoÿ‚Øþ‰þ'™ÿ9P&õºL¨mãcíò–Zè¯Öÿx¤$fýƒký«ïòÛ«÷vºÿÿèX÷0¨÷….[²ûvR†Mþ!ðN!þ>üWþ̪üW‘^¦ÿ›«ÿþÔÿkÇþ:ÖÇ_‡ÁË»ÿÓÆý…¾µÁÿô ¨[Pþÿ»2þREý8Ûqÿjœþ@/þƒÞüsÚý= þf¯ÿ¿–iÿÈaþO<þ2<ÿïÿÛÿQIÿ2ÿ$`ÿ9ÿ”äþè‘ÿ-ß$XKvÍ·¿æf²ñ4ùãsÌüOûˆÿ=þ¬*ôÕøúßþõ¢ [úø%¹ZtúÔ#·²ý6œ;ÿÌhüQKþCeýŠ“ê_ÿjÿÈ[Â2ÿæ-vO4\þÿg=ý":GMj9´ÿ©ÿvÒÿtÎýItÿÇPgÿ_Cþâ©ü:‘ý1 þuÿ®æÿ ÿðÍþ+zþG ÿneÿDÿD³ÿ9ÿÝÿˆWÿ‡WÿtgÿW4%ðÇIqÌkêÒ ñAÎÞvÓû yE,=þûXõ¤-ùn“ügIâðŽUø·æFסÈùTÍLþ wyÿ‡ýß‹þ Óý戆<ÿ2UP6Ðúþÿ *–ˆ»ßÿ ýØÿÍ‹:¼1…ÿÔÇþ‚ªá3ÿøþ8½ëONþ(Çür-ý"Mþ“Òþ9Ÿþn¡þJzÿŠ.ÿR’þÖæþò¸ÿuþTÿXåþí]ÿ^}ÿÉÿ˜7&)#G²Ùíï1ÿïCîÙéwýèai;ÇÿÞÞõ\ûødÏûzÑ,tz÷•÷Wðýùú)üÈýÞE\Üý·Úý¨‚ý¬Ê¿lÿ‰Hpìÿ rÿJÿ ñÞÅ¥ÿ3Yý‘ÿ(AÊø^DþŸ·ÿ1H6ÿÿјÐñþ±ü=Xý$þüþ˜Åý¢rýÈÿc_N¸þÌ&þ<ÒÿqUé½ÿ#ƒÿÖþŠåþÛßþ.&m D&Ü0ò…Kï‚äØO þ‹3€Ì(¨ÿdûõ3¢ú>‚ú38Rúù‰öBž þ¿OüñcPRýôa$¾iýÖý¾aþvAá ÿܦ; å€ÿcÜy|,Äo¾ÿ©ýœ„ÿ _5 ¯Ãcœþ­‘þ€FD/TÇÿXÂÿ÷cüÌý–Ôý2þ ¾ýLËü?ÿø€Âúÿ…Rþ ÅþýR†k>àbþ7êýÅÈþW€#P¿?"^iñôžñxyÙ©ZüùÞ¿::¡‘÷½øçeúéðDÇì»ög÷ý,îüWëûýä¯ÓÿóYþ‹þ;Qþþ‹ÿ+ÿÈKµ¾ÿÜ ÿ“~ jhÿf¹ýyAÿ”?¥´³ÞkÿçÎþÌOû«|Q&°¨ÃÿZü‰yý} þIÊý©Æý< ýWgþ9¹ÿšÍà›ÿÙHþ¤yÿò’GbŽþOýøfþD±!*U;Üä Eê÷HœóNÎÚCpûötz‹4üëˆöáîøÌ|ú½Kr=”÷ÁÎóý6þœ› ˜üUâÑö%þ#ÝýFuþD5ÿ”–ÿ¡k íþž[ÿq šßAÎÿK þ8îþ…=œ£/uþÓÿGíÿvñÿZ“‚i¹}…Úþt’üÓ¿ýÑýóý¹ý}ý”Zþ_Üþ‚ C;+éþCÿvÍÿÈ,kðþ³ÿýP þŸ pM5n AÑü'ö{ŒÛ˜eüI/>€ÃM@õ÷Bô÷:¶úf»¯ÀHøø–êýgTÿðŠüyþ(¹GÉýÏþÿÿ Úþê±ÿß2|¡þž%ôq,bîÿìôÿ.VþÌòþ•,²€€üw2. òÓÒ¾îÉ@ºþMñüÜ3þˆ˜ýþ»ýý©ýƒØþ2´þµÿVtÿ'ÿÿdÿŒÁÿèÏþ„\þZfþ |´/wÉ ,itg÷óâÞçnþ@Ì e0¢)н÷ FøM5üØ©’‹ÿýÚù»€ÚŸý=èþäúÎGýxÎUc½ýÓ³þ@ÿ¿=þvñÿSÿÊ0Ap#GX%@Fq§ßå:•¾Aÿªý.BþñÜýLñýë8þ3+þæïþPúþÆ(ÿè=ÿ‰ƒÿ­’ÿQnÿB7ÿ„”þªþÄþžþÍL.7 µYúÀ¾ö–}äð<Ùö#ÿyfÿR'úûÂyübñ@èÖø3#\±ÿÙýØŸýÀýHtË5…ïýÖþz¯þcîÿ!=ÿØX\Òÿ]ÿ° ÿ‰ÿù×%kD²þ¬1$ŠÿÖÑÿº¥ÿ¢½ÿ=˜ÿ¶ÿç÷ÿ6f\$+œÿ]ûþÒ,ÿDÛþT’þä þƒþ 6þEþ}þŠ˜þ9üÿÖ{ÿñ ÿäþ³þ@ŸþzªCX(WÎý¶[œôEåãÞšÿæ i¯Â6ñû(7ú‰æúÎ*I=¬üõìTþÊCþìp•_þ³‘8Ì·Aÿ3¦þJàÿZÿßhýç ü²ÿËZÿI'Pó–¤eoRàþ•|ÿÖ8ÿªãÿ-+ 1Wÿ×ÿáàÿý:xÿ0žÿù…þ‚Nþ™Ýþ£°þ#þº'ÿ“eþ]Iþògþ×þaÂÿîcÿCÒþüïþ…ÿÀ“þ#ô”"þ@Y¾:÷³7élWþíÆ>ØQ ý$Äû£‚ûORý<Õ þ¼{þ2ËþmþÉæþHPþþ ©ÿØÿr@™5ÿÈýZ;Ïðÿtÿ<ßñ±`|CØ$ÿ?ÿ_@ÿÄ$—Öçÿáo”ÿÏõÿ«±I9‹ÿ°—þIþŽÏþ"ÿÎÿuÜþ«¢þiÌþ¦­þÜÿrkÿvAÿ&×þ…¬þ-÷þáþYè *NŠÊǪ̀ü}Û푤ù•l“Ó4© ÿÏüzü§~ÿ`‹»2þ'F7SþPˆÿ ÚÿWà%Z„ÿüÿ¥Hmþ¼°ÿ·%¨ÿ‚tA4‡ƒpdÿ3ÿlÿ; ×MŸÿ tÊ(n—ÿš OBÙ^µÿÿ±þÃvþä¬þ‹+ÿ;>ÿe'ÿIíþ ÿAìþ‚éþô7ÿÂ#ÿi ÿIÆþúÌþÎìþäÍ Á%¥š# Œüd•ðüFüiûÊTKQ\~ÿ¹œý²®üY4{¶ÀÖþtœÿ°ÿ¶‹¿Âÿ–>Š@3žúÿ§}ÇøÿÔ{þ}wÿ·¯ÿ® ÿ+Àè¤9 R5aÿ‰\ÿw@ÿÑøÿª!  lwÇÿ!¨ÿ.ÄÿŠ3¼­’†ÿ­þøvþíñþ¥JÿÂLÿ!ÿŒ&ÿç`ÿWâþ«þíÿPÿ,"ÿ.Êþ®¯þfÒþ™ ¬al%þ°Ëý‹wó§üêÁõFfuýRþvý*N¿iÿƒâ¼»_ÿ=ƒ$°ÏKÕcï!iHWÜÿï‡þ" ÿº¢ÿÐ5Óÿ‘çÿO¹t¶®q }ªÄÿQ+…ÿºKþŠ ÿäTÿuÖþ¾¾ÿ¹²#õáÿsÅÿ«èÿ oÿÀØÿ'/.ŽÏÿªKÿWLÿà:ÿ&R°{¶ÿ—­þ=éþƒ:ÿ± ÿÿ8nÿ\[ÿG&ÿ+èþºëþw2ÿÎIÿ¢ÿ@íþLªþë†þ’ê ÌÎ,æ ŸrÖÆýÌ5÷ŸCþ]~T؉Zë¸/eÿ)Œþ24òX*øÿòä[{«O £æ´E»J›TÛ²ÿ¤åÿ;­ÿÕ‹þ‡ÊþÌ,ÿÍùþ¸Çÿú°pÒjKÿ¬ÿh}ÿP»‰ÿ wÿåaÿBÿšÿ%¡WFÏÿ3²þÿBÿÎõþ!ÿ`sÿ½yÿ'ÿ¥ÿ8 ÿf=ÿ9ÿL ÿÅéþ®þ÷…þ¬R ÜOL Š—’ þJŸްþ³‹øót–Ðÿûÿ±M½º¨)÷°\̵G±o^ŒƒTrìÿKâÿ© ÿÚ˜þ—Öþ ÿ±óþûc¹`tÇŠ>¹ÿ`ÄÿÖ¹ÿÃùÿZGÿÿŠÿ“©ÿ3Úÿ_"öÿÇ;ÿwæþe'ÿ“ÿÝëþÛ8ÿ pÿÚlÿ—RÿÊPÿ™/ÿ3 ÿÿÝÿöøþb¹þþ”þÉÊqCùí ÁŸ.2þ&+úöÿoÙÞ€ÓbàOÑumÿÔÙ™&Dž‹ƒ(¬ÿ¾™ñ­m°ÛR†ZXX¥ÿy­ÿ ÿëˆþƒÿ„ô1¦íY“! ÿ•ÿ+ûÿ–’ÿ5ÿA—ÿ¿ÿ£”ÿÁ—ÿšÄÿí¾ÿ¯}ÿ?ÿ8îþ%çþ‹ÞþCHÿø–ÿÉÿÑlÿ5@ÿ§ÿòþôþ•ÿ& ÿ5×þc™þé¹Ü‘êͬÐÃ4ÿüþ»ý~Nm¾n²Ë½1ãÿ¯ªÿ}úâ’ªÖùAÿ:&l¼ÅßÛµ…Ýÿ P>$ú®ÿX(ÿ0@ÿ£ÊÿÓÿš|ÿŸ_0+üa(O’ÆÿwÿJ~ÿ~R å«ÿ cÿ¨MÿŠ£ÿ¿¼ÿõÿ÷ˆÿ¸{ÿ.ÿm•þGÔþVŒÿ8Òÿš«ÿ¸Oÿ'9ÿY&ÿ)öþ”êþ®ÿÊ=ÿéúþd¹þAÚ³› ý– çþ3üZ×ÿöS…+¹¥ƒlÈ–a#ÿÔ–ÿ•×$tÚÖ*X1ÿÇ€ÚwÆøF7Ddn;òÿÖ¦ÿ9Þ“ÿ"9þìõþ–íÿe!!&u=óaÿ„4ÿ/Bg’¿ïÿH2ÿã'ÿEJÿ‰ÿ Äÿ¯oÿpcÿcsÿxÿ«ÈþW ÿ £ÿpÙÿì©ÿ(1ÿˆøþ¼ÿ}ÿþ—÷þ8 ÿ=ÿ<òþRÁþäõžÄZ ÎdNÿ°üýÿMîæo‡’|ÞþòqÿD0Þóÿ_÷4*fZÿëõÿEž ¾u~•td§Ûÿ7iþ½®þ=ÿyNÿ6ñlŠðÿkNÿ3pÿÙnÕÿ`Rÿ³Zÿ=2ÿXÿŸrÿŽ¥ÿÉ]ÿ˜ÿTEÿéaÿ¸'ÿo>ÿ¹Øÿ¿ÿ 4ÿ õþ{Éþâÿi ÿ2ÿ¢$ÿÜ÷þ£õþA§c xû nï™KÿÌðü²Hÿ²ra—¥ ý,gê]nÿÑüþ­ æÿ©~„hÌÿ,V4N)]»^ðùÿÅ<SlkªþÊFþ^3ÿãòþ'XÿØ.p[Beÿ ÿ…ÀÿáUO‹!Æÿ_†ÿ Ãÿ?tÿÿhÿôÿ&ÿ¥ÿzÿ†'ÿÔ ÿ^ÿ1hÿòdÿW¥ÿ‹ÿ•dÿÿþ¼Áþ=îþúÿÜ7ÿÁÿL ÿE ÿç(qRú|óºÍþ¹ýÆÿþ•QÛˆÝpŒâˆÿ=4ÿŒaW¯ÿMlP{œÿ“d¥‹º+–#ÌžOÃû ›bþ­þ8:ÿ,çþØÅÿ:L!òÿVÿy$ÿ$÷ÿiuíTÒÿýy,þm.ÿVfÊ¿Äÿ4ÿŒhÿN¾ÿþ#õÎ0·ÿÒþÜ®ÿ@ÖÿÖCÿ åþ\Yÿ®ØÿCæÿÇÿƒyÿ*Kÿ–Xÿ[ÿh9ÿT ÿÕõþ÷þþþ8 ÿ«]ÿÜ´ÿÒyÿE$ÿ¤AhÜ’=<°YÙ÷› øÛ`þ$­&î  Rý#ÿ\‚ÿnèý>‚þ"_$„•ÿ€RÿYÿµ‰ÂÎ £ÿïædÒ²ÿMYoÿåXÿÄÍýr~þé÷ÿ–w\öÿ±Dÿ®ÿ •ÿ#9«“ÃWÁÿ…Íþ(&ÿÔÿ0–ÿÂ=ÿÿTÿpƒÿ÷ÍÿçÛÿ~ÿ/7ÿ.ÿ94ÿÿt ÿÿ}ÿþþÿdÿ÷šÿúyÿò*ÿ²æùŠKJˆr¨öÞIö¨.þ]ó¤üºYç(ü‡þ" /þ¢,þõÿ]ùeÿ¤ ÿO$ÿŒ5 ÆÉ ¢ÿŸ”ÿ1ãÿÔéÿNz€úDÿõýî)ÿz³ÿñõþgJÿÉOÿ­ÿ̱yÓ_ Ž8ÿÛ ÿquÿ7­ÿ@¦ÿb‰ÿ ÿüºÿ×ÿ+”ÿ¶êþøôþ’Xÿ*#ÿÖ×þ¼Öþçõþi%ÿ‚ƒÿy¨ÿ°®ÿ…“ÿ\ëþòÊ ¦4ãD{´”ó•kõŒèþý‚ˆpqôÿRûl{þ“9¢Cþ@þîÿ®bk6ÿž¯þbÿ&+€Σÿâ ÿ]ÿ·ÿ¯*Í$¢bOOþ]bþ0ÄÿüŸþQéþ•7ÿ‰aÿ4‚gîDÿÿÇ#ÿý ÿþPÿuÿ”âÿžÏÿ–ÿ¸ÆÿhóÿOfÿþØÂþ\Iÿ/)ÿdÍþè~þ ªþÈRÿKÍÿ+éÿi½ÿ#(ÿì·þNŒ $kT­ÿZXñ ØóÔiÿ”y&äà„ÿCÉùÍóýnÝZuþƒžýÅ®t?åþ@¦þöþg÷ÿ€u ß–ÿUÿ•¾þˆ#ÿ Vvä/-ÃÿL<þÿ¿Èÿ¸ÖþÚrþ¿&ÿ·1ÿ¸Ûÿd ×S«%ÿçþûÿ?Eÿ!æÿ¦\(äÿï²ÿÍ´ÿP8ÿùÒþ$ýþt ÿ;Èþ`žþþŸÓþlPÿ{åÿÆxÿÃ×þûÿ7} N£ 3ˆý+dïµ ò[$×€èsñþwøÇMýA† ðþËéüãýÿ{°¥þKcþßìþå.·~ÿYôþ†Ðþ{@þ‹hA\ái«ÏKÿÝnþ…ÔþÎ0ÿù‰þ¥þ_aÿ]rÿ9SE±×~ÿq¶þvúþ.ýþ¼^ÿÎu©‚üÌÿaÿHÅþK8ÿ‹ÿ¢õþ PþeSþÓ¨þªÿyÿÞÿ[Ëÿ´0ÿ_ÿABÿÂÐ r·" ß!Y¡ÉîÐÚïÜíÿÑ @®tþ«Œ÷•ü㇔ÿm–ü3`ÿp)†þ Iþ¦Øþ`îÿôfI˜ÿÕ—ÿèþv¨þÛ9þºLÿ#X³5T»÷=èDÿbæý½]þ0*ÿê†þ54ÿOŒÿc•ÿégñ%çþ ·þ1ÿWÿ ðÿr½[¾ÿ׳þ޲þ3<ÿ §ÿó6ÿ^þ³9þà»þ­Iÿ §ÿr¨ÿ€aÿ‘LÿòIÿˆ,ÿщ ”Ù#$¡8OîítþµŽ a•#þŸ÷‰Kûò«Š7mü bþˆ5¼þeåý¬ÿØÅÿ‰èjÿ¿Rÿ³ÿÁþ lþÕàþñÀ—”žBW¸þ}ùü²ÞþP;ÿ,×þŸªÿ†rÿägÿ.T¢ÿˆuþ;%ÿ¥kÿÒyÿ! ûžÿ§´þÓþ®öþQ\ÿ³œÿ?ðþU@þQ¾þs†ÿk¼ÿkhÿAÿ™eÿQKÿäýþ"`È(vC#‡ØÐí$lí /ÿb ´Ô-ü/¨öÔiû&ñjÁ&ÚûD;þZrxxþ#øý.ÿí[q4ÿ¤ïþÜËþ•þûMþ…Kÿ 4Â&ž™C )Œ%nÿÞ üøåý›ÿÁ'ÿ'bÿ6“ÿ¨þ7´ÿb=Í¥þFúþEîÿ%aÿÕÿÖ?ÿ˜ÆþÑOþYþ³ÿç²ÿShÿ¦þDÉþ ÿªÿú`ÿåeÿš3ÿýòþÙþiLl,§F!¼ ÿuî6îpÿ* ]$jú†Höݺûõ£m“ûÀþ>7ÙcþD;þM5ÿ¥=Åq¿Üþ%™þ:Æþ0þó[þíÿpÁ@‹Ê‹gp,»;»2ýcýŒ£ÿ¢”ÿ`GÿÊÿYþ°þâY”iÿeÿÄR]ÿÂþKßþŒþh1þ2]þ7ÏþAdÿ™¿ÿHTÿŠÞþâ~ÿ~¶ÿã“ÿ-tÿ…åþ‰¶þØñþüõÝ0,à ÿŸû)£ïQ@ïÍTþ£aXûèôù&¥õèªû™Z‡åû¤Zýtï«þ…@þò¥ÿP"U׎þŸ>þÈ ÿ4þW£þã["g6kÿG)(#½Àÿôý[þÇÑü×Pÿ~Åÿ­pÿlÚÿ¡þœåý›ÿôb‡lÿÞœÿÑ„ÿÇ“þW\þ‘4þÝ\þ${þ‡‚þ)üþ­çÿ]æÿÈ÷þWWÿÎÿ^¹ÿõ`ÿZ»þfÂþ8ÿþ¡0$<5«>8íõÝñÊñ[VÿH³›M`ùRÍô@2üv~oÿe0ü†ýÀà¯ÏþVþÛÿÿ){þÜ!þRþ^ÿŒ³ýÙÿ)ÊvMåîþMBV~´ÿL†ôÿ;Mý"*ÿ®ÿ »ÿñ“ÿœ¯þ«ý|åþÝF›ÿ³lÿžÿVþzøýŸþ•tþ€†þiþçèþÕÿIìÿ@ÿúQÿê¢ÿ¯£ÿ?ÿQÒþÁÅþrýþCÓƒÜ9çÜ8}ð¾…òßSó%kÿi] ^Ѿ£ùÞ½ó†#üE '«þtýÕ£ü{É7.ÿôBþIXŠÊÿ<Þûý· þ£²ÿdý¸ ÿiäºýÿ?¦þŽgÿ/÷Ùÿ¶ÎÿÎÀý¡ÿoåÿ>ÒÿC3ÿÍçþìÁý+gþEÜ—Íÿž€ÿˆ‚ÿþûéýW þßkþÒuþÅ„þÿ1¥ÿÓÿ©iÿVÿÈsÿÍYÿ>ÿEøþÈÈþHÿT€´è>Pñ ëÄ÷óWÖôë5‚ È—§ù¼fòöœü2ˆE+þú#þc€üÕߟ=ÿVdþÊ:#Èÿ ŸŸ¢ýîwþ»±ÿXýè.9d£ÿ¥uþ^éþ_Îp-æ0vøÿgPþÀMÿ› dÒÿ•öþÑÿ¢Êý\þò/ÿÿXÕÿ*þþÒþ}þsýý-Zþ¸eþ2¼þ@Eÿ¿¥ÿu¼ÿßQÿDÿÿê•þNÿ׈ÿâ &Ú”B ®=wê\è贈߻s •¨/)ú¨âý ø€üŒoýXÂÖ½õñ:h^þ!ûð _¥ý%¯*¼ÿ°ƒýÌ:þÝýlç 2ÿ@]_õÿŸXÿ4}ÿ×þ¦NC!ÿ›G¨rÿ\9B4Kÿîâþ½‘ÿ2ýÿ3òþ+uX´!1®Èþ“ý±iþÒþöþW”ýAý_ªý³ÝýÌþþÐÐ µoÿiãýŒÒþ—ÿí$Òï;“ZÕðWºð#bà3t œ®´ü[üþ­Þ÷±Yü>™üºƒ7‘ø÷%\ ÌýåVýO{ )ý”¢Ýþÿ_þm¦ýs>þÿÄBÿ:†+Öÿ§{ÿ•ÿ×ÿäm‘:Ö!ÿñ@0Þÿº×ÿÿÓÿôoÿ±rÿ…šÿ‘½ÿ@ÿ‹­ò›ÙDÆÿªýJþZÿ? þÙýAcýìËýÚëýƒþ,–×Ò^ÿ²Éýy‚þ_”ÿ—Ïk5–Óé|õ ìóîÔáÙI¢§8—þJÉþúõøÿ üºÊû:ø c÷ Bw/ÿ¢Qý‹Wýž¾ŒrÍ•ýØ$þ½þ‹3Ñ+ÿÙŽÈÿ1±ÿ •ÿÿ㜗TæþÉ$}$°šÿÎÿV¦ÿÛÿÚ©ÿ5±ÿ+¹ÿÙti«Íÿ9ÿrÅþò2þúÕþþLïýöçýÆ'þ®ûý°˜þÁùÿ¶‚~ÿÖSþm\þZ)ÿUÔGS*u¤óYþÍ*÷‚Ö¸`oWöÒý¡Qþˆ¨³ÿ~ªWKtùP.0ÿî.üâã!þV9Í.kþ©¥þf8Iÿ%îþ\zt¯üý1°ìþJøþú~Ëp=i¸¢4ÑÿO¢þ5´ÿ^•ÿš8ëÑÿhOÿOæÿxâÿ»ˆ&’s81Þÿ oÿ{îþáBþ‚þëcþXkþªþÈEþ°zþ~TÿÈÿmmÿHD‰8%¹,8øÇ?ü0ã÷t¤ Žj<œùaýñ@þëOm…þÊüU×ÿÀåúkU‡þ‡íýXíúÎþïyí¢ÿÓþlHþâh9”µ+ÿAÏï)–þ´Ûÿ3ÿü5ÿ"°^çBíâÿè'5ÿùÕþÏrÿ5™ÿŒ=‘æÿˆÿß“ÿIÕÿ\j+Qò)”ÿdÿ…ÿø/þ•„þïÿ±þJ_þrþšÔþÙfÿ±aÿÎ ÿyQJþ ÂÁÖûÛ]û0ù6ÝBàhÇûÇšý¡vþRˆººþBµNÿ]Vüv0ìþ<ÃþT’‹ƒÿGr³ÿ;ÿfþÚpÎkªÿ Æ®íòþ±Âÿ:9ÿ78ÿ0ûÿ^qãÐÿ~YB…ÿ#öþcaÿGóÿ`äÿ<]ÿü°ÿ€íÿ)h¥óøÿx ÿP1ÿ‡*ÿÆçþ‘ÕþõöþR©þüxþŽâþ%ÿi:ÿýÿ–ëþp6DÒ)‚èû¿Éû¦öùE\Žršýxþpdþ-1é8ÿGÅ#ºÿ0ý¸ç/ÿ¨ ÿv¥4îÿ“yÎÿqÿ²×þŒ9]Ëéÿý‘‚/Ù.ÿ„µÿý^ÿˆ_ÿUìÿ8Ä ãá}»™ÿ=wÿŠÞÿ6ÿŸÌÿN•ÿuzÿ‰áÿà¦.,×ÿ±ÛÿÓÆÿÁ“ÿÿWòþ)Åþ¸ÿðøþͱþ‚ÿ½;ÿ´"ÿÒÿýòþ™àºYt áüiHüô¸ú¯ ÷ þzŠþŒpþXûÿ¿ÿ "=Q”Ïýù› --ÿ¼À<¼æ÷ÿ€¨ÿ +ÿÆÿ÷Yü@ Yÿܶÿ™…ÿ­_ÿE‘ÿu G²õ›²t–cÿnûþgÿmÂÿ´~ÿéÉÿÈÐÿJ¹ÿøõÿX­ÿ`2´Sj`ÿÂñþEÿMÿÌÿ‰áþ”Áþ‹ÿHSÿõþþÅÿ]ÿ^ø f>ÜD ÓˆüÏWüéÇûk.LPhÿ ×þü°þ›Œÿž3áY$vþ•™«ÿб‹_ªÖãÿìÊÿpIÿx…ÿaºëÔ÷ÿ%£ÿ6µÿ!nÿògÿ$ÿVæÿÆG8øÔhÿ»ÿ:ÿ:BÿXºÿg´ÿb ÿ‹iÿð’ÿ’0«FÓ9f®ÿ%ÿxBÿ$–ÿyOÿPÉþÖÐþt ÿø6ÿÈ-ÿ1üþÿúÿÅæ ôµŒ é›üÖ™ü1Ÿü©‹Á¸Ëÿøÿ:þþKi‰˜ÿÎ(šµ€¾þ ”$]f»ÿñ«ÜcìðÒÔÿö³ÿOwÿºLÿàVE$xÚÿ-ÈÿÒºÿsŽÿTÿ{Kÿtbÿ'h« Uyÿ9Eÿ›`ÿb[ÿÏŽÿ;™ÿ¤gÿŽ{ÿ©9öcáÔÿsÅÿ´ÿQ‘ÿ˜ƒÿ.lÿŽ4ÿ_Ýþ—ÿþÜ;ÿb.ÿéÿpëþKûþ ÿ­ ­Ùž nü1Àüþsý%ÝfÎ_fëJÿZ5ÿ‘ ;1M+fŸVÿE̼;aÿ܇U%âFAGÿn ÿ^Gÿ‚‘B9ŸÛÿù¶ÿ½ 8hÿ“žÿþ+@-9&þ[‡ÿLŽÿŽlÿ ÿ_ÿuÿ¡Úÿ «Üÿ²Öÿ£ÿ•Ãÿ§DÐÿ[JÿÆNÿM|ÿîÿß÷þ-ÿnÿÛþeèþ”õþúþ; @ÚJW *šü©¸üûHþÎ…‹ ¶zpÿ¨Qÿ°ÓÚØá¹ÿdxÉÿ£USçi²ÿ¾bÎ ¦ÐôC-ÿ°pÿ{ÿW0õâÿé0Ó;›”ÿ}öÿŠ5?G¨ÿM—ÿHùÿT¤ÿó;æ`ÿFWÿ¹ÿ0^ÿ‚óÿÀ=_£ÿ£cÿ+ÍÿœÿñÎÿ‚™Øÿa‡ÿ©`ÿ¢ÿ÷OÿÃÞþ7ëþþÞþáþ4æþXÚþÑßþ ¢*yº —üÃHýhÕþJà>ïzûBÿ‡sÿ³ipTEŽF§ž®ÿÔÿ×ÿ†»ùÿ,‘zùÿQ¶ÿ±7ÿc`ÿ95ˆìÿ<>oÓÿ.Ýÿ+Ñû<Qÿ(¢ÿŸÿ#Qÿ»ÖÿÔ,€²ÿe…ÿ×KÿŒ_ÿzâÿÓ²ÿnÿŽ•ÿܨÿ=‡ÿŒÿëÿ³ÔÎÿÿ¥ÿõŸÿ¨)ÿä²þ@Äþ¼òþ Ýþ‹»þ^¶þ‡Üþg tŽ Ü†ü¢mýTÿgÜû0ÇOíøþÄ2ÿbkJd< ßeÿ#À‚6Ú^ÿ=#0Qêpf£ÿÞÿ€ÿl ÿ…¿ÿ§24(S|ÿâ¦d…wÿ¦¸ÿå°ÿÍ%ÿŒÿŸÿm¦ÿr©ÿ9yÿÿÉùÿàÿUcÿUÿÒ°ÿª‘ÿn‡ÿª:ÿø\ÿèÿ×Zëòÿ•ÿ•ýþäšþrÇþGðþþÒþáŠþ¿„þ2Úþßn âZžÃ ‹DüwTý!íÿ‹óJk"#ÌþæÚþö1zî Wéÿ>ÁûkÿœK»àÿŠ}S  ÿS…ÿò<ÿXãÿoëÿ’¼ÿP$B1«•ÿÕI†Sÿ6Jÿâÿm;ÿ#zÿ¶§ÿzyÿžÒÿ]3ˆÛÿû@ÿ)‘ÿÙÿ±ÿtRÿ­ÿŒÿöÍÿ‡úÿ=Pÿ,§ÿ; ÿòþÎþí¿þƒÖþ¾œþsþ×¼þ‹N ûo4`ü(UüäkßÎþóI8AþjþŒ£‘tNÿ޼¹ˆÛšT"ÿ~øÿ,31Èÿ9Çÿ«ÿRÚþXþÿ4øÿ\kÿ(‘”ìz×ÿ«ÿè(îMÿ*”ÿ›ÒÿVþþþÿΉÿðcÿ7íÿáÿ>£ÿLeÿnºÿkÎÿ†ÿ?:ÿðþþ•£ÿàÕÿl¸ÿܹ  ™ÿ1ÿ• ÿºÿù´þ,“þßþ6Žþ6Âþ²£ &Ó?I |0û®&üx¹ï ²Ì±(‘èý¡þéÍ"*ÿ5“åÿHÌEXÿM»øÿ sÃÿ‘‰ÿyKÿ%ÿEOe³ÿ¾zÿÆøß¥Ð?ÿ…ÅÿÖ)H0ÿ.ËÿêÀÿKÿVÀÿs¡ÿhtÿíÓÿÌÔÿaÿŒÿ¶ÿÖÿ ÿÏp™ÔÿÏ÷þ:PÿÜÿŒWÿ}ÿ ÿ ÿ`ÆLGWÿkÑþ”DÿK1ÿ6öþÿñþ+½þxÊþãþdÿÇŠÿµx uJÑÅö^ùëÒеZ¸Ëìþ‚Öû•ký †ÜUíUý Jÿ`ÿjÎDÿÔ-þjêÿ©xÙÿÎ:ÿGrÿÿ‹ÿS:ÿ7K±9èwÿ6kÿ8Õ¯åÊþËþ5rÿË]ÿÿï#ÿÄŽÿúPÿŸ¥ÿUCPõ1 Jÿ9"ÿ؃ÿ žÿENÿ ÿûŸÿÅ}zJ€'ÿîþµ&ÿ¦ÿ‹âþ¯ÅþY­þ“«þòÿc€ÿNÅÿÞ® i_!³°‹£õ8øUFnI­ËÊ7þ{8û±!ý5 Mã<Çüãþe:ÿ|mD<ÿåªýÉÂÿžƒrÿ4Üþacÿ©bÿé”ÿ\· ÷ÿû§ÿPÿtÕÿ\ý¶ÿügþ6ÿ­¾ÿˆ ÿ–þþØÿ&ÍþÞ:ÿ¶ûÿVP÷ƒó¥ÿÿO#ÿT‘ÿ¾ŠÿkTÿËÊÿ‹=ð}*ÿúßþ7!ÿ¶óþVµþ‡þØŒþݧþh2ÿ{¥ÿ«»ÿì8¢–#OQŒcôº4÷«¼<òí_dýz§ú}äüÍ‘ðT9üÑþ¶ÿÚOÉOÿ Tý}ÿ-³Rÿúkþ)Xÿ oÿ½ŽÿËX@ôÿ<„ÿ&ÿW/ÿŠš¨ˆ ÿ;þH¸ÿ‘›ÿº¶þH½ÿý™þ\¡þ ìÿ—+ó•™îÿÍJÿÂÚþšRÿ¥ÏÿЛÿ¬.+±´ÿ>[ÿëêþ%þþ@Ìþ¸«þáiþuuþ—Äþ:ÿØÿ‡¾ÿ#c¹u&á9¶¼òЉ÷@)3·“ÓSü®6ú¯ýühS™ˆaÖû7‰þ ÿª^­ûþ+0ý•¨ÿÏëOÿ þZZÿxÿb™ÿPáX÷ÿÍfÿp¢ÿ/êþìð®ç þ®<ÿ›œ½þÓÇÿi¥þIþƒÆÿÁ–‡ôÕÿ1ŽÿîõþÇþ1òÿ®Hi¶µÿÂ=ÿ=‡ÿi*ÿLÖþ¹þYþª‘þžŒþeßþnjÿfÞÿÙ¤ÿƯù(UäŒñµx÷ª‡" ”‡ð`ûÝâùYàü³ë½=Yû.^þÖûþ4NóÈþ ÷ü7ˆÿäóÿ´TÿnõýñWÿT™ÿº|ÿÀÚ,Ð@ÿé„ÿP¼þÿXb·ëئþ§Œþb# ÿãŠÿeÚþu#þÿzÿ$1h»žÿ%‹ÿ§Hÿ¯lþš²ÿ¤ØÜ†¡ˆÿ‚æþ¡CÿéYÿäèþ´Vþ¦Iþ«þç¯þfÿÏ¡ÿ@—ÿÑpÿM¢u+–~ï—ð1ú÷ÉÅdO /Wæ úÓ°ùÇü1ú¨Xþú<þbåþ I:ˆþýÙüXÿ)¿ÿ_ÿNùý’_ÿ¶ÿ­ ÿþöU:¼WÿßJÿ§þ?ÿ ¹ ¹œÿV¡þö§ÿ!Fÿu_ÿüèþcþ‡<ÿXüÿ I~ÿ“YÿTWÿI¬þŸ…ÿÑ×ˑەÿ‡µþ,ÉþKGÿB ÿè`þ÷þŸþ úþÈsÿG»ÿSCÿÜAÿÄ´9-wœÚïÕx÷=cñ:ïw>Iú5où•mü7H Ès~úVüý¨òþaÍÿ“ÿþípüˆÿ—ÿvÿbTþÜ ÿVÓ®ÿ ª\‡eÿÅLÿ‹ þÒÿA×ÿ¾ŽtE…ÿ@ÿ*ÿl\ÿÝÒþh»þEÿ_¶ÿ~ûÿã¬ÿhÿJ$ÿH6ÿ\–ÿI?ůˆÜÿˆ‘þ/CþŒÿ 9ÿЇþKþcoþ¸FÿÊÀÿ¼¦ÿ+ÿGÿÕ%%}0`¦ÿíŸ0ú³Þµâ…Žªù¿fùmLü=þ“lú4oþVþþ Mmþ}rü6ÿ2DÿµÂÿyHþ2ÿAèÿÏ`còÿÁÿ<Ûþ°ðýCÿ„•ÿŸ[²›¢»ÿ­†þ;ÿBŸþBâþ›}ÿÔ¨ÿüÿ˜nÿ'kÿÊÿ8Ðÿ¾ÿ€­ÿ„²ÑÝÿ3Hþ±þÉþŸ)ÿkšþ]þfiþYxÿñÿ zÿgÿPûþ¾9}3A¤‰vëNû¯•ïÄ¿†0ù°ù¯ ü)òw#:ú‹ÅþQþj§ÿdæþlºûübÿÿÂïÿ¦þ;Îþ¢ƒ]ÿ®[Âøÿ‹ÿÿ§µý—KÿÍôþ̶é®GµÿÄØý¥ÿÊÇþ_°þœÞÿLJÿЇÿö$ÿ?ÿúCÿ[.* GÿêméñÿY6þ>úýÿŠþ‘ýþ"³þ./þ_þíoÿôþÿ®oÿAÿùãþסöp6zÙ ÚèèyŸþÔN"+ìƒ7øÐ-ùÀÃûa­xúgLúY ÿe/ý‚ðÿÖxþ ‰ûOªþ… °þèïþ'Î)ÿâÍ TÿαÿâVÿ[–ý Mÿ¯zþL*'À Nîz¨ºý‘]ÿtÐþ äþ¢ºkÿÈ|ÿíüþûþ4eÿ’Œ]”5Aÿlöÿ‚ÉÿÛHþÙ þŽpþ_»þ€¹þ•JþNYþ&eÿ¥öÿÑ\ÿ“þþyÐþÙ«F9ÕJKæÃ_¹Ç )\¯ÝöMùуû6;ù”"úé¯ò]ü Õÿ—þ/xûnÜÂÞý¹Qgûþ×þ£+…gÿ˜¥XÿÞ:Juÿ,ý”Œÿ’Žþe¹ÿóPàu©8bËýcÿµÙþ„(ÿˆ1¤bÿŠÿšËþjÃþÖuÿ«¿@CSÿõxÿ®©ÿúƒþÄ<þkHþSlþ^Êþ»Sþ9:þrLÿóáÿkOÿåßþAÜþ×O¬;åán,ä!†ùÄu\"rõ¨¶ùÇÄûö¢’®ÝcúôЪ.û1•ÿþ{cû—:žKýžP„þFÀý[‰xÿ2´UÿóFDÿnäü‚)Ûþ˜"ÿŠy˜¤y§.þSÿj›þ8Yÿ&ƒPjÿ'‹ÿBŸþÇþºˆÿp©ôrÛžÿ~ÿ™ÿØþþ¶lþÖ þKJþ:¿þŠ;þ /þd%ÿ9Ìÿ¸LÿéÓþ ÿ = n=x´ÏDâ2+%ôÿ¿¢ÑSÑóhsú[lüÖh7î˺úÍ}U>úHÖêªÿm‰ú­êÂpüD÷ÿÙ+ÿsý p×Ãÿ¾§ˆÿq-´ÿÌü%[ÓýîþJˆÃU·¥P­þÈ=ÿÞNþÉIÿEàhÿSbÿÌÎþ Óþ[cÿˆ]"ÓÙ$FÉþ!Åÿ¡mÿØhþ[þgþƒþº,þâþÍðþZ”ÿ‰ÿ`ßþmõþÞ#%E=aòùžuåT ¤üÅŸ¤šÅóIÓûÏ¢üÆÁ¸»ÿËÄüž]BtùDô£åý yû_ I±û /IÞý_µýª²G‚©ŒF‡þP.5Øþ.Úü†!usýW(ÿKYàŽ>\ú ÿíÿÆý £ÿš½Qÿ ’ÿ&Úþ÷ÔþZKÿ¡?éçà‡& ÿ±ïÿE„ÿTeþ9GþÊñýƒ,þË"þýý°£þÎzÿJÖÿBñþâþÛ¯#uÚ.ƒ7ÿGÿñKÿÙ‹ÿÒŠÿåBÿ¥ÿjhÿ¡÷ÿ:Ùÿ°ôÿbý­ÿ¹Šÿt¿ÿêÿu$ÿ° ÿëþºÅþp®þû»þÛòþÿMÈBÌ1’ù£ýØNþÊN¢á¬þìýœŽþC ú:˜åìùÈÕþe#x!ÖÅÿ1y,2Á±¥ÿŒÿõæþ)/ÿ'>GÿçÓÿJ1ÈÿÿeQÿTªÿÅdÿ~çÿµÉ¡ÑÿÿáÌÿLÿ"ÿ…¢ÿ¡fÿZ)ÿ OÿÿåóÿuG°ÿ`xÿœõÿB/|Âÿ…Rÿ¬*ÿËWÿŸ+ÿ£þcþDÍþÀçþ˜ÛþWî Ófn‚ °ù”Tüµ<ÿWw¨E­þÚíýI†þ•÷–=D6ÕœÁ+ÿ’©Eýôþ*›ìSÀzÓÿ[ÿEÿOƒþ,Êÿ”Îÿ5˜ÿ­ÿSêÿ¨ÿ Tÿmºÿ*ÊN¾Ntÿ§±ÿ¶ºÿänÿ§ªÿÁ*ÿ¦Dÿì›ÿA5ÿÑcÿäðÿŸ,ÿ2ÿ†äÿF²âªÿr³ÿI…ÿ0ÿbÿþ#Åþù¢þÐþCšþ5¦þ× Ê÷ƶ FÔùüÐØÿÛã^©<áþûý"þ°FÍ/Q kÆÕuÿØ“öýó\ WcáˆXþš:ýoøý8jü/ÎÌÿèGYÏÿ7H´ÊdÿøÿÖ2+ÈÏþ;EÿÏxÿ ÿWÖÿ~ÿÁÿ0¸ÿË2ÿ£H¹úëÿf ÿ9âÿ1çÿÄÿëðÿ‘ÿËÿx^ÿɆÿ;öÿäúÿ~¬ÿæ1ÿ×Bÿ!Œÿáÿ”mÿlÿ¬Šÿ·5ŽJwëÿ•”ÿ³|ÿÃ%ÿµÖþ£™þ6VþýSþØ|þ“ù ‰ºûýyøºûUL $¡ÌêJþâêü’ý¢·É1ÒþZÉXæÿÜÃw5åÿàaÓ‡5ôÿîüþ€ÀþöEÿÜöþÑ)û²ÿ'Tÿk¬]ÿ–)Õ¬¶¥ÿ`5ÿÄÿÂûÿ™Íàÿ´/ÿÎ3ÿ‡ÿ4Žÿ`óÿ¹ýÿƒÿ§éþ \ÿªðÿ*uÿó"ÿ\2ÿuÖÿ;5ÿÿIªÿ£hÿº¦ÿlÿïþMþ6rþדþ¢€þey s- ì<÷’'ûI47rú«œõýùiü¯sýßKþ+EÇÿêxáêÐþRXˆ· ­öþð¦þdÿcÆþ~N§ÿ·—ÿ§ ÿt-…Ëòÿu©þVÿJìÓÿ‚œÿEZÿä]ÿ­­ÿU§ÿÐ3nøÿCÿ¦Ûþ)ÿ¥Öÿ™ÿ®?ÿõ,ÿ¥³ÿÑh{þÿ6UÿdWÿCmÿüÿÑïþDÈþ[”þZ¥þëËþl<9 R o&õÙaüýçùs‘Åýá&ü‚ýˆøT4Üý¦ÒLÿ£Ö4£=þÍh”œÛÿ»¿þþÿÿZêþ%SvÿÉnÿî7ƒÿiÜÿpOìþÉëþØ>ý{ÿ?OÿØfÿFÿ¦²ÿ(5<·ùÿ™}ÿÍÄþèþ‰ºÿà£ÿ/ÿm1ÿüXMé ÿÒXÿí_ÿåÿL©þ`¦þy«þ±Çþaâþs(ÿ ©¸è"ÿY ¤UóˆüÌ*¶!üàû)´ý$©rw”ùü®æÿ7KÿßïkÝÑýDa¡Àÿ~þµÿ­qÿ¶ûþϯÿìêþ2ýþÞŽÿÌ0Éke<ÿ£þVN%‹ÿæ“þ’<ÿ‹Tÿ.‚ÿ!&ÿ>âúÿeÑÿÇÿê×þÒ†ÿM®ÿs%ÿTJÿ²ñ›¹ÿdÿ€ÿøªþ¥}þÇxþõ–þYËþ„ÿHŸÿÔ³Ñ%ŠWb—ñŽ®üÞ…ªûMÚî2û¬û(þËG.ç×$ü\¼ÿ­kÿàåˆ|ýûü›ÿÔ”þ˜íþcÚÿí>ÿôÞ™nÿ…áþåðÿÔ­þ2-ÿžåÿ˜5± ÿCÒÿÍÿ%Æþ%©þ@ÿzÿÁJéÿÍîÿlsÿl8ÿƒcÿŠ‹ÿúxÿ!ÿwŠ¥ÿ®éÿéÌÿòÊþ\[þw6þOþþžþàþÛOÿ!±ÿ Àø)Ϙ qÄïŠýG~ƒÈt¿@úD‚û 5þnå–Ì^uûuºÿ&Aÿˆì‚Èÿèý Ŧÿýÿ”wþÒÝþÞ°Mÿ÷ß$ÿ|ÿ_®ÿËPþ”ßþTÿ‹isÇ‚ §›ÿ Xÿ§ÿ£Kþ«=ÿ™ÿÆ{ÿo5Z¿ÿ.Èÿíÿú‘ÿ[ÿ«ÿ>ýÿ3óÿšÿuÿ–ãÿÀ¶ÿÜ}þ_ þ&þŸ…þ¥þ‚ÿ•ÿã´ÿýîuˆ,-ß gîYNÿÅŸY 'øžùµ’ûÚOþ¡†:<û7ôÿìþ1,`PÿèßüÊìÿ=cÿWÌÿ®Nþ~#ÿÄ.YEÿ»á-bÿQÿ~fÿˆþ¶Ñþ`¸þ­HrÎïbêÿmèþuFÿŽ)þqJÿ|çÿwCÿà2{ÿFœÿ]Àÿ§ÇÿêyÿÈüþþ2(*DsÿK‚ÿÔ—ÿP^ÿGkþÀùýjþ¾aþáþ«Jÿa©ÿ­æÿ\îÿ³/¸¬ —솑U;Ö¿®íøÓ|û‹þ_¸5aèúU*.€þuø!ÿéoü;Úÿ&ÿ£ÜÿÇBþ˜4ÿ¢aÿtâ÷ÿJ±ÿjfÿXÉý¾þþ[.$f›N¯þ‘ ÿw1þ"ÿÆ@E0ÿü¼ÿÛ3ÿd+ÿ—÷ÿÒÃÿ8‘ÿæ[ÿ Ûÿ¢ôÿ,˜ÿµ’ÿÚ9ÿâÌþXþZ þvþ_8þRçþš’ÿ¨´ÿ Øÿ *Ô2Za Æ êœúq* á¨v/øærû7Üý³²ÍÑæYú/ÕÕ&þ…ŸiÿÔü/&—þÖúÿ|²þ¢„þ;´ÿñ¸rõþyüÿׯÿó4ýúÝþ! þÕÿò 0„[‚èþ®¶þéQþ¶ÿXw™[ÿ[‡ÿxÿjÉþJðÿz°ÿ¶»ÿ«ªÿ™—ÿˆÃÿÿ¹ÿ›ÿNTþ9Aþˆ0þ‡üý·5þÜÎþ³ÿjÂÿf²ÿúâë’5™¡¹eèø³!Ö ÷ÿc[Úz÷´³û‰ÌýY–ZR°ú±ûÁ*ýWÉ:žÿ$üt( øý™5&þÍ2þå¯Ö¨þôÁBÿ#? xÿñü€oÿÐßý;ÕþnÝXq’ƒ:ÿÎþhVþhBÿpŠA”ÿŒ{ÿâõþ^¦þˆÊÿ»QÁÿùÿ´Öÿu3ÿLõÿT ÿï"þþp9þYÝý¶)þùÆþ»ÿ‚Íÿ;¶ÿÏr7 ªÿ“wèNˆüÿÆÑÿVaþ¸ö4Fü]þdº7ÿ|¶ûËY üû"j4ÿ$ýûñPì=ýÒiÿöýBþx;?RÿqqÖþŠŒ/WÿýëLÿqýÍÕþ™Ë~œŸyÿ0ÿØQþŸAÿ „Çÿ@tÿ²Êþߺþâ¢ÿIEØÛÿ’ÏAÿ&MRÿÿôýþkþª»ý•#þÑþ‰ÿ2èÿ\µÿÑ~»‡7~/zmæWó¶²ÿFþëØ€‡õ_µü…•þ×™¨Ò=ûÛCÏü*¨÷WY!ûlS-xüµ|›QþYýIbnÿ„ÿÓ“QÖÿîSükÿX©ýãnþ!~^<›í Øÿïœÿ]fþgÿZ˜cªÿjÂÿ[Ðþw¤þŒÿ’%ë:ÃÔÿ´6„éÿ‰Iþ?ÿn8þžæý½ýèý½&þÒYþí%ÿüÿ_Éÿ¼8(Þ5d“ÿ‡ãè¸â{ý½ãÿë›óõ)Rýxÿü«µØÿ%ý‚Kûö÷oªÿ‘™úd®q`ü^8û¾þ(ðýaóoôÿ”̲ÿÏœ gÿE üæsÿ"¾ý„~þTK9uîNùÿ—çÿ6þ2ÿßWf’ÿ…yÒþ(šþþ­ÿã+Û~x«ÿ–;×2JÃÿ1®ÿ%yþh ý¼ý-Óý”þs*þ)ßþÒÖÿì"’/<•2@wÿ#þëZô©=ü{?Ù…eõô»ý2%ÿÄ–µþ¶Ù})ûõîÀîþ…gûjƒÈüî·h¿þò-þZ€G…LÍŠþ¶·Áiÿ³ôü=Šÿ»ÒýÛ¯þñnkWσ½ESI)˜þ7Jÿr:ò9ÿ¥%¼ÿñþûÝÿ 7€Nóÿ­àÿ”á‰j8Šÿòžÿ‘Åþo·ý€áýøôýçÿý (þÙ¶þà™ÿ³eP¥N .‰ÝÿQøïé!´¢ûE™½EöApýÍ0ÿÕ®uYÿýr%¾úâ'™¬þ(þû¡³2±ýBz¢áþ¶oþTýÿ ¤42€˜þ2´oÿ‹ýÏgÿ®Uþâõþù•K7c¢RüÚþgOÿXÞÿ½"ÿ&+?ÿX²þ§ÎÿýÙ[uLúÿ4\¸N³Ðÿ@ÿ»ÿþgCþq®ý©þ <þNMþH¸þð@ÿœ4¥†:6'¡éÎBðþ1{À±ó5Lù7 ýIÂÿädZdæý tþai±{üà€óýÇÏÿKºÿ¥~þŒq ZL­hÿIäÿ˜ 'ýb(ÿ'·þï¯þƵÿÖx{G>BÛÿfŽÿlþÿdèþûUÿ°@ÿLÂþ£«ÿ˜$A‹û3ÿŒùþê‹ÿÓƒ’[Úÿ`ÿò6ÿtÙþ”þHxþpUþ1Oþиþ·§1¾$­Íñ†óöɪP÷ê9UŸúÄü›–þÛÄ2ìÿ3IãFþaP¶gÿ[þ±óÿ(ÿ÷/[ýþ@ÿðNÿõm%$ëÖþÏ[‹ÉÿEWþŠEÿ‹Öþ¡ÿ:±ÿ$Æ®óH¼C1rÿ£Âÿ!áÿ{Nÿ_†ÿ3þþø°þ Wÿ4ß;µŠÿÝMÿ‹Úÿý+=Oëÿ”aÿjdÿÿ›ªþášþ½ˆþaSþ8‚þ3b(G#ì{Smó‚ÿiQçvŸûð¯üçÕýcòôUƒ"/’œþ|! lþ] ŸÿÊS:êþTÿj ÿgŸÿá+šÿÒÿ‡ ˜Ùþ0=ÿè ÿ‘oÿ~ÿÏ®BA^Ül!ÿlÿO¾ÿXOÿ¿ÂÿÑ1ÿÌþø6ÿK¨ÿ#èÿühÿjWÿ~õÿÔvHJmêÿ{ÿuPÿúFÿrÔþ‘¨þ®þ9þÌBþo‘{"L¥¼óQ(þŠƒ3×høûYÅü”•ý²6¾®G¢ÿ½Ô^Öþ èáÎÜUþ‘VQÀÿDRÚþÝ4ÿÉ4ÿ,Îþ5åJÿ‚ÿüÿŠÿ)gÿ®&ÿüqÿæÅÿ¼»oJ ÊÿÕ“ÿŸ¶ÿ1<ÿ[?ÿ–ŒÿP•ÿh#ÿk:ÿò\ÿ`•ÿ óÿ¶qÿ±ÿ'¯ÿoà}_j,Éÿ®Uÿ13ÿ3âþ ¿þNšþ¯+þDþMi"Ñ` •òýý7Î~Û‰œÛ±ûLzü|Æý·û<æÍþ*—iKÿ=pi-þÉeøûÿÊèÿþÃþ%^ÿáêþŽ%—ÿ²ÿý÷ÿÊÿ€ÿñþXtÿKüŸ”Øhÿäÿe×ÿaÖþfÿ'ÏÿR‰ÿÚ(ÿ7ÿI’ÿÖÔÿÁtÿÿ#ƒÿCÑÿ¤ÿNâÿüŒtXbÿìKÿÿw ÿ@ÿ=cþ2þºXþ ‘zX#™«°ò|Q)¡ùd ç ûK…ü)þ?Ày ëŸþHÓó%ÿÆùÍ¢þ‡ìÿk‹ÿ Wþ6±þ?áþTVÿkÞñgÿ­©ÿÕßÿØ4ÿŽ<ÿʹþá¨ÿMÔùÿÿ,Dÿª-aÿÝþÏøÿäÿ‹bÿ&ÿÖ4ÿŒ…ÿ2Ñÿ“ÿ":ÿõ”ÿ)Žÿ8ÿ5îÿ‰´Ò(™ÿ¸Rÿñ(ÿñÿ©þNiþ´ZþÊeþõ¼¹$5s )Lñ§Ï®'óžgL²wúK¢üFõþŸ·1˦ý¨\ÿ5›8ußýžÄ—èÿ°mÿ€þÓ›þÞMÿžÿðÀ[¢ÿÂÿ÷Ýÿ±°þlBÿX˜þFyÿÙd—Sò& ÿ¸áÿ3‘ÿ$Ýþîûÿ—µÿ/.ÿ±Jÿ®|ÿBÇÿ™öÿ+¶ÿfIÿpÿÿúþâ'ƒ¦óñÿ@¾ÿ~oÿÙÛþÝÏþgmþVZþ –þ;¹þ{v<:'B ÉãñË”¡$ýàµÿ”™ú»–ý}úõüþý&¥Lóþ þqÒþ:þýçÿõ6ÿƒÝÿ'þ6ÿU ÿú@ÿq‰·vÿ!M·Aÿ®Dþ.AÿVþ²Tÿ¡Y}¯J6 ÿ­ÿ‘"ÿµþÛ²ÿf•ÿü;ÿ“ÿ5Âÿ³óÿüCi«ÿŠÿXÒþ Úþæ—ÿÓnfT}Ñÿ±ßÿü%ÿ|þûtþ½6þQþ>¨þðþÑ*z*‰«Äïd×—”öÿƒ†¦›ú}3þÝŠ†O¢þ#]ü+V­ýý9eÕ ÿÎüÏ:ÿº ÿ- Ë#þÉOÿÊìúþÖ¯¬ÿ †ÿEþ¥Cÿ=ÇýýßþY¦ŽÕF±ØPÿòùþȱþ3‰þ„ˆÿ WŽÿ=uÿ[­ÿ£Êÿ«1Êÿ-ÿafþö¾þ®(ùxc)ªÛÿ†Ÿÿšéþ$þ;þ¡Aþ]Œþ‡²þÂÿ:| Q,nØÃOðt$`ùþðÿb1vþúíÓþ”•z(iýâ€ýét&ý#õòpþ¦düLÛþëìþšNî"þ«ÿV2Oeÿ\ßÇÿ«v¼þ…>þpÓþqxý\ÿW•Øcýÿ€ÿSÏþBƒþØÞþ€’ÿ‹9öÿÙ@ÿrÿ}¸ÿL&t)ÿ bþüÙþ†f_Å+/ȯÿfnÿ˸þdþÉ4þ¨Hþa€þųþÐBÿ>ã,e-Ýím:T/ßÒý–X¶úɧþ¹c ì»,ÿéXü tO3ýŒD­ÿ»ðûÌvþƒ?þG¤8þIöþDÁ;ÿìžþÿÌÿãŒýÉÿ¯ý¥™þ®hfÏ>\9ÿÁ þ‚ÿ³jÿg“ÿâ:ó[ÿ^1ÿ³Ñÿ-Ý.Ö~ÿÚ‚þ³þ`Wèˆ$|‘ÿqÿ½þbþNþ Jþhþ •þ ÿ0PòŸ*£ð8iïŽ}gÃÏþþ¼4ŽùC,þʰÕLÿ‡Oý:~ ;ýŽýæ¼ÿÙü#ÿ›³ýŠ;eäþõþ ½ÿ®äÿhìÿ‚œýÿõ"þî¦þààÿ ¹'9m?ÙûÿG=ÿòVÿ«TÿáÿXæÿRXÿ%ÿùíÿ•LÆ=­±ÿ0åþ0šþÌ]k(N’ÿ¯šÿÌÞþ38þ þfþ+9þ†„þæÚþ«Øm#âx oó€ëîØ¢°ÿ¤‡kòû«%þÃlÛrÿÿ+²ýj¸4ÿÞx€ãÿíý]ÄþU¼þžž†þHÿs®.[('¥ØZÑþÕkýÄdþS¶þñÄÿݵTãdÄépÿHÿGÿНÿåöÿs ˜Öÿùþ,ÿ}—ÿ^íÿyKQ”ÿ­4ÿóÿ<:ÿ^UEö~j­ˆÿÓDÿjàþ–nþWêþríþÇsþ>®þ)-010,(%""#&7:51.*(&&'(+.2FKRZ^begcfeb_\VMI:6;8" *)*+,47:>BU[_ekorvyyswxtqmjf`[W=95QROLIC*---.04>AEIMQekosw{‚†‰ŒƒŠŠ†‚~zwtplhd?<83gd`]ZXU00001235FIMPTX]aflpuz~ƒˆŒ|€©¬¬•’Œˆ…‚~zvrnLHD@WRjfb^[ +,.0258;PUZ^bJehW[_cglˆ‹z~¨‰°´•²³±ˆ„‚~{wtplgc_[WSOJFA<841.+ #%'*,/269<@CF_cgkp^bgkpuz…‹–¼º¼¿ˆ†}yuqmie`\XTPLGC>:51.*  "$(+.26PSW[]bfjotx}‚‡uz„ŠºÀŸÀĈ…€|xtokgb^ZVQMID@<73/+(= !$(+GJNRVZ^cglpuz~ƒˆ’~‚ˆŒº¸—–¾‹ˆ‚~zuqlhd_[WRNJFA=951.*@=  "%(,/LPSW[dhlpty~‚‡Œx|€…ˆ®´µ¸Š†‚}yuplhd_[WSOJGC?;842TQ (+.147;Y\`:\`dintzb‰lpst¡xjurojfa\XSNJE@<73/+(%"  $'+/2REJNSW\`dimruxzmnmkhsokfb]YTPKFB>;742/036:>B.5:>CHLPTWY[MLKHD@ID@:61,($  "&*.268;==20.730   5:>NQTTTROJ<72.+('&&(*.1(,010-)&#""%openal-soft-1.24.2/include/000077500000000000000000000000001474041540300154545ustar00rootroot00000000000000openal-soft-1.24.2/include/AL/000077500000000000000000000000001474041540300157505ustar00rootroot00000000000000openal-soft-1.24.2/include/AL/al.h000066400000000000000000000716261474041540300165310ustar00rootroot00000000000000#ifndef AL_AL_H #define AL_AL_H /* NOLINTBEGIN */ #ifdef __cplusplus extern "C" { #ifdef _MSVC_LANG #define AL_CPLUSPLUS _MSVC_LANG #else #define AL_CPLUSPLUS __cplusplus #endif #ifndef AL_DISABLE_NOEXCEPT #if AL_CPLUSPLUS >= 201103L #define AL_API_NOEXCEPT noexcept #else #define AL_API_NOEXCEPT #endif #if AL_CPLUSPLUS >= 201703L #define AL_API_NOEXCEPT17 noexcept #else #define AL_API_NOEXCEPT17 #endif #else /* AL_DISABLE_NOEXCEPT */ #define AL_API_NOEXCEPT #define AL_API_NOEXCEPT17 #endif #undef AL_CPLUSPLUS #else /* __cplusplus */ #define AL_API_NOEXCEPT #define AL_API_NOEXCEPT17 #endif #ifndef AL_API #if defined(AL_LIBTYPE_STATIC) #define AL_API #elif defined(_WIN32) #define AL_API __declspec(dllimport) #else #define AL_API extern #endif #endif #ifdef _WIN32 #define AL_APIENTRY __cdecl #else #define AL_APIENTRY #endif /* Deprecated macros. */ #define OPENAL #define ALAPI AL_API #define ALAPIENTRY AL_APIENTRY #define AL_INVALID (-1) #define AL_ILLEGAL_ENUM AL_INVALID_ENUM #define AL_ILLEGAL_COMMAND AL_INVALID_OPERATION /* Supported AL versions. */ #define AL_VERSION_1_0 #define AL_VERSION_1_1 /** 8-bit boolean */ typedef char ALboolean; /** character */ typedef char ALchar; /** signed 8-bit integer */ typedef signed char ALbyte; /** unsigned 8-bit integer */ typedef unsigned char ALubyte; /** signed 16-bit integer */ typedef short ALshort; /** unsigned 16-bit integer */ typedef unsigned short ALushort; /** signed 32-bit integer */ typedef int ALint; /** unsigned 32-bit integer */ typedef unsigned int ALuint; /** non-negative 32-bit integer size */ typedef int ALsizei; /** 32-bit enumeration value */ typedef int ALenum; /** 32-bit IEEE-754 floating-point */ typedef float ALfloat; /** 64-bit IEEE-754 floating-point */ typedef double ALdouble; /** void type (opaque pointers only) */ typedef void ALvoid; /* Enumeration values begin at column 50. Do not use tabs. */ /** No distance model or no buffer */ #define AL_NONE 0 /** Boolean False. */ #define AL_FALSE 0 /** Boolean True. */ #define AL_TRUE 1 /** * Relative source. * Type: ALboolean * Range: [AL_FALSE, AL_TRUE] * Default: AL_FALSE * * Specifies if the source uses relative coordinates. */ #define AL_SOURCE_RELATIVE 0x202 /** * Inner cone angle, in degrees. * Type: ALint, ALfloat * Range: [0 - 360] * Default: 360 * * The angle covered by the inner cone, the area within which the source will * not be attenuated by direction. */ #define AL_CONE_INNER_ANGLE 0x1001 /** * Outer cone angle, in degrees. * Range: [0 - 360] * Default: 360 * * The angle covered by the outer cone, the area outside of which the source * will be fully attenuated by direction. */ #define AL_CONE_OUTER_ANGLE 0x1002 /** * Source pitch. * Type: ALfloat * Range: [0.5 - 2.0] * Default: 1.0 * * A multiplier for the sample rate of the source's buffer. */ #define AL_PITCH 0x1003 /** * Source or listener position. * Type: ALfloat[3], ALint[3] * Default: {0, 0, 0} * * The source or listener location in three dimensional space. * * OpenAL uses a right handed coordinate system, like OpenGL, where with a * default view, X points right (thumb), Y points up (index finger), and Z * points towards the viewer/camera (middle finger). * * To change from or to a left handed coordinate system, negate the Z * component. */ #define AL_POSITION 0x1004 /** * Source direction. * Type: ALfloat[3], ALint[3] * Default: {0, 0, 0} * * Specifies the current direction in local space. A zero-length vector * specifies an omni-directional source (cone is ignored). * * To change from or to a left handed coordinate system, negate the Z * component. */ #define AL_DIRECTION 0x1005 /** * Source or listener velocity. * Type: ALfloat[3], ALint[3] * Default: {0, 0, 0} * * Specifies the current velocity, relative to the position. * * To change from or to a left handed coordinate system, negate the Z * component. */ #define AL_VELOCITY 0x1006 /** * Source looping. * Type: ALboolean * Range: [AL_FALSE, AL_TRUE] * Default: AL_FALSE * * Specifies whether source playback loops. */ #define AL_LOOPING 0x1007 /** * Source buffer. * Type: ALuint * Range: any valid Buffer ID * Default: AL_NONE * * Specifies the buffer to provide sound samples for a source. */ #define AL_BUFFER 0x1009 /** * Source or listener gain. * Type: ALfloat * Range: [0.0 - ] * * For sources, an initial linear gain value (before attenuation is applied). * For the listener, an output linear gain adjustment. * * A value of 1.0 means unattenuated. Each division by 2 equals an attenuation * of about -6dB. Each multiplication by 2 equals an amplification of about * +6dB. */ #define AL_GAIN 0x100A /** * Minimum source gain. * Type: ALfloat * Range: [0.0 - 1.0] * * The minimum gain allowed for a source, after distance and cone attenuation * are applied (if applicable). */ #define AL_MIN_GAIN 0x100D /** * Maximum source gain. * Type: ALfloat * Range: [0.0 - 1.0] * * The maximum gain allowed for a source, after distance and cone attenuation * are applied (if applicable). */ #define AL_MAX_GAIN 0x100E /** * Listener orientation. * Type: ALfloat[6] * Default: {0.0, 0.0, -1.0, 0.0, 1.0, 0.0} * * Effectively two three dimensional vectors. The first vector is the front (or * "at") and the second is the top (or "up"). Both vectors are relative to the * listener position. * * To change from or to a left handed coordinate system, negate the Z * component of both vectors. */ #define AL_ORIENTATION 0x100F /** * Source state (query only). * Type: ALenum * Range: [AL_INITIAL, AL_PLAYING, AL_PAUSED, AL_STOPPED] */ #define AL_SOURCE_STATE 0x1010 /* Source state values. */ #define AL_INITIAL 0x1011 #define AL_PLAYING 0x1012 #define AL_PAUSED 0x1013 #define AL_STOPPED 0x1014 /** * Source Buffer Queue size (query only). * Type: ALint * * The number of buffers queued using alSourceQueueBuffers, minus the buffers * removed with alSourceUnqueueBuffers. */ #define AL_BUFFERS_QUEUED 0x1015 /** * Source Buffer Queue processed count (query only). * Type: ALint * * The number of queued buffers that have been fully processed, and can be * removed with alSourceUnqueueBuffers. * * Looping sources will never fully process buffers because they will be set to * play again for when the source loops. */ #define AL_BUFFERS_PROCESSED 0x1016 /** * Source reference distance. * Type: ALfloat * Range: [0.0 - ] * Default: 1.0 * * The distance in units that no distance attenuation occurs. * * At 0.0, no distance attenuation occurs with non-linear attenuation models. */ #define AL_REFERENCE_DISTANCE 0x1020 /** * Source rolloff factor. * Type: ALfloat * Range: [0.0 - ] * Default: 1.0 * * Multiplier to exaggerate or diminish distance attenuation. * * At 0.0, no distance attenuation ever occurs. */ #define AL_ROLLOFF_FACTOR 0x1021 /** * Outer cone gain. * Type: ALfloat * Range: [0.0 - 1.0] * Default: 0.0 * * The gain attenuation applied when the listener is outside of the source's * outer cone angle. */ #define AL_CONE_OUTER_GAIN 0x1022 /** * Source maximum distance. * Type: ALfloat * Range: [0.0 - ] * Default: FLT_MAX * * The distance above which the source is not attenuated any further with a * clamped distance model, or where attenuation reaches 0.0 gain for linear * distance models with a default rolloff factor. */ #define AL_MAX_DISTANCE 0x1023 /** Source buffer offset, in seconds */ #define AL_SEC_OFFSET 0x1024 /** Source buffer offset, in sample frames */ #define AL_SAMPLE_OFFSET 0x1025 /** Source buffer offset, in bytes */ #define AL_BYTE_OFFSET 0x1026 /** * Source type (query only). * Type: ALenum * Range: [AL_STATIC, AL_STREAMING, AL_UNDETERMINED] * * A Source is Static if a Buffer has been attached using AL_BUFFER. * * A Source is Streaming if one or more Buffers have been attached using * alSourceQueueBuffers. * * A Source is Undetermined when it has the NULL buffer attached using * AL_BUFFER. */ #define AL_SOURCE_TYPE 0x1027 /* Source type values. */ #define AL_STATIC 0x1028 #define AL_STREAMING 0x1029 #define AL_UNDETERMINED 0x1030 /** Unsigned 8-bit mono buffer format. */ #define AL_FORMAT_MONO8 0x1100 /** Signed 16-bit mono buffer format. */ #define AL_FORMAT_MONO16 0x1101 /** Unsigned 8-bit stereo buffer format. */ #define AL_FORMAT_STEREO8 0x1102 /** Signed 16-bit stereo buffer format. */ #define AL_FORMAT_STEREO16 0x1103 /** Buffer frequency/sample rate (query only). */ #define AL_FREQUENCY 0x2001 /** Buffer bits per sample (query only). */ #define AL_BITS 0x2002 /** Buffer channel count (query only). */ #define AL_CHANNELS 0x2003 /** Buffer data size in bytes (query only). */ #define AL_SIZE 0x2004 /* Buffer state. Not for public use. */ #define AL_UNUSED 0x2010 #define AL_PENDING 0x2011 #define AL_PROCESSED 0x2012 /** No error. */ #define AL_NO_ERROR 0 /** Invalid name (ID) passed to an AL call. */ #define AL_INVALID_NAME 0xA001 /** Invalid enumeration passed to AL call. */ #define AL_INVALID_ENUM 0xA002 /** Invalid value passed to AL call. */ #define AL_INVALID_VALUE 0xA003 /** Illegal AL call. */ #define AL_INVALID_OPERATION 0xA004 /** Not enough memory to execute the AL call. */ #define AL_OUT_OF_MEMORY 0xA005 /** Context string: Vendor name. */ #define AL_VENDOR 0xB001 /** Context string: Version. */ #define AL_VERSION 0xB002 /** Context string: Renderer name. */ #define AL_RENDERER 0xB003 /** Context string: Space-separated extension list. */ #define AL_EXTENSIONS 0xB004 /** * Doppler scale. * Type: ALfloat * Range: [0.0 - ] * Default: 1.0 * * Scale for source and listener velocities. */ #define AL_DOPPLER_FACTOR 0xC000 /** * Doppler velocity (deprecated). * * A multiplier applied to the Speed of Sound. */ #define AL_DOPPLER_VELOCITY 0xC001 /** * Speed of Sound, in units per second. * Type: ALfloat * Range: [0.0001 - ] * Default: 343.3 * * The speed at which sound waves are assumed to travel, when calculating the * doppler effect from source and listener velocities. */ #define AL_SPEED_OF_SOUND 0xC003 /** * Distance attenuation model. * Type: ALenum * Range: [AL_NONE, AL_INVERSE_DISTANCE, AL_INVERSE_DISTANCE_CLAMPED, * AL_LINEAR_DISTANCE, AL_LINEAR_DISTANCE_CLAMPED, * AL_EXPONENT_DISTANCE, AL_EXPONENT_DISTANCE_CLAMPED] * Default: AL_INVERSE_DISTANCE_CLAMPED * * The model by which sources attenuate with distance. * * None - No distance attenuation. * Inverse - Doubling the distance halves the source gain. * Linear - Linear gain scaling between the reference and max distances. * Exponent - Exponential gain dropoff. * * Clamped variations work like the non-clamped counterparts, except the * distance calculated is clamped between the reference and max distances. */ #define AL_DISTANCE_MODEL 0xD000 /* Distance model values. */ #define AL_INVERSE_DISTANCE 0xD001 #define AL_INVERSE_DISTANCE_CLAMPED 0xD002 #define AL_LINEAR_DISTANCE 0xD003 #define AL_LINEAR_DISTANCE_CLAMPED 0xD004 #define AL_EXPONENT_DISTANCE 0xD005 #define AL_EXPONENT_DISTANCE_CLAMPED 0xD006 #ifndef AL_NO_PROTOTYPES /* Renderer State management. */ AL_API void AL_APIENTRY alEnable(ALenum capability) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alDisable(ALenum capability) AL_API_NOEXCEPT; AL_API ALboolean AL_APIENTRY alIsEnabled(ALenum capability) AL_API_NOEXCEPT; /* Context state setting. */ AL_API void AL_APIENTRY alDopplerFactor(ALfloat value) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alDopplerVelocity(ALfloat value) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alSpeedOfSound(ALfloat value) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alDistanceModel(ALenum distanceModel) AL_API_NOEXCEPT; /* Context state retrieval. */ AL_API const ALchar* AL_APIENTRY alGetString(ALenum param) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alGetBooleanv(ALenum param, ALboolean *values) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alGetIntegerv(ALenum param, ALint *values) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alGetFloatv(ALenum param, ALfloat *values) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alGetDoublev(ALenum param, ALdouble *values) AL_API_NOEXCEPT; AL_API ALboolean AL_APIENTRY alGetBoolean(ALenum param) AL_API_NOEXCEPT; AL_API ALint AL_APIENTRY alGetInteger(ALenum param) AL_API_NOEXCEPT; AL_API ALfloat AL_APIENTRY alGetFloat(ALenum param) AL_API_NOEXCEPT; AL_API ALdouble AL_APIENTRY alGetDouble(ALenum param) AL_API_NOEXCEPT; /** * Obtain the first error generated in the AL context since the last call to * this function. */ AL_API ALenum AL_APIENTRY alGetError(void) AL_API_NOEXCEPT; /** Query for the presence of an extension on the AL context. */ AL_API ALboolean AL_APIENTRY alIsExtensionPresent(const ALchar *extname) AL_API_NOEXCEPT; /** * Retrieve the address of a function. The returned function may be context- * specific. */ AL_API void* AL_APIENTRY alGetProcAddress(const ALchar *fname) AL_API_NOEXCEPT; /** * Retrieve the value of an enum. The returned value may be context-specific. */ AL_API ALenum AL_APIENTRY alGetEnumValue(const ALchar *ename) AL_API_NOEXCEPT; /* Set listener parameters. */ AL_API void AL_APIENTRY alListenerf(ALenum param, ALfloat value) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alListener3f(ALenum param, ALfloat value1, ALfloat value2, ALfloat value3) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alListenerfv(ALenum param, const ALfloat *values) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alListeneri(ALenum param, ALint value) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alListener3i(ALenum param, ALint value1, ALint value2, ALint value3) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alListeneriv(ALenum param, const ALint *values) AL_API_NOEXCEPT; /* Get listener parameters. */ AL_API void AL_APIENTRY alGetListenerf(ALenum param, ALfloat *value) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alGetListener3f(ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alGetListenerfv(ALenum param, ALfloat *values) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alGetListeneri(ALenum param, ALint *value) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alGetListener3i(ALenum param, ALint *value1, ALint *value2, ALint *value3) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alGetListeneriv(ALenum param, ALint *values) AL_API_NOEXCEPT; /** Create source objects. */ AL_API void AL_APIENTRY alGenSources(ALsizei n, ALuint *sources) AL_API_NOEXCEPT; /** Delete source objects. */ AL_API void AL_APIENTRY alDeleteSources(ALsizei n, const ALuint *sources) AL_API_NOEXCEPT; /** Verify an ID is for a valid source. */ AL_API ALboolean AL_APIENTRY alIsSource(ALuint source) AL_API_NOEXCEPT; /* Set source parameters. */ AL_API void AL_APIENTRY alSourcef(ALuint source, ALenum param, ALfloat value) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alSource3f(ALuint source, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alSourcefv(ALuint source, ALenum param, const ALfloat *values) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alSourcei(ALuint source, ALenum param, ALint value) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alSource3i(ALuint source, ALenum param, ALint value1, ALint value2, ALint value3) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alSourceiv(ALuint source, ALenum param, const ALint *values) AL_API_NOEXCEPT; /* Get source parameters. */ AL_API void AL_APIENTRY alGetSourcef(ALuint source, ALenum param, ALfloat *value) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alGetSource3f(ALuint source, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alGetSourcefv(ALuint source, ALenum param, ALfloat *values) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alGetSourcei(ALuint source, ALenum param, ALint *value) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alGetSource3i(ALuint source, ALenum param, ALint *value1, ALint *value2, ALint *value3) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alGetSourceiv(ALuint source, ALenum param, ALint *values) AL_API_NOEXCEPT; /** Play, restart, or resume a source, setting its state to AL_PLAYING. */ AL_API void AL_APIENTRY alSourcePlay(ALuint source) AL_API_NOEXCEPT; /** Stop a source, setting its state to AL_STOPPED if playing or paused. */ AL_API void AL_APIENTRY alSourceStop(ALuint source) AL_API_NOEXCEPT; /** Rewind a source, setting its state to AL_INITIAL. */ AL_API void AL_APIENTRY alSourceRewind(ALuint source) AL_API_NOEXCEPT; /** Pause a source, setting its state to AL_PAUSED if playing. */ AL_API void AL_APIENTRY alSourcePause(ALuint source) AL_API_NOEXCEPT; /** Play, restart, or resume a list of sources atomically. */ AL_API void AL_APIENTRY alSourcePlayv(ALsizei n, const ALuint *sources) AL_API_NOEXCEPT; /** Stop a list of sources atomically. */ AL_API void AL_APIENTRY alSourceStopv(ALsizei n, const ALuint *sources) AL_API_NOEXCEPT; /** Rewind a list of sources atomically. */ AL_API void AL_APIENTRY alSourceRewindv(ALsizei n, const ALuint *sources) AL_API_NOEXCEPT; /** Pause a list of sources atomically. */ AL_API void AL_APIENTRY alSourcePausev(ALsizei n, const ALuint *sources) AL_API_NOEXCEPT; /** Queue buffers onto a source */ AL_API void AL_APIENTRY alSourceQueueBuffers(ALuint source, ALsizei nb, const ALuint *buffers) AL_API_NOEXCEPT; /** Unqueue processed buffers from a source */ AL_API void AL_APIENTRY alSourceUnqueueBuffers(ALuint source, ALsizei nb, ALuint *buffers) AL_API_NOEXCEPT; /** Create buffer objects */ AL_API void AL_APIENTRY alGenBuffers(ALsizei n, ALuint *buffers) AL_API_NOEXCEPT; /** Delete buffer objects */ AL_API void AL_APIENTRY alDeleteBuffers(ALsizei n, const ALuint *buffers) AL_API_NOEXCEPT; /** Verify an ID is a valid buffer (including the NULL buffer) */ AL_API ALboolean AL_APIENTRY alIsBuffer(ALuint buffer) AL_API_NOEXCEPT; /** * Copies data into the buffer, interpreting it using the specified format and * samplerate. */ AL_API void AL_APIENTRY alBufferData(ALuint buffer, ALenum format, const ALvoid *data, ALsizei size, ALsizei samplerate) AL_API_NOEXCEPT; /* Set buffer parameters. */ AL_API void AL_APIENTRY alBufferf(ALuint buffer, ALenum param, ALfloat value) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alBuffer3f(ALuint buffer, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alBufferfv(ALuint buffer, ALenum param, const ALfloat *values) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alBufferi(ALuint buffer, ALenum param, ALint value) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alBuffer3i(ALuint buffer, ALenum param, ALint value1, ALint value2, ALint value3) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alBufferiv(ALuint buffer, ALenum param, const ALint *values) AL_API_NOEXCEPT; /* Get buffer parameters. */ AL_API void AL_APIENTRY alGetBufferf(ALuint buffer, ALenum param, ALfloat *value) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alGetBuffer3f(ALuint buffer, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alGetBufferfv(ALuint buffer, ALenum param, ALfloat *values) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alGetBufferi(ALuint buffer, ALenum param, ALint *value) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alGetBuffer3i(ALuint buffer, ALenum param, ALint *value1, ALint *value2, ALint *value3) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alGetBufferiv(ALuint buffer, ALenum param, ALint *values) AL_API_NOEXCEPT; #endif /* AL_NO_PROTOTYPES */ /* Pointer-to-function types, useful for storing dynamically loaded AL entry * points. */ typedef void (AL_APIENTRY *LPALENABLE)(ALenum capability) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALDISABLE)(ALenum capability) AL_API_NOEXCEPT17; typedef ALboolean (AL_APIENTRY *LPALISENABLED)(ALenum capability) AL_API_NOEXCEPT17; typedef const ALchar* (AL_APIENTRY *LPALGETSTRING)(ALenum param) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETBOOLEANV)(ALenum param, ALboolean *values) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETINTEGERV)(ALenum param, ALint *values) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETFLOATV)(ALenum param, ALfloat *values) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETDOUBLEV)(ALenum param, ALdouble *values) AL_API_NOEXCEPT17; typedef ALboolean (AL_APIENTRY *LPALGETBOOLEAN)(ALenum param) AL_API_NOEXCEPT17; typedef ALint (AL_APIENTRY *LPALGETINTEGER)(ALenum param) AL_API_NOEXCEPT17; typedef ALfloat (AL_APIENTRY *LPALGETFLOAT)(ALenum param) AL_API_NOEXCEPT17; typedef ALdouble (AL_APIENTRY *LPALGETDOUBLE)(ALenum param) AL_API_NOEXCEPT17; typedef ALenum (AL_APIENTRY *LPALGETERROR)(void) AL_API_NOEXCEPT17; typedef ALboolean (AL_APIENTRY *LPALISEXTENSIONPRESENT)(const ALchar *extname) AL_API_NOEXCEPT17; typedef void* (AL_APIENTRY *LPALGETPROCADDRESS)(const ALchar *fname) AL_API_NOEXCEPT17; typedef ALenum (AL_APIENTRY *LPALGETENUMVALUE)(const ALchar *ename) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALLISTENERF)(ALenum param, ALfloat value) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALLISTENER3F)(ALenum param, ALfloat value1, ALfloat value2, ALfloat value3) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALLISTENERFV)(ALenum param, const ALfloat *values) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALLISTENERI)(ALenum param, ALint value) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALLISTENER3I)(ALenum param, ALint value1, ALint value2, ALint value3) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALLISTENERIV)(ALenum param, const ALint *values) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETLISTENERF)(ALenum param, ALfloat *value) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETLISTENER3F)(ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETLISTENERFV)(ALenum param, ALfloat *values) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETLISTENERI)(ALenum param, ALint *value) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETLISTENER3I)(ALenum param, ALint *value1, ALint *value2, ALint *value3) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETLISTENERIV)(ALenum param, ALint *values) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGENSOURCES)(ALsizei n, ALuint *sources) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALDELETESOURCES)(ALsizei n, const ALuint *sources) AL_API_NOEXCEPT17; typedef ALboolean (AL_APIENTRY *LPALISSOURCE)(ALuint source) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALSOURCEF)(ALuint source, ALenum param, ALfloat value) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALSOURCE3F)(ALuint source, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALSOURCEFV)(ALuint source, ALenum param, const ALfloat *values) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALSOURCEI)(ALuint source, ALenum param, ALint value) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALSOURCE3I)(ALuint source, ALenum param, ALint value1, ALint value2, ALint value3) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALSOURCEIV)(ALuint source, ALenum param, const ALint *values) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETSOURCEF)(ALuint source, ALenum param, ALfloat *value) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETSOURCE3F)(ALuint source, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETSOURCEFV)(ALuint source, ALenum param, ALfloat *values) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETSOURCEI)(ALuint source, ALenum param, ALint *value) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETSOURCE3I)(ALuint source, ALenum param, ALint *value1, ALint *value2, ALint *value3) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETSOURCEIV)(ALuint source, ALenum param, ALint *values) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALSOURCEPLAYV)(ALsizei n, const ALuint *sources) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALSOURCESTOPV)(ALsizei n, const ALuint *sources) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALSOURCEREWINDV)(ALsizei n, const ALuint *sources) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALSOURCEPAUSEV)(ALsizei n, const ALuint *sources) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALSOURCEPLAY)(ALuint source) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALSOURCESTOP)(ALuint source) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALSOURCEREWIND)(ALuint source) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALSOURCEPAUSE)(ALuint source) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALSOURCEQUEUEBUFFERS)(ALuint source, ALsizei nb, const ALuint *buffers) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALSOURCEUNQUEUEBUFFERS)(ALuint source, ALsizei nb, ALuint *buffers) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGENBUFFERS)(ALsizei n, ALuint *buffers) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALDELETEBUFFERS)(ALsizei n, const ALuint *buffers) AL_API_NOEXCEPT17; typedef ALboolean (AL_APIENTRY *LPALISBUFFER)(ALuint buffer) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALBUFFERDATA)(ALuint buffer, ALenum format, const ALvoid *data, ALsizei size, ALsizei samplerate) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALBUFFERF)(ALuint buffer, ALenum param, ALfloat value) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALBUFFER3F)(ALuint buffer, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALBUFFERFV)(ALuint buffer, ALenum param, const ALfloat *values) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALBUFFERI)(ALuint buffer, ALenum param, ALint value) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALBUFFER3I)(ALuint buffer, ALenum param, ALint value1, ALint value2, ALint value3) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALBUFFERIV)(ALuint buffer, ALenum param, const ALint *values) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETBUFFERF)(ALuint buffer, ALenum param, ALfloat *value) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETBUFFER3F)(ALuint buffer, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETBUFFERFV)(ALuint buffer, ALenum param, ALfloat *values) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETBUFFERI)(ALuint buffer, ALenum param, ALint *value) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETBUFFER3I)(ALuint buffer, ALenum param, ALint *value1, ALint *value2, ALint *value3) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETBUFFERIV)(ALuint buffer, ALenum param, ALint *values) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALDOPPLERFACTOR)(ALfloat value) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALDOPPLERVELOCITY)(ALfloat value) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALSPEEDOFSOUND)(ALfloat value) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALDISTANCEMODEL)(ALenum distanceModel) AL_API_NOEXCEPT17; #ifdef __cplusplus } /* extern "C" */ #endif /* NOLINTEND */ #endif /* AL_AL_H */ openal-soft-1.24.2/include/AL/alc.h000066400000000000000000000255731474041540300166740ustar00rootroot00000000000000#ifndef AL_ALC_H #define AL_ALC_H /* NOLINTBEGIN */ #ifdef __cplusplus extern "C" { #ifdef _MSVC_LANG #define ALC_CPLUSPLUS _MSVC_LANG #else #define ALC_CPLUSPLUS __cplusplus #endif #ifndef AL_DISABLE_NOEXCEPT #if ALC_CPLUSPLUS >= 201103L #define ALC_API_NOEXCEPT noexcept #else #define ALC_API_NOEXCEPT #endif #if ALC_CPLUSPLUS >= 201703L #define ALC_API_NOEXCEPT17 noexcept #else #define ALC_API_NOEXCEPT17 #endif #else /* AL_DISABLE_NOEXCEPT */ #define ALC_API_NOEXCEPT #define ALC_API_NOEXCEPT17 #endif #undef ALC_CPLUSPLUS #else /* __cplusplus */ #define ALC_API_NOEXCEPT #define ALC_API_NOEXCEPT17 #endif #ifndef ALC_API #if defined(AL_LIBTYPE_STATIC) #define ALC_API #elif defined(_WIN32) #define ALC_API __declspec(dllimport) #else #define ALC_API extern #endif #endif #ifdef _WIN32 #define ALC_APIENTRY __cdecl #else #define ALC_APIENTRY #endif /* Deprecated macros. */ #define ALCAPI ALC_API #define ALCAPIENTRY ALC_APIENTRY #define ALC_INVALID 0 /** Supported ALC version? */ #define ALC_VERSION_0_1 1 /** Opaque device handle */ typedef struct ALCdevice ALCdevice; /** Opaque context handle */ typedef struct ALCcontext ALCcontext; /** 8-bit boolean */ typedef char ALCboolean; /** character */ typedef char ALCchar; /** signed 8-bit integer */ typedef signed char ALCbyte; /** unsigned 8-bit integer */ typedef unsigned char ALCubyte; /** signed 16-bit integer */ typedef short ALCshort; /** unsigned 16-bit integer */ typedef unsigned short ALCushort; /** signed 32-bit integer */ typedef int ALCint; /** unsigned 32-bit integer */ typedef unsigned int ALCuint; /** non-negative 32-bit integer size */ typedef int ALCsizei; /** 32-bit enumeration value */ typedef int ALCenum; /** 32-bit IEEE-754 floating-point */ typedef float ALCfloat; /** 64-bit IEEE-754 floating-point */ typedef double ALCdouble; /** void type (for opaque pointers only) */ typedef void ALCvoid; /* Enumeration values begin at column 50. Do not use tabs. */ /** Boolean False. */ #define ALC_FALSE 0 /** Boolean True. */ #define ALC_TRUE 1 /** Context attribute: Hz. */ #define ALC_FREQUENCY 0x1007 /** Context attribute: Hz. */ #define ALC_REFRESH 0x1008 /** Context attribute: AL_TRUE or AL_FALSE synchronous context? */ #define ALC_SYNC 0x1009 /** Context attribute: requested Mono (3D) Sources. */ #define ALC_MONO_SOURCES 0x1010 /** Context attribute: requested Stereo Sources. */ #define ALC_STEREO_SOURCES 0x1011 /** No error. */ #define ALC_NO_ERROR 0 /** Invalid device handle. */ #define ALC_INVALID_DEVICE 0xA001 /** Invalid context handle. */ #define ALC_INVALID_CONTEXT 0xA002 /** Invalid enumeration passed to an ALC call. */ #define ALC_INVALID_ENUM 0xA003 /** Invalid value passed to an ALC call. */ #define ALC_INVALID_VALUE 0xA004 /** Out of memory. */ #define ALC_OUT_OF_MEMORY 0xA005 /** Runtime ALC major version. */ #define ALC_MAJOR_VERSION 0x1000 /** Runtime ALC minor version. */ #define ALC_MINOR_VERSION 0x1001 /** Context attribute list size. */ #define ALC_ATTRIBUTES_SIZE 0x1002 /** Context attribute list properties. */ #define ALC_ALL_ATTRIBUTES 0x1003 /** String for the default device specifier. */ #define ALC_DEFAULT_DEVICE_SPECIFIER 0x1004 /** * Device specifier string. * * If device handle is NULL, it is instead a null-character separated list of * strings of known device specifiers (list ends with an empty string). */ #define ALC_DEVICE_SPECIFIER 0x1005 /** String for space-separated list of ALC extensions. */ #define ALC_EXTENSIONS 0x1006 /** Capture extension */ #define ALC_EXT_CAPTURE 1 /** * Capture device specifier string. * * If device handle is NULL, it is instead a null-character separated list of * strings of known capture device specifiers (list ends with an empty string). */ #define ALC_CAPTURE_DEVICE_SPECIFIER 0x310 /** String for the default capture device specifier. */ #define ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER 0x311 /** Number of sample frames available for capture. */ #define ALC_CAPTURE_SAMPLES 0x312 /** Enumerate All extension */ #define ALC_ENUMERATE_ALL_EXT 1 /** String for the default extended device specifier. */ #define ALC_DEFAULT_ALL_DEVICES_SPECIFIER 0x1012 /** * Device's extended specifier string. * * If device handle is NULL, it is instead a null-character separated list of * strings of known extended device specifiers (list ends with an empty string). */ #define ALC_ALL_DEVICES_SPECIFIER 0x1013 #ifndef ALC_NO_PROTOTYPES /* Context management. */ /** Create and attach a context to the given device. */ ALC_API ALCcontext* ALC_APIENTRY alcCreateContext(ALCdevice *device, const ALCint *attrlist) ALC_API_NOEXCEPT; /** * Makes the given context the active process-wide context. Passing NULL clears * the active context. */ ALC_API ALCboolean ALC_APIENTRY alcMakeContextCurrent(ALCcontext *context) ALC_API_NOEXCEPT; /** Resumes processing updates for the given context. */ ALC_API void ALC_APIENTRY alcProcessContext(ALCcontext *context) ALC_API_NOEXCEPT; /** Suspends updates for the given context. */ ALC_API void ALC_APIENTRY alcSuspendContext(ALCcontext *context) ALC_API_NOEXCEPT; /** Remove a context from its device and destroys it. */ ALC_API void ALC_APIENTRY alcDestroyContext(ALCcontext *context) ALC_API_NOEXCEPT; /** Returns the currently active context. */ ALC_API ALCcontext* ALC_APIENTRY alcGetCurrentContext(void) ALC_API_NOEXCEPT; /** Returns the device that a particular context is attached to. */ ALC_API ALCdevice* ALC_APIENTRY alcGetContextsDevice(ALCcontext *context) ALC_API_NOEXCEPT; /* Device management. */ /** Opens the named playback device. */ ALC_API ALCdevice* ALC_APIENTRY alcOpenDevice(const ALCchar *devicename) ALC_API_NOEXCEPT; /** Closes the given playback device. */ ALC_API ALCboolean ALC_APIENTRY alcCloseDevice(ALCdevice *device) ALC_API_NOEXCEPT; /* Error support. */ /** Obtain the most recent Device error. */ ALC_API ALCenum ALC_APIENTRY alcGetError(ALCdevice *device) ALC_API_NOEXCEPT; /* Extension support. */ /** * Query for the presence of an extension on the device. Pass a NULL device to * query a device-inspecific extension. */ ALC_API ALCboolean ALC_APIENTRY alcIsExtensionPresent(ALCdevice *device, const ALCchar *extname) ALC_API_NOEXCEPT; /** * Retrieve the address of a function. Given a non-NULL device, the returned * function may be device-specific. */ ALC_API ALCvoid* ALC_APIENTRY alcGetProcAddress(ALCdevice *device, const ALCchar *funcname) ALC_API_NOEXCEPT; /** * Retrieve the value of an enum. Given a non-NULL device, the returned value * may be device-specific. */ ALC_API ALCenum ALC_APIENTRY alcGetEnumValue(ALCdevice *device, const ALCchar *enumname) ALC_API_NOEXCEPT; /* Query functions. */ /** Returns information about the device, and error strings. */ ALC_API const ALCchar* ALC_APIENTRY alcGetString(ALCdevice *device, ALCenum param) ALC_API_NOEXCEPT; /** Returns information about the device and the version of OpenAL. */ ALC_API void ALC_APIENTRY alcGetIntegerv(ALCdevice *device, ALCenum param, ALCsizei size, ALCint *values) ALC_API_NOEXCEPT; /* Capture functions. */ /** * Opens the named capture device with the given frequency, format, and buffer * size. */ ALC_API ALCdevice* ALC_APIENTRY alcCaptureOpenDevice(const ALCchar *devicename, ALCuint frequency, ALCenum format, ALCsizei buffersize) ALC_API_NOEXCEPT; /** Closes the given capture device. */ ALC_API ALCboolean ALC_APIENTRY alcCaptureCloseDevice(ALCdevice *device) ALC_API_NOEXCEPT; /** Starts capturing samples into the device buffer. */ ALC_API void ALC_APIENTRY alcCaptureStart(ALCdevice *device) ALC_API_NOEXCEPT; /** Stops capturing samples. Samples in the device buffer remain available. */ ALC_API void ALC_APIENTRY alcCaptureStop(ALCdevice *device) ALC_API_NOEXCEPT; /** Reads samples from the device buffer. */ ALC_API void ALC_APIENTRY alcCaptureSamples(ALCdevice *device, ALCvoid *buffer, ALCsizei samples) ALC_API_NOEXCEPT; #endif /* ALC_NO_PROTOTYPES */ /* Pointer-to-function types, useful for storing dynamically loaded ALC entry * points. */ typedef ALCcontext* (ALC_APIENTRY *LPALCCREATECONTEXT)(ALCdevice *device, const ALCint *attrlist) ALC_API_NOEXCEPT17; typedef ALCboolean (ALC_APIENTRY *LPALCMAKECONTEXTCURRENT)(ALCcontext *context) ALC_API_NOEXCEPT17; typedef void (ALC_APIENTRY *LPALCPROCESSCONTEXT)(ALCcontext *context) ALC_API_NOEXCEPT17; typedef void (ALC_APIENTRY *LPALCSUSPENDCONTEXT)(ALCcontext *context) ALC_API_NOEXCEPT17; typedef void (ALC_APIENTRY *LPALCDESTROYCONTEXT)(ALCcontext *context) ALC_API_NOEXCEPT17; typedef ALCcontext* (ALC_APIENTRY *LPALCGETCURRENTCONTEXT)(void) ALC_API_NOEXCEPT17; typedef ALCdevice* (ALC_APIENTRY *LPALCGETCONTEXTSDEVICE)(ALCcontext *context) ALC_API_NOEXCEPT17; typedef ALCdevice* (ALC_APIENTRY *LPALCOPENDEVICE)(const ALCchar *devicename) ALC_API_NOEXCEPT17; typedef ALCboolean (ALC_APIENTRY *LPALCCLOSEDEVICE)(ALCdevice *device) ALC_API_NOEXCEPT17; typedef ALCenum (ALC_APIENTRY *LPALCGETERROR)(ALCdevice *device) ALC_API_NOEXCEPT17; typedef ALCboolean (ALC_APIENTRY *LPALCISEXTENSIONPRESENT)(ALCdevice *device, const ALCchar *extname) ALC_API_NOEXCEPT17; typedef ALCvoid* (ALC_APIENTRY *LPALCGETPROCADDRESS)(ALCdevice *device, const ALCchar *funcname) ALC_API_NOEXCEPT17; typedef ALCenum (ALC_APIENTRY *LPALCGETENUMVALUE)(ALCdevice *device, const ALCchar *enumname) ALC_API_NOEXCEPT17; typedef const ALCchar* (ALC_APIENTRY *LPALCGETSTRING)(ALCdevice *device, ALCenum param) ALC_API_NOEXCEPT17; typedef void (ALC_APIENTRY *LPALCGETINTEGERV)(ALCdevice *device, ALCenum param, ALCsizei size, ALCint *values) ALC_API_NOEXCEPT17; typedef ALCdevice* (ALC_APIENTRY *LPALCCAPTUREOPENDEVICE)(const ALCchar *devicename, ALCuint frequency, ALCenum format, ALCsizei buffersize) ALC_API_NOEXCEPT17; typedef ALCboolean (ALC_APIENTRY *LPALCCAPTURECLOSEDEVICE)(ALCdevice *device) ALC_API_NOEXCEPT17; typedef void (ALC_APIENTRY *LPALCCAPTURESTART)(ALCdevice *device) ALC_API_NOEXCEPT17; typedef void (ALC_APIENTRY *LPALCCAPTURESTOP)(ALCdevice *device) ALC_API_NOEXCEPT17; typedef void (ALC_APIENTRY *LPALCCAPTURESAMPLES)(ALCdevice *device, ALCvoid *buffer, ALCsizei samples) ALC_API_NOEXCEPT17; #ifdef __cplusplus } /* extern "C" */ #endif /* NOLINTEND */ #endif /* AL_ALC_H */ openal-soft-1.24.2/include/AL/alext.h000066400000000000000000002212611474041540300172420ustar00rootroot00000000000000#ifndef AL_ALEXT_H #define AL_ALEXT_H /* NOLINTBEGIN */ #include /* Define int64 and uint64 types */ #if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || \ (defined(__cplusplus) && __cplusplus >= 201103L) #include typedef int64_t _alsoft_int64_t; typedef uint64_t _alsoft_uint64_t; #elif defined(_WIN32) typedef __int64 _alsoft_int64_t; typedef unsigned __int64 _alsoft_uint64_t; #else /* Fallback if nothing above works */ #include typedef int64_t _alsoft_int64_t; typedef uint64_t _alsoft_uint64_t; #endif #include "alc.h" #include "al.h" #ifdef __cplusplus extern "C" { #endif struct _GUID; #ifndef AL_LOKI_IMA_ADPCM_format #define AL_LOKI_IMA_ADPCM_format 1 #define AL_FORMAT_IMA_ADPCM_MONO16_EXT 0x10000 #define AL_FORMAT_IMA_ADPCM_STEREO16_EXT 0x10001 #endif #ifndef AL_LOKI_WAVE_format #define AL_LOKI_WAVE_format 1 #define AL_FORMAT_WAVE_EXT 0x10002 #endif #ifndef AL_EXT_vorbis #define AL_EXT_vorbis 1 #define AL_FORMAT_VORBIS_EXT 0x10003 #endif #ifndef AL_LOKI_quadriphonic #define AL_LOKI_quadriphonic 1 #define AL_FORMAT_QUAD8_LOKI 0x10004 #define AL_FORMAT_QUAD16_LOKI 0x10005 #endif #ifndef AL_EXT_float32 #define AL_EXT_float32 1 #define AL_FORMAT_MONO_FLOAT32 0x10010 #define AL_FORMAT_STEREO_FLOAT32 0x10011 #endif #ifndef AL_EXT_double #define AL_EXT_double 1 #define AL_FORMAT_MONO_DOUBLE_EXT 0x10012 #define AL_FORMAT_STEREO_DOUBLE_EXT 0x10013 #endif #ifndef AL_EXT_MULAW #define AL_EXT_MULAW 1 #define AL_FORMAT_MONO_MULAW_EXT 0x10014 #define AL_FORMAT_STEREO_MULAW_EXT 0x10015 #endif #ifndef AL_EXT_ALAW #define AL_EXT_ALAW 1 #define AL_FORMAT_MONO_ALAW_EXT 0x10016 #define AL_FORMAT_STEREO_ALAW_EXT 0x10017 #endif #ifndef ALC_LOKI_audio_channel #define ALC_LOKI_audio_channel 1 #define ALC_CHAN_MAIN_LOKI 0x500001 #define ALC_CHAN_PCM_LOKI 0x500002 #define ALC_CHAN_CD_LOKI 0x500003 #endif #ifndef AL_EXT_MCFORMATS #define AL_EXT_MCFORMATS 1 /* Provides support for surround sound buffer formats with 8, 16, and 32-bit * samples. * * QUAD8: Unsigned 8-bit, Quadraphonic (Front Left, Front Right, Rear Left, * Rear Right). * QUAD16: Signed 16-bit, Quadraphonic. * QUAD32: 32-bit float, Quadraphonic. * REAR8: Unsigned 8-bit, Rear Stereo (Rear Left, Rear Right). * REAR16: Signed 16-bit, Rear Stereo. * REAR32: 32-bit float, Rear Stereo. * 51CHN8: Unsigned 8-bit, 5.1 Surround (Front Left, Front Right, Front Center, * LFE, Side Left, Side Right). Note that some audio systems may label * 5.1's Side channels as Rear or Surround; they are equivalent for the * purposes of this extension. * 51CHN16: Signed 16-bit, 5.1 Surround. * 51CHN32: 32-bit float, 5.1 Surround. * 61CHN8: Unsigned 8-bit, 6.1 Surround (Front Left, Front Right, Front Center, * LFE, Rear Center, Side Left, Side Right). * 61CHN16: Signed 16-bit, 6.1 Surround. * 61CHN32: 32-bit float, 6.1 Surround. * 71CHN8: Unsigned 8-bit, 7.1 Surround (Front Left, Front Right, Front Center, * LFE, Rear Left, Rear Right, Side Left, Side Right). * 71CHN16: Signed 16-bit, 7.1 Surround. * 71CHN32: 32-bit float, 7.1 Surround. */ #define AL_FORMAT_QUAD8 0x1204 #define AL_FORMAT_QUAD16 0x1205 #define AL_FORMAT_QUAD32 0x1206 #define AL_FORMAT_REAR8 0x1207 #define AL_FORMAT_REAR16 0x1208 #define AL_FORMAT_REAR32 0x1209 #define AL_FORMAT_51CHN8 0x120A #define AL_FORMAT_51CHN16 0x120B #define AL_FORMAT_51CHN32 0x120C #define AL_FORMAT_61CHN8 0x120D #define AL_FORMAT_61CHN16 0x120E #define AL_FORMAT_61CHN32 0x120F #define AL_FORMAT_71CHN8 0x1210 #define AL_FORMAT_71CHN16 0x1211 #define AL_FORMAT_71CHN32 0x1212 #endif #ifndef AL_EXT_MULAW_MCFORMATS #define AL_EXT_MULAW_MCFORMATS 1 #define AL_FORMAT_MONO_MULAW 0x10014 #define AL_FORMAT_STEREO_MULAW 0x10015 #define AL_FORMAT_QUAD_MULAW 0x10021 #define AL_FORMAT_REAR_MULAW 0x10022 #define AL_FORMAT_51CHN_MULAW 0x10023 #define AL_FORMAT_61CHN_MULAW 0x10024 #define AL_FORMAT_71CHN_MULAW 0x10025 #endif #ifndef AL_EXT_IMA4 #define AL_EXT_IMA4 1 #define AL_FORMAT_MONO_IMA4 0x1300 #define AL_FORMAT_STEREO_IMA4 0x1301 #endif #ifndef AL_EXT_STATIC_BUFFER #define AL_EXT_STATIC_BUFFER 1 typedef void (AL_APIENTRY*PFNALBUFFERDATASTATICPROC)(const ALuint,ALenum,ALvoid*,ALsizei,ALsizei) AL_API_NOEXCEPT17; #ifdef AL_ALEXT_PROTOTYPES void AL_APIENTRY alBufferDataStatic(const ALuint buffer, ALenum format, ALvoid *data, ALsizei size, ALsizei freq) AL_API_NOEXCEPT; #endif #endif #ifndef ALC_EXT_EFX #define ALC_EXT_EFX 1 #include "efx.h" #endif #ifndef ALC_EXT_disconnect #define ALC_EXT_disconnect 1 #define ALC_CONNECTED 0x313 #endif #ifndef ALC_EXT_thread_local_context #define ALC_EXT_thread_local_context 1 typedef ALCboolean (ALC_APIENTRY*PFNALCSETTHREADCONTEXTPROC)(ALCcontext *context) ALC_API_NOEXCEPT17; typedef ALCcontext* (ALC_APIENTRY*PFNALCGETTHREADCONTEXTPROC)(void) ALC_API_NOEXCEPT17; #ifdef AL_ALEXT_PROTOTYPES ALC_API ALCboolean ALC_APIENTRY alcSetThreadContext(ALCcontext *context) ALC_API_NOEXCEPT; ALC_API ALCcontext* ALC_APIENTRY alcGetThreadContext(void) ALC_API_NOEXCEPT; #endif #endif #ifndef AL_EXT_source_distance_model #define AL_EXT_source_distance_model 1 #define AL_SOURCE_DISTANCE_MODEL 0x200 #endif #ifndef AL_SOFT_buffer_sub_data #define AL_SOFT_buffer_sub_data 1 #define AL_BYTE_RW_OFFSETS_SOFT 0x1031 #define AL_SAMPLE_RW_OFFSETS_SOFT 0x1032 typedef void (AL_APIENTRY*PFNALBUFFERSUBDATASOFTPROC)(ALuint,ALenum,const ALvoid*,ALsizei,ALsizei) AL_API_NOEXCEPT17; #ifdef AL_ALEXT_PROTOTYPES AL_API void AL_APIENTRY alBufferSubDataSOFT(ALuint buffer,ALenum format,const ALvoid *data,ALsizei offset,ALsizei length) AL_API_NOEXCEPT; #endif #endif #ifndef AL_SOFT_loop_points #define AL_SOFT_loop_points 1 #define AL_LOOP_POINTS_SOFT 0x2015 #endif #ifndef AL_EXT_FOLDBACK #define AL_EXT_FOLDBACK 1 #define AL_EXT_FOLDBACK_NAME "AL_EXT_FOLDBACK" #define AL_FOLDBACK_EVENT_BLOCK 0x4112 #define AL_FOLDBACK_EVENT_START 0x4111 #define AL_FOLDBACK_EVENT_STOP 0x4113 #define AL_FOLDBACK_MODE_MONO 0x4101 #define AL_FOLDBACK_MODE_STEREO 0x4102 typedef void (AL_APIENTRY*LPALFOLDBACKCALLBACK)(ALenum,ALsizei) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY*LPALREQUESTFOLDBACKSTART)(ALenum,ALsizei,ALsizei,ALfloat*,LPALFOLDBACKCALLBACK) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY*LPALREQUESTFOLDBACKSTOP)(void) AL_API_NOEXCEPT17; #ifdef AL_ALEXT_PROTOTYPES AL_API void AL_APIENTRY alRequestFoldbackStart(ALenum mode,ALsizei count,ALsizei length,ALfloat *mem,LPALFOLDBACKCALLBACK callback) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alRequestFoldbackStop(void) AL_API_NOEXCEPT; #endif #endif #ifndef ALC_EXT_DEDICATED #define ALC_EXT_DEDICATED 1 #define AL_DEDICATED_GAIN 0x0001 #define AL_EFFECT_DEDICATED_DIALOGUE 0x9001 #define AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT 0x9000 #endif #ifndef AL_SOFT_buffer_samples #define AL_SOFT_buffer_samples 1 /* Channel configurations */ #define AL_MONO_SOFT 0x1500 #define AL_STEREO_SOFT 0x1501 #define AL_REAR_SOFT 0x1502 #define AL_QUAD_SOFT 0x1503 #define AL_5POINT1_SOFT 0x1504 #define AL_6POINT1_SOFT 0x1505 #define AL_7POINT1_SOFT 0x1506 /* Sample types */ #define AL_BYTE_SOFT 0x1400 #define AL_UNSIGNED_BYTE_SOFT 0x1401 #define AL_SHORT_SOFT 0x1402 #define AL_UNSIGNED_SHORT_SOFT 0x1403 #define AL_INT_SOFT 0x1404 #define AL_UNSIGNED_INT_SOFT 0x1405 #define AL_FLOAT_SOFT 0x1406 #define AL_DOUBLE_SOFT 0x1407 #define AL_BYTE3_SOFT 0x1408 #define AL_UNSIGNED_BYTE3_SOFT 0x1409 /* Storage formats */ #define AL_MONO8_SOFT 0x1100 #define AL_MONO16_SOFT 0x1101 #define AL_MONO32F_SOFT 0x10010 #define AL_STEREO8_SOFT 0x1102 #define AL_STEREO16_SOFT 0x1103 #define AL_STEREO32F_SOFT 0x10011 #define AL_QUAD8_SOFT 0x1204 #define AL_QUAD16_SOFT 0x1205 #define AL_QUAD32F_SOFT 0x1206 #define AL_REAR8_SOFT 0x1207 #define AL_REAR16_SOFT 0x1208 #define AL_REAR32F_SOFT 0x1209 #define AL_5POINT1_8_SOFT 0x120A #define AL_5POINT1_16_SOFT 0x120B #define AL_5POINT1_32F_SOFT 0x120C #define AL_6POINT1_8_SOFT 0x120D #define AL_6POINT1_16_SOFT 0x120E #define AL_6POINT1_32F_SOFT 0x120F #define AL_7POINT1_8_SOFT 0x1210 #define AL_7POINT1_16_SOFT 0x1211 #define AL_7POINT1_32F_SOFT 0x1212 /* Buffer attributes */ #define AL_INTERNAL_FORMAT_SOFT 0x2008 #define AL_BYTE_LENGTH_SOFT 0x2009 #define AL_SAMPLE_LENGTH_SOFT 0x200A #define AL_SEC_LENGTH_SOFT 0x200B typedef void (AL_APIENTRY*LPALBUFFERSAMPLESSOFT)(ALuint,ALuint,ALenum,ALsizei,ALenum,ALenum,const ALvoid*) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY*LPALBUFFERSUBSAMPLESSOFT)(ALuint,ALsizei,ALsizei,ALenum,ALenum,const ALvoid*) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY*LPALGETBUFFERSAMPLESSOFT)(ALuint,ALsizei,ALsizei,ALenum,ALenum,ALvoid*) AL_API_NOEXCEPT17; typedef ALboolean (AL_APIENTRY*LPALISBUFFERFORMATSUPPORTEDSOFT)(ALenum) AL_API_NOEXCEPT17; #ifdef AL_ALEXT_PROTOTYPES AL_API void AL_APIENTRY alBufferSamplesSOFT(ALuint buffer, ALuint samplerate, ALenum internalformat, ALsizei samples, ALenum channels, ALenum type, const ALvoid *data) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alBufferSubSamplesSOFT(ALuint buffer, ALsizei offset, ALsizei samples, ALenum channels, ALenum type, const ALvoid *data) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alGetBufferSamplesSOFT(ALuint buffer, ALsizei offset, ALsizei samples, ALenum channels, ALenum type, ALvoid *data) AL_API_NOEXCEPT; AL_API ALboolean AL_APIENTRY alIsBufferFormatSupportedSOFT(ALenum format) AL_API_NOEXCEPT; #endif #endif #ifndef AL_SOFT_direct_channels #define AL_SOFT_direct_channels 1 #define AL_DIRECT_CHANNELS_SOFT 0x1033 #endif #ifndef ALC_SOFT_loopback #define ALC_SOFT_loopback 1 #define ALC_FORMAT_CHANNELS_SOFT 0x1990 #define ALC_FORMAT_TYPE_SOFT 0x1991 /* Sample types */ #define ALC_BYTE_SOFT 0x1400 #define ALC_UNSIGNED_BYTE_SOFT 0x1401 #define ALC_SHORT_SOFT 0x1402 #define ALC_UNSIGNED_SHORT_SOFT 0x1403 #define ALC_INT_SOFT 0x1404 #define ALC_UNSIGNED_INT_SOFT 0x1405 #define ALC_FLOAT_SOFT 0x1406 /* Channel configurations */ #define ALC_MONO_SOFT 0x1500 #define ALC_STEREO_SOFT 0x1501 #define ALC_QUAD_SOFT 0x1503 #define ALC_5POINT1_SOFT 0x1504 #define ALC_6POINT1_SOFT 0x1505 #define ALC_7POINT1_SOFT 0x1506 typedef ALCdevice* (ALC_APIENTRY*LPALCLOOPBACKOPENDEVICESOFT)(const ALCchar*) ALC_API_NOEXCEPT17; typedef ALCboolean (ALC_APIENTRY*LPALCISRENDERFORMATSUPPORTEDSOFT)(ALCdevice*,ALCsizei,ALCenum,ALCenum) ALC_API_NOEXCEPT17; typedef void (ALC_APIENTRY*LPALCRENDERSAMPLESSOFT)(ALCdevice*,ALCvoid*,ALCsizei) ALC_API_NOEXCEPT17; #ifdef AL_ALEXT_PROTOTYPES ALC_API ALCdevice* ALC_APIENTRY alcLoopbackOpenDeviceSOFT(const ALCchar *deviceName) AL_API_NOEXCEPT; ALC_API ALCboolean ALC_APIENTRY alcIsRenderFormatSupportedSOFT(ALCdevice *device, ALCsizei freq, ALCenum channels, ALCenum type) AL_API_NOEXCEPT; ALC_API void ALC_APIENTRY alcRenderSamplesSOFT(ALCdevice *device, ALCvoid *buffer, ALCsizei samples) AL_API_NOEXCEPT; #endif #endif #ifndef AL_EXT_STEREO_ANGLES #define AL_EXT_STEREO_ANGLES 1 #define AL_STEREO_ANGLES 0x1030 #endif #ifndef AL_EXT_SOURCE_RADIUS #define AL_EXT_SOURCE_RADIUS 1 #define AL_SOURCE_RADIUS 0x1031 #endif #ifndef AL_SOFT_source_latency #define AL_SOFT_source_latency 1 #define AL_SAMPLE_OFFSET_LATENCY_SOFT 0x1200 #define AL_SEC_OFFSET_LATENCY_SOFT 0x1201 typedef _alsoft_int64_t ALint64SOFT; typedef _alsoft_uint64_t ALuint64SOFT; typedef void (AL_APIENTRY*LPALSOURCEDSOFT)(ALuint,ALenum,ALdouble) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY*LPALSOURCE3DSOFT)(ALuint,ALenum,ALdouble,ALdouble,ALdouble) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY*LPALSOURCEDVSOFT)(ALuint,ALenum,const ALdouble*) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY*LPALGETSOURCEDSOFT)(ALuint,ALenum,ALdouble*) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY*LPALGETSOURCE3DSOFT)(ALuint,ALenum,ALdouble*,ALdouble*,ALdouble*) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY*LPALGETSOURCEDVSOFT)(ALuint,ALenum,ALdouble*) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY*LPALSOURCEI64SOFT)(ALuint,ALenum,ALint64SOFT) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY*LPALSOURCE3I64SOFT)(ALuint,ALenum,ALint64SOFT,ALint64SOFT,ALint64SOFT) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY*LPALSOURCEI64VSOFT)(ALuint,ALenum,const ALint64SOFT*) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY*LPALGETSOURCEI64SOFT)(ALuint,ALenum,ALint64SOFT*) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY*LPALGETSOURCE3I64SOFT)(ALuint,ALenum,ALint64SOFT*,ALint64SOFT*,ALint64SOFT*) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY*LPALGETSOURCEI64VSOFT)(ALuint,ALenum,ALint64SOFT*) AL_API_NOEXCEPT17; #ifdef AL_ALEXT_PROTOTYPES AL_API void AL_APIENTRY alSourcedSOFT(ALuint source, ALenum param, ALdouble value) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alSource3dSOFT(ALuint source, ALenum param, ALdouble value1, ALdouble value2, ALdouble value3) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alSourcedvSOFT(ALuint source, ALenum param, const ALdouble *values) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alGetSourcedSOFT(ALuint source, ALenum param, ALdouble *value) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alGetSource3dSOFT(ALuint source, ALenum param, ALdouble *value1, ALdouble *value2, ALdouble *value3) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alGetSourcedvSOFT(ALuint source, ALenum param, ALdouble *values) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alSourcei64SOFT(ALuint source, ALenum param, ALint64SOFT value) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alSource3i64SOFT(ALuint source, ALenum param, ALint64SOFT value1, ALint64SOFT value2, ALint64SOFT value3) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alSourcei64vSOFT(ALuint source, ALenum param, const ALint64SOFT *values) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alGetSourcei64SOFT(ALuint source, ALenum param, ALint64SOFT *value) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alGetSource3i64SOFT(ALuint source, ALenum param, ALint64SOFT *value1, ALint64SOFT *value2, ALint64SOFT *value3) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alGetSourcei64vSOFT(ALuint source, ALenum param, ALint64SOFT *values) AL_API_NOEXCEPT; #endif #endif #ifndef ALC_EXT_DEFAULT_FILTER_ORDER #define ALC_EXT_DEFAULT_FILTER_ORDER 1 #define ALC_DEFAULT_FILTER_ORDER 0x1100 #endif #ifndef AL_SOFT_deferred_updates #define AL_SOFT_deferred_updates 1 #define AL_DEFERRED_UPDATES_SOFT 0xC002 typedef void (AL_APIENTRY*LPALDEFERUPDATESSOFT)(void) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY*LPALPROCESSUPDATESSOFT)(void) AL_API_NOEXCEPT17; #ifdef AL_ALEXT_PROTOTYPES AL_API void AL_APIENTRY alDeferUpdatesSOFT(void) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alProcessUpdatesSOFT(void) AL_API_NOEXCEPT; #endif #endif #ifndef AL_SOFT_block_alignment #define AL_SOFT_block_alignment 1 #define AL_UNPACK_BLOCK_ALIGNMENT_SOFT 0x200C #define AL_PACK_BLOCK_ALIGNMENT_SOFT 0x200D #endif #ifndef AL_SOFT_MSADPCM #define AL_SOFT_MSADPCM 1 #define AL_FORMAT_MONO_MSADPCM_SOFT 0x1302 #define AL_FORMAT_STEREO_MSADPCM_SOFT 0x1303 #endif #ifndef AL_SOFT_source_length #define AL_SOFT_source_length 1 /*#define AL_BYTE_LENGTH_SOFT 0x2009*/ /*#define AL_SAMPLE_LENGTH_SOFT 0x200A*/ /*#define AL_SEC_LENGTH_SOFT 0x200B*/ #endif #ifndef AL_SOFT_buffer_length_query #define AL_SOFT_buffer_length_query 1 /*#define AL_BYTE_LENGTH_SOFT 0x2009*/ /*#define AL_SAMPLE_LENGTH_SOFT 0x200A*/ /*#define AL_SEC_LENGTH_SOFT 0x200B*/ #endif #ifndef ALC_SOFT_pause_device #define ALC_SOFT_pause_device 1 typedef void (ALC_APIENTRY*LPALCDEVICEPAUSESOFT)(ALCdevice *device) ALC_API_NOEXCEPT17; typedef void (ALC_APIENTRY*LPALCDEVICERESUMESOFT)(ALCdevice *device) ALC_API_NOEXCEPT17; #ifdef AL_ALEXT_PROTOTYPES ALC_API void ALC_APIENTRY alcDevicePauseSOFT(ALCdevice *device) ALC_API_NOEXCEPT; ALC_API void ALC_APIENTRY alcDeviceResumeSOFT(ALCdevice *device) ALC_API_NOEXCEPT; #endif #endif #ifndef AL_EXT_BFORMAT #define AL_EXT_BFORMAT 1 /* Provides support for B-Format ambisonic buffers (first-order, FuMa scaling * and layout). * * BFORMAT2D_8: Unsigned 8-bit, 3-channel non-periphonic (WXY). * BFORMAT2D_16: Signed 16-bit, 3-channel non-periphonic (WXY). * BFORMAT2D_FLOAT32: 32-bit float, 3-channel non-periphonic (WXY). * BFORMAT3D_8: Unsigned 8-bit, 4-channel periphonic (WXYZ). * BFORMAT3D_16: Signed 16-bit, 4-channel periphonic (WXYZ). * BFORMAT3D_FLOAT32: 32-bit float, 4-channel periphonic (WXYZ). */ #define AL_FORMAT_BFORMAT2D_8 0x20021 #define AL_FORMAT_BFORMAT2D_16 0x20022 #define AL_FORMAT_BFORMAT2D_FLOAT32 0x20023 #define AL_FORMAT_BFORMAT3D_8 0x20031 #define AL_FORMAT_BFORMAT3D_16 0x20032 #define AL_FORMAT_BFORMAT3D_FLOAT32 0x20033 #endif #ifndef AL_EXT_MULAW_BFORMAT #define AL_EXT_MULAW_BFORMAT 1 #define AL_FORMAT_BFORMAT2D_MULAW 0x10031 #define AL_FORMAT_BFORMAT3D_MULAW 0x10032 #endif #ifndef ALC_SOFT_HRTF #define ALC_SOFT_HRTF 1 #define ALC_HRTF_SOFT 0x1992 #define ALC_DONT_CARE_SOFT 0x0002 #define ALC_HRTF_STATUS_SOFT 0x1993 #define ALC_HRTF_DISABLED_SOFT 0x0000 #define ALC_HRTF_ENABLED_SOFT 0x0001 #define ALC_HRTF_DENIED_SOFT 0x0002 #define ALC_HRTF_REQUIRED_SOFT 0x0003 #define ALC_HRTF_HEADPHONES_DETECTED_SOFT 0x0004 #define ALC_HRTF_UNSUPPORTED_FORMAT_SOFT 0x0005 #define ALC_NUM_HRTF_SPECIFIERS_SOFT 0x1994 #define ALC_HRTF_SPECIFIER_SOFT 0x1995 #define ALC_HRTF_ID_SOFT 0x1996 typedef const ALCchar* (ALC_APIENTRY*LPALCGETSTRINGISOFT)(ALCdevice *device, ALCenum paramName, ALCsizei index) ALC_API_NOEXCEPT17; typedef ALCboolean (ALC_APIENTRY*LPALCRESETDEVICESOFT)(ALCdevice *device, const ALCint *attribs) ALC_API_NOEXCEPT17; #ifdef AL_ALEXT_PROTOTYPES ALC_API const ALCchar* ALC_APIENTRY alcGetStringiSOFT(ALCdevice *device, ALCenum paramName, ALCsizei index) ALC_API_NOEXCEPT; ALC_API ALCboolean ALC_APIENTRY alcResetDeviceSOFT(ALCdevice *device, const ALCint *attribs) ALC_API_NOEXCEPT; #endif #endif #ifndef AL_SOFT_gain_clamp_ex #define AL_SOFT_gain_clamp_ex 1 #define AL_GAIN_LIMIT_SOFT 0x200E #endif #ifndef AL_SOFT_source_resampler #define AL_SOFT_source_resampler #define AL_NUM_RESAMPLERS_SOFT 0x1210 #define AL_DEFAULT_RESAMPLER_SOFT 0x1211 #define AL_SOURCE_RESAMPLER_SOFT 0x1212 #define AL_RESAMPLER_NAME_SOFT 0x1213 typedef const ALchar* (AL_APIENTRY*LPALGETSTRINGISOFT)(ALenum pname, ALsizei index) AL_API_NOEXCEPT17; #ifdef AL_ALEXT_PROTOTYPES AL_API const ALchar* AL_APIENTRY alGetStringiSOFT(ALenum pname, ALsizei index) AL_API_NOEXCEPT; #endif #endif #ifndef AL_SOFT_source_spatialize #define AL_SOFT_source_spatialize #define AL_SOURCE_SPATIALIZE_SOFT 0x1214 #define AL_AUTO_SOFT 0x0002 #endif #ifndef ALC_SOFT_output_limiter #define ALC_SOFT_output_limiter #define ALC_OUTPUT_LIMITER_SOFT 0x199A #endif #ifndef ALC_SOFT_device_clock #define ALC_SOFT_device_clock 1 typedef _alsoft_int64_t ALCint64SOFT; typedef _alsoft_uint64_t ALCuint64SOFT; #define ALC_DEVICE_CLOCK_SOFT 0x1600 #define ALC_DEVICE_LATENCY_SOFT 0x1601 #define ALC_DEVICE_CLOCK_LATENCY_SOFT 0x1602 #define AL_SAMPLE_OFFSET_CLOCK_SOFT 0x1202 #define AL_SEC_OFFSET_CLOCK_SOFT 0x1203 typedef void (ALC_APIENTRY*LPALCGETINTEGER64VSOFT)(ALCdevice *device, ALCenum pname, ALsizei size, ALCint64SOFT *values) ALC_API_NOEXCEPT17; #ifdef AL_ALEXT_PROTOTYPES ALC_API void ALC_APIENTRY alcGetInteger64vSOFT(ALCdevice *device, ALCenum pname, ALsizei size, ALCint64SOFT *values) ALC_API_NOEXCEPT; #endif #endif #ifndef AL_SOFT_direct_channels_remix #define AL_SOFT_direct_channels_remix 1 #define AL_DROP_UNMATCHED_SOFT 0x0001 #define AL_REMIX_UNMATCHED_SOFT 0x0002 #endif #ifndef AL_SOFT_bformat_ex #define AL_SOFT_bformat_ex 1 #define AL_AMBISONIC_LAYOUT_SOFT 0x1997 #define AL_AMBISONIC_SCALING_SOFT 0x1998 /* Ambisonic layouts */ #define AL_FUMA_SOFT 0x0000 #define AL_ACN_SOFT 0x0001 /* Ambisonic scalings (normalization) */ /*#define AL_FUMA_SOFT*/ #define AL_SN3D_SOFT 0x0001 #define AL_N3D_SOFT 0x0002 #endif #ifndef ALC_SOFT_loopback_bformat #define ALC_SOFT_loopback_bformat 1 #define ALC_AMBISONIC_LAYOUT_SOFT 0x1997 #define ALC_AMBISONIC_SCALING_SOFT 0x1998 #define ALC_AMBISONIC_ORDER_SOFT 0x1999 #define ALC_MAX_AMBISONIC_ORDER_SOFT 0x199B #define ALC_BFORMAT3D_SOFT 0x1507 /* Ambisonic layouts */ #define ALC_FUMA_SOFT 0x0000 #define ALC_ACN_SOFT 0x0001 /* Ambisonic scalings (normalization) */ /*#define ALC_FUMA_SOFT*/ #define ALC_SN3D_SOFT 0x0001 #define ALC_N3D_SOFT 0x0002 #endif #ifndef AL_SOFT_effect_target #define AL_SOFT_effect_target #define AL_EFFECTSLOT_TARGET_SOFT 0x199C #endif #ifndef AL_SOFT_events #define AL_SOFT_events 1 #define AL_EVENT_CALLBACK_FUNCTION_SOFT 0x19A2 #define AL_EVENT_CALLBACK_USER_PARAM_SOFT 0x19A3 #define AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT 0x19A4 #define AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT 0x19A5 #define AL_EVENT_TYPE_DISCONNECTED_SOFT 0x19A6 typedef void (AL_APIENTRY*ALEVENTPROCSOFT)(ALenum eventType, ALuint object, ALuint param, ALsizei length, const ALchar *message, void *userParam) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY*LPALEVENTCONTROLSOFT)(ALsizei count, const ALenum *types, ALboolean enable) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY*LPALEVENTCALLBACKSOFT)(ALEVENTPROCSOFT callback, void *userParam) AL_API_NOEXCEPT17; typedef void* (AL_APIENTRY*LPALGETPOINTERSOFT)(ALenum pname) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY*LPALGETPOINTERVSOFT)(ALenum pname, void **values) AL_API_NOEXCEPT17; #ifdef AL_ALEXT_PROTOTYPES AL_API void AL_APIENTRY alEventControlSOFT(ALsizei count, const ALenum *types, ALboolean enable) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alEventCallbackSOFT(ALEVENTPROCSOFT callback, void *userParam) AL_API_NOEXCEPT; AL_API void* AL_APIENTRY alGetPointerSOFT(ALenum pname) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alGetPointervSOFT(ALenum pname, void **values) AL_API_NOEXCEPT; #endif #endif #ifndef ALC_SOFT_reopen_device #define ALC_SOFT_reopen_device typedef ALCboolean (ALC_APIENTRY*LPALCREOPENDEVICESOFT)(ALCdevice *device, const ALCchar *deviceName, const ALCint *attribs) ALC_API_NOEXCEPT17; #ifdef AL_ALEXT_PROTOTYPES ALCboolean ALC_APIENTRY alcReopenDeviceSOFT(ALCdevice *device, const ALCchar *deviceName, const ALCint *attribs) ALC_API_NOEXCEPT; #endif #endif #ifndef AL_SOFT_callback_buffer #define AL_SOFT_callback_buffer #define AL_BUFFER_CALLBACK_FUNCTION_SOFT 0x19A0 #define AL_BUFFER_CALLBACK_USER_PARAM_SOFT 0x19A1 typedef ALsizei (AL_APIENTRY*ALBUFFERCALLBACKTYPESOFT)(ALvoid *userptr, ALvoid *sampledata, ALsizei numbytes) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY*LPALBUFFERCALLBACKSOFT)(ALuint buffer, ALenum format, ALsizei freq, ALBUFFERCALLBACKTYPESOFT callback, ALvoid *userptr) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY*LPALGETBUFFERPTRSOFT)(ALuint buffer, ALenum param, ALvoid **value) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY*LPALGETBUFFER3PTRSOFT)(ALuint buffer, ALenum param, ALvoid **value1, ALvoid **value2, ALvoid **value3) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY*LPALGETBUFFERPTRVSOFT)(ALuint buffer, ALenum param, ALvoid **values) AL_API_NOEXCEPT17; #ifdef AL_ALEXT_PROTOTYPES AL_API void AL_APIENTRY alBufferCallbackSOFT(ALuint buffer, ALenum format, ALsizei freq, ALBUFFERCALLBACKTYPESOFT callback, ALvoid *userptr) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alGetBufferPtrSOFT(ALuint buffer, ALenum param, ALvoid **ptr) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alGetBuffer3PtrSOFT(ALuint buffer, ALenum param, ALvoid **ptr0, ALvoid **ptr1, ALvoid **ptr2) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alGetBufferPtrvSOFT(ALuint buffer, ALenum param, ALvoid **ptr) AL_API_NOEXCEPT; #endif #endif #ifndef AL_SOFT_UHJ #define AL_SOFT_UHJ #define AL_FORMAT_UHJ2CHN8_SOFT 0x19A2 #define AL_FORMAT_UHJ2CHN16_SOFT 0x19A3 #define AL_FORMAT_UHJ2CHN_FLOAT32_SOFT 0x19A4 #define AL_FORMAT_UHJ3CHN8_SOFT 0x19A5 #define AL_FORMAT_UHJ3CHN16_SOFT 0x19A6 #define AL_FORMAT_UHJ3CHN_FLOAT32_SOFT 0x19A7 #define AL_FORMAT_UHJ4CHN8_SOFT 0x19A8 #define AL_FORMAT_UHJ4CHN16_SOFT 0x19A9 #define AL_FORMAT_UHJ4CHN_FLOAT32_SOFT 0x19AA #define AL_STEREO_MODE_SOFT 0x19B0 #define AL_NORMAL_SOFT 0x0000 #define AL_SUPER_STEREO_SOFT 0x0001 #define AL_SUPER_STEREO_WIDTH_SOFT 0x19B1 #endif #ifndef AL_SOFT_UHJ_ex #define AL_SOFT_UHJ_ex #define AL_FORMAT_UHJ2CHN_MULAW_SOFT 0x19B3 #define AL_FORMAT_UHJ2CHN_ALAW_SOFT 0x19B4 #define AL_FORMAT_UHJ2CHN_IMA4_SOFT 0x19B5 #define AL_FORMAT_UHJ2CHN_MSADPCM_SOFT 0x19B6 #define AL_FORMAT_UHJ3CHN_MULAW_SOFT 0x19B7 #define AL_FORMAT_UHJ3CHN_ALAW_SOFT 0x19B8 #define AL_FORMAT_UHJ4CHN_MULAW_SOFT 0x19B9 #define AL_FORMAT_UHJ4CHN_ALAW_SOFT 0x19BA #endif #ifndef ALC_SOFT_output_mode #define ALC_SOFT_output_mode #define ALC_OUTPUT_MODE_SOFT 0x19AC #define ALC_ANY_SOFT 0x19AD /*#define ALC_MONO_SOFT 0x1500*/ /*#define ALC_STEREO_SOFT 0x1501*/ #define ALC_STEREO_BASIC_SOFT 0x19AE #define ALC_STEREO_UHJ_SOFT 0x19AF #define ALC_STEREO_HRTF_SOFT 0x19B2 /*#define ALC_QUAD_SOFT 0x1503*/ #define ALC_SURROUND_5_1_SOFT 0x1504 #define ALC_SURROUND_6_1_SOFT 0x1505 #define ALC_SURROUND_7_1_SOFT 0x1506 #endif #ifndef AL_SOFT_source_start_delay #define AL_SOFT_source_start_delay typedef void (AL_APIENTRY*LPALSOURCEPLAYATTIMESOFT)(ALuint source, ALint64SOFT start_time) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY*LPALSOURCEPLAYATTIMEVSOFT)(ALsizei n, const ALuint *sources, ALint64SOFT start_time) AL_API_NOEXCEPT17; #ifdef AL_ALEXT_PROTOTYPES void AL_APIENTRY alSourcePlayAtTimeSOFT(ALuint source, ALint64SOFT start_time) AL_API_NOEXCEPT; void AL_APIENTRY alSourcePlayAtTimevSOFT(ALsizei n, const ALuint *sources, ALint64SOFT start_time) AL_API_NOEXCEPT; #endif #endif #ifndef ALC_EXT_debug #define ALC_EXT_debug #define ALC_CONTEXT_FLAGS_EXT 0x19CF #define ALC_CONTEXT_DEBUG_BIT_EXT 0x0001 #endif #ifndef AL_EXT_debug #define AL_EXT_debug #define AL_DONT_CARE_EXT 0x0002 #define AL_DEBUG_OUTPUT_EXT 0x19B2 #define AL_DEBUG_CALLBACK_FUNCTION_EXT 0x19B3 #define AL_DEBUG_CALLBACK_USER_PARAM_EXT 0x19B4 #define AL_DEBUG_SOURCE_API_EXT 0x19B5 #define AL_DEBUG_SOURCE_AUDIO_SYSTEM_EXT 0x19B6 #define AL_DEBUG_SOURCE_THIRD_PARTY_EXT 0x19B7 #define AL_DEBUG_SOURCE_APPLICATION_EXT 0x19B8 #define AL_DEBUG_SOURCE_OTHER_EXT 0x19B9 #define AL_DEBUG_TYPE_ERROR_EXT 0x19BA #define AL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_EXT 0x19BB #define AL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_EXT 0x19BC #define AL_DEBUG_TYPE_PORTABILITY_EXT 0x19BD #define AL_DEBUG_TYPE_PERFORMANCE_EXT 0x19BE #define AL_DEBUG_TYPE_MARKER_EXT 0x19BF #define AL_DEBUG_TYPE_PUSH_GROUP_EXT 0x19C0 #define AL_DEBUG_TYPE_POP_GROUP_EXT 0x19C1 #define AL_DEBUG_TYPE_OTHER_EXT 0x19C2 #define AL_DEBUG_SEVERITY_HIGH_EXT 0x19C3 #define AL_DEBUG_SEVERITY_MEDIUM_EXT 0x19C4 #define AL_DEBUG_SEVERITY_LOW_EXT 0x19C5 #define AL_DEBUG_SEVERITY_NOTIFICATION_EXT 0x19C6 #define AL_DEBUG_LOGGED_MESSAGES_EXT 0x19C7 #define AL_DEBUG_NEXT_LOGGED_MESSAGE_LENGTH_EXT 0x19C8 #define AL_MAX_DEBUG_MESSAGE_LENGTH_EXT 0x19C9 #define AL_MAX_DEBUG_LOGGED_MESSAGES_EXT 0x19CA #define AL_MAX_DEBUG_GROUP_STACK_DEPTH_EXT 0x19CB #define AL_MAX_LABEL_LENGTH_EXT 0x19CC #define AL_STACK_OVERFLOW_EXT 0x19CD #define AL_STACK_UNDERFLOW_EXT 0x19CE #define AL_CONTEXT_FLAGS_EXT 0x19CF #define AL_BUFFER_EXT 0x1009 /* Same as AL_BUFFER */ #define AL_SOURCE_EXT 0x19D0 #define AL_FILTER_EXT 0x19D1 #define AL_EFFECT_EXT 0x19D2 #define AL_AUXILIARY_EFFECT_SLOT_EXT 0x19D3 typedef void (AL_APIENTRY*ALDEBUGPROCEXT)(ALenum source, ALenum type, ALuint id, ALenum severity, ALsizei length, const ALchar *message, void *userParam) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY*LPALDEBUGMESSAGECALLBACKEXT)(ALDEBUGPROCEXT callback, void *userParam) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY*LPALDEBUGMESSAGEINSERTEXT)(ALenum source, ALenum type, ALuint id, ALenum severity, ALsizei length, const ALchar *message) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY*LPALDEBUGMESSAGECONTROLEXT)(ALenum source, ALenum type, ALenum severity, ALsizei count, const ALuint *ids, ALboolean enable) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY*LPALPUSHDEBUGGROUPEXT)(ALenum source, ALuint id, ALsizei length, const ALchar *message) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY*LPALPOPDEBUGGROUPEXT)(void) AL_API_NOEXCEPT17; typedef ALuint (AL_APIENTRY*LPALGETDEBUGMESSAGELOGEXT)(ALuint count, ALsizei logBufSize, ALenum *sources, ALenum *types, ALuint *ids, ALenum *severities, ALsizei *lengths, ALchar *logBuf) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY*LPALOBJECTLABELEXT)(ALenum identifier, ALuint name, ALsizei length, const ALchar *label) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY*LPALGETOBJECTLABELEXT)(ALenum identifier, ALuint name, ALsizei bufSize, ALsizei *length, ALchar *label) AL_API_NOEXCEPT17; typedef void* (AL_APIENTRY*LPALGETPOINTEREXT)(ALenum pname) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY*LPALGETPOINTERVEXT)(ALenum pname, void **values) AL_API_NOEXCEPT17; #ifdef AL_ALEXT_PROTOTYPES void AL_APIENTRY alDebugMessageCallbackEXT(ALDEBUGPROCEXT callback, void *userParam) AL_API_NOEXCEPT; void AL_APIENTRY alDebugMessageInsertEXT(ALenum source, ALenum type, ALuint id, ALenum severity, ALsizei length, const ALchar *message) AL_API_NOEXCEPT; void AL_APIENTRY alDebugMessageControlEXT(ALenum source, ALenum type, ALenum severity, ALsizei count, const ALuint *ids, ALboolean enable) AL_API_NOEXCEPT; void AL_APIENTRY alPushDebugGroupEXT(ALenum source, ALuint id, ALsizei length, const ALchar *message) AL_API_NOEXCEPT; void AL_APIENTRY alPopDebugGroupEXT(void) AL_API_NOEXCEPT; ALuint AL_APIENTRY alGetDebugMessageLogEXT(ALuint count, ALsizei logBufSize, ALenum *sources, ALenum *types, ALuint *ids, ALenum *severities, ALsizei *lengths, ALchar *logBuf) AL_API_NOEXCEPT; void AL_APIENTRY alObjectLabelEXT(ALenum identifier, ALuint name, ALsizei length, const ALchar *label) AL_API_NOEXCEPT; void AL_APIENTRY alGetObjectLabelEXT(ALenum identifier, ALuint name, ALsizei bufSize, ALsizei *length, ALchar *label) AL_API_NOEXCEPT; void* AL_APIENTRY alGetPointerEXT(ALenum pname) AL_API_NOEXCEPT; void AL_APIENTRY alGetPointervEXT(ALenum pname, void **values) AL_API_NOEXCEPT; #endif #endif #ifndef ALC_SOFT_system_events #define ALC_SOFT_system_events #define ALC_PLAYBACK_DEVICE_SOFT 0x19D4 #define ALC_CAPTURE_DEVICE_SOFT 0x19D5 #define ALC_EVENT_TYPE_DEFAULT_DEVICE_CHANGED_SOFT 0x19D6 #define ALC_EVENT_TYPE_DEVICE_ADDED_SOFT 0x19D7 #define ALC_EVENT_TYPE_DEVICE_REMOVED_SOFT 0x19D8 #define ALC_EVENT_SUPPORTED_SOFT 0x19D9 #define ALC_EVENT_NOT_SUPPORTED_SOFT 0x19DA typedef void (ALC_APIENTRY*ALCEVENTPROCTYPESOFT)(ALCenum eventType, ALCenum deviceType, ALCdevice *device, ALCsizei length, const ALCchar *message, void *userParam) ALC_API_NOEXCEPT17; typedef ALCenum (ALC_APIENTRY*LPALCEVENTISSUPPORTEDSOFT)(ALCenum eventType, ALCenum deviceType) ALC_API_NOEXCEPT17; typedef ALCboolean (ALC_APIENTRY*LPALCEVENTCONTROLSOFT)(ALCsizei count, const ALCenum *events, ALCboolean enable) ALC_API_NOEXCEPT17; typedef void (ALC_APIENTRY*LPALCEVENTCALLBACKSOFT)(ALCEVENTPROCTYPESOFT callback, void *userParam) ALC_API_NOEXCEPT17; #ifdef AL_ALEXT_PROTOTYPES ALCenum ALC_APIENTRY alcEventIsSupportedSOFT(ALCenum eventType, ALCenum deviceType) ALC_API_NOEXCEPT; ALCboolean ALC_APIENTRY alcEventControlSOFT(ALCsizei count, const ALCenum *events, ALCboolean enable) ALC_API_NOEXCEPT; void ALC_APIENTRY alcEventCallbackSOFT(ALCEVENTPROCTYPESOFT callback, void *userParam) ALC_API_NOEXCEPT; #endif #endif #ifndef AL_EXT_direct_context #define AL_EXT_direct_context typedef ALCvoid* (ALC_APIENTRY *LPALCGETPROCADDRESS2)(ALCdevice *device, const ALCchar *funcname) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALENABLEDIRECT)(ALCcontext *context, ALenum capability) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALDISABLEDIRECT)(ALCcontext *context, ALenum capability) AL_API_NOEXCEPT17; typedef ALboolean (AL_APIENTRY *LPALISENABLEDDIRECT)(ALCcontext *context, ALenum capability) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALDOPPLERFACTORDIRECT)(ALCcontext *context, ALfloat value) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALSPEEDOFSOUNDDIRECT)(ALCcontext *context, ALfloat value) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALDISTANCEMODELDIRECT)(ALCcontext *context, ALenum distanceModel) AL_API_NOEXCEPT17; typedef const ALchar* (AL_APIENTRY *LPALGETSTRINGDIRECT)(ALCcontext *context, ALenum param) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETBOOLEANVDIRECT)(ALCcontext *context, ALenum param, ALboolean *values) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETINTEGERVDIRECT)(ALCcontext *context, ALenum param, ALint *values) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETFLOATVDIRECT)(ALCcontext *context, ALenum param, ALfloat *values) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETDOUBLEVDIRECT)(ALCcontext *context, ALenum param, ALdouble *values) AL_API_NOEXCEPT17; typedef ALboolean (AL_APIENTRY *LPALGETBOOLEANDIRECT)(ALCcontext *context, ALenum param) AL_API_NOEXCEPT17; typedef ALint (AL_APIENTRY *LPALGETINTEGERDIRECT)(ALCcontext *context, ALenum param) AL_API_NOEXCEPT17; typedef ALfloat (AL_APIENTRY *LPALGETFLOATDIRECT)(ALCcontext *context, ALenum param) AL_API_NOEXCEPT17; typedef ALdouble (AL_APIENTRY *LPALGETDOUBLEDIRECT)(ALCcontext *context, ALenum param) AL_API_NOEXCEPT17; typedef ALenum (AL_APIENTRY *LPALGETERRORDIRECT)(ALCcontext *context) AL_API_NOEXCEPT17; typedef ALboolean (AL_APIENTRY *LPALISEXTENSIONPRESENTDIRECT)(ALCcontext *context, const ALchar *extname) AL_API_NOEXCEPT17; typedef void* (AL_APIENTRY *LPALGETPROCADDRESSDIRECT)(ALCcontext *context, const ALchar *fname) AL_API_NOEXCEPT17; typedef ALenum (AL_APIENTRY *LPALGETENUMVALUEDIRECT)(ALCcontext *context, const ALchar *ename) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALLISTENERFDIRECT)(ALCcontext *context, ALenum param, ALfloat value) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALLISTENER3FDIRECT)(ALCcontext *context, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALLISTENERFVDIRECT)(ALCcontext *context, ALenum param, const ALfloat *values) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALLISTENERIDIRECT)(ALCcontext *context, ALenum param, ALint value) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALLISTENER3IDIRECT)(ALCcontext *context, ALenum param, ALint value1, ALint value2, ALint value3) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALLISTENERIVDIRECT)(ALCcontext *context, ALenum param, const ALint *values) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETLISTENERFDIRECT)(ALCcontext *context, ALenum param, ALfloat *value) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETLISTENER3FDIRECT)(ALCcontext *context, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETLISTENERFVDIRECT)(ALCcontext *context, ALenum param, ALfloat *values) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETLISTENERIDIRECT)(ALCcontext *context, ALenum param, ALint *value) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETLISTENER3IDIRECT)(ALCcontext *context, ALenum param, ALint *value1, ALint *value2, ALint *value3) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETLISTENERIVDIRECT)(ALCcontext *context, ALenum param, ALint *values) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGENSOURCESDIRECT)(ALCcontext *context, ALsizei n, ALuint *sources) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALDELETESOURCESDIRECT)(ALCcontext *context, ALsizei n, const ALuint *sources) AL_API_NOEXCEPT17; typedef ALboolean (AL_APIENTRY *LPALISSOURCEDIRECT)(ALCcontext *context, ALuint source) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALSOURCEFDIRECT)(ALCcontext *context, ALuint source, ALenum param, ALfloat value) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALSOURCE3FDIRECT)(ALCcontext *context, ALuint source, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALSOURCEFVDIRECT)(ALCcontext *context, ALuint source, ALenum param, const ALfloat *values) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALSOURCEIDIRECT)(ALCcontext *context, ALuint source, ALenum param, ALint value) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALSOURCE3IDIRECT)(ALCcontext *context, ALuint source, ALenum param, ALint value1, ALint value2, ALint value3) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALSOURCEIVDIRECT)(ALCcontext *context, ALuint source, ALenum param, const ALint *values) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETSOURCEFDIRECT)(ALCcontext *context, ALuint source, ALenum param, ALfloat *value) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETSOURCE3FDIRECT)(ALCcontext *context, ALuint source, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETSOURCEFVDIRECT)(ALCcontext *context, ALuint source, ALenum param, ALfloat *values) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETSOURCEIDIRECT)(ALCcontext *context, ALuint source, ALenum param, ALint *value) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETSOURCE3IDIRECT)(ALCcontext *context, ALuint source, ALenum param, ALint *value1, ALint *value2, ALint *value3) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETSOURCEIVDIRECT)(ALCcontext *context, ALuint source, ALenum param, ALint *values) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALSOURCEPLAYDIRECT)(ALCcontext *context, ALuint source) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALSOURCESTOPDIRECT)(ALCcontext *context, ALuint source) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALSOURCEREWINDDIRECT)(ALCcontext *context, ALuint source) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALSOURCEPAUSEDIRECT)(ALCcontext *context, ALuint source) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALSOURCEPLAYVDIRECT)(ALCcontext *context, ALsizei n, const ALuint *sources) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALSOURCESTOPVDIRECT)(ALCcontext *context, ALsizei n, const ALuint *sources) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALSOURCEREWINDVDIRECT)(ALCcontext *context, ALsizei n, const ALuint *sources) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALSOURCEPAUSEVDIRECT)(ALCcontext *context, ALsizei n, const ALuint *sources) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALSOURCEQUEUEBUFFERSDIRECT)(ALCcontext *context, ALuint source, ALsizei nb, const ALuint *buffers) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALSOURCEUNQUEUEBUFFERSDIRECT)(ALCcontext *context, ALuint source, ALsizei nb, ALuint *buffers) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGENBUFFERSDIRECT)(ALCcontext *context, ALsizei n, ALuint *buffers) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALDELETEBUFFERSDIRECT)(ALCcontext *context, ALsizei n, const ALuint *buffers) AL_API_NOEXCEPT17; typedef ALboolean (AL_APIENTRY *LPALISBUFFERDIRECT)(ALCcontext *context, ALuint buffer) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALBUFFERDATADIRECT)(ALCcontext *context, ALuint buffer, ALenum format, const ALvoid *data, ALsizei size, ALsizei samplerate) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALBUFFERFDIRECT)(ALCcontext *context, ALuint buffer, ALenum param, ALfloat value) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALBUFFER3FDIRECT)(ALCcontext *context, ALuint buffer, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALBUFFERFVDIRECT)(ALCcontext *context, ALuint buffer, ALenum param, const ALfloat *values) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALBUFFERIDIRECT)(ALCcontext *context, ALuint buffer, ALenum param, ALint value) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALBUFFER3IDIRECT)(ALCcontext *context, ALuint buffer, ALenum param, ALint value1, ALint value2, ALint value3) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALBUFFERIVDIRECT)(ALCcontext *context, ALuint buffer, ALenum param, const ALint *values) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETBUFFERFDIRECT)(ALCcontext *context, ALuint buffer, ALenum param, ALfloat *value) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETBUFFER3FDIRECT)(ALCcontext *context, ALuint buffer, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETBUFFERFVDIRECT)(ALCcontext *context, ALuint buffer, ALenum param, ALfloat *values) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETBUFFERIDIRECT)(ALCcontext *context, ALuint buffer, ALenum param, ALint *value) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETBUFFER3IDIRECT)(ALCcontext *context, ALuint buffer, ALenum param, ALint *value1, ALint *value2, ALint *value3) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETBUFFERIVDIRECT)(ALCcontext *context, ALuint buffer, ALenum param, ALint *values) AL_API_NOEXCEPT17; /* ALC_EXT_EFX */ typedef void (AL_APIENTRY *LPALGENEFFECTSDIRECT)(ALCcontext *context, ALsizei n, ALuint *effects) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALDELETEEFFECTSDIRECT)(ALCcontext *context, ALsizei n, const ALuint *effects) AL_API_NOEXCEPT17; typedef ALboolean (AL_APIENTRY *LPALISEFFECTDIRECT)(ALCcontext *context, ALuint effect) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALEFFECTIDIRECT)(ALCcontext *context, ALuint effect, ALenum param, ALint value) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALEFFECTIVDIRECT)(ALCcontext *context, ALuint effect, ALenum param, const ALint *values) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALEFFECTFDIRECT)(ALCcontext *context, ALuint effect, ALenum param, ALfloat value) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALEFFECTFVDIRECT)(ALCcontext *context, ALuint effect, ALenum param, const ALfloat *values) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETEFFECTIDIRECT)(ALCcontext *context, ALuint effect, ALenum param, ALint *value) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETEFFECTIVDIRECT)(ALCcontext *context, ALuint effect, ALenum param, ALint *values) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETEFFECTFDIRECT)(ALCcontext *context, ALuint effect, ALenum param, ALfloat *value) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETEFFECTFVDIRECT)(ALCcontext *context, ALuint effect, ALenum param, ALfloat *values) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGENFILTERSDIRECT)(ALCcontext *context, ALsizei n, ALuint *filters) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALDELETEFILTERSDIRECT)(ALCcontext *context, ALsizei n, const ALuint *filters) AL_API_NOEXCEPT17; typedef ALboolean (AL_APIENTRY *LPALISFILTERDIRECT)(ALCcontext *context, ALuint filter) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALFILTERIDIRECT)(ALCcontext *context, ALuint filter, ALenum param, ALint value) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALFILTERIVDIRECT)(ALCcontext *context, ALuint filter, ALenum param, const ALint *values) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALFILTERFDIRECT)(ALCcontext *context, ALuint filter, ALenum param, ALfloat value) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALFILTERFVDIRECT)(ALCcontext *context, ALuint filter, ALenum param, const ALfloat *values) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETFILTERIDIRECT)(ALCcontext *context, ALuint filter, ALenum param, ALint *value) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETFILTERIVDIRECT)(ALCcontext *context, ALuint filter, ALenum param, ALint *values) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETFILTERFDIRECT)(ALCcontext *context, ALuint filter, ALenum param, ALfloat *value) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETFILTERFVDIRECT)(ALCcontext *context, ALuint filter, ALenum param, ALfloat *values) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGENAUXILIARYEFFECTSLOTSDIRECT)(ALCcontext *context, ALsizei n, ALuint *effectslots) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALDELETEAUXILIARYEFFECTSLOTSDIRECT)(ALCcontext *context, ALsizei n, const ALuint *effectslots) AL_API_NOEXCEPT17; typedef ALboolean (AL_APIENTRY *LPALISAUXILIARYEFFECTSLOTDIRECT)(ALCcontext *context, ALuint effectslot) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALAUXILIARYEFFECTSLOTIDIRECT)(ALCcontext *context, ALuint effectslot, ALenum param, ALint value) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALAUXILIARYEFFECTSLOTIVDIRECT)(ALCcontext *context, ALuint effectslot, ALenum param, const ALint *values) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALAUXILIARYEFFECTSLOTFDIRECT)(ALCcontext *context, ALuint effectslot, ALenum param, ALfloat value) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALAUXILIARYEFFECTSLOTFVDIRECT)(ALCcontext *context, ALuint effectslot, ALenum param, const ALfloat *values) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETAUXILIARYEFFECTSLOTIDIRECT)(ALCcontext *context, ALuint effectslot, ALenum param, ALint *value) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETAUXILIARYEFFECTSLOTIVDIRECT)(ALCcontext *context, ALuint effectslot, ALenum param, ALint *values) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETAUXILIARYEFFECTSLOTFDIRECT)(ALCcontext *context, ALuint effectslot, ALenum param, ALfloat *value) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETAUXILIARYEFFECTSLOTFVDIRECT)(ALCcontext *context, ALuint effectslot, ALenum param, ALfloat *values) AL_API_NOEXCEPT17; /* AL_EXT_BUFFER_DATA_STATIC */ typedef void (AL_APIENTRY *LPALBUFFERDATASTATICDIRECT)(ALCcontext *context, ALuint buffer, ALenum format, ALvoid *data, ALsizei size, ALsizei freq) AL_API_NOEXCEPT17; /* AL_EXT_debug */ typedef void (AL_APIENTRY*LPALDEBUGMESSAGECALLBACKDIRECTEXT)(ALCcontext *context, ALDEBUGPROCEXT callback, void *userParam) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY*LPALDEBUGMESSAGEINSERTDIRECTEXT)(ALCcontext *context, ALenum source, ALenum type, ALuint id, ALenum severity, ALsizei length, const ALchar *message) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY*LPALDEBUGMESSAGECONTROLDIRECTEXT)(ALCcontext *context, ALenum source, ALenum type, ALenum severity, ALsizei count, const ALuint *ids, ALboolean enable) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY*LPALPUSHDEBUGGROUPDIRECTEXT)(ALCcontext *context, ALenum source, ALuint id, ALsizei length, const ALchar *message) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY*LPALPOPDEBUGGROUPDIRECTEXT)(ALCcontext *context) AL_API_NOEXCEPT17; typedef ALuint (AL_APIENTRY*LPALGETDEBUGMESSAGELOGDIRECTEXT)(ALCcontext *context, ALuint count, ALsizei logBufSize, ALenum *sources, ALenum *types, ALuint *ids, ALenum *severities, ALsizei *lengths, ALchar *logBuf) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY*LPALOBJECTLABELDIRECTEXT)(ALCcontext *context, ALenum identifier, ALuint name, ALsizei length, const ALchar *label) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY*LPALGETOBJECTLABELDIRECTEXT)(ALCcontext *context, ALenum identifier, ALuint name, ALsizei bufSize, ALsizei *length, ALchar *label) AL_API_NOEXCEPT17; typedef void* (AL_APIENTRY*LPALGETPOINTERDIRECTEXT)(ALCcontext *context, ALenum pname) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY*LPALGETPOINTERVDIRECTEXT)(ALCcontext *context, ALenum pname, void **values) AL_API_NOEXCEPT17; /* AL_EXT_FOLDBACK */ typedef void (AL_APIENTRY *LPALREQUESTFOLDBACKSTARTDIRECT)(ALCcontext *context, ALenum mode, ALsizei count, ALsizei length, ALfloat *mem, LPALFOLDBACKCALLBACK callback) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALREQUESTFOLDBACKSTOPDIRECT)(ALCcontext *context) AL_API_NOEXCEPT17; /* AL_SOFT_buffer_sub_data */ typedef void (AL_APIENTRY *LPALBUFFERSUBDATADIRECTSOFT)(ALCcontext *context, ALuint buffer, ALenum format, const ALvoid *data, ALsizei offset, ALsizei length) AL_API_NOEXCEPT17; /* AL_SOFT_source_latency */ typedef void (AL_APIENTRY *LPALSOURCEDDIRECTSOFT)(ALCcontext*,ALuint,ALenum,ALdouble) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALSOURCE3DDIRECTSOFT)(ALCcontext*,ALuint,ALenum,ALdouble,ALdouble,ALdouble) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALSOURCEDVDIRECTSOFT)(ALCcontext*,ALuint,ALenum,const ALdouble*) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETSOURCEDDIRECTSOFT)(ALCcontext*,ALuint,ALenum,ALdouble*) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETSOURCE3DDIRECTSOFT)(ALCcontext*,ALuint,ALenum,ALdouble*,ALdouble*,ALdouble*) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETSOURCEDVDIRECTSOFT)(ALCcontext*,ALuint,ALenum,ALdouble*) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALSOURCEI64DIRECTSOFT)(ALCcontext*,ALuint,ALenum,ALint64SOFT) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALSOURCE3I64DIRECTSOFT)(ALCcontext*,ALuint,ALenum,ALint64SOFT,ALint64SOFT,ALint64SOFT) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALSOURCEI64VDIRECTSOFT)(ALCcontext*,ALuint,ALenum,const ALint64SOFT*) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETSOURCEI64DIRECTSOFT)(ALCcontext*,ALuint,ALenum,ALint64SOFT*) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETSOURCE3I64DIRECTSOFT)(ALCcontext*,ALuint,ALenum,ALint64SOFT*,ALint64SOFT*,ALint64SOFT*) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETSOURCEI64VDIRECTSOFT)(ALCcontext*,ALuint,ALenum,ALint64SOFT*) AL_API_NOEXCEPT17; /* AL_SOFT_deferred_updates */ typedef void (AL_APIENTRY *LPALDEFERUPDATESDIRECTSOFT)(ALCcontext *context) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALPROCESSUPDATESDIRECTSOFT)(ALCcontext *context) AL_API_NOEXCEPT17; /* AL_SOFT_source_resampler */ typedef const ALchar* (AL_APIENTRY *LPALGETSTRINGIDIRECTSOFT)(ALCcontext *context, ALenum pname, ALsizei index) AL_API_NOEXCEPT17; /* AL_SOFT_events */ typedef void (AL_APIENTRY *LPALEVENTCONTROLDIRECTSOFT)(ALCcontext *context, ALsizei count, const ALenum *types, ALboolean enable) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALEVENTCALLBACKDIRECTSOFT)(ALCcontext *context, ALEVENTPROCSOFT callback, void *userParam) AL_API_NOEXCEPT17; typedef void* (AL_APIENTRY *LPALGETPOINTERDIRECTSOFT)(ALCcontext *context, ALenum pname) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETPOINTERVDIRECTSOFT)(ALCcontext *context, ALenum pname, void **values) AL_API_NOEXCEPT17; /* AL_SOFT_callback_buffer */ typedef void (AL_APIENTRY *LPALBUFFERCALLBACKDIRECTSOFT)(ALCcontext *context, ALuint buffer, ALenum format, ALsizei freq, ALBUFFERCALLBACKTYPESOFT callback, ALvoid *userptr) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETBUFFERPTRDIRECTSOFT)(ALCcontext *context, ALuint buffer, ALenum param, ALvoid **value) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETBUFFER3PTRDIRECTSOFT)(ALCcontext *context, ALuint buffer, ALenum param, ALvoid **value1, ALvoid **value2, ALvoid **value3) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETBUFFERPTRVDIRECTSOFT)(ALCcontext *context, ALuint buffer, ALenum param, ALvoid **values) AL_API_NOEXCEPT17; /* AL_SOFT_source_start_delay */ typedef void (AL_APIENTRY *LPALSOURCEPLAYATTIMEDIRECTSOFT)(ALCcontext *context, ALuint source, ALint64SOFT start_time) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALSOURCEPLAYATTIMEVDIRECTSOFT)(ALCcontext *context, ALsizei n, const ALuint *sources, ALint64SOFT start_time) AL_API_NOEXCEPT17; /* EAX */ typedef ALenum (AL_APIENTRY *LPEAXSETDIRECT)(ALCcontext *context, const struct _GUID *property_set_id, ALuint property_id, ALuint source_id, ALvoid *value, ALuint value_size) AL_API_NOEXCEPT17; typedef ALenum (AL_APIENTRY *LPEAXGETDIRECT)(ALCcontext *context, const struct _GUID *property_set_id, ALuint property_id, ALuint source_id, ALvoid *value, ALuint value_size) AL_API_NOEXCEPT17; typedef ALboolean (AL_APIENTRY *LPEAXSETBUFFERMODEDIRECT)(ALCcontext *context, ALsizei n, const ALuint *buffers, ALint value) AL_API_NOEXCEPT17; typedef ALenum (AL_APIENTRY *LPEAXGETBUFFERMODEDIRECT)(ALCcontext *context, ALuint buffer, ALint *pReserved) AL_API_NOEXCEPT17; #ifdef AL_ALEXT_PROTOTYPES ALCvoid* ALC_APIENTRY alcGetProcAddress2(ALCdevice *device, const ALCchar *funcName) AL_API_NOEXCEPT; void AL_APIENTRY alEnableDirect(ALCcontext *context, ALenum capability) AL_API_NOEXCEPT; void AL_APIENTRY alDisableDirect(ALCcontext *context, ALenum capability) AL_API_NOEXCEPT; ALboolean AL_APIENTRY alIsEnabledDirect(ALCcontext *context, ALenum capability) AL_API_NOEXCEPT; void AL_APIENTRY alDopplerFactorDirect(ALCcontext *context, ALfloat value) AL_API_NOEXCEPT; void AL_APIENTRY alSpeedOfSoundDirect(ALCcontext *context, ALfloat value) AL_API_NOEXCEPT; void AL_APIENTRY alDistanceModelDirect(ALCcontext *context, ALenum distanceModel) AL_API_NOEXCEPT; const ALchar* AL_APIENTRY alGetStringDirect(ALCcontext *context, ALenum param) AL_API_NOEXCEPT; void AL_APIENTRY alGetBooleanvDirect(ALCcontext *context, ALenum param, ALboolean *values) AL_API_NOEXCEPT; void AL_APIENTRY alGetIntegervDirect(ALCcontext *context, ALenum param, ALint *values) AL_API_NOEXCEPT; void AL_APIENTRY alGetFloatvDirect(ALCcontext *context, ALenum param, ALfloat *values) AL_API_NOEXCEPT; void AL_APIENTRY alGetDoublevDirect(ALCcontext *context, ALenum param, ALdouble *values) AL_API_NOEXCEPT; ALboolean AL_APIENTRY alGetBooleanDirect(ALCcontext *context, ALenum param) AL_API_NOEXCEPT; ALint AL_APIENTRY alGetIntegerDirect(ALCcontext *context, ALenum param) AL_API_NOEXCEPT; ALfloat AL_APIENTRY alGetFloatDirect(ALCcontext *context, ALenum param) AL_API_NOEXCEPT; ALdouble AL_APIENTRY alGetDoubleDirect(ALCcontext *context, ALenum param) AL_API_NOEXCEPT; ALenum AL_APIENTRY alGetErrorDirect(ALCcontext *context) AL_API_NOEXCEPT; ALboolean AL_APIENTRY alIsExtensionPresentDirect(ALCcontext *context, const ALchar *extname) AL_API_NOEXCEPT; void* AL_APIENTRY alGetProcAddressDirect(ALCcontext *context, const ALchar *fname) AL_API_NOEXCEPT; ALenum AL_APIENTRY alGetEnumValueDirect(ALCcontext *context, const ALchar *ename) AL_API_NOEXCEPT; void AL_APIENTRY alListenerfDirect(ALCcontext *context, ALenum param, ALfloat value) AL_API_NOEXCEPT; void AL_APIENTRY alListener3fDirect(ALCcontext *context, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3) AL_API_NOEXCEPT; void AL_APIENTRY alListenerfvDirect(ALCcontext *context, ALenum param, const ALfloat *values) AL_API_NOEXCEPT; void AL_APIENTRY alListeneriDirect(ALCcontext *context, ALenum param, ALint value) AL_API_NOEXCEPT; void AL_APIENTRY alListener3iDirect(ALCcontext *context, ALenum param, ALint value1, ALint value2, ALint value3) AL_API_NOEXCEPT; void AL_APIENTRY alListenerivDirect(ALCcontext *context, ALenum param, const ALint *values) AL_API_NOEXCEPT; void AL_APIENTRY alGetListenerfDirect(ALCcontext *context, ALenum param, ALfloat *value) AL_API_NOEXCEPT; void AL_APIENTRY alGetListener3fDirect(ALCcontext *context, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3) AL_API_NOEXCEPT; void AL_APIENTRY alGetListenerfvDirect(ALCcontext *context, ALenum param, ALfloat *values) AL_API_NOEXCEPT; void AL_APIENTRY alGetListeneriDirect(ALCcontext *context, ALenum param, ALint *value) AL_API_NOEXCEPT; void AL_APIENTRY alGetListener3iDirect(ALCcontext *context, ALenum param, ALint *value1, ALint *value2, ALint *value3) AL_API_NOEXCEPT; void AL_APIENTRY alGetListenerivDirect(ALCcontext *context, ALenum param, ALint *values) AL_API_NOEXCEPT; void AL_APIENTRY alGenSourcesDirect(ALCcontext *context, ALsizei n, ALuint *sources) AL_API_NOEXCEPT; void AL_APIENTRY alDeleteSourcesDirect(ALCcontext *context, ALsizei n, const ALuint *sources) AL_API_NOEXCEPT; ALboolean AL_APIENTRY alIsSourceDirect(ALCcontext *context, ALuint source) AL_API_NOEXCEPT; void AL_APIENTRY alSourcefDirect(ALCcontext *context, ALuint source, ALenum param, ALfloat value) AL_API_NOEXCEPT; void AL_APIENTRY alSource3fDirect(ALCcontext *context, ALuint source, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3) AL_API_NOEXCEPT; void AL_APIENTRY alSourcefvDirect(ALCcontext *context, ALuint source, ALenum param, const ALfloat *values) AL_API_NOEXCEPT; void AL_APIENTRY alSourceiDirect(ALCcontext *context, ALuint source, ALenum param, ALint value) AL_API_NOEXCEPT; void AL_APIENTRY alSource3iDirect(ALCcontext *context, ALuint source, ALenum param, ALint value1, ALint value2, ALint value3) AL_API_NOEXCEPT; void AL_APIENTRY alSourceivDirect(ALCcontext *context, ALuint source, ALenum param, const ALint *values) AL_API_NOEXCEPT; void AL_APIENTRY alGetSourcefDirect(ALCcontext *context, ALuint source, ALenum param, ALfloat *value) AL_API_NOEXCEPT; void AL_APIENTRY alGetSource3fDirect(ALCcontext *context, ALuint source, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3) AL_API_NOEXCEPT; void AL_APIENTRY alGetSourcefvDirect(ALCcontext *context, ALuint source, ALenum param, ALfloat *values) AL_API_NOEXCEPT; void AL_APIENTRY alGetSourceiDirect(ALCcontext *context, ALuint source, ALenum param, ALint *value) AL_API_NOEXCEPT; void AL_APIENTRY alGetSource3iDirect(ALCcontext *context, ALuint source, ALenum param, ALint *value1, ALint *value2, ALint *value3) AL_API_NOEXCEPT; void AL_APIENTRY alGetSourceivDirect(ALCcontext *context, ALuint source, ALenum param, ALint *values) AL_API_NOEXCEPT; void AL_APIENTRY alSourcePlayDirect(ALCcontext *context, ALuint source) AL_API_NOEXCEPT; void AL_APIENTRY alSourceStopDirect(ALCcontext *context, ALuint source) AL_API_NOEXCEPT; void AL_APIENTRY alSourceRewindDirect(ALCcontext *context, ALuint source) AL_API_NOEXCEPT; void AL_APIENTRY alSourcePauseDirect(ALCcontext *context, ALuint source) AL_API_NOEXCEPT; void AL_APIENTRY alSourcePlayvDirect(ALCcontext *context, ALsizei n, const ALuint *sources) AL_API_NOEXCEPT; void AL_APIENTRY alSourceStopvDirect(ALCcontext *context, ALsizei n, const ALuint *sources) AL_API_NOEXCEPT; void AL_APIENTRY alSourceRewindvDirect(ALCcontext *context, ALsizei n, const ALuint *sources) AL_API_NOEXCEPT; void AL_APIENTRY alSourcePausevDirect(ALCcontext *context, ALsizei n, const ALuint *sources) AL_API_NOEXCEPT; void AL_APIENTRY alSourceQueueBuffersDirect(ALCcontext *context, ALuint source, ALsizei nb, const ALuint *buffers) AL_API_NOEXCEPT; void AL_APIENTRY alSourceUnqueueBuffersDirect(ALCcontext *context, ALuint source, ALsizei nb, ALuint *buffers) AL_API_NOEXCEPT; void AL_APIENTRY alGenBuffersDirect(ALCcontext *context, ALsizei n, ALuint *buffers) AL_API_NOEXCEPT; void AL_APIENTRY alDeleteBuffersDirect(ALCcontext *context, ALsizei n, const ALuint *buffers) AL_API_NOEXCEPT; ALboolean AL_APIENTRY alIsBufferDirect(ALCcontext *context, ALuint buffer) AL_API_NOEXCEPT; void AL_APIENTRY alBufferDataDirect(ALCcontext *context, ALuint buffer, ALenum format, const ALvoid *data, ALsizei size, ALsizei samplerate) AL_API_NOEXCEPT; void AL_APIENTRY alBufferfDirect(ALCcontext *context, ALuint buffer, ALenum param, ALfloat value) AL_API_NOEXCEPT; void AL_APIENTRY alBuffer3fDirect(ALCcontext *context, ALuint buffer, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3) AL_API_NOEXCEPT; void AL_APIENTRY alBufferfvDirect(ALCcontext *context, ALuint buffer, ALenum param, const ALfloat *values) AL_API_NOEXCEPT; void AL_APIENTRY alBufferiDirect(ALCcontext *context, ALuint buffer, ALenum param, ALint value) AL_API_NOEXCEPT; void AL_APIENTRY alBuffer3iDirect(ALCcontext *context, ALuint buffer, ALenum param, ALint value1, ALint value2, ALint value3) AL_API_NOEXCEPT; void AL_APIENTRY alBufferivDirect(ALCcontext *context, ALuint buffer, ALenum param, const ALint *values) AL_API_NOEXCEPT; void AL_APIENTRY alGetBufferfDirect(ALCcontext *context, ALuint buffer, ALenum param, ALfloat *value) AL_API_NOEXCEPT; void AL_APIENTRY alGetBuffer3fDirect(ALCcontext *context, ALuint buffer, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3) AL_API_NOEXCEPT; void AL_APIENTRY alGetBufferfvDirect(ALCcontext *context, ALuint buffer, ALenum param, ALfloat *values) AL_API_NOEXCEPT; void AL_APIENTRY alGetBufferiDirect(ALCcontext *context, ALuint buffer, ALenum param, ALint *value) AL_API_NOEXCEPT; void AL_APIENTRY alGetBuffer3iDirect(ALCcontext *context, ALuint buffer, ALenum param, ALint *value1, ALint *value2, ALint *value3) AL_API_NOEXCEPT; void AL_APIENTRY alGetBufferivDirect(ALCcontext *context, ALuint buffer, ALenum param, ALint *values) AL_API_NOEXCEPT; void AL_APIENTRY alGenEffectsDirect(ALCcontext *context, ALsizei n, ALuint *effects) AL_API_NOEXCEPT; void AL_APIENTRY alDeleteEffectsDirect(ALCcontext *context, ALsizei n, const ALuint *effects) AL_API_NOEXCEPT; ALboolean AL_APIENTRY alIsEffectDirect(ALCcontext *context, ALuint effect) AL_API_NOEXCEPT; void AL_APIENTRY alEffectiDirect(ALCcontext *context, ALuint effect, ALenum param, ALint iValue) AL_API_NOEXCEPT; void AL_APIENTRY alEffectivDirect(ALCcontext *context, ALuint effect, ALenum param, const ALint *piValues) AL_API_NOEXCEPT; void AL_APIENTRY alEffectfDirect(ALCcontext *context, ALuint effect, ALenum param, ALfloat flValue) AL_API_NOEXCEPT; void AL_APIENTRY alEffectfvDirect(ALCcontext *context, ALuint effect, ALenum param, const ALfloat *pflValues) AL_API_NOEXCEPT; void AL_APIENTRY alGetEffectiDirect(ALCcontext *context, ALuint effect, ALenum param, ALint *piValue) AL_API_NOEXCEPT; void AL_APIENTRY alGetEffectivDirect(ALCcontext *context, ALuint effect, ALenum param, ALint *piValues) AL_API_NOEXCEPT; void AL_APIENTRY alGetEffectfDirect(ALCcontext *context, ALuint effect, ALenum param, ALfloat *pflValue) AL_API_NOEXCEPT; void AL_APIENTRY alGetEffectfvDirect(ALCcontext *context, ALuint effect, ALenum param, ALfloat *pflValues) AL_API_NOEXCEPT; void AL_APIENTRY alGenFiltersDirect(ALCcontext *context, ALsizei n, ALuint *filters) AL_API_NOEXCEPT; void AL_APIENTRY alDeleteFiltersDirect(ALCcontext *context, ALsizei n, const ALuint *filters) AL_API_NOEXCEPT; ALboolean AL_APIENTRY alIsFilterDirect(ALCcontext *context, ALuint filter) AL_API_NOEXCEPT; void AL_APIENTRY alFilteriDirect(ALCcontext *context, ALuint filter, ALenum param, ALint iValue) AL_API_NOEXCEPT; void AL_APIENTRY alFilterivDirect(ALCcontext *context, ALuint filter, ALenum param, const ALint *piValues) AL_API_NOEXCEPT; void AL_APIENTRY alFilterfDirect(ALCcontext *context, ALuint filter, ALenum param, ALfloat flValue) AL_API_NOEXCEPT; void AL_APIENTRY alFilterfvDirect(ALCcontext *context, ALuint filter, ALenum param, const ALfloat *pflValues) AL_API_NOEXCEPT; void AL_APIENTRY alGetFilteriDirect(ALCcontext *context, ALuint filter, ALenum param, ALint *piValue) AL_API_NOEXCEPT; void AL_APIENTRY alGetFilterivDirect(ALCcontext *context, ALuint filter, ALenum param, ALint *piValues) AL_API_NOEXCEPT; void AL_APIENTRY alGetFilterfDirect(ALCcontext *context, ALuint filter, ALenum param, ALfloat *pflValue) AL_API_NOEXCEPT; void AL_APIENTRY alGetFilterfvDirect(ALCcontext *context, ALuint filter, ALenum param, ALfloat *pflValues) AL_API_NOEXCEPT; void AL_APIENTRY alGenAuxiliaryEffectSlotsDirect(ALCcontext *context, ALsizei n, ALuint *effectslots) AL_API_NOEXCEPT; void AL_APIENTRY alDeleteAuxiliaryEffectSlotsDirect(ALCcontext *context, ALsizei n, const ALuint *effectslots) AL_API_NOEXCEPT; ALboolean AL_APIENTRY alIsAuxiliaryEffectSlotDirect(ALCcontext *context, ALuint effectslot) AL_API_NOEXCEPT; void AL_APIENTRY alAuxiliaryEffectSlotiDirect(ALCcontext *context, ALuint effectslot, ALenum param, ALint iValue) AL_API_NOEXCEPT; void AL_APIENTRY alAuxiliaryEffectSlotivDirect(ALCcontext *context, ALuint effectslot, ALenum param, const ALint *piValues) AL_API_NOEXCEPT; void AL_APIENTRY alAuxiliaryEffectSlotfDirect(ALCcontext *context, ALuint effectslot, ALenum param, ALfloat flValue) AL_API_NOEXCEPT; void AL_APIENTRY alAuxiliaryEffectSlotfvDirect(ALCcontext *context, ALuint effectslot, ALenum param, const ALfloat *pflValues) AL_API_NOEXCEPT; void AL_APIENTRY alGetAuxiliaryEffectSlotiDirect(ALCcontext *context, ALuint effectslot, ALenum param, ALint *piValue) AL_API_NOEXCEPT; void AL_APIENTRY alGetAuxiliaryEffectSlotivDirect(ALCcontext *context, ALuint effectslot, ALenum param, ALint *piValues) AL_API_NOEXCEPT; void AL_APIENTRY alGetAuxiliaryEffectSlotfDirect(ALCcontext *context, ALuint effectslot, ALenum param, ALfloat *pflValue) AL_API_NOEXCEPT; void AL_APIENTRY alGetAuxiliaryEffectSlotfvDirect(ALCcontext *context, ALuint effectslot, ALenum param, ALfloat *pflValues) AL_API_NOEXCEPT; void AL_APIENTRY alBufferDataStaticDirect(ALCcontext *context, ALuint buffer, ALenum format, ALvoid *data, ALsizei size, ALsizei freq) AL_API_NOEXCEPT; void AL_APIENTRY alDebugMessageCallbackDirectEXT(ALCcontext *context, ALDEBUGPROCEXT callback, void *userParam) AL_API_NOEXCEPT; void AL_APIENTRY alDebugMessageInsertDirectEXT(ALCcontext *context, ALenum source, ALenum type, ALuint id, ALenum severity, ALsizei length, const ALchar *message) AL_API_NOEXCEPT; void AL_APIENTRY alDebugMessageControlDirectEXT(ALCcontext *context, ALenum source, ALenum type, ALenum severity, ALsizei count, const ALuint *ids, ALboolean enable) AL_API_NOEXCEPT; void AL_APIENTRY alPushDebugGroupDirectEXT(ALCcontext *context, ALenum source, ALuint id, ALsizei length, const ALchar *message) AL_API_NOEXCEPT; void AL_APIENTRY alPopDebugGroupDirectEXT(ALCcontext *context) AL_API_NOEXCEPT; ALuint AL_APIENTRY alGetDebugMessageLogDirectEXT(ALCcontext *context, ALuint count, ALsizei logBufSize, ALenum *sources, ALenum *types, ALuint *ids, ALenum *severities, ALsizei *lengths, ALchar *logBuf) AL_API_NOEXCEPT; void AL_APIENTRY alObjectLabelDirectEXT(ALCcontext *context, ALenum identifier, ALuint name, ALsizei length, const ALchar *label) AL_API_NOEXCEPT; void AL_APIENTRY alGetObjectLabelDirectEXT(ALCcontext *context, ALenum identifier, ALuint name, ALsizei bufSize, ALsizei *length, ALchar *label) AL_API_NOEXCEPT; void* AL_APIENTRY alGetPointerDirectEXT(ALCcontext *context, ALenum pname) AL_API_NOEXCEPT; void AL_APIENTRY alGetPointervDirectEXT(ALCcontext *context, ALenum pname, void **values) AL_API_NOEXCEPT; void AL_APIENTRY alRequestFoldbackStartDirect(ALCcontext *context, ALenum mode, ALsizei count, ALsizei length, ALfloat *mem, LPALFOLDBACKCALLBACK callback) AL_API_NOEXCEPT; void AL_APIENTRY alRequestFoldbackStopDirect(ALCcontext *context) AL_API_NOEXCEPT; void AL_APIENTRY alBufferSubDataDirectSOFT(ALCcontext *context, ALuint buffer, ALenum format, const ALvoid *data, ALsizei offset, ALsizei length) AL_API_NOEXCEPT; void AL_APIENTRY alSourcedDirectSOFT(ALCcontext *context, ALuint source, ALenum param, ALdouble value) AL_API_NOEXCEPT; void AL_APIENTRY alSource3dDirectSOFT(ALCcontext *context, ALuint source, ALenum param, ALdouble value1, ALdouble value2, ALdouble value3) AL_API_NOEXCEPT; void AL_APIENTRY alSourcedvDirectSOFT(ALCcontext *context, ALuint source, ALenum param, const ALdouble *values) AL_API_NOEXCEPT; void AL_APIENTRY alGetSourcedDirectSOFT(ALCcontext *context, ALuint source, ALenum param, ALdouble *value) AL_API_NOEXCEPT; void AL_APIENTRY alGetSource3dDirectSOFT(ALCcontext *context, ALuint source, ALenum param, ALdouble *value1, ALdouble *value2, ALdouble *value3) AL_API_NOEXCEPT; void AL_APIENTRY alGetSourcedvDirectSOFT(ALCcontext *context, ALuint source, ALenum param, ALdouble *values) AL_API_NOEXCEPT; void AL_APIENTRY alSourcei64DirectSOFT(ALCcontext *context, ALuint source, ALenum param, ALint64SOFT value) AL_API_NOEXCEPT; void AL_APIENTRY alSource3i64DirectSOFT(ALCcontext *context, ALuint source, ALenum param, ALint64SOFT value1, ALint64SOFT value2, ALint64SOFT value3) AL_API_NOEXCEPT; void AL_APIENTRY alSourcei64vDirectSOFT(ALCcontext *context, ALuint source, ALenum param, const ALint64SOFT *values) AL_API_NOEXCEPT; void AL_APIENTRY alGetSourcei64DirectSOFT(ALCcontext *context, ALuint source, ALenum param, ALint64SOFT *value) AL_API_NOEXCEPT; void AL_APIENTRY alGetSource3i64DirectSOFT(ALCcontext *context, ALuint source, ALenum param, ALint64SOFT *value1, ALint64SOFT *value2, ALint64SOFT *value3) AL_API_NOEXCEPT; void AL_APIENTRY alGetSourcei64vDirectSOFT(ALCcontext *context, ALuint source, ALenum param, ALint64SOFT *values) AL_API_NOEXCEPT; void AL_APIENTRY alDeferUpdatesDirectSOFT(ALCcontext *context) AL_API_NOEXCEPT; void AL_APIENTRY alProcessUpdatesDirectSOFT(ALCcontext *context) AL_API_NOEXCEPT; const ALchar* AL_APIENTRY alGetStringiDirectSOFT(ALCcontext *context, ALenum pname, ALsizei index) AL_API_NOEXCEPT; void AL_APIENTRY alEventControlDirectSOFT(ALCcontext *context, ALsizei count, const ALenum *types, ALboolean enable) AL_API_NOEXCEPT; void AL_APIENTRY alEventCallbackDirectSOFT(ALCcontext *context, ALEVENTPROCSOFT callback, void *userParam) AL_API_NOEXCEPT; void* AL_APIENTRY alGetPointerDirectSOFT(ALCcontext *context, ALenum pname) AL_API_NOEXCEPT; void AL_APIENTRY alGetPointervDirectSOFT(ALCcontext *context, ALenum pname, void **values) AL_API_NOEXCEPT; void AL_APIENTRY alBufferCallbackDirectSOFT(ALCcontext *context, ALuint buffer, ALenum format, ALsizei freq, ALBUFFERCALLBACKTYPESOFT callback, ALvoid *userptr) AL_API_NOEXCEPT; void AL_APIENTRY alGetBufferPtrDirectSOFT(ALCcontext *context, ALuint buffer, ALenum param, ALvoid **ptr) AL_API_NOEXCEPT; void AL_APIENTRY alGetBuffer3PtrDirectSOFT(ALCcontext *context, ALuint buffer, ALenum param, ALvoid **ptr0, ALvoid **ptr1, ALvoid **ptr2) AL_API_NOEXCEPT; void AL_APIENTRY alGetBufferPtrvDirectSOFT(ALCcontext *context, ALuint buffer, ALenum param, ALvoid **ptr) AL_API_NOEXCEPT; void AL_APIENTRY alSourcePlayAtTimeDirectSOFT(ALCcontext *context, ALuint source, ALint64SOFT start_time) AL_API_NOEXCEPT; void AL_APIENTRY alSourcePlayAtTimevDirectSOFT(ALCcontext *context, ALsizei n, const ALuint *sources, ALint64SOFT start_time) AL_API_NOEXCEPT; ALenum AL_APIENTRY EAXSetDirect(ALCcontext *context, const struct _GUID *property_set_id, ALuint property_id, ALuint source_id, ALvoid *value, ALuint value_size) AL_API_NOEXCEPT; ALenum AL_APIENTRY EAXGetDirect(ALCcontext *context, const struct _GUID *property_set_id, ALuint property_id, ALuint source_id, ALvoid *value, ALuint value_size) AL_API_NOEXCEPT; ALboolean AL_APIENTRY EAXSetBufferModeDirect(ALCcontext *context, ALsizei n, const ALuint *buffers, ALint value) AL_API_NOEXCEPT; ALenum AL_APIENTRY EAXGetBufferModeDirect(ALCcontext *context, ALuint buffer, ALint *pReserved) AL_API_NOEXCEPT; #endif #endif #ifndef AL_SOFT_bformat_hoa #define AL_SOFT_bformat_hoa #define AL_UNPACK_AMBISONIC_ORDER_SOFT 0x199D #endif #ifdef __cplusplus } #endif /* NOLINTEND */ #endif openal-soft-1.24.2/include/AL/efx-creative.h000066400000000000000000000002641474041540300205050ustar00rootroot00000000000000/* The tokens that would be defined here are already defined in efx.h. This * empty file is here to provide compatibility with Windows-based projects * that would include it. */ openal-soft-1.24.2/include/AL/efx-presets.h000066400000000000000000001052721474041540300203750ustar00rootroot00000000000000/* Reverb presets for EFX */ #ifndef EFX_PRESETS_H #define EFX_PRESETS_H /* NOLINTBEGIN */ #ifndef EFXEAXREVERBPROPERTIES_DEFINED #define EFXEAXREVERBPROPERTIES_DEFINED typedef struct { float flDensity; float flDiffusion; float flGain; float flGainHF; float flGainLF; float flDecayTime; float flDecayHFRatio; float flDecayLFRatio; float flReflectionsGain; float flReflectionsDelay; float flReflectionsPan[3]; float flLateReverbGain; float flLateReverbDelay; float flLateReverbPan[3]; float flEchoTime; float flEchoDepth; float flModulationTime; float flModulationDepth; float flAirAbsorptionGainHF; float flHFReference; float flLFReference; float flRoomRolloffFactor; int iDecayHFLimit; } EFXEAXREVERBPROPERTIES, *LPEFXEAXREVERBPROPERTIES; #endif /* Default Presets */ #define EFX_REVERB_PRESET_GENERIC \ { 1.0000f, 1.0000f, 0.3162f, 0.8913f, 1.0000f, 1.4900f, 0.8300f, 1.0000f, 0.0500f, 0.0070f, { 0.0000f, 0.0000f, 0.0000f }, 1.2589f, 0.0110f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_PADDEDCELL \ { 0.1715f, 1.0000f, 0.3162f, 0.0010f, 1.0000f, 0.1700f, 0.1000f, 1.0000f, 0.2500f, 0.0010f, { 0.0000f, 0.0000f, 0.0000f }, 1.2691f, 0.0020f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_ROOM \ { 0.4287f, 1.0000f, 0.3162f, 0.5929f, 1.0000f, 0.4000f, 0.8300f, 1.0000f, 0.1503f, 0.0020f, { 0.0000f, 0.0000f, 0.0000f }, 1.0629f, 0.0030f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_BATHROOM \ { 0.1715f, 1.0000f, 0.3162f, 0.2512f, 1.0000f, 1.4900f, 0.5400f, 1.0000f, 0.6531f, 0.0070f, { 0.0000f, 0.0000f, 0.0000f }, 3.2734f, 0.0110f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_LIVINGROOM \ { 0.9766f, 1.0000f, 0.3162f, 0.0010f, 1.0000f, 0.5000f, 0.1000f, 1.0000f, 0.2051f, 0.0030f, { 0.0000f, 0.0000f, 0.0000f }, 0.2805f, 0.0040f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_STONEROOM \ { 1.0000f, 1.0000f, 0.3162f, 0.7079f, 1.0000f, 2.3100f, 0.6400f, 1.0000f, 0.4411f, 0.0120f, { 0.0000f, 0.0000f, 0.0000f }, 1.1003f, 0.0170f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_AUDITORIUM \ { 1.0000f, 1.0000f, 0.3162f, 0.5781f, 1.0000f, 4.3200f, 0.5900f, 1.0000f, 0.4032f, 0.0200f, { 0.0000f, 0.0000f, 0.0000f }, 0.7170f, 0.0300f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_CONCERTHALL \ { 1.0000f, 1.0000f, 0.3162f, 0.5623f, 1.0000f, 3.9200f, 0.7000f, 1.0000f, 0.2427f, 0.0200f, { 0.0000f, 0.0000f, 0.0000f }, 0.9977f, 0.0290f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_CAVE \ { 1.0000f, 1.0000f, 0.3162f, 1.0000f, 1.0000f, 2.9100f, 1.3000f, 1.0000f, 0.5000f, 0.0150f, { 0.0000f, 0.0000f, 0.0000f }, 0.7063f, 0.0220f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x0 } #define EFX_REVERB_PRESET_ARENA \ { 1.0000f, 1.0000f, 0.3162f, 0.4477f, 1.0000f, 7.2400f, 0.3300f, 1.0000f, 0.2612f, 0.0200f, { 0.0000f, 0.0000f, 0.0000f }, 1.0186f, 0.0300f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_HANGAR \ { 1.0000f, 1.0000f, 0.3162f, 0.3162f, 1.0000f, 10.0500f, 0.2300f, 1.0000f, 0.5000f, 0.0200f, { 0.0000f, 0.0000f, 0.0000f }, 1.2560f, 0.0300f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_CARPETEDHALLWAY \ { 0.4287f, 1.0000f, 0.3162f, 0.0100f, 1.0000f, 0.3000f, 0.1000f, 1.0000f, 0.1215f, 0.0020f, { 0.0000f, 0.0000f, 0.0000f }, 0.1531f, 0.0300f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_HALLWAY \ { 0.3645f, 1.0000f, 0.3162f, 0.7079f, 1.0000f, 1.4900f, 0.5900f, 1.0000f, 0.2458f, 0.0070f, { 0.0000f, 0.0000f, 0.0000f }, 1.6615f, 0.0110f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_STONECORRIDOR \ { 1.0000f, 1.0000f, 0.3162f, 0.7612f, 1.0000f, 2.7000f, 0.7900f, 1.0000f, 0.2472f, 0.0130f, { 0.0000f, 0.0000f, 0.0000f }, 1.5758f, 0.0200f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_ALLEY \ { 1.0000f, 0.3000f, 0.3162f, 0.7328f, 1.0000f, 1.4900f, 0.8600f, 1.0000f, 0.2500f, 0.0070f, { 0.0000f, 0.0000f, 0.0000f }, 0.9954f, 0.0110f, { 0.0000f, 0.0000f, 0.0000f }, 0.1250f, 0.9500f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_FOREST \ { 1.0000f, 0.3000f, 0.3162f, 0.0224f, 1.0000f, 1.4900f, 0.5400f, 1.0000f, 0.0525f, 0.1620f, { 0.0000f, 0.0000f, 0.0000f }, 0.7682f, 0.0880f, { 0.0000f, 0.0000f, 0.0000f }, 0.1250f, 1.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_CITY \ { 1.0000f, 0.5000f, 0.3162f, 0.3981f, 1.0000f, 1.4900f, 0.6700f, 1.0000f, 0.0730f, 0.0070f, { 0.0000f, 0.0000f, 0.0000f }, 0.1427f, 0.0110f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_MOUNTAINS \ { 1.0000f, 0.2700f, 0.3162f, 0.0562f, 1.0000f, 1.4900f, 0.2100f, 1.0000f, 0.0407f, 0.3000f, { 0.0000f, 0.0000f, 0.0000f }, 0.1919f, 0.1000f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 1.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x0 } #define EFX_REVERB_PRESET_QUARRY \ { 1.0000f, 1.0000f, 0.3162f, 0.3162f, 1.0000f, 1.4900f, 0.8300f, 1.0000f, 0.0000f, 0.0610f, { 0.0000f, 0.0000f, 0.0000f }, 1.7783f, 0.0250f, { 0.0000f, 0.0000f, 0.0000f }, 0.1250f, 0.7000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_PLAIN \ { 1.0000f, 0.2100f, 0.3162f, 0.1000f, 1.0000f, 1.4900f, 0.5000f, 1.0000f, 0.0585f, 0.1790f, { 0.0000f, 0.0000f, 0.0000f }, 0.1089f, 0.1000f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 1.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_PARKINGLOT \ { 1.0000f, 1.0000f, 0.3162f, 1.0000f, 1.0000f, 1.6500f, 1.5000f, 1.0000f, 0.2082f, 0.0080f, { 0.0000f, 0.0000f, 0.0000f }, 0.2652f, 0.0120f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x0 } #define EFX_REVERB_PRESET_SEWERPIPE \ { 0.3071f, 0.8000f, 0.3162f, 0.3162f, 1.0000f, 2.8100f, 0.1400f, 1.0000f, 1.6387f, 0.0140f, { 0.0000f, 0.0000f, 0.0000f }, 3.2471f, 0.0210f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_UNDERWATER \ { 0.3645f, 1.0000f, 0.3162f, 0.0100f, 1.0000f, 1.4900f, 0.1000f, 1.0000f, 0.5963f, 0.0070f, { 0.0000f, 0.0000f, 0.0000f }, 7.0795f, 0.0110f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 1.1800f, 0.3480f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_DRUGGED \ { 0.4287f, 0.5000f, 0.3162f, 1.0000f, 1.0000f, 8.3900f, 1.3900f, 1.0000f, 0.8760f, 0.0020f, { 0.0000f, 0.0000f, 0.0000f }, 3.1081f, 0.0300f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 1.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x0 } #define EFX_REVERB_PRESET_DIZZY \ { 0.3645f, 0.6000f, 0.3162f, 0.6310f, 1.0000f, 17.2300f, 0.5600f, 1.0000f, 0.1392f, 0.0200f, { 0.0000f, 0.0000f, 0.0000f }, 0.4937f, 0.0300f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 1.0000f, 0.8100f, 0.3100f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x0 } #define EFX_REVERB_PRESET_PSYCHOTIC \ { 0.0625f, 0.5000f, 0.3162f, 0.8404f, 1.0000f, 7.5600f, 0.9100f, 1.0000f, 0.4864f, 0.0200f, { 0.0000f, 0.0000f, 0.0000f }, 2.4378f, 0.0300f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 4.0000f, 1.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x0 } /* Castle Presets */ #define EFX_REVERB_PRESET_CASTLE_SMALLROOM \ { 1.0000f, 0.8900f, 0.3162f, 0.3981f, 0.1000f, 1.2200f, 0.8300f, 0.3100f, 0.8913f, 0.0220f, { 0.0000f, 0.0000f, 0.0000f }, 1.9953f, 0.0110f, { 0.0000f, 0.0000f, 0.0000f }, 0.1380f, 0.0800f, 0.2500f, 0.0000f, 0.9943f, 5168.6001f, 139.5000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_CASTLE_SHORTPASSAGE \ { 1.0000f, 0.8900f, 0.3162f, 0.3162f, 0.1000f, 2.3200f, 0.8300f, 0.3100f, 0.8913f, 0.0070f, { 0.0000f, 0.0000f, 0.0000f }, 1.2589f, 0.0230f, { 0.0000f, 0.0000f, 0.0000f }, 0.1380f, 0.0800f, 0.2500f, 0.0000f, 0.9943f, 5168.6001f, 139.5000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_CASTLE_MEDIUMROOM \ { 1.0000f, 0.9300f, 0.3162f, 0.2818f, 0.1000f, 2.0400f, 0.8300f, 0.4600f, 0.6310f, 0.0220f, { 0.0000f, 0.0000f, 0.0000f }, 1.5849f, 0.0110f, { 0.0000f, 0.0000f, 0.0000f }, 0.1550f, 0.0300f, 0.2500f, 0.0000f, 0.9943f, 5168.6001f, 139.5000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_CASTLE_LARGEROOM \ { 1.0000f, 0.8200f, 0.3162f, 0.2818f, 0.1259f, 2.5300f, 0.8300f, 0.5000f, 0.4467f, 0.0340f, { 0.0000f, 0.0000f, 0.0000f }, 1.2589f, 0.0160f, { 0.0000f, 0.0000f, 0.0000f }, 0.1850f, 0.0700f, 0.2500f, 0.0000f, 0.9943f, 5168.6001f, 139.5000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_CASTLE_LONGPASSAGE \ { 1.0000f, 0.8900f, 0.3162f, 0.3981f, 0.1000f, 3.4200f, 0.8300f, 0.3100f, 0.8913f, 0.0070f, { 0.0000f, 0.0000f, 0.0000f }, 1.4125f, 0.0230f, { 0.0000f, 0.0000f, 0.0000f }, 0.1380f, 0.0800f, 0.2500f, 0.0000f, 0.9943f, 5168.6001f, 139.5000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_CASTLE_HALL \ { 1.0000f, 0.8100f, 0.3162f, 0.2818f, 0.1778f, 3.1400f, 0.7900f, 0.6200f, 0.1778f, 0.0560f, { 0.0000f, 0.0000f, 0.0000f }, 1.1220f, 0.0240f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5168.6001f, 139.5000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_CASTLE_CUPBOARD \ { 1.0000f, 0.8900f, 0.3162f, 0.2818f, 0.1000f, 0.6700f, 0.8700f, 0.3100f, 1.4125f, 0.0100f, { 0.0000f, 0.0000f, 0.0000f }, 3.5481f, 0.0070f, { 0.0000f, 0.0000f, 0.0000f }, 0.1380f, 0.0800f, 0.2500f, 0.0000f, 0.9943f, 5168.6001f, 139.5000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_CASTLE_COURTYARD \ { 1.0000f, 0.4200f, 0.3162f, 0.4467f, 0.1995f, 2.1300f, 0.6100f, 0.2300f, 0.2239f, 0.1600f, { 0.0000f, 0.0000f, 0.0000f }, 0.7079f, 0.0360f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.3700f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x0 } #define EFX_REVERB_PRESET_CASTLE_ALCOVE \ { 1.0000f, 0.8900f, 0.3162f, 0.5012f, 0.1000f, 1.6400f, 0.8700f, 0.3100f, 1.0000f, 0.0070f, { 0.0000f, 0.0000f, 0.0000f }, 1.4125f, 0.0340f, { 0.0000f, 0.0000f, 0.0000f }, 0.1380f, 0.0800f, 0.2500f, 0.0000f, 0.9943f, 5168.6001f, 139.5000f, 0.0000f, 0x1 } /* Factory Presets */ #define EFX_REVERB_PRESET_FACTORY_SMALLROOM \ { 0.3645f, 0.8200f, 0.3162f, 0.7943f, 0.5012f, 1.7200f, 0.6500f, 1.3100f, 0.7079f, 0.0100f, { 0.0000f, 0.0000f, 0.0000f }, 1.7783f, 0.0240f, { 0.0000f, 0.0000f, 0.0000f }, 0.1190f, 0.0700f, 0.2500f, 0.0000f, 0.9943f, 3762.6001f, 362.5000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_FACTORY_SHORTPASSAGE \ { 0.3645f, 0.6400f, 0.2512f, 0.7943f, 0.5012f, 2.5300f, 0.6500f, 1.3100f, 1.0000f, 0.0100f, { 0.0000f, 0.0000f, 0.0000f }, 1.2589f, 0.0380f, { 0.0000f, 0.0000f, 0.0000f }, 0.1350f, 0.2300f, 0.2500f, 0.0000f, 0.9943f, 3762.6001f, 362.5000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_FACTORY_MEDIUMROOM \ { 0.4287f, 0.8200f, 0.2512f, 0.7943f, 0.5012f, 2.7600f, 0.6500f, 1.3100f, 0.2818f, 0.0220f, { 0.0000f, 0.0000f, 0.0000f }, 1.4125f, 0.0230f, { 0.0000f, 0.0000f, 0.0000f }, 0.1740f, 0.0700f, 0.2500f, 0.0000f, 0.9943f, 3762.6001f, 362.5000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_FACTORY_LARGEROOM \ { 0.4287f, 0.7500f, 0.2512f, 0.7079f, 0.6310f, 4.2400f, 0.5100f, 1.3100f, 0.1778f, 0.0390f, { 0.0000f, 0.0000f, 0.0000f }, 1.1220f, 0.0230f, { 0.0000f, 0.0000f, 0.0000f }, 0.2310f, 0.0700f, 0.2500f, 0.0000f, 0.9943f, 3762.6001f, 362.5000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_FACTORY_LONGPASSAGE \ { 0.3645f, 0.6400f, 0.2512f, 0.7943f, 0.5012f, 4.0600f, 0.6500f, 1.3100f, 1.0000f, 0.0200f, { 0.0000f, 0.0000f, 0.0000f }, 1.2589f, 0.0370f, { 0.0000f, 0.0000f, 0.0000f }, 0.1350f, 0.2300f, 0.2500f, 0.0000f, 0.9943f, 3762.6001f, 362.5000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_FACTORY_HALL \ { 0.4287f, 0.7500f, 0.3162f, 0.7079f, 0.6310f, 7.4300f, 0.5100f, 1.3100f, 0.0631f, 0.0730f, { 0.0000f, 0.0000f, 0.0000f }, 0.8913f, 0.0270f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0700f, 0.2500f, 0.0000f, 0.9943f, 3762.6001f, 362.5000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_FACTORY_CUPBOARD \ { 0.3071f, 0.6300f, 0.2512f, 0.7943f, 0.5012f, 0.4900f, 0.6500f, 1.3100f, 1.2589f, 0.0100f, { 0.0000f, 0.0000f, 0.0000f }, 1.9953f, 0.0320f, { 0.0000f, 0.0000f, 0.0000f }, 0.1070f, 0.0700f, 0.2500f, 0.0000f, 0.9943f, 3762.6001f, 362.5000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_FACTORY_COURTYARD \ { 0.3071f, 0.5700f, 0.3162f, 0.3162f, 0.6310f, 2.3200f, 0.2900f, 0.5600f, 0.2239f, 0.1400f, { 0.0000f, 0.0000f, 0.0000f }, 0.3981f, 0.0390f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.2900f, 0.2500f, 0.0000f, 0.9943f, 3762.6001f, 362.5000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_FACTORY_ALCOVE \ { 0.3645f, 0.5900f, 0.2512f, 0.7943f, 0.5012f, 3.1400f, 0.6500f, 1.3100f, 1.4125f, 0.0100f, { 0.0000f, 0.0000f, 0.0000f }, 1.0000f, 0.0380f, { 0.0000f, 0.0000f, 0.0000f }, 0.1140f, 0.1000f, 0.2500f, 0.0000f, 0.9943f, 3762.6001f, 362.5000f, 0.0000f, 0x1 } /* Ice Palace Presets */ #define EFX_REVERB_PRESET_ICEPALACE_SMALLROOM \ { 1.0000f, 0.8400f, 0.3162f, 0.5623f, 0.2818f, 1.5100f, 1.5300f, 0.2700f, 0.8913f, 0.0100f, { 0.0000f, 0.0000f, 0.0000f }, 1.4125f, 0.0110f, { 0.0000f, 0.0000f, 0.0000f }, 0.1640f, 0.1400f, 0.2500f, 0.0000f, 0.9943f, 12428.5000f, 99.6000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_ICEPALACE_SHORTPASSAGE \ { 1.0000f, 0.7500f, 0.3162f, 0.5623f, 0.2818f, 1.7900f, 1.4600f, 0.2800f, 0.5012f, 0.0100f, { 0.0000f, 0.0000f, 0.0000f }, 1.1220f, 0.0190f, { 0.0000f, 0.0000f, 0.0000f }, 0.1770f, 0.0900f, 0.2500f, 0.0000f, 0.9943f, 12428.5000f, 99.6000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_ICEPALACE_MEDIUMROOM \ { 1.0000f, 0.8700f, 0.3162f, 0.5623f, 0.4467f, 2.2200f, 1.5300f, 0.3200f, 0.3981f, 0.0390f, { 0.0000f, 0.0000f, 0.0000f }, 1.1220f, 0.0270f, { 0.0000f, 0.0000f, 0.0000f }, 0.1860f, 0.1200f, 0.2500f, 0.0000f, 0.9943f, 12428.5000f, 99.6000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_ICEPALACE_LARGEROOM \ { 1.0000f, 0.8100f, 0.3162f, 0.5623f, 0.4467f, 3.1400f, 1.5300f, 0.3200f, 0.2512f, 0.0390f, { 0.0000f, 0.0000f, 0.0000f }, 1.0000f, 0.0270f, { 0.0000f, 0.0000f, 0.0000f }, 0.2140f, 0.1100f, 0.2500f, 0.0000f, 0.9943f, 12428.5000f, 99.6000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_ICEPALACE_LONGPASSAGE \ { 1.0000f, 0.7700f, 0.3162f, 0.5623f, 0.3981f, 3.0100f, 1.4600f, 0.2800f, 0.7943f, 0.0120f, { 0.0000f, 0.0000f, 0.0000f }, 1.2589f, 0.0250f, { 0.0000f, 0.0000f, 0.0000f }, 0.1860f, 0.0400f, 0.2500f, 0.0000f, 0.9943f, 12428.5000f, 99.6000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_ICEPALACE_HALL \ { 1.0000f, 0.7600f, 0.3162f, 0.4467f, 0.5623f, 5.4900f, 1.5300f, 0.3800f, 0.1122f, 0.0540f, { 0.0000f, 0.0000f, 0.0000f }, 0.6310f, 0.0520f, { 0.0000f, 0.0000f, 0.0000f }, 0.2260f, 0.1100f, 0.2500f, 0.0000f, 0.9943f, 12428.5000f, 99.6000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_ICEPALACE_CUPBOARD \ { 1.0000f, 0.8300f, 0.3162f, 0.5012f, 0.2239f, 0.7600f, 1.5300f, 0.2600f, 1.1220f, 0.0120f, { 0.0000f, 0.0000f, 0.0000f }, 1.9953f, 0.0160f, { 0.0000f, 0.0000f, 0.0000f }, 0.1430f, 0.0800f, 0.2500f, 0.0000f, 0.9943f, 12428.5000f, 99.6000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_ICEPALACE_COURTYARD \ { 1.0000f, 0.5900f, 0.3162f, 0.2818f, 0.3162f, 2.0400f, 1.2000f, 0.3800f, 0.3162f, 0.1730f, { 0.0000f, 0.0000f, 0.0000f }, 0.3162f, 0.0430f, { 0.0000f, 0.0000f, 0.0000f }, 0.2350f, 0.4800f, 0.2500f, 0.0000f, 0.9943f, 12428.5000f, 99.6000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_ICEPALACE_ALCOVE \ { 1.0000f, 0.8400f, 0.3162f, 0.5623f, 0.2818f, 2.7600f, 1.4600f, 0.2800f, 1.1220f, 0.0100f, { 0.0000f, 0.0000f, 0.0000f }, 0.8913f, 0.0300f, { 0.0000f, 0.0000f, 0.0000f }, 0.1610f, 0.0900f, 0.2500f, 0.0000f, 0.9943f, 12428.5000f, 99.6000f, 0.0000f, 0x1 } /* Space Station Presets */ #define EFX_REVERB_PRESET_SPACESTATION_SMALLROOM \ { 0.2109f, 0.7000f, 0.3162f, 0.7079f, 0.8913f, 1.7200f, 0.8200f, 0.5500f, 0.7943f, 0.0070f, { 0.0000f, 0.0000f, 0.0000f }, 1.4125f, 0.0130f, { 0.0000f, 0.0000f, 0.0000f }, 0.1880f, 0.2600f, 0.2500f, 0.0000f, 0.9943f, 3316.1001f, 458.2000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_SPACESTATION_SHORTPASSAGE \ { 0.2109f, 0.8700f, 0.3162f, 0.6310f, 0.8913f, 3.5700f, 0.5000f, 0.5500f, 1.0000f, 0.0120f, { 0.0000f, 0.0000f, 0.0000f }, 1.1220f, 0.0160f, { 0.0000f, 0.0000f, 0.0000f }, 0.1720f, 0.2000f, 0.2500f, 0.0000f, 0.9943f, 3316.1001f, 458.2000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_SPACESTATION_MEDIUMROOM \ { 0.2109f, 0.7500f, 0.3162f, 0.6310f, 0.8913f, 3.0100f, 0.5000f, 0.5500f, 0.3981f, 0.0340f, { 0.0000f, 0.0000f, 0.0000f }, 1.1220f, 0.0350f, { 0.0000f, 0.0000f, 0.0000f }, 0.2090f, 0.3100f, 0.2500f, 0.0000f, 0.9943f, 3316.1001f, 458.2000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_SPACESTATION_LARGEROOM \ { 0.3645f, 0.8100f, 0.3162f, 0.6310f, 0.8913f, 3.8900f, 0.3800f, 0.6100f, 0.3162f, 0.0560f, { 0.0000f, 0.0000f, 0.0000f }, 0.8913f, 0.0350f, { 0.0000f, 0.0000f, 0.0000f }, 0.2330f, 0.2800f, 0.2500f, 0.0000f, 0.9943f, 3316.1001f, 458.2000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_SPACESTATION_LONGPASSAGE \ { 0.4287f, 0.8200f, 0.3162f, 0.6310f, 0.8913f, 4.6200f, 0.6200f, 0.5500f, 1.0000f, 0.0120f, { 0.0000f, 0.0000f, 0.0000f }, 1.2589f, 0.0310f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.2300f, 0.2500f, 0.0000f, 0.9943f, 3316.1001f, 458.2000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_SPACESTATION_HALL \ { 0.4287f, 0.8700f, 0.3162f, 0.6310f, 0.8913f, 7.1100f, 0.3800f, 0.6100f, 0.1778f, 0.1000f, { 0.0000f, 0.0000f, 0.0000f }, 0.6310f, 0.0470f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.2500f, 0.2500f, 0.0000f, 0.9943f, 3316.1001f, 458.2000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_SPACESTATION_CUPBOARD \ { 0.1715f, 0.5600f, 0.3162f, 0.7079f, 0.8913f, 0.7900f, 0.8100f, 0.5500f, 1.4125f, 0.0070f, { 0.0000f, 0.0000f, 0.0000f }, 1.7783f, 0.0180f, { 0.0000f, 0.0000f, 0.0000f }, 0.1810f, 0.3100f, 0.2500f, 0.0000f, 0.9943f, 3316.1001f, 458.2000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_SPACESTATION_ALCOVE \ { 0.2109f, 0.7800f, 0.3162f, 0.7079f, 0.8913f, 1.1600f, 0.8100f, 0.5500f, 1.4125f, 0.0070f, { 0.0000f, 0.0000f, 0.0000f }, 1.0000f, 0.0180f, { 0.0000f, 0.0000f, 0.0000f }, 0.1920f, 0.2100f, 0.2500f, 0.0000f, 0.9943f, 3316.1001f, 458.2000f, 0.0000f, 0x1 } /* Wooden Galleon Presets */ #define EFX_REVERB_PRESET_WOODEN_SMALLROOM \ { 1.0000f, 1.0000f, 0.3162f, 0.1122f, 0.3162f, 0.7900f, 0.3200f, 0.8700f, 1.0000f, 0.0320f, { 0.0000f, 0.0000f, 0.0000f }, 0.8913f, 0.0290f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 4705.0000f, 99.6000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_WOODEN_SHORTPASSAGE \ { 1.0000f, 1.0000f, 0.3162f, 0.1259f, 0.3162f, 1.7500f, 0.5000f, 0.8700f, 0.8913f, 0.0120f, { 0.0000f, 0.0000f, 0.0000f }, 0.6310f, 0.0240f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 4705.0000f, 99.6000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_WOODEN_MEDIUMROOM \ { 1.0000f, 1.0000f, 0.3162f, 0.1000f, 0.2818f, 1.4700f, 0.4200f, 0.8200f, 0.8913f, 0.0490f, { 0.0000f, 0.0000f, 0.0000f }, 0.8913f, 0.0290f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 4705.0000f, 99.6000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_WOODEN_LARGEROOM \ { 1.0000f, 1.0000f, 0.3162f, 0.0891f, 0.2818f, 2.6500f, 0.3300f, 0.8200f, 0.8913f, 0.0660f, { 0.0000f, 0.0000f, 0.0000f }, 0.7943f, 0.0490f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 4705.0000f, 99.6000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_WOODEN_LONGPASSAGE \ { 1.0000f, 1.0000f, 0.3162f, 0.1000f, 0.3162f, 1.9900f, 0.4000f, 0.7900f, 1.0000f, 0.0200f, { 0.0000f, 0.0000f, 0.0000f }, 0.4467f, 0.0360f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 4705.0000f, 99.6000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_WOODEN_HALL \ { 1.0000f, 1.0000f, 0.3162f, 0.0794f, 0.2818f, 3.4500f, 0.3000f, 0.8200f, 0.8913f, 0.0880f, { 0.0000f, 0.0000f, 0.0000f }, 0.7943f, 0.0630f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 4705.0000f, 99.6000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_WOODEN_CUPBOARD \ { 1.0000f, 1.0000f, 0.3162f, 0.1413f, 0.3162f, 0.5600f, 0.4600f, 0.9100f, 1.1220f, 0.0120f, { 0.0000f, 0.0000f, 0.0000f }, 1.1220f, 0.0280f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 4705.0000f, 99.6000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_WOODEN_COURTYARD \ { 1.0000f, 0.6500f, 0.3162f, 0.0794f, 0.3162f, 1.7900f, 0.3500f, 0.7900f, 0.5623f, 0.1230f, { 0.0000f, 0.0000f, 0.0000f }, 0.1000f, 0.0320f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 4705.0000f, 99.6000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_WOODEN_ALCOVE \ { 1.0000f, 1.0000f, 0.3162f, 0.1259f, 0.3162f, 1.2200f, 0.6200f, 0.9100f, 1.1220f, 0.0120f, { 0.0000f, 0.0000f, 0.0000f }, 0.7079f, 0.0240f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 4705.0000f, 99.6000f, 0.0000f, 0x1 } /* Sports Presets */ #define EFX_REVERB_PRESET_SPORT_EMPTYSTADIUM \ { 1.0000f, 1.0000f, 0.3162f, 0.4467f, 0.7943f, 6.2600f, 0.5100f, 1.1000f, 0.0631f, 0.1830f, { 0.0000f, 0.0000f, 0.0000f }, 0.3981f, 0.0380f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_SPORT_SQUASHCOURT \ { 1.0000f, 0.7500f, 0.3162f, 0.3162f, 0.7943f, 2.2200f, 0.9100f, 1.1600f, 0.4467f, 0.0070f, { 0.0000f, 0.0000f, 0.0000f }, 0.7943f, 0.0110f, { 0.0000f, 0.0000f, 0.0000f }, 0.1260f, 0.1900f, 0.2500f, 0.0000f, 0.9943f, 7176.8999f, 211.2000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_SPORT_SMALLSWIMMINGPOOL \ { 1.0000f, 0.7000f, 0.3162f, 0.7943f, 0.8913f, 2.7600f, 1.2500f, 1.1400f, 0.6310f, 0.0200f, { 0.0000f, 0.0000f, 0.0000f }, 0.7943f, 0.0300f, { 0.0000f, 0.0000f, 0.0000f }, 0.1790f, 0.1500f, 0.8950f, 0.1900f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x0 } #define EFX_REVERB_PRESET_SPORT_LARGESWIMMINGPOOL \ { 1.0000f, 0.8200f, 0.3162f, 0.7943f, 1.0000f, 5.4900f, 1.3100f, 1.1400f, 0.4467f, 0.0390f, { 0.0000f, 0.0000f, 0.0000f }, 0.5012f, 0.0490f, { 0.0000f, 0.0000f, 0.0000f }, 0.2220f, 0.5500f, 1.1590f, 0.2100f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x0 } #define EFX_REVERB_PRESET_SPORT_GYMNASIUM \ { 1.0000f, 0.8100f, 0.3162f, 0.4467f, 0.8913f, 3.1400f, 1.0600f, 1.3500f, 0.3981f, 0.0290f, { 0.0000f, 0.0000f, 0.0000f }, 0.5623f, 0.0450f, { 0.0000f, 0.0000f, 0.0000f }, 0.1460f, 0.1400f, 0.2500f, 0.0000f, 0.9943f, 7176.8999f, 211.2000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_SPORT_FULLSTADIUM \ { 1.0000f, 1.0000f, 0.3162f, 0.0708f, 0.7943f, 5.2500f, 0.1700f, 0.8000f, 0.1000f, 0.1880f, { 0.0000f, 0.0000f, 0.0000f }, 0.2818f, 0.0380f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_SPORT_STADIUMTANNOY \ { 1.0000f, 0.7800f, 0.3162f, 0.5623f, 0.5012f, 2.5300f, 0.8800f, 0.6800f, 0.2818f, 0.2300f, { 0.0000f, 0.0000f, 0.0000f }, 0.5012f, 0.0630f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.2000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } /* Prefab Presets */ #define EFX_REVERB_PRESET_PREFAB_WORKSHOP \ { 0.4287f, 1.0000f, 0.3162f, 0.1413f, 0.3981f, 0.7600f, 1.0000f, 1.0000f, 1.0000f, 0.0120f, { 0.0000f, 0.0000f, 0.0000f }, 1.1220f, 0.0120f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x0 } #define EFX_REVERB_PRESET_PREFAB_SCHOOLROOM \ { 0.4022f, 0.6900f, 0.3162f, 0.6310f, 0.5012f, 0.9800f, 0.4500f, 0.1800f, 1.4125f, 0.0170f, { 0.0000f, 0.0000f, 0.0000f }, 1.4125f, 0.0150f, { 0.0000f, 0.0000f, 0.0000f }, 0.0950f, 0.1400f, 0.2500f, 0.0000f, 0.9943f, 7176.8999f, 211.2000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_PREFAB_PRACTISEROOM \ { 0.4022f, 0.8700f, 0.3162f, 0.3981f, 0.5012f, 1.1200f, 0.5600f, 0.1800f, 1.2589f, 0.0100f, { 0.0000f, 0.0000f, 0.0000f }, 1.4125f, 0.0110f, { 0.0000f, 0.0000f, 0.0000f }, 0.0950f, 0.1400f, 0.2500f, 0.0000f, 0.9943f, 7176.8999f, 211.2000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_PREFAB_OUTHOUSE \ { 1.0000f, 0.8200f, 0.3162f, 0.1122f, 0.1585f, 1.3800f, 0.3800f, 0.3500f, 0.8913f, 0.0240f, { 0.0000f, 0.0000f, -0.0000f }, 0.6310f, 0.0440f, { 0.0000f, 0.0000f, 0.0000f }, 0.1210f, 0.1700f, 0.2500f, 0.0000f, 0.9943f, 2854.3999f, 107.5000f, 0.0000f, 0x0 } #define EFX_REVERB_PRESET_PREFAB_CARAVAN \ { 1.0000f, 1.0000f, 0.3162f, 0.0891f, 0.1259f, 0.4300f, 1.5000f, 1.0000f, 1.0000f, 0.0120f, { 0.0000f, 0.0000f, 0.0000f }, 1.9953f, 0.0120f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x0 } /* Dome and Pipe Presets */ #define EFX_REVERB_PRESET_DOME_TOMB \ { 1.0000f, 0.7900f, 0.3162f, 0.3548f, 0.2239f, 4.1800f, 0.2100f, 0.1000f, 0.3868f, 0.0300f, { 0.0000f, 0.0000f, 0.0000f }, 1.6788f, 0.0220f, { 0.0000f, 0.0000f, 0.0000f }, 0.1770f, 0.1900f, 0.2500f, 0.0000f, 0.9943f, 2854.3999f, 20.0000f, 0.0000f, 0x0 } #define EFX_REVERB_PRESET_PIPE_SMALL \ { 1.0000f, 1.0000f, 0.3162f, 0.3548f, 0.2239f, 5.0400f, 0.1000f, 0.1000f, 0.5012f, 0.0320f, { 0.0000f, 0.0000f, 0.0000f }, 2.5119f, 0.0150f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 2854.3999f, 20.0000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_DOME_SAINTPAULS \ { 1.0000f, 0.8700f, 0.3162f, 0.3548f, 0.2239f, 10.4800f, 0.1900f, 0.1000f, 0.1778f, 0.0900f, { 0.0000f, 0.0000f, 0.0000f }, 1.2589f, 0.0420f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.1200f, 0.2500f, 0.0000f, 0.9943f, 2854.3999f, 20.0000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_PIPE_LONGTHIN \ { 0.2560f, 0.9100f, 0.3162f, 0.4467f, 0.2818f, 9.2100f, 0.1800f, 0.1000f, 0.7079f, 0.0100f, { 0.0000f, 0.0000f, 0.0000f }, 0.7079f, 0.0220f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 2854.3999f, 20.0000f, 0.0000f, 0x0 } #define EFX_REVERB_PRESET_PIPE_LARGE \ { 1.0000f, 1.0000f, 0.3162f, 0.3548f, 0.2239f, 8.4500f, 0.1000f, 0.1000f, 0.3981f, 0.0460f, { 0.0000f, 0.0000f, 0.0000f }, 1.5849f, 0.0320f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 2854.3999f, 20.0000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_PIPE_RESONANT \ { 0.1373f, 0.9100f, 0.3162f, 0.4467f, 0.2818f, 6.8100f, 0.1800f, 0.1000f, 0.7079f, 0.0100f, { 0.0000f, 0.0000f, 0.0000f }, 1.0000f, 0.0220f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 2854.3999f, 20.0000f, 0.0000f, 0x0 } /* Outdoors Presets */ #define EFX_REVERB_PRESET_OUTDOORS_BACKYARD \ { 1.0000f, 0.4500f, 0.3162f, 0.2512f, 0.5012f, 1.1200f, 0.3400f, 0.4600f, 0.4467f, 0.0690f, { 0.0000f, 0.0000f, -0.0000f }, 0.7079f, 0.0230f, { 0.0000f, 0.0000f, 0.0000f }, 0.2180f, 0.3400f, 0.2500f, 0.0000f, 0.9943f, 4399.1001f, 242.9000f, 0.0000f, 0x0 } #define EFX_REVERB_PRESET_OUTDOORS_ROLLINGPLAINS \ { 1.0000f, 0.0000f, 0.3162f, 0.0112f, 0.6310f, 2.1300f, 0.2100f, 0.4600f, 0.1778f, 0.3000f, { 0.0000f, 0.0000f, -0.0000f }, 0.4467f, 0.0190f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 1.0000f, 0.2500f, 0.0000f, 0.9943f, 4399.1001f, 242.9000f, 0.0000f, 0x0 } #define EFX_REVERB_PRESET_OUTDOORS_DEEPCANYON \ { 1.0000f, 0.7400f, 0.3162f, 0.1778f, 0.6310f, 3.8900f, 0.2100f, 0.4600f, 0.3162f, 0.2230f, { 0.0000f, 0.0000f, -0.0000f }, 0.3548f, 0.0190f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 1.0000f, 0.2500f, 0.0000f, 0.9943f, 4399.1001f, 242.9000f, 0.0000f, 0x0 } #define EFX_REVERB_PRESET_OUTDOORS_CREEK \ { 1.0000f, 0.3500f, 0.3162f, 0.1778f, 0.5012f, 2.1300f, 0.2100f, 0.4600f, 0.3981f, 0.1150f, { 0.0000f, 0.0000f, -0.0000f }, 0.1995f, 0.0310f, { 0.0000f, 0.0000f, 0.0000f }, 0.2180f, 0.3400f, 0.2500f, 0.0000f, 0.9943f, 4399.1001f, 242.9000f, 0.0000f, 0x0 } #define EFX_REVERB_PRESET_OUTDOORS_VALLEY \ { 1.0000f, 0.2800f, 0.3162f, 0.0282f, 0.1585f, 2.8800f, 0.2600f, 0.3500f, 0.1413f, 0.2630f, { 0.0000f, 0.0000f, -0.0000f }, 0.3981f, 0.1000f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.3400f, 0.2500f, 0.0000f, 0.9943f, 2854.3999f, 107.5000f, 0.0000f, 0x0 } /* Mood Presets */ #define EFX_REVERB_PRESET_MOOD_HEAVEN \ { 1.0000f, 0.9400f, 0.3162f, 0.7943f, 0.4467f, 5.0400f, 1.1200f, 0.5600f, 0.2427f, 0.0200f, { 0.0000f, 0.0000f, 0.0000f }, 1.2589f, 0.0290f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0800f, 2.7420f, 0.0500f, 0.9977f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_MOOD_HELL \ { 1.0000f, 0.5700f, 0.3162f, 0.3548f, 0.4467f, 3.5700f, 0.4900f, 2.0000f, 0.0000f, 0.0200f, { 0.0000f, 0.0000f, 0.0000f }, 1.4125f, 0.0300f, { 0.0000f, 0.0000f, 0.0000f }, 0.1100f, 0.0400f, 2.1090f, 0.5200f, 0.9943f, 5000.0000f, 139.5000f, 0.0000f, 0x0 } #define EFX_REVERB_PRESET_MOOD_MEMORY \ { 1.0000f, 0.8500f, 0.3162f, 0.6310f, 0.3548f, 4.0600f, 0.8200f, 0.5600f, 0.0398f, 0.0000f, { 0.0000f, 0.0000f, 0.0000f }, 1.1220f, 0.0000f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.4740f, 0.4500f, 0.9886f, 5000.0000f, 250.0000f, 0.0000f, 0x0 } /* Driving Presets */ #define EFX_REVERB_PRESET_DRIVING_COMMENTATOR \ { 1.0000f, 0.0000f, 0.3162f, 0.5623f, 0.5012f, 2.4200f, 0.8800f, 0.6800f, 0.1995f, 0.0930f, { 0.0000f, 0.0000f, 0.0000f }, 0.2512f, 0.0170f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 1.0000f, 0.2500f, 0.0000f, 0.9886f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_DRIVING_PITGARAGE \ { 0.4287f, 0.5900f, 0.3162f, 0.7079f, 0.5623f, 1.7200f, 0.9300f, 0.8700f, 0.5623f, 0.0000f, { 0.0000f, 0.0000f, 0.0000f }, 1.2589f, 0.0160f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.1100f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x0 } #define EFX_REVERB_PRESET_DRIVING_INCAR_RACER \ { 0.0832f, 0.8000f, 0.3162f, 1.0000f, 0.7943f, 0.1700f, 2.0000f, 0.4100f, 1.7783f, 0.0070f, { 0.0000f, 0.0000f, 0.0000f }, 0.7079f, 0.0150f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 10268.2002f, 251.0000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_DRIVING_INCAR_SPORTS \ { 0.0832f, 0.8000f, 0.3162f, 0.6310f, 1.0000f, 0.1700f, 0.7500f, 0.4100f, 1.0000f, 0.0100f, { 0.0000f, 0.0000f, 0.0000f }, 0.5623f, 0.0000f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 10268.2002f, 251.0000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_DRIVING_INCAR_LUXURY \ { 0.2560f, 1.0000f, 0.3162f, 0.1000f, 0.5012f, 0.1300f, 0.4100f, 0.4600f, 0.7943f, 0.0100f, { 0.0000f, 0.0000f, 0.0000f }, 1.5849f, 0.0100f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 10268.2002f, 251.0000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_DRIVING_FULLGRANDSTAND \ { 1.0000f, 1.0000f, 0.3162f, 0.2818f, 0.6310f, 3.0100f, 1.3700f, 1.2800f, 0.3548f, 0.0900f, { 0.0000f, 0.0000f, 0.0000f }, 0.1778f, 0.0490f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 10420.2002f, 250.0000f, 0.0000f, 0x0 } #define EFX_REVERB_PRESET_DRIVING_EMPTYGRANDSTAND \ { 1.0000f, 1.0000f, 0.3162f, 1.0000f, 0.7943f, 4.6200f, 1.7500f, 1.4000f, 0.2082f, 0.0900f, { 0.0000f, 0.0000f, 0.0000f }, 0.2512f, 0.0490f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.0000f, 0.9943f, 10420.2002f, 250.0000f, 0.0000f, 0x0 } #define EFX_REVERB_PRESET_DRIVING_TUNNEL \ { 1.0000f, 0.8100f, 0.3162f, 0.3981f, 0.8913f, 3.4200f, 0.9400f, 1.3100f, 0.7079f, 0.0510f, { 0.0000f, 0.0000f, 0.0000f }, 0.7079f, 0.0470f, { 0.0000f, 0.0000f, 0.0000f }, 0.2140f, 0.0500f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 155.3000f, 0.0000f, 0x1 } /* City Presets */ #define EFX_REVERB_PRESET_CITY_STREETS \ { 1.0000f, 0.7800f, 0.3162f, 0.7079f, 0.8913f, 1.7900f, 1.1200f, 0.9100f, 0.2818f, 0.0460f, { 0.0000f, 0.0000f, 0.0000f }, 0.1995f, 0.0280f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.2000f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_CITY_SUBWAY \ { 1.0000f, 0.7400f, 0.3162f, 0.7079f, 0.8913f, 3.0100f, 1.2300f, 0.9100f, 0.7079f, 0.0460f, { 0.0000f, 0.0000f, 0.0000f }, 1.2589f, 0.0280f, { 0.0000f, 0.0000f, 0.0000f }, 0.1250f, 0.2100f, 0.2500f, 0.0000f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_CITY_MUSEUM \ { 1.0000f, 0.8200f, 0.3162f, 0.1778f, 0.1778f, 3.2800f, 1.4000f, 0.5700f, 0.2512f, 0.0390f, { 0.0000f, 0.0000f, -0.0000f }, 0.8913f, 0.0340f, { 0.0000f, 0.0000f, 0.0000f }, 0.1300f, 0.1700f, 0.2500f, 0.0000f, 0.9943f, 2854.3999f, 107.5000f, 0.0000f, 0x0 } #define EFX_REVERB_PRESET_CITY_LIBRARY \ { 1.0000f, 0.8200f, 0.3162f, 0.2818f, 0.0891f, 2.7600f, 0.8900f, 0.4100f, 0.3548f, 0.0290f, { 0.0000f, 0.0000f, -0.0000f }, 0.8913f, 0.0200f, { 0.0000f, 0.0000f, 0.0000f }, 0.1300f, 0.1700f, 0.2500f, 0.0000f, 0.9943f, 2854.3999f, 107.5000f, 0.0000f, 0x0 } #define EFX_REVERB_PRESET_CITY_UNDERPASS \ { 1.0000f, 0.8200f, 0.3162f, 0.4467f, 0.8913f, 3.5700f, 1.1200f, 0.9100f, 0.3981f, 0.0590f, { 0.0000f, 0.0000f, 0.0000f }, 0.8913f, 0.0370f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.1400f, 0.2500f, 0.0000f, 0.9920f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_CITY_ABANDONED \ { 1.0000f, 0.6900f, 0.3162f, 0.7943f, 0.8913f, 3.2800f, 1.1700f, 0.9100f, 0.4467f, 0.0440f, { 0.0000f, 0.0000f, 0.0000f }, 0.2818f, 0.0240f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.2000f, 0.2500f, 0.0000f, 0.9966f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } /* Misc. Presets */ #define EFX_REVERB_PRESET_DUSTYROOM \ { 0.3645f, 0.5600f, 0.3162f, 0.7943f, 0.7079f, 1.7900f, 0.3800f, 0.2100f, 0.5012f, 0.0020f, { 0.0000f, 0.0000f, 0.0000f }, 1.2589f, 0.0060f, { 0.0000f, 0.0000f, 0.0000f }, 0.2020f, 0.0500f, 0.2500f, 0.0000f, 0.9886f, 13046.0000f, 163.3000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_CHAPEL \ { 1.0000f, 0.8400f, 0.3162f, 0.5623f, 1.0000f, 4.6200f, 0.6400f, 1.2300f, 0.4467f, 0.0320f, { 0.0000f, 0.0000f, 0.0000f }, 0.7943f, 0.0490f, { 0.0000f, 0.0000f, 0.0000f }, 0.2500f, 0.0000f, 0.2500f, 0.1100f, 0.9943f, 5000.0000f, 250.0000f, 0.0000f, 0x1 } #define EFX_REVERB_PRESET_SMALLWATERROOM \ { 1.0000f, 0.7000f, 0.3162f, 0.4477f, 1.0000f, 1.5100f, 1.2500f, 1.1400f, 0.8913f, 0.0200f, { 0.0000f, 0.0000f, 0.0000f }, 1.4125f, 0.0300f, { 0.0000f, 0.0000f, 0.0000f }, 0.1790f, 0.1500f, 0.8950f, 0.1900f, 0.9920f, 5000.0000f, 250.0000f, 0.0000f, 0x0 } /* NOLINTEND */ #endif /* EFX_PRESETS_H */ openal-soft-1.24.2/include/AL/efx.h000066400000000000000000001044131474041540300167060ustar00rootroot00000000000000#ifndef AL_EFX_H #define AL_EFX_H /* NOLINTBEGIN */ #include #include "alc.h" #include "al.h" #ifdef __cplusplus extern "C" { #endif #define ALC_EXT_EFX_NAME "ALC_EXT_EFX" #define ALC_EFX_MAJOR_VERSION 0x20001 #define ALC_EFX_MINOR_VERSION 0x20002 #define ALC_MAX_AUXILIARY_SENDS 0x20003 /* Listener properties. */ #define AL_METERS_PER_UNIT 0x20004 /* Source properties. */ #define AL_DIRECT_FILTER 0x20005 #define AL_AUXILIARY_SEND_FILTER 0x20006 #define AL_AIR_ABSORPTION_FACTOR 0x20007 #define AL_ROOM_ROLLOFF_FACTOR 0x20008 #define AL_CONE_OUTER_GAINHF 0x20009 #define AL_DIRECT_FILTER_GAINHF_AUTO 0x2000A #define AL_AUXILIARY_SEND_FILTER_GAIN_AUTO 0x2000B #define AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO 0x2000C /* Effect properties. */ /* Reverb effect parameters */ #define AL_REVERB_DENSITY 0x0001 #define AL_REVERB_DIFFUSION 0x0002 #define AL_REVERB_GAIN 0x0003 #define AL_REVERB_GAINHF 0x0004 #define AL_REVERB_DECAY_TIME 0x0005 #define AL_REVERB_DECAY_HFRATIO 0x0006 #define AL_REVERB_REFLECTIONS_GAIN 0x0007 #define AL_REVERB_REFLECTIONS_DELAY 0x0008 #define AL_REVERB_LATE_REVERB_GAIN 0x0009 #define AL_REVERB_LATE_REVERB_DELAY 0x000A #define AL_REVERB_AIR_ABSORPTION_GAINHF 0x000B #define AL_REVERB_ROOM_ROLLOFF_FACTOR 0x000C #define AL_REVERB_DECAY_HFLIMIT 0x000D /* EAX Reverb effect parameters */ #define AL_EAXREVERB_DENSITY 0x0001 #define AL_EAXREVERB_DIFFUSION 0x0002 #define AL_EAXREVERB_GAIN 0x0003 #define AL_EAXREVERB_GAINHF 0x0004 #define AL_EAXREVERB_GAINLF 0x0005 #define AL_EAXREVERB_DECAY_TIME 0x0006 #define AL_EAXREVERB_DECAY_HFRATIO 0x0007 #define AL_EAXREVERB_DECAY_LFRATIO 0x0008 #define AL_EAXREVERB_REFLECTIONS_GAIN 0x0009 #define AL_EAXREVERB_REFLECTIONS_DELAY 0x000A #define AL_EAXREVERB_REFLECTIONS_PAN 0x000B #define AL_EAXREVERB_LATE_REVERB_GAIN 0x000C #define AL_EAXREVERB_LATE_REVERB_DELAY 0x000D #define AL_EAXREVERB_LATE_REVERB_PAN 0x000E #define AL_EAXREVERB_ECHO_TIME 0x000F #define AL_EAXREVERB_ECHO_DEPTH 0x0010 #define AL_EAXREVERB_MODULATION_TIME 0x0011 #define AL_EAXREVERB_MODULATION_DEPTH 0x0012 #define AL_EAXREVERB_AIR_ABSORPTION_GAINHF 0x0013 #define AL_EAXREVERB_HFREFERENCE 0x0014 #define AL_EAXREVERB_LFREFERENCE 0x0015 #define AL_EAXREVERB_ROOM_ROLLOFF_FACTOR 0x0016 #define AL_EAXREVERB_DECAY_HFLIMIT 0x0017 /* Chorus effect parameters */ #define AL_CHORUS_WAVEFORM 0x0001 #define AL_CHORUS_PHASE 0x0002 #define AL_CHORUS_RATE 0x0003 #define AL_CHORUS_DEPTH 0x0004 #define AL_CHORUS_FEEDBACK 0x0005 #define AL_CHORUS_DELAY 0x0006 /* Distortion effect parameters */ #define AL_DISTORTION_EDGE 0x0001 #define AL_DISTORTION_GAIN 0x0002 #define AL_DISTORTION_LOWPASS_CUTOFF 0x0003 #define AL_DISTORTION_EQCENTER 0x0004 #define AL_DISTORTION_EQBANDWIDTH 0x0005 /* Echo effect parameters */ #define AL_ECHO_DELAY 0x0001 #define AL_ECHO_LRDELAY 0x0002 #define AL_ECHO_DAMPING 0x0003 #define AL_ECHO_FEEDBACK 0x0004 #define AL_ECHO_SPREAD 0x0005 /* Flanger effect parameters */ #define AL_FLANGER_WAVEFORM 0x0001 #define AL_FLANGER_PHASE 0x0002 #define AL_FLANGER_RATE 0x0003 #define AL_FLANGER_DEPTH 0x0004 #define AL_FLANGER_FEEDBACK 0x0005 #define AL_FLANGER_DELAY 0x0006 /* Frequency shifter effect parameters */ #define AL_FREQUENCY_SHIFTER_FREQUENCY 0x0001 #define AL_FREQUENCY_SHIFTER_LEFT_DIRECTION 0x0002 #define AL_FREQUENCY_SHIFTER_RIGHT_DIRECTION 0x0003 /* Vocal morpher effect parameters */ #define AL_VOCAL_MORPHER_PHONEMEA 0x0001 #define AL_VOCAL_MORPHER_PHONEMEA_COARSE_TUNING 0x0002 #define AL_VOCAL_MORPHER_PHONEMEB 0x0003 #define AL_VOCAL_MORPHER_PHONEMEB_COARSE_TUNING 0x0004 #define AL_VOCAL_MORPHER_WAVEFORM 0x0005 #define AL_VOCAL_MORPHER_RATE 0x0006 /* Pitchshifter effect parameters */ #define AL_PITCH_SHIFTER_COARSE_TUNE 0x0001 #define AL_PITCH_SHIFTER_FINE_TUNE 0x0002 /* Ringmodulator effect parameters */ #define AL_RING_MODULATOR_FREQUENCY 0x0001 #define AL_RING_MODULATOR_HIGHPASS_CUTOFF 0x0002 #define AL_RING_MODULATOR_WAVEFORM 0x0003 /* Autowah effect parameters */ #define AL_AUTOWAH_ATTACK_TIME 0x0001 #define AL_AUTOWAH_RELEASE_TIME 0x0002 #define AL_AUTOWAH_RESONANCE 0x0003 #define AL_AUTOWAH_PEAK_GAIN 0x0004 /* Compressor effect parameters */ #define AL_COMPRESSOR_ONOFF 0x0001 /* Equalizer effect parameters */ #define AL_EQUALIZER_LOW_GAIN 0x0001 #define AL_EQUALIZER_LOW_CUTOFF 0x0002 #define AL_EQUALIZER_MID1_GAIN 0x0003 #define AL_EQUALIZER_MID1_CENTER 0x0004 #define AL_EQUALIZER_MID1_WIDTH 0x0005 #define AL_EQUALIZER_MID2_GAIN 0x0006 #define AL_EQUALIZER_MID2_CENTER 0x0007 #define AL_EQUALIZER_MID2_WIDTH 0x0008 #define AL_EQUALIZER_HIGH_GAIN 0x0009 #define AL_EQUALIZER_HIGH_CUTOFF 0x000A /* Effect type */ #define AL_EFFECT_FIRST_PARAMETER 0x0000 #define AL_EFFECT_LAST_PARAMETER 0x8000 #define AL_EFFECT_TYPE 0x8001 /* Effect types, used with the AL_EFFECT_TYPE property */ #define AL_EFFECT_NULL 0x0000 #define AL_EFFECT_REVERB 0x0001 #define AL_EFFECT_CHORUS 0x0002 #define AL_EFFECT_DISTORTION 0x0003 #define AL_EFFECT_ECHO 0x0004 #define AL_EFFECT_FLANGER 0x0005 #define AL_EFFECT_FREQUENCY_SHIFTER 0x0006 #define AL_EFFECT_VOCAL_MORPHER 0x0007 #define AL_EFFECT_PITCH_SHIFTER 0x0008 #define AL_EFFECT_RING_MODULATOR 0x0009 #define AL_EFFECT_AUTOWAH 0x000A #define AL_EFFECT_COMPRESSOR 0x000B #define AL_EFFECT_EQUALIZER 0x000C #define AL_EFFECT_EAXREVERB 0x8000 /* Auxiliary Effect Slot properties. */ #define AL_EFFECTSLOT_EFFECT 0x0001 #define AL_EFFECTSLOT_GAIN 0x0002 #define AL_EFFECTSLOT_AUXILIARY_SEND_AUTO 0x0003 /* NULL Auxiliary Slot ID to disable a source send. */ #define AL_EFFECTSLOT_NULL 0x0000 /* Filter properties. */ /* Lowpass filter parameters */ #define AL_LOWPASS_GAIN 0x0001 #define AL_LOWPASS_GAINHF 0x0002 /* Highpass filter parameters */ #define AL_HIGHPASS_GAIN 0x0001 #define AL_HIGHPASS_GAINLF 0x0002 /* Bandpass filter parameters */ #define AL_BANDPASS_GAIN 0x0001 #define AL_BANDPASS_GAINLF 0x0002 #define AL_BANDPASS_GAINHF 0x0003 /* Filter type */ #define AL_FILTER_FIRST_PARAMETER 0x0000 #define AL_FILTER_LAST_PARAMETER 0x8000 #define AL_FILTER_TYPE 0x8001 /* Filter types, used with the AL_FILTER_TYPE property */ #define AL_FILTER_NULL 0x0000 #define AL_FILTER_LOWPASS 0x0001 #define AL_FILTER_HIGHPASS 0x0002 #define AL_FILTER_BANDPASS 0x0003 /* Effect object function types. */ typedef void (AL_APIENTRY *LPALGENEFFECTS)(ALsizei, ALuint*) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALDELETEEFFECTS)(ALsizei, const ALuint*) AL_API_NOEXCEPT17; typedef ALboolean (AL_APIENTRY *LPALISEFFECT)(ALuint) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALEFFECTI)(ALuint, ALenum, ALint) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALEFFECTIV)(ALuint, ALenum, const ALint*) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALEFFECTF)(ALuint, ALenum, ALfloat) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALEFFECTFV)(ALuint, ALenum, const ALfloat*) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETEFFECTI)(ALuint, ALenum, ALint*) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETEFFECTIV)(ALuint, ALenum, ALint*) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETEFFECTF)(ALuint, ALenum, ALfloat*) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETEFFECTFV)(ALuint, ALenum, ALfloat*) AL_API_NOEXCEPT17; /* Filter object function types. */ typedef void (AL_APIENTRY *LPALGENFILTERS)(ALsizei, ALuint*) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALDELETEFILTERS)(ALsizei, const ALuint*) AL_API_NOEXCEPT17; typedef ALboolean (AL_APIENTRY *LPALISFILTER)(ALuint) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALFILTERI)(ALuint, ALenum, ALint) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALFILTERIV)(ALuint, ALenum, const ALint*) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALFILTERF)(ALuint, ALenum, ALfloat) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALFILTERFV)(ALuint, ALenum, const ALfloat*) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETFILTERI)(ALuint, ALenum, ALint*) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETFILTERIV)(ALuint, ALenum, ALint*) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETFILTERF)(ALuint, ALenum, ALfloat*) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETFILTERFV)(ALuint, ALenum, ALfloat*) AL_API_NOEXCEPT17; /* Auxiliary Effect Slot object function types. */ typedef void (AL_APIENTRY *LPALGENAUXILIARYEFFECTSLOTS)(ALsizei, ALuint*) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALDELETEAUXILIARYEFFECTSLOTS)(ALsizei, const ALuint*) AL_API_NOEXCEPT17; typedef ALboolean (AL_APIENTRY *LPALISAUXILIARYEFFECTSLOT)(ALuint) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALAUXILIARYEFFECTSLOTI)(ALuint, ALenum, ALint) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALAUXILIARYEFFECTSLOTIV)(ALuint, ALenum, const ALint*) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALAUXILIARYEFFECTSLOTF)(ALuint, ALenum, ALfloat) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALAUXILIARYEFFECTSLOTFV)(ALuint, ALenum, const ALfloat*) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETAUXILIARYEFFECTSLOTI)(ALuint, ALenum, ALint*) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETAUXILIARYEFFECTSLOTIV)(ALuint, ALenum, ALint*) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETAUXILIARYEFFECTSLOTF)(ALuint, ALenum, ALfloat*) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETAUXILIARYEFFECTSLOTFV)(ALuint, ALenum, ALfloat*) AL_API_NOEXCEPT17; #ifdef AL_ALEXT_PROTOTYPES AL_API void AL_APIENTRY alGenEffects(ALsizei n, ALuint *effects) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alDeleteEffects(ALsizei n, const ALuint *effects) AL_API_NOEXCEPT; AL_API ALboolean AL_APIENTRY alIsEffect(ALuint effect) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alEffecti(ALuint effect, ALenum param, ALint iValue) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alEffectiv(ALuint effect, ALenum param, const ALint *piValues) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alEffectf(ALuint effect, ALenum param, ALfloat flValue) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alEffectfv(ALuint effect, ALenum param, const ALfloat *pflValues) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alGetEffecti(ALuint effect, ALenum param, ALint *piValue) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alGetEffectiv(ALuint effect, ALenum param, ALint *piValues) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alGetEffectf(ALuint effect, ALenum param, ALfloat *pflValue) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alGetEffectfv(ALuint effect, ALenum param, ALfloat *pflValues) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alGenFilters(ALsizei n, ALuint *filters) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alDeleteFilters(ALsizei n, const ALuint *filters) AL_API_NOEXCEPT; AL_API ALboolean AL_APIENTRY alIsFilter(ALuint filter) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alFilteri(ALuint filter, ALenum param, ALint iValue) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alFilteriv(ALuint filter, ALenum param, const ALint *piValues) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alFilterf(ALuint filter, ALenum param, ALfloat flValue) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alFilterfv(ALuint filter, ALenum param, const ALfloat *pflValues) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alGetFilteri(ALuint filter, ALenum param, ALint *piValue) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alGetFilteriv(ALuint filter, ALenum param, ALint *piValues) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alGetFilterf(ALuint filter, ALenum param, ALfloat *pflValue) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alGetFilterfv(ALuint filter, ALenum param, ALfloat *pflValues) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alGenAuxiliaryEffectSlots(ALsizei n, ALuint *effectslots) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alDeleteAuxiliaryEffectSlots(ALsizei n, const ALuint *effectslots) AL_API_NOEXCEPT; AL_API ALboolean AL_APIENTRY alIsAuxiliaryEffectSlot(ALuint effectslot) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alAuxiliaryEffectSloti(ALuint effectslot, ALenum param, ALint iValue) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alAuxiliaryEffectSlotiv(ALuint effectslot, ALenum param, const ALint *piValues) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alAuxiliaryEffectSlotf(ALuint effectslot, ALenum param, ALfloat flValue) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alAuxiliaryEffectSlotfv(ALuint effectslot, ALenum param, const ALfloat *pflValues) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alGetAuxiliaryEffectSloti(ALuint effectslot, ALenum param, ALint *piValue) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alGetAuxiliaryEffectSlotiv(ALuint effectslot, ALenum param, ALint *piValues) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alGetAuxiliaryEffectSlotf(ALuint effectslot, ALenum param, ALfloat *pflValue) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alGetAuxiliaryEffectSlotfv(ALuint effectslot, ALenum param, ALfloat *pflValues) AL_API_NOEXCEPT; #endif /* Filter ranges and defaults. */ /* Lowpass filter */ #define AL_LOWPASS_MIN_GAIN (0.0f) #define AL_LOWPASS_MAX_GAIN (1.0f) #define AL_LOWPASS_DEFAULT_GAIN (1.0f) #define AL_LOWPASS_MIN_GAINHF (0.0f) #define AL_LOWPASS_MAX_GAINHF (1.0f) #define AL_LOWPASS_DEFAULT_GAINHF (1.0f) /* Highpass filter */ #define AL_HIGHPASS_MIN_GAIN (0.0f) #define AL_HIGHPASS_MAX_GAIN (1.0f) #define AL_HIGHPASS_DEFAULT_GAIN (1.0f) #define AL_HIGHPASS_MIN_GAINLF (0.0f) #define AL_HIGHPASS_MAX_GAINLF (1.0f) #define AL_HIGHPASS_DEFAULT_GAINLF (1.0f) /* Bandpass filter */ #define AL_BANDPASS_MIN_GAIN (0.0f) #define AL_BANDPASS_MAX_GAIN (1.0f) #define AL_BANDPASS_DEFAULT_GAIN (1.0f) #define AL_BANDPASS_MIN_GAINHF (0.0f) #define AL_BANDPASS_MAX_GAINHF (1.0f) #define AL_BANDPASS_DEFAULT_GAINHF (1.0f) #define AL_BANDPASS_MIN_GAINLF (0.0f) #define AL_BANDPASS_MAX_GAINLF (1.0f) #define AL_BANDPASS_DEFAULT_GAINLF (1.0f) /* Effect parameter ranges and defaults. */ /* Standard reverb effect */ #define AL_REVERB_MIN_DENSITY (0.0f) #define AL_REVERB_MAX_DENSITY (1.0f) #define AL_REVERB_DEFAULT_DENSITY (1.0f) #define AL_REVERB_MIN_DIFFUSION (0.0f) #define AL_REVERB_MAX_DIFFUSION (1.0f) #define AL_REVERB_DEFAULT_DIFFUSION (1.0f) #define AL_REVERB_MIN_GAIN (0.0f) #define AL_REVERB_MAX_GAIN (1.0f) #define AL_REVERB_DEFAULT_GAIN (0.32f) #define AL_REVERB_MIN_GAINHF (0.0f) #define AL_REVERB_MAX_GAINHF (1.0f) #define AL_REVERB_DEFAULT_GAINHF (0.89f) #define AL_REVERB_MIN_DECAY_TIME (0.1f) #define AL_REVERB_MAX_DECAY_TIME (20.0f) #define AL_REVERB_DEFAULT_DECAY_TIME (1.49f) #define AL_REVERB_MIN_DECAY_HFRATIO (0.1f) #define AL_REVERB_MAX_DECAY_HFRATIO (2.0f) #define AL_REVERB_DEFAULT_DECAY_HFRATIO (0.83f) #define AL_REVERB_MIN_REFLECTIONS_GAIN (0.0f) #define AL_REVERB_MAX_REFLECTIONS_GAIN (3.16f) #define AL_REVERB_DEFAULT_REFLECTIONS_GAIN (0.05f) #define AL_REVERB_MIN_REFLECTIONS_DELAY (0.0f) #define AL_REVERB_MAX_REFLECTIONS_DELAY (0.3f) #define AL_REVERB_DEFAULT_REFLECTIONS_DELAY (0.007f) #define AL_REVERB_MIN_LATE_REVERB_GAIN (0.0f) #define AL_REVERB_MAX_LATE_REVERB_GAIN (10.0f) #define AL_REVERB_DEFAULT_LATE_REVERB_GAIN (1.26f) #define AL_REVERB_MIN_LATE_REVERB_DELAY (0.0f) #define AL_REVERB_MAX_LATE_REVERB_DELAY (0.1f) #define AL_REVERB_DEFAULT_LATE_REVERB_DELAY (0.011f) #define AL_REVERB_MIN_AIR_ABSORPTION_GAINHF (0.892f) #define AL_REVERB_MAX_AIR_ABSORPTION_GAINHF (1.0f) #define AL_REVERB_DEFAULT_AIR_ABSORPTION_GAINHF (0.994f) #define AL_REVERB_MIN_ROOM_ROLLOFF_FACTOR (0.0f) #define AL_REVERB_MAX_ROOM_ROLLOFF_FACTOR (10.0f) #define AL_REVERB_DEFAULT_ROOM_ROLLOFF_FACTOR (0.0f) #define AL_REVERB_MIN_DECAY_HFLIMIT AL_FALSE #define AL_REVERB_MAX_DECAY_HFLIMIT AL_TRUE #define AL_REVERB_DEFAULT_DECAY_HFLIMIT AL_TRUE /* EAX reverb effect */ #define AL_EAXREVERB_MIN_DENSITY (0.0f) #define AL_EAXREVERB_MAX_DENSITY (1.0f) #define AL_EAXREVERB_DEFAULT_DENSITY (1.0f) #define AL_EAXREVERB_MIN_DIFFUSION (0.0f) #define AL_EAXREVERB_MAX_DIFFUSION (1.0f) #define AL_EAXREVERB_DEFAULT_DIFFUSION (1.0f) #define AL_EAXREVERB_MIN_GAIN (0.0f) #define AL_EAXREVERB_MAX_GAIN (1.0f) #define AL_EAXREVERB_DEFAULT_GAIN (0.32f) #define AL_EAXREVERB_MIN_GAINHF (0.0f) #define AL_EAXREVERB_MAX_GAINHF (1.0f) #define AL_EAXREVERB_DEFAULT_GAINHF (0.89f) #define AL_EAXREVERB_MIN_GAINLF (0.0f) #define AL_EAXREVERB_MAX_GAINLF (1.0f) #define AL_EAXREVERB_DEFAULT_GAINLF (1.0f) #define AL_EAXREVERB_MIN_DECAY_TIME (0.1f) #define AL_EAXREVERB_MAX_DECAY_TIME (20.0f) #define AL_EAXREVERB_DEFAULT_DECAY_TIME (1.49f) #define AL_EAXREVERB_MIN_DECAY_HFRATIO (0.1f) #define AL_EAXREVERB_MAX_DECAY_HFRATIO (2.0f) #define AL_EAXREVERB_DEFAULT_DECAY_HFRATIO (0.83f) #define AL_EAXREVERB_MIN_DECAY_LFRATIO (0.1f) #define AL_EAXREVERB_MAX_DECAY_LFRATIO (2.0f) #define AL_EAXREVERB_DEFAULT_DECAY_LFRATIO (1.0f) #define AL_EAXREVERB_MIN_REFLECTIONS_GAIN (0.0f) #define AL_EAXREVERB_MAX_REFLECTIONS_GAIN (3.16f) #define AL_EAXREVERB_DEFAULT_REFLECTIONS_GAIN (0.05f) #define AL_EAXREVERB_MIN_REFLECTIONS_DELAY (0.0f) #define AL_EAXREVERB_MAX_REFLECTIONS_DELAY (0.3f) #define AL_EAXREVERB_DEFAULT_REFLECTIONS_DELAY (0.007f) #define AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ (0.0f) #define AL_EAXREVERB_MIN_LATE_REVERB_GAIN (0.0f) #define AL_EAXREVERB_MAX_LATE_REVERB_GAIN (10.0f) #define AL_EAXREVERB_DEFAULT_LATE_REVERB_GAIN (1.26f) #define AL_EAXREVERB_MIN_LATE_REVERB_DELAY (0.0f) #define AL_EAXREVERB_MAX_LATE_REVERB_DELAY (0.1f) #define AL_EAXREVERB_DEFAULT_LATE_REVERB_DELAY (0.011f) #define AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ (0.0f) #define AL_EAXREVERB_MIN_ECHO_TIME (0.075f) #define AL_EAXREVERB_MAX_ECHO_TIME (0.25f) #define AL_EAXREVERB_DEFAULT_ECHO_TIME (0.25f) #define AL_EAXREVERB_MIN_ECHO_DEPTH (0.0f) #define AL_EAXREVERB_MAX_ECHO_DEPTH (1.0f) #define AL_EAXREVERB_DEFAULT_ECHO_DEPTH (0.0f) #define AL_EAXREVERB_MIN_MODULATION_TIME (0.04f) #define AL_EAXREVERB_MAX_MODULATION_TIME (4.0f) #define AL_EAXREVERB_DEFAULT_MODULATION_TIME (0.25f) #define AL_EAXREVERB_MIN_MODULATION_DEPTH (0.0f) #define AL_EAXREVERB_MAX_MODULATION_DEPTH (1.0f) #define AL_EAXREVERB_DEFAULT_MODULATION_DEPTH (0.0f) #define AL_EAXREVERB_MIN_AIR_ABSORPTION_GAINHF (0.892f) #define AL_EAXREVERB_MAX_AIR_ABSORPTION_GAINHF (1.0f) #define AL_EAXREVERB_DEFAULT_AIR_ABSORPTION_GAINHF (0.994f) #define AL_EAXREVERB_MIN_HFREFERENCE (1000.0f) #define AL_EAXREVERB_MAX_HFREFERENCE (20000.0f) #define AL_EAXREVERB_DEFAULT_HFREFERENCE (5000.0f) #define AL_EAXREVERB_MIN_LFREFERENCE (20.0f) #define AL_EAXREVERB_MAX_LFREFERENCE (1000.0f) #define AL_EAXREVERB_DEFAULT_LFREFERENCE (250.0f) #define AL_EAXREVERB_MIN_ROOM_ROLLOFF_FACTOR (0.0f) #define AL_EAXREVERB_MAX_ROOM_ROLLOFF_FACTOR (10.0f) #define AL_EAXREVERB_DEFAULT_ROOM_ROLLOFF_FACTOR (0.0f) #define AL_EAXREVERB_MIN_DECAY_HFLIMIT AL_FALSE #define AL_EAXREVERB_MAX_DECAY_HFLIMIT AL_TRUE #define AL_EAXREVERB_DEFAULT_DECAY_HFLIMIT AL_TRUE /* Chorus effect */ #define AL_CHORUS_WAVEFORM_SINUSOID (0) #define AL_CHORUS_WAVEFORM_TRIANGLE (1) #define AL_CHORUS_MIN_WAVEFORM (0) #define AL_CHORUS_MAX_WAVEFORM (1) #define AL_CHORUS_DEFAULT_WAVEFORM (1) #define AL_CHORUS_MIN_PHASE (-180) #define AL_CHORUS_MAX_PHASE (180) #define AL_CHORUS_DEFAULT_PHASE (90) #define AL_CHORUS_MIN_RATE (0.0f) #define AL_CHORUS_MAX_RATE (10.0f) #define AL_CHORUS_DEFAULT_RATE (1.1f) #define AL_CHORUS_MIN_DEPTH (0.0f) #define AL_CHORUS_MAX_DEPTH (1.0f) #define AL_CHORUS_DEFAULT_DEPTH (0.1f) #define AL_CHORUS_MIN_FEEDBACK (-1.0f) #define AL_CHORUS_MAX_FEEDBACK (1.0f) #define AL_CHORUS_DEFAULT_FEEDBACK (0.25f) #define AL_CHORUS_MIN_DELAY (0.0f) #define AL_CHORUS_MAX_DELAY (0.016f) #define AL_CHORUS_DEFAULT_DELAY (0.016f) /* Distortion effect */ #define AL_DISTORTION_MIN_EDGE (0.0f) #define AL_DISTORTION_MAX_EDGE (1.0f) #define AL_DISTORTION_DEFAULT_EDGE (0.2f) #define AL_DISTORTION_MIN_GAIN (0.01f) #define AL_DISTORTION_MAX_GAIN (1.0f) #define AL_DISTORTION_DEFAULT_GAIN (0.05f) #define AL_DISTORTION_MIN_LOWPASS_CUTOFF (80.0f) #define AL_DISTORTION_MAX_LOWPASS_CUTOFF (24000.0f) #define AL_DISTORTION_DEFAULT_LOWPASS_CUTOFF (8000.0f) #define AL_DISTORTION_MIN_EQCENTER (80.0f) #define AL_DISTORTION_MAX_EQCENTER (24000.0f) #define AL_DISTORTION_DEFAULT_EQCENTER (3600.0f) #define AL_DISTORTION_MIN_EQBANDWIDTH (80.0f) #define AL_DISTORTION_MAX_EQBANDWIDTH (24000.0f) #define AL_DISTORTION_DEFAULT_EQBANDWIDTH (3600.0f) /* Echo effect */ #define AL_ECHO_MIN_DELAY (0.0f) #define AL_ECHO_MAX_DELAY (0.207f) #define AL_ECHO_DEFAULT_DELAY (0.1f) #define AL_ECHO_MIN_LRDELAY (0.0f) #define AL_ECHO_MAX_LRDELAY (0.404f) #define AL_ECHO_DEFAULT_LRDELAY (0.1f) #define AL_ECHO_MIN_DAMPING (0.0f) #define AL_ECHO_MAX_DAMPING (0.99f) #define AL_ECHO_DEFAULT_DAMPING (0.5f) #define AL_ECHO_MIN_FEEDBACK (0.0f) #define AL_ECHO_MAX_FEEDBACK (1.0f) #define AL_ECHO_DEFAULT_FEEDBACK (0.5f) #define AL_ECHO_MIN_SPREAD (-1.0f) #define AL_ECHO_MAX_SPREAD (1.0f) #define AL_ECHO_DEFAULT_SPREAD (-1.0f) /* Flanger effect */ #define AL_FLANGER_WAVEFORM_SINUSOID (0) #define AL_FLANGER_WAVEFORM_TRIANGLE (1) #define AL_FLANGER_MIN_WAVEFORM (0) #define AL_FLANGER_MAX_WAVEFORM (1) #define AL_FLANGER_DEFAULT_WAVEFORM (1) #define AL_FLANGER_MIN_PHASE (-180) #define AL_FLANGER_MAX_PHASE (180) #define AL_FLANGER_DEFAULT_PHASE (0) #define AL_FLANGER_MIN_RATE (0.0f) #define AL_FLANGER_MAX_RATE (10.0f) #define AL_FLANGER_DEFAULT_RATE (0.27f) #define AL_FLANGER_MIN_DEPTH (0.0f) #define AL_FLANGER_MAX_DEPTH (1.0f) #define AL_FLANGER_DEFAULT_DEPTH (1.0f) #define AL_FLANGER_MIN_FEEDBACK (-1.0f) #define AL_FLANGER_MAX_FEEDBACK (1.0f) #define AL_FLANGER_DEFAULT_FEEDBACK (-0.5f) #define AL_FLANGER_MIN_DELAY (0.0f) #define AL_FLANGER_MAX_DELAY (0.004f) #define AL_FLANGER_DEFAULT_DELAY (0.002f) /* Frequency shifter effect */ #define AL_FREQUENCY_SHIFTER_MIN_FREQUENCY (0.0f) #define AL_FREQUENCY_SHIFTER_MAX_FREQUENCY (24000.0f) #define AL_FREQUENCY_SHIFTER_DEFAULT_FREQUENCY (0.0f) #define AL_FREQUENCY_SHIFTER_MIN_LEFT_DIRECTION (0) #define AL_FREQUENCY_SHIFTER_MAX_LEFT_DIRECTION (2) #define AL_FREQUENCY_SHIFTER_DEFAULT_LEFT_DIRECTION (0) #define AL_FREQUENCY_SHIFTER_DIRECTION_DOWN (0) #define AL_FREQUENCY_SHIFTER_DIRECTION_UP (1) #define AL_FREQUENCY_SHIFTER_DIRECTION_OFF (2) #define AL_FREQUENCY_SHIFTER_MIN_RIGHT_DIRECTION (0) #define AL_FREQUENCY_SHIFTER_MAX_RIGHT_DIRECTION (2) #define AL_FREQUENCY_SHIFTER_DEFAULT_RIGHT_DIRECTION (0) /* Vocal morpher effect */ #define AL_VOCAL_MORPHER_MIN_PHONEMEA (0) #define AL_VOCAL_MORPHER_MAX_PHONEMEA (29) #define AL_VOCAL_MORPHER_DEFAULT_PHONEMEA (0) #define AL_VOCAL_MORPHER_MIN_PHONEMEA_COARSE_TUNING (-24) #define AL_VOCAL_MORPHER_MAX_PHONEMEA_COARSE_TUNING (24) #define AL_VOCAL_MORPHER_DEFAULT_PHONEMEA_COARSE_TUNING (0) #define AL_VOCAL_MORPHER_MIN_PHONEMEB (0) #define AL_VOCAL_MORPHER_MAX_PHONEMEB (29) #define AL_VOCAL_MORPHER_DEFAULT_PHONEMEB (10) #define AL_VOCAL_MORPHER_MIN_PHONEMEB_COARSE_TUNING (-24) #define AL_VOCAL_MORPHER_MAX_PHONEMEB_COARSE_TUNING (24) #define AL_VOCAL_MORPHER_DEFAULT_PHONEMEB_COARSE_TUNING (0) #define AL_VOCAL_MORPHER_PHONEME_A (0) #define AL_VOCAL_MORPHER_PHONEME_E (1) #define AL_VOCAL_MORPHER_PHONEME_I (2) #define AL_VOCAL_MORPHER_PHONEME_O (3) #define AL_VOCAL_MORPHER_PHONEME_U (4) #define AL_VOCAL_MORPHER_PHONEME_AA (5) #define AL_VOCAL_MORPHER_PHONEME_AE (6) #define AL_VOCAL_MORPHER_PHONEME_AH (7) #define AL_VOCAL_MORPHER_PHONEME_AO (8) #define AL_VOCAL_MORPHER_PHONEME_EH (9) #define AL_VOCAL_MORPHER_PHONEME_ER (10) #define AL_VOCAL_MORPHER_PHONEME_IH (11) #define AL_VOCAL_MORPHER_PHONEME_IY (12) #define AL_VOCAL_MORPHER_PHONEME_UH (13) #define AL_VOCAL_MORPHER_PHONEME_UW (14) #define AL_VOCAL_MORPHER_PHONEME_B (15) #define AL_VOCAL_MORPHER_PHONEME_D (16) #define AL_VOCAL_MORPHER_PHONEME_F (17) #define AL_VOCAL_MORPHER_PHONEME_G (18) #define AL_VOCAL_MORPHER_PHONEME_J (19) #define AL_VOCAL_MORPHER_PHONEME_K (20) #define AL_VOCAL_MORPHER_PHONEME_L (21) #define AL_VOCAL_MORPHER_PHONEME_M (22) #define AL_VOCAL_MORPHER_PHONEME_N (23) #define AL_VOCAL_MORPHER_PHONEME_P (24) #define AL_VOCAL_MORPHER_PHONEME_R (25) #define AL_VOCAL_MORPHER_PHONEME_S (26) #define AL_VOCAL_MORPHER_PHONEME_T (27) #define AL_VOCAL_MORPHER_PHONEME_V (28) #define AL_VOCAL_MORPHER_PHONEME_Z (29) #define AL_VOCAL_MORPHER_WAVEFORM_SINUSOID (0) #define AL_VOCAL_MORPHER_WAVEFORM_TRIANGLE (1) #define AL_VOCAL_MORPHER_WAVEFORM_SAWTOOTH (2) #define AL_VOCAL_MORPHER_MIN_WAVEFORM (0) #define AL_VOCAL_MORPHER_MAX_WAVEFORM (2) #define AL_VOCAL_MORPHER_DEFAULT_WAVEFORM (0) #define AL_VOCAL_MORPHER_MIN_RATE (0.0f) #define AL_VOCAL_MORPHER_MAX_RATE (10.0f) #define AL_VOCAL_MORPHER_DEFAULT_RATE (1.41f) /* Pitch shifter effect */ #define AL_PITCH_SHIFTER_MIN_COARSE_TUNE (-12) #define AL_PITCH_SHIFTER_MAX_COARSE_TUNE (12) #define AL_PITCH_SHIFTER_DEFAULT_COARSE_TUNE (12) #define AL_PITCH_SHIFTER_MIN_FINE_TUNE (-50) #define AL_PITCH_SHIFTER_MAX_FINE_TUNE (50) #define AL_PITCH_SHIFTER_DEFAULT_FINE_TUNE (0) /* Ring modulator effect */ #define AL_RING_MODULATOR_MIN_FREQUENCY (0.0f) #define AL_RING_MODULATOR_MAX_FREQUENCY (8000.0f) #define AL_RING_MODULATOR_DEFAULT_FREQUENCY (440.0f) #define AL_RING_MODULATOR_MIN_HIGHPASS_CUTOFF (0.0f) #define AL_RING_MODULATOR_MAX_HIGHPASS_CUTOFF (24000.0f) #define AL_RING_MODULATOR_DEFAULT_HIGHPASS_CUTOFF (800.0f) #define AL_RING_MODULATOR_SINUSOID (0) #define AL_RING_MODULATOR_SAWTOOTH (1) #define AL_RING_MODULATOR_SQUARE (2) #define AL_RING_MODULATOR_MIN_WAVEFORM (0) #define AL_RING_MODULATOR_MAX_WAVEFORM (2) #define AL_RING_MODULATOR_DEFAULT_WAVEFORM (0) /* Autowah effect */ #define AL_AUTOWAH_MIN_ATTACK_TIME (0.0001f) #define AL_AUTOWAH_MAX_ATTACK_TIME (1.0f) #define AL_AUTOWAH_DEFAULT_ATTACK_TIME (0.06f) #define AL_AUTOWAH_MIN_RELEASE_TIME (0.0001f) #define AL_AUTOWAH_MAX_RELEASE_TIME (1.0f) #define AL_AUTOWAH_DEFAULT_RELEASE_TIME (0.06f) #define AL_AUTOWAH_MIN_RESONANCE (2.0f) #define AL_AUTOWAH_MAX_RESONANCE (1000.0f) #define AL_AUTOWAH_DEFAULT_RESONANCE (1000.0f) #define AL_AUTOWAH_MIN_PEAK_GAIN (0.00003f) #define AL_AUTOWAH_MAX_PEAK_GAIN (31621.0f) #define AL_AUTOWAH_DEFAULT_PEAK_GAIN (11.22f) /* Compressor effect */ #define AL_COMPRESSOR_MIN_ONOFF (0) #define AL_COMPRESSOR_MAX_ONOFF (1) #define AL_COMPRESSOR_DEFAULT_ONOFF (1) /* Equalizer effect */ #define AL_EQUALIZER_MIN_LOW_GAIN (0.126f) #define AL_EQUALIZER_MAX_LOW_GAIN (7.943f) #define AL_EQUALIZER_DEFAULT_LOW_GAIN (1.0f) #define AL_EQUALIZER_MIN_LOW_CUTOFF (50.0f) #define AL_EQUALIZER_MAX_LOW_CUTOFF (800.0f) #define AL_EQUALIZER_DEFAULT_LOW_CUTOFF (200.0f) #define AL_EQUALIZER_MIN_MID1_GAIN (0.126f) #define AL_EQUALIZER_MAX_MID1_GAIN (7.943f) #define AL_EQUALIZER_DEFAULT_MID1_GAIN (1.0f) #define AL_EQUALIZER_MIN_MID1_CENTER (200.0f) #define AL_EQUALIZER_MAX_MID1_CENTER (3000.0f) #define AL_EQUALIZER_DEFAULT_MID1_CENTER (500.0f) #define AL_EQUALIZER_MIN_MID1_WIDTH (0.01f) #define AL_EQUALIZER_MAX_MID1_WIDTH (1.0f) #define AL_EQUALIZER_DEFAULT_MID1_WIDTH (1.0f) #define AL_EQUALIZER_MIN_MID2_GAIN (0.126f) #define AL_EQUALIZER_MAX_MID2_GAIN (7.943f) #define AL_EQUALIZER_DEFAULT_MID2_GAIN (1.0f) #define AL_EQUALIZER_MIN_MID2_CENTER (1000.0f) #define AL_EQUALIZER_MAX_MID2_CENTER (8000.0f) #define AL_EQUALIZER_DEFAULT_MID2_CENTER (3000.0f) #define AL_EQUALIZER_MIN_MID2_WIDTH (0.01f) #define AL_EQUALIZER_MAX_MID2_WIDTH (1.0f) #define AL_EQUALIZER_DEFAULT_MID2_WIDTH (1.0f) #define AL_EQUALIZER_MIN_HIGH_GAIN (0.126f) #define AL_EQUALIZER_MAX_HIGH_GAIN (7.943f) #define AL_EQUALIZER_DEFAULT_HIGH_GAIN (1.0f) #define AL_EQUALIZER_MIN_HIGH_CUTOFF (4000.0f) #define AL_EQUALIZER_MAX_HIGH_CUTOFF (16000.0f) #define AL_EQUALIZER_DEFAULT_HIGH_CUTOFF (6000.0f) /* Source parameter value ranges and defaults. */ #define AL_MIN_AIR_ABSORPTION_FACTOR (0.0f) #define AL_MAX_AIR_ABSORPTION_FACTOR (10.0f) #define AL_DEFAULT_AIR_ABSORPTION_FACTOR (0.0f) #define AL_MIN_ROOM_ROLLOFF_FACTOR (0.0f) #define AL_MAX_ROOM_ROLLOFF_FACTOR (10.0f) #define AL_DEFAULT_ROOM_ROLLOFF_FACTOR (0.0f) #define AL_MIN_CONE_OUTER_GAINHF (0.0f) #define AL_MAX_CONE_OUTER_GAINHF (1.0f) #define AL_DEFAULT_CONE_OUTER_GAINHF (1.0f) #define AL_MIN_DIRECT_FILTER_GAINHF_AUTO AL_FALSE #define AL_MAX_DIRECT_FILTER_GAINHF_AUTO AL_TRUE #define AL_DEFAULT_DIRECT_FILTER_GAINHF_AUTO AL_TRUE #define AL_MIN_AUXILIARY_SEND_FILTER_GAIN_AUTO AL_FALSE #define AL_MAX_AUXILIARY_SEND_FILTER_GAIN_AUTO AL_TRUE #define AL_DEFAULT_AUXILIARY_SEND_FILTER_GAIN_AUTO AL_TRUE #define AL_MIN_AUXILIARY_SEND_FILTER_GAINHF_AUTO AL_FALSE #define AL_MAX_AUXILIARY_SEND_FILTER_GAINHF_AUTO AL_TRUE #define AL_DEFAULT_AUXILIARY_SEND_FILTER_GAINHF_AUTO AL_TRUE /* Listener parameter value ranges and defaults. */ #define AL_MIN_METERS_PER_UNIT FLT_MIN #define AL_MAX_METERS_PER_UNIT FLT_MAX #define AL_DEFAULT_METERS_PER_UNIT (1.0f) #ifdef __cplusplus } /* extern "C" */ #endif /* NOLINTEND */ #endif /* AL_EFX_H */ openal-soft-1.24.2/libopenal.version000066400000000000000000000066461474041540300174210ustar00rootroot00000000000000{ global: alBuffer3f; alBuffer3i; alBufferData; alBufferf; alBufferfv; alBufferi; alBufferiv; alcCaptureCloseDevice; alcCaptureOpenDevice; alcCaptureSamples; alcCaptureStart; alcCaptureStop; alcCloseDevice; alcCreateContext; alcDestroyContext; alcGetContextsDevice; alcGetCurrentContext; alcGetEnumValue; alcGetError; alcGetIntegerv; alcGetProcAddress; alcGetString; alcIsExtensionPresent; alcMakeContextCurrent; alcOpenDevice; alcProcessContext; alcSuspendContext; alDeleteBuffers; alDeleteSources; alDisable; alDistanceModel; alDopplerFactor; alEnable; alGenBuffers; alGenSources; alGetBoolean; alGetBooleanv; alGetBuffer3f; alGetBuffer3i; alGetBufferf; alGetBufferfv; alGetBufferi; alGetBufferiv; alGetDouble; alGetDoublev; alGetEnumValue; alGetError; alGetFloat; alGetFloatv; alGetInteger; alGetIntegerv; alGetListener3f; alGetListener3i; alGetListenerf; alGetListenerfv; alGetListeneri; alGetListeneriv; alGetProcAddress; alGetSource3f; alGetSource3i; alGetSourcef; alGetSourcefv; alGetSourcei; alGetSourceiv; alGetString; alIsBuffer; alIsEnabled; alIsExtensionPresent; alIsSource; alListener3f; alListener3i; alListenerf; alListenerfv; alListeneri; alListeneriv; alSource3f; alSource3i; alSourcef; alSourcefv; alSourcei; alSourceiv; alSourcePause; alSourcePausev; alSourcePlay; alSourcePlayv; alSourceQueueBuffers; alSourceRewind; alSourceRewindv; alSourceStop; alSourceStopv; alSourceUnqueueBuffers; alSpeedOfSound; # Deprecated in AL 1.1, kept for compatibility. alDopplerVelocity; # EFX, effectively standard at this point. alAuxiliaryEffectSlotf; alAuxiliaryEffectSlotfv; alAuxiliaryEffectSloti; alAuxiliaryEffectSlotiv; alDeleteAuxiliaryEffectSlots; alDeleteEffects; alDeleteFilters; alEffectf; alEffectfv; alEffecti; alEffectiv; alFilterf; alFilterfv; alFilteri; alFilteriv; alGenAuxiliaryEffectSlots; alGenEffects; alGenFilters; alGetAuxiliaryEffectSlotf; alGetAuxiliaryEffectSlotfv; alGetAuxiliaryEffectSloti; alGetAuxiliaryEffectSlotiv; alGetEffectf; alGetEffectfv; alGetEffecti; alGetEffectiv; alGetFilterf; alGetFilterfv; alGetFilteri; alGetFilteriv; alIsAuxiliaryEffectSlot; alIsEffect; alIsFilter; # Non-standard alsoft_get_version; # These extension functions shouldn't be exported here, but they were exported # by mistake in previous releases, so need to stay for compatibility with apps # that may have directly linked to them. Remove them if it can be done without # breaking anything. alAuxiliaryEffectSlotPlaySOFT; alAuxiliaryEffectSlotPlayvSOFT; alAuxiliaryEffectSlotStopSOFT; alAuxiliaryEffectSlotStopvSOFT; alBufferCallbackSOFT; alBufferSamplesSOFT; alBufferStorageSOFT; alBufferSubDataSOFT; alBufferSubSamplesSOFT; alcDevicePauseSOFT; alcDeviceResumeSOFT; alcGetInteger64vSOFT; alcGetStringiSOFT; alcGetThreadContext; alcIsRenderFormatSupportedSOFT; alcLoopbackOpenDeviceSOFT; alcRenderSamplesSOFT; alcResetDeviceSOFT; alcSetThreadContext; alDeferUpdatesSOFT; alEventCallbackSOFT; alEventControlSOFT; alFlushMappedBufferSOFT; alGetBuffer3PtrSOFT; alGetBufferPtrSOFT; alGetBufferPtrvSOFT; alGetBufferSamplesSOFT; alGetInteger64SOFT; alGetInteger64vSOFT; alGetPointerSOFT; alGetPointervSOFT; alGetSource3dSOFT; alGetSource3i64SOFT; alGetSourcedSOFT; alGetSourcedvSOFT; alGetSourcei64SOFT; alGetSourcei64vSOFT; alGetStringiSOFT; alIsBufferFormatSupportedSOFT; alMapBufferSOFT; alProcessUpdatesSOFT; alSource3dSOFT; alSource3i64SOFT; alSourcedSOFT; alSourcedvSOFT; alSourcei64SOFT; alSourcei64vSOFT; alSourceQueueBufferLayersSOFT; alUnmapBufferSOFT; local: *; }; openal-soft-1.24.2/openal.pc.in000066400000000000000000000005461474041540300162450ustar00rootroot00000000000000prefix=@prefix@ exec_prefix=@exec_prefix@ libdir=@libdir@ includedir=@includedir@ Name: OpenAL Description: OpenAL is a cross-platform 3D audio API Requires: @PKG_CONFIG_REQUIRES@ Version: @PACKAGE_VERSION@ Libs: -L${libdir} -l@LIBNAME@ @PKG_CONFIG_LIBS@ Libs.private:@PKG_CONFIG_PRIVATE_LIBS@ Cflags: -I${includedir} -I${includedir}/AL @PKG_CONFIG_CFLAGS@ openal-soft-1.24.2/presets/000077500000000000000000000000001474041540300155165ustar00rootroot00000000000000openal-soft-1.24.2/presets/3D7.1.ambdec000066400000000000000000000053051474041540300173520ustar00rootroot00000000000000# AmbDec configuration # Written by Ambisonic Decoder Toolbox, version 8.0 # input channel order: W Y Z X /description 3D7-noCenter_1h1v_pinv_even_energy_rV_max_rE_2_band # In OpenAL Soft, 3D7.1 is a distinct configuration that uses the standard 5.1 # channels (LF, RF, CE, LS, RS), plus two auxiliary channels (AUX0, AUX1) in # place of the rear speakers. AUX0 corresponds to the LB speaker (upper back # center), and AUX1 corresponds to the RB speaker (lower front center). # Similar to the the ITU-5.1-nocenter configuration, the front-center is # declared here so that an appropriate distance may be set (for proper delaying # or attenuating of dialog and such which feed it directly). It otherwise does # not contribute to positional sound output due to its irregular position. /version 3 /dec/chan_mask f /dec/freq_bands 2 /dec/speakers 6 /dec/coeff_scale n3d /opt/input_scale n3d /opt/nfeff_comp input /opt/delay_comp on /opt/level_comp on /opt/xover_freq 400.000000 /opt/xover_ratio 0.000000 /speakers/{ # id dist azim elev conn #----------------------------------------------------------------------- add_spkr LF 1.828800 51.000000 24.000000 add_spkr RF 1.828800 -51.000000 24.000000 add_spkr CE 1.828800 0.000000 0.000000 add_spkr AUX0 1.828800 180.000000 55.000000 add_spkr AUX1 1.828800 0.000000 -55.000000 add_spkr LS 1.828800 129.000000 -24.000000 add_spkr RS 1.828800 -129.000000 -24.000000 /} /lfmatrix/{ order_gain 1.00000000e+00 1.00000000e+00 0.000000 0.000000 add_row 1.666666667e-01 2.033043281e-01 1.175581508e-01 1.678904388e-01 add_row 1.666666667e-01 -2.033043281e-01 1.175581508e-01 1.678904388e-01 add_row 0.000000000e+00 0.000000000e+00 0.000000000e+00 0.000000000e+00 add_row 1.666666667e-01 0.000000000e+00 2.356640879e-01 -1.667265410e-01 add_row 1.666666667e-01 0.000000000e+00 -2.356640879e-01 1.667265410e-01 add_row 1.666666667e-01 2.033043281e-01 -1.175581508e-01 -1.678904388e-01 add_row 1.666666667e-01 -2.033043281e-01 -1.175581508e-01 -1.678904388e-01 /} /hfmatrix/{ order_gain 1.73205081e+00 1.00000000e+00 0.000000 0.000000 add_row 1.666666667e-01 2.033043281e-01 1.175581508e-01 1.678904388e-01 add_row 1.666666667e-01 -2.033043281e-01 1.175581508e-01 1.678904388e-01 add_row 0.000000000e+00 0.000000000e+00 0.000000000e+00 0.000000000e+00 add_row 1.666666667e-01 0.000000000e+00 2.356640879e-01 -1.667265410e-01 add_row 1.666666667e-01 0.000000000e+00 -2.356640879e-01 1.667265410e-01 add_row 1.666666667e-01 2.033043281e-01 -1.175581508e-01 -1.678904388e-01 add_row 1.666666667e-01 -2.033043281e-01 -1.175581508e-01 -1.678904388e-01 /} /end openal-soft-1.24.2/presets/hex-quad.ambdec000066400000000000000000000036361474041540300203770ustar00rootroot00000000000000# AmbDec configuration # Written by Ambisonic Decoder Toolbox, version 8.0 # input channel order: W Y Z X /description 11_1_1h1v_allrad_5200_rE_max_1_band /version 3 /dec/chan_mask f /dec/freq_bands 1 /dec/speakers 11 /dec/coeff_scale n3d /opt/input_scale n3d /opt/nfeff_comp output /opt/delay_comp on /opt/level_comp on /opt/xover_freq 400.000000 /opt/xover_ratio 0.000000 /speakers/{ # id dist azim elev conn #----------------------------------------------------------------------- add_spkr LF 1.000000 30.000000 0.000000 add_spkr RF 1.000000 -30.000000 0.000000 add_spkr CE 1.000000 0.000000 0.000000 add_spkr LS 1.000000 90.000000 0.000000 add_spkr RS 1.000000 -90.000000 0.000000 add_spkr LB 1.000000 150.000000 0.000000 add_spkr RB 1.000000 -150.000000 0.000000 add_spkr LFT 1.000000 45.000000 35.000000 add_spkr RFT 1.000000 -45.000000 35.000000 add_spkr LBT 1.000000 135.000000 35.000000 add_spkr RBT 1.000000 -135.000000 35.000000 /} /matrix/{ order_gain 1.00000000e+00 1.00000000e+00 0.000000 0.000000 add_row 1.27149251e-01 7.63047539e-02 -3.64373750e-02 1.59700680e-01 add_row 1.07005418e-01 -7.67638760e-02 -4.92129762e-02 1.29012797e-01 add_row 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 add_row 1.26400196e-01 1.77494694e-01 -3.71203389e-02 0.00000000e+00 add_row 1.26396516e-01 -1.77488059e-01 -3.71297878e-02 0.00000000e+00 add_row 1.06996956e-01 7.67615256e-02 -4.92166307e-02 -1.29001640e-01 add_row 1.27145671e-01 -7.63003471e-02 -3.64353304e-02 -1.59697510e-01 add_row 8.80919747e-02 7.48940670e-02 9.08786244e-02 6.22527183e-02 add_row 1.57880745e-01 -7.28755272e-02 1.82364187e-01 8.74240284e-02 add_row 1.57892225e-01 7.28944768e-02 1.82363474e-01 -8.74301086e-02 add_row 8.80892603e-02 -7.48948724e-02 9.08779842e-02 -6.22480443e-02 /} /end openal-soft-1.24.2/presets/hexagon.ambdec000066400000000000000000000031511474041540300203040ustar00rootroot00000000000000# AmbDec configuration # Written by Ambisonic Decoder Toolbox, version 8.0 /description Hexagon_2h0p_pinv_match_rV_max_rE_2_band /version 3 /dec/chan_mask 11b /dec/freq_bands 2 /dec/speakers 6 /dec/coeff_scale fuma /opt/input_scale fuma /opt/nfeff_comp input /opt/delay_comp on /opt/level_comp on /opt/xover_freq 400.000000 /opt/xover_ratio 0.000000 /speakers/{ # id dist azim elev conn #----------------------------------------------------------------------- add_spkr LF 1.000000 30.000000 0.000000 add_spkr RF 1.000000 -30.000000 0.000000 add_spkr RS 1.000000 -90.000000 0.000000 add_spkr RB 1.000000 -150.000000 0.000000 add_spkr LB 1.000000 150.000000 0.000000 add_spkr LS 1.000000 90.000000 0.000000 /} /lfmatrix/{ order_gain 1.000000 1.000000 1.000000 0.000000 add_row 0.235702 0.166667 0.288675 0.288675 0.166667 add_row 0.235702 -0.166667 0.288675 -0.288675 0.166667 add_row 0.235702 -0.333333 0.000000 -0.000000 -0.333333 add_row 0.235702 -0.166667 -0.288675 0.288675 0.166667 add_row 0.235702 0.166667 -0.288675 -0.288675 0.166667 add_row 0.235702 0.333333 0.000000 -0.000000 -0.333333 /} /hfmatrix/{ order_gain 1.414214 1.224745 0.707107 0.000000 add_row 0.235702 0.166667 0.288675 0.288675 0.166667 add_row 0.235702 -0.166667 0.288675 -0.288675 0.166667 add_row 0.235702 -0.333333 0.000000 -0.000000 -0.333333 add_row 0.235702 -0.166667 -0.288675 0.288675 0.166667 add_row 0.235702 0.166667 -0.288675 -0.288675 0.166667 add_row 0.235702 0.333333 0.000000 -0.000000 -0.333333 /} /end openal-soft-1.24.2/presets/itu5.1-nocenter.ambdec000066400000000000000000000032621474041540300215160ustar00rootroot00000000000000# AmbDec configuration # Written by Ambisonic Decoder Toolbox, version 8.0 # input channel order: WYXVU /description itu50-noCenter_2h0p_allrad_5200_rE_max_1_band # Although unused in this configuration, the front-center is declared here so # that an appropriate distance may be set (for proper delaying or attenuating # of dialog and such which feed it directly). It otherwise does not contribute # to positional sound output. /version 3 /dec/chan_mask 11b /dec/freq_bands 1 /dec/speakers 5 /dec/coeff_scale fuma /opt/input_scale fuma /opt/nfeff_comp input /opt/delay_comp on /opt/level_comp on /opt/xover_freq 400.000000 /opt/xover_ratio 0.000000 /speakers/{ # id dist azim elev conn #----------------------------------------------------------------------- add_spkr LS 1.000000 110.000000 0.000000 system:playback_3 add_spkr LF 1.000000 30.000000 0.000000 system:playback_1 add_spkr CE 1.000000 0.000000 0.000000 system:playback_5 add_spkr RF 1.000000 -30.000000 0.000000 system:playback_2 add_spkr RS 1.000000 -110.000000 0.000000 system:playback_4 /} /matrix/{ order_gain 1.00000000e+00 8.66025404e-01 5.00000000e-01 0.000000 add_row 4.70934222e-01 3.78169605e-01 -4.00084750e-01 -8.22264454e-02 -4.43765986e-02 add_row 2.66639870e-01 2.55418584e-01 3.32591390e-01 2.82949132e-01 8.16816772e-02 add_row 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 add_row 2.66634915e-01 -2.55421639e-01 3.32586482e-01 -2.82947688e-01 8.16782588e-02 add_row 4.70935891e-01 -3.78173080e-01 -4.00080588e-01 8.22279700e-02 -4.43716394e-02 /} /end openal-soft-1.24.2/presets/itu5.1.ambdec000066400000000000000000000031131474041540300176760ustar00rootroot00000000000000# AmbDec configuration /description itu50_2h0p_idhoa /version 3 /dec/chan_mask 11b /dec/freq_bands 2 /dec/speakers 5 /dec/coeff_scale fuma /opt/input_scale fuma /opt/nfeff_comp output /opt/delay_comp on /opt/level_comp on /opt/xover_freq 400.000000 /opt/xover_ratio 0.000000 /speakers/{ # id dist azim elev conn #----------------------------------------------------------------------- add_spkr LS 1.000000 110.000000 0.000000 add_spkr LF 1.000000 30.000000 0.000000 add_spkr CE 1.000000 0.000000 0.000000 add_spkr RF 1.000000 -30.000000 0.000000 add_spkr RS 1.000000 -110.000000 0.000000 /} /lfmatrix/{ order_gain 1.000000 1.000000 1.000000 0.000000 add_row 4.9010985e-1 3.7730501e-1 -3.7310699e-1 -1.2591453e-1 1.4513300e-2 add_row 1.4908573e-1 3.0356168e-1 1.5329006e-1 2.4511248e-1 -1.5075313e-1 add_row 1.3765492e-1 0.0000000e+0 4.4941794e-1 0.0000000e+0 2.5784407e-1 add_row 1.4908573e-1 -3.0356168e-1 1.5329006e-1 -2.4511248e-1 -1.5075313e-1 add_row 4.9010985e-1 -3.7730501e-1 -3.7310699e-1 1.2591453e-1 1.4513300e-2 /} /hfmatrix/{ order_gain 1.000000 1.000000 1.000000 0.000000 add_row 5.6731600e-1 4.2292000e-1 -3.1549500e-1 -6.3449000e-2 -2.9238000e-2 add_row 3.6858400e-1 2.7234900e-1 3.2161600e-1 1.9264500e-1 4.8260000e-2 add_row 1.8357900e-1 0.0000000e+0 1.9958800e-1 0.0000000e+0 9.6282000e-2 add_row 3.6858400e-1 -2.7234900e-1 3.2161600e-1 -1.9264500e-1 4.8260000e-2 add_row 5.6731600e-1 -4.2292000e-1 -3.1549500e-1 6.3449000e-2 -2.9238000e-2 /} /end openal-soft-1.24.2/presets/presets.txt000066400000000000000000000057271474041540300177570ustar00rootroot00000000000000Ambisonic decoder configuration presets are provided here for common surround sound speaker layouts. The presets are prepared to work with OpenAL Soft's high quality decoder. By default all of the speaker distances within a preset are set to the same value, which results in no effect from distance compensation. If this doesn't match your physical speaker setup, it may be worth copying the preset and modifying the distance values to match (note that modifying the azimuth and elevation values in the presets will not have any effect; the specified angles do not change the decoder behavior). Details of the individual presets are as follows. square.ambdec Specifies a basic square speaker setup for Quadraphonic output, with identical width and depth. Front speakers are placed at +45 and -45 degrees, and back speakers are placed at +135 and -135 degrees. rectangle.ambdec Specifies a narrower speaker setup for Quadraphonic output, with a little less width but a little more depth over a basic square setup. Front speakers are placed at +30 and -30 degrees, providing a bit more compatibility for existing stereo content, with back speakers at +150 and -150 degrees. itu5.1.ambdec Specifies a standard ITU 5.0/5.1 setup for 5.1 Surround output. The front- center speaker is placed directly in front at 0 degrees, with the front-left and front-right at +30 and -30 degrees, and the surround speakers (side or back) at +110 and -110 degrees. hexagon.ambdec Specifies a flat-front hexagonal speaker setup for 7.1 Surround output. The front left and right speakers are placed at +30 and -30 degrees, the side speakers are placed at +90 and -90 degrees, and the back speakers are placed at +150 and -150 degrees. Although this is for 7.1 output, no front-center speaker is defined for the decoder, meaning that speaker will be silent for 3D sound (however it may still be used with AL_SOFT_direct_channels or ALC_EXT_DEDICATED output). A "proper" 7.1 decoder may be provided in the future, but due to the nature of the speaker configuration will have trade-offs. hex-quad.ambdec Specifies a flat-front hexagonal speaker setup, plus an elevated quad speaker setup, for 7.1.4 Surround output. The front left and right speakers are placed at +30 and -30 degrees, the side speakers are placed at +90 and -90 degrees, and the back speakers are placed at +150 and -150 degrees. The elevated speakers are placed at an elevation of +35 degrees, with the top front left and right speakers placed at +45 and -45 degrees, and the top back left and right speakers placed at +135 and -135 degrees. Similar to 7.1, the front-center speaker is not used for 3D sound, but will be used as appropriate with AL_SOFT_direct_channels or ALC_EXT_DEDICATED. 3D7.1.ambdec Specifies a 3D7.1 speaker setup for 3D7.1 Surround output. Please see docs/3D7.1.txt for information about speaker placement. Similar to 7.1, the front-center speaker is not used for 3D sound, but will be used as appropriate with AL_SOFT_direct_channels or ALC_EXT_DEDICATED. openal-soft-1.24.2/presets/rectangle.ambdec000066400000000000000000000022051474041540300206160ustar00rootroot00000000000000# AmbDec configuration # Written by Ambisonic Decoder Toolbox, version 8.0 /description Rectangle_1h0p_pinv_match_rV_max_rE_2_band /version 3 /dec/chan_mask b /dec/freq_bands 2 /dec/speakers 4 /dec/coeff_scale fuma /opt/input_scale fuma /opt/nfeff_comp input /opt/delay_comp on /opt/level_comp on /opt/xover_freq 400.000000 /opt/xover_ratio 0.000000 /speakers/{ # id dist azim elev conn #----------------------------------------------------------------------- add_spkr LF 1.000000 30.000000 0.000000 add_spkr RF 1.000000 -30.000000 0.000000 add_spkr RB 1.000000 -150.000000 0.000000 add_spkr LB 1.000000 150.000000 0.000000 /} /lfmatrix/{ order_gain 1.000000 1.000000 0.000000 0.000000 add_row 0.353553 0.500000 0.288675 add_row 0.353553 -0.500000 0.288675 add_row 0.353553 -0.500000 -0.288675 add_row 0.353553 0.500000 -0.288675 /} /hfmatrix/{ order_gain 1.414214 1.000000 0.000000 0.000000 add_row 0.353553 0.500000 0.288675 add_row 0.353553 -0.500000 0.288675 add_row 0.353553 -0.500000 -0.288675 add_row 0.353553 0.500000 -0.288675 /} /end openal-soft-1.24.2/presets/square.ambdec000066400000000000000000000022021474041540300201470ustar00rootroot00000000000000# AmbDec configuration # Written by Ambisonic Decoder Toolbox, version 8.0 /description Square_1h0p_pinv_match_rV_max_rE_2_band /version 3 /dec/chan_mask b /dec/freq_bands 2 /dec/speakers 4 /dec/coeff_scale fuma /opt/input_scale fuma /opt/nfeff_comp input /opt/delay_comp on /opt/level_comp on /opt/xover_freq 400.000000 /opt/xover_ratio 0.000000 /speakers/{ # id dist azim elev conn #----------------------------------------------------------------------- add_spkr LF 1.000000 45.000000 0.000000 add_spkr RF 1.000000 -45.000000 0.000000 add_spkr RB 1.000000 -135.000000 0.000000 add_spkr LB 1.000000 135.000000 0.000000 /} /lfmatrix/{ order_gain 1.000000 1.000000 0.000000 0.000000 add_row 0.353553 0.353553 0.353553 add_row 0.353553 -0.353553 0.353553 add_row 0.353553 -0.353553 -0.353553 add_row 0.353553 0.353553 -0.353553 /} /hfmatrix/{ order_gain 1.414214 1.000000 0.000000 0.000000 add_row 0.353553 0.353553 0.353553 add_row 0.353553 -0.353553 0.353553 add_row 0.353553 -0.353553 -0.353553 add_row 0.353553 0.353553 -0.353553 /} /end openal-soft-1.24.2/resources/000077500000000000000000000000001474041540300160435ustar00rootroot00000000000000openal-soft-1.24.2/resources/openal32.rc000066400000000000000000000040541474041540300200170ustar00rootroot00000000000000#pragma code_page(65001) // Microsoft Visual C++ generated resource script. // #include #include "resource.h" #include "version.h" ///////////////////////////////////////////////////////////////////////////// // English (United States) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US #ifdef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // TEXTINCLUDE // 1 TEXTINCLUDE BEGIN "resource.h\0" END 2 TEXTINCLUDE BEGIN "\0" END 3 TEXTINCLUDE BEGIN "\r\n" "\0" END #endif // APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Version // VS_VERSION_INFO VERSIONINFO FILEVERSION ALSOFT_VERSION_NUM PRODUCTVERSION ALSOFT_VERSION_NUM FILEFLAGSMASK VS_FFI_FILEFLAGSMASK #ifdef _DEBUG FILEFLAGS VS_FF_DEBUG #else FILEFLAGS 0x0L #endif FILEOS VOS_NT_WINDOWS32 FILETYPE VFT_DLL FILESUBTYPE VFT2_UNKNOWN BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "040904b0" BEGIN VALUE "CompanyName", "" VALUE "FileDescription", "Main implementation library" VALUE "FileVersion", ALSOFT_VERSION VALUE "InternalName", "OpenAL32.dll" VALUE "LegalCopyright", "GNU LGPL - Version 2, June 1991" VALUE "OriginalFilename", "OpenAL32.dll" VALUE "ProductName", "OpenAL Soft" VALUE "ProductVersion", ALSOFT_VERSION END END BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0x409, 1200 END END #endif // English (United States) resources ///////////////////////////////////////////////////////////////////////////// #ifndef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 3 resource. // ///////////////////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED openal-soft-1.24.2/resources/resource.h000066400000000000000000000006421474041540300200450ustar00rootroot00000000000000//{{NO_DEPENDENCIES}} // Microsoft Visual C++ generated include file. // Used by openal32.rc, router.rc, soft_oal.rc // // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 101 #define _APS_NEXT_COMMAND_VALUE 40001 #define _APS_NEXT_CONTROL_VALUE 1000 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif openal-soft-1.24.2/resources/router.rc000066400000000000000000000040371474041540300177150ustar00rootroot00000000000000#pragma code_page(65001) // Microsoft Visual C++ generated resource script. // #include #include "resource.h" #include "version.h" ///////////////////////////////////////////////////////////////////////////// // English (United States) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US #ifdef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // TEXTINCLUDE // 1 TEXTINCLUDE BEGIN "resource.h\0" END 2 TEXTINCLUDE BEGIN "\0" END 3 TEXTINCLUDE BEGIN "\r\n" "\0" END #endif // APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Version // VS_VERSION_INFO VERSIONINFO FILEVERSION ALSOFT_VERSION_NUM PRODUCTVERSION ALSOFT_VERSION_NUM FILEFLAGSMASK VS_FFI_FILEFLAGSMASK #ifdef _DEBUG FILEFLAGS VS_FF_DEBUG #else FILEFLAGS 0x0L #endif FILEOS VOS_NT_WINDOWS32 FILETYPE VFT_DLL FILESUBTYPE VFT2_UNKNOWN BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "040904b0" BEGIN VALUE "CompanyName", "" VALUE "FileDescription", "Router library" VALUE "FileVersion", ALSOFT_VERSION VALUE "InternalName", "OpenAL32.dll" VALUE "LegalCopyright", "GNU LGPL - Version 2, June 1991" VALUE "OriginalFilename", "OpenAL32.dll" VALUE "ProductName", "OpenAL Soft" VALUE "ProductVersion", ALSOFT_VERSION END END BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0x409, 1200 END END #endif // English (United States) resources ///////////////////////////////////////////////////////////////////////////// #ifndef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 3 resource. // ///////////////////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED openal-soft-1.24.2/resources/soft_oal.rc000066400000000000000000000040541474041540300202020ustar00rootroot00000000000000#pragma code_page(65001) // Microsoft Visual C++ generated resource script. // #include #include "resource.h" #include "version.h" ///////////////////////////////////////////////////////////////////////////// // English (United States) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US #ifdef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // TEXTINCLUDE // 1 TEXTINCLUDE BEGIN "resource.h\0" END 2 TEXTINCLUDE BEGIN "\0" END 3 TEXTINCLUDE BEGIN "\r\n" "\0" END #endif // APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Version // VS_VERSION_INFO VERSIONINFO FILEVERSION ALSOFT_VERSION_NUM PRODUCTVERSION ALSOFT_VERSION_NUM FILEFLAGSMASK VS_FFI_FILEFLAGSMASK #ifdef _DEBUG FILEFLAGS VS_FF_DEBUG #else FILEFLAGS 0x0L #endif FILEOS VOS_NT_WINDOWS32 FILETYPE VFT_DLL FILESUBTYPE VFT2_UNKNOWN BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "040904b0" BEGIN VALUE "CompanyName", "" VALUE "FileDescription", "Main implementation library" VALUE "FileVersion", ALSOFT_VERSION VALUE "InternalName", "soft_oal.dll" VALUE "LegalCopyright", "GNU LGPL - Version 2, June 1991" VALUE "OriginalFilename", "soft_oal.dll" VALUE "ProductName", "OpenAL Soft" VALUE "ProductVersion", ALSOFT_VERSION END END BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0x409, 1200 END END #endif // English (United States) resources ///////////////////////////////////////////////////////////////////////////// #ifndef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 3 resource. // ///////////////////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED openal-soft-1.24.2/router/000077500000000000000000000000001474041540300153515ustar00rootroot00000000000000openal-soft-1.24.2/router/al.cpp000066400000000000000000000213141474041540300164520ustar00rootroot00000000000000 #include "config.h" #include #include "AL/al.h" #include "router.h" #define DECL_THUNK1(R,n,T1) \ AL_API auto AL_APIENTRY n(T1 a) noexcept -> R \ { \ DriverIface *iface = GetThreadDriver(); \ if(!iface) iface = CurrentCtxDriver.load(std::memory_order_acquire); \ return iface->n(a); \ } #define DECL_THUNK2(R,n,T1,T2) \ AL_API auto AL_APIENTRY n(T1 a, T2 b) noexcept -> R \ { \ DriverIface *iface = GetThreadDriver(); \ if(!iface) iface = CurrentCtxDriver.load(std::memory_order_acquire); \ return iface->n(a, b); \ } #define DECL_THUNK3(R,n,T1,T2,T3) \ AL_API auto AL_APIENTRY n(T1 a, T2 b, T3 c) noexcept -> R \ { \ DriverIface *iface = GetThreadDriver(); \ if(!iface) iface = CurrentCtxDriver.load(std::memory_order_acquire); \ return iface->n(a, b, c); \ } #define DECL_THUNK4(R,n,T1,T2,T3,T4) \ AL_API auto AL_APIENTRY n(T1 a, T2 b, T3 c, T4 d) noexcept -> R \ { \ DriverIface *iface = GetThreadDriver(); \ if(!iface) iface = CurrentCtxDriver.load(std::memory_order_acquire); \ return iface->n(a, b, c, d); \ } #define DECL_THUNK5(R,n,T1,T2,T3,T4,T5) \ AL_API auto AL_APIENTRY n(T1 a, T2 b, T3 c, T4 d, T5 e) noexcept -> R \ { \ DriverIface *iface = GetThreadDriver(); \ if(!iface) iface = CurrentCtxDriver.load(std::memory_order_acquire); \ return iface->n(a, b, c, d, e); \ } /* Ugly hack for some apps calling alGetError without a current context, and * expecting it to be AL_NO_ERROR. */ AL_API auto AL_APIENTRY alGetError() noexcept -> ALenum { DriverIface *iface = GetThreadDriver(); if(!iface) iface = CurrentCtxDriver.load(std::memory_order_acquire); return iface ? iface->alGetError() : AL_NO_ERROR; } DECL_THUNK1(void, alDopplerFactor, ALfloat) DECL_THUNK1(void, alDopplerVelocity, ALfloat) DECL_THUNK1(void, alSpeedOfSound, ALfloat) DECL_THUNK1(void, alDistanceModel, ALenum) DECL_THUNK1(void, alEnable, ALenum) DECL_THUNK1(void, alDisable, ALenum) DECL_THUNK1(ALboolean, alIsEnabled, ALenum) DECL_THUNK1(const ALchar*, alGetString, ALenum) DECL_THUNK2(void, alGetBooleanv, ALenum, ALboolean*) DECL_THUNK2(void, alGetIntegerv, ALenum, ALint*) DECL_THUNK2(void, alGetFloatv, ALenum, ALfloat*) DECL_THUNK2(void, alGetDoublev, ALenum, ALdouble*) DECL_THUNK1(ALboolean, alGetBoolean, ALenum) DECL_THUNK1(ALint, alGetInteger, ALenum) DECL_THUNK1(ALfloat, alGetFloat, ALenum) DECL_THUNK1(ALdouble, alGetDouble, ALenum) DECL_THUNK1(ALboolean, alIsExtensionPresent, const ALchar*) DECL_THUNK1(void*, alGetProcAddress, const ALchar*) DECL_THUNK1(ALenum, alGetEnumValue, const ALchar*) DECL_THUNK2(void, alListenerf, ALenum, ALfloat) DECL_THUNK4(void, alListener3f, ALenum, ALfloat, ALfloat, ALfloat) DECL_THUNK2(void, alListenerfv, ALenum, const ALfloat*) DECL_THUNK2(void, alListeneri, ALenum, ALint) DECL_THUNK4(void, alListener3i, ALenum, ALint, ALint, ALint) DECL_THUNK2(void, alListeneriv, ALenum, const ALint*) DECL_THUNK2(void, alGetListenerf, ALenum, ALfloat*) DECL_THUNK4(void, alGetListener3f, ALenum, ALfloat*, ALfloat*, ALfloat*) DECL_THUNK2(void, alGetListenerfv, ALenum, ALfloat*) DECL_THUNK2(void, alGetListeneri, ALenum, ALint*) DECL_THUNK4(void, alGetListener3i, ALenum, ALint*, ALint*, ALint*) DECL_THUNK2(void, alGetListeneriv, ALenum, ALint*) DECL_THUNK2(void, alGenSources, ALsizei, ALuint*) DECL_THUNK2(void, alDeleteSources, ALsizei, const ALuint*) DECL_THUNK1(ALboolean, alIsSource, ALuint) DECL_THUNK3(void, alSourcef, ALuint, ALenum, ALfloat) DECL_THUNK5(void, alSource3f, ALuint, ALenum, ALfloat, ALfloat, ALfloat) DECL_THUNK3(void, alSourcefv, ALuint, ALenum, const ALfloat*) DECL_THUNK3(void, alSourcei, ALuint, ALenum, ALint) DECL_THUNK5(void, alSource3i, ALuint, ALenum, ALint, ALint, ALint) DECL_THUNK3(void, alSourceiv, ALuint, ALenum, const ALint*) DECL_THUNK3(void, alGetSourcef, ALuint, ALenum, ALfloat*) DECL_THUNK5(void, alGetSource3f, ALuint, ALenum, ALfloat*, ALfloat*, ALfloat*) DECL_THUNK3(void, alGetSourcefv, ALuint, ALenum, ALfloat*) DECL_THUNK3(void, alGetSourcei, ALuint, ALenum, ALint*) DECL_THUNK5(void, alGetSource3i, ALuint, ALenum, ALint*, ALint*, ALint*) DECL_THUNK3(void, alGetSourceiv, ALuint, ALenum, ALint*) DECL_THUNK2(void, alSourcePlayv, ALsizei, const ALuint*) DECL_THUNK2(void, alSourceStopv, ALsizei, const ALuint*) DECL_THUNK2(void, alSourceRewindv, ALsizei, const ALuint*) DECL_THUNK2(void, alSourcePausev, ALsizei, const ALuint*) DECL_THUNK1(void, alSourcePlay, ALuint) DECL_THUNK1(void, alSourceStop, ALuint) DECL_THUNK1(void, alSourceRewind, ALuint) DECL_THUNK1(void, alSourcePause, ALuint) DECL_THUNK3(void, alSourceQueueBuffers, ALuint, ALsizei, const ALuint*) DECL_THUNK3(void, alSourceUnqueueBuffers, ALuint, ALsizei, ALuint*) DECL_THUNK2(void, alGenBuffers, ALsizei, ALuint*) DECL_THUNK2(void, alDeleteBuffers, ALsizei, const ALuint*) DECL_THUNK1(ALboolean, alIsBuffer, ALuint) DECL_THUNK3(void, alBufferf, ALuint, ALenum, ALfloat) DECL_THUNK5(void, alBuffer3f, ALuint, ALenum, ALfloat, ALfloat, ALfloat) DECL_THUNK3(void, alBufferfv, ALuint, ALenum, const ALfloat*) DECL_THUNK3(void, alBufferi, ALuint, ALenum, ALint) DECL_THUNK5(void, alBuffer3i, ALuint, ALenum, ALint, ALint, ALint) DECL_THUNK3(void, alBufferiv, ALuint, ALenum, const ALint*) DECL_THUNK3(void, alGetBufferf, ALuint, ALenum, ALfloat*) DECL_THUNK5(void, alGetBuffer3f, ALuint, ALenum, ALfloat*, ALfloat*, ALfloat*) DECL_THUNK3(void, alGetBufferfv, ALuint, ALenum, ALfloat*) DECL_THUNK3(void, alGetBufferi, ALuint, ALenum, ALint*) DECL_THUNK5(void, alGetBuffer3i, ALuint, ALenum, ALint*, ALint*, ALint*) DECL_THUNK3(void, alGetBufferiv, ALuint, ALenum, ALint*) DECL_THUNK5(void, alBufferData, ALuint, ALenum, const ALvoid*, ALsizei, ALsizei) /* EFX 1.0. Required here to be exported from libOpenAL32.dll.a/OpenAL32.lib * with the router enabled. */ DECL_THUNK2(void, alGenFilters, ALsizei, ALuint*) DECL_THUNK2(void, alDeleteFilters, ALsizei, const ALuint*) DECL_THUNK1(ALboolean, alIsFilter, ALuint) DECL_THUNK3(void, alFilterf, ALuint, ALenum, ALfloat) DECL_THUNK3(void, alFilterfv, ALuint, ALenum, const ALfloat*) DECL_THUNK3(void, alFilteri, ALuint, ALenum, ALint) DECL_THUNK3(void, alFilteriv, ALuint, ALenum, const ALint*) DECL_THUNK3(void, alGetFilterf, ALuint, ALenum, ALfloat*) DECL_THUNK3(void, alGetFilterfv, ALuint, ALenum, ALfloat*) DECL_THUNK3(void, alGetFilteri, ALuint, ALenum, ALint*) DECL_THUNK3(void, alGetFilteriv, ALuint, ALenum, ALint*) DECL_THUNK2(void, alGenEffects, ALsizei, ALuint*) DECL_THUNK2(void, alDeleteEffects, ALsizei, const ALuint*) DECL_THUNK1(ALboolean, alIsEffect, ALuint) DECL_THUNK3(void, alEffectf, ALuint, ALenum, ALfloat) DECL_THUNK3(void, alEffectfv, ALuint, ALenum, const ALfloat*) DECL_THUNK3(void, alEffecti, ALuint, ALenum, ALint) DECL_THUNK3(void, alEffectiv, ALuint, ALenum, const ALint*) DECL_THUNK3(void, alGetEffectf, ALuint, ALenum, ALfloat*) DECL_THUNK3(void, alGetEffectfv, ALuint, ALenum, ALfloat*) DECL_THUNK3(void, alGetEffecti, ALuint, ALenum, ALint*) DECL_THUNK3(void, alGetEffectiv, ALuint, ALenum, ALint*) DECL_THUNK2(void, alGenAuxiliaryEffectSlots, ALsizei, ALuint*) DECL_THUNK2(void, alDeleteAuxiliaryEffectSlots, ALsizei, const ALuint*) DECL_THUNK1(ALboolean, alIsAuxiliaryEffectSlot, ALuint) DECL_THUNK3(void, alAuxiliaryEffectSlotf, ALuint, ALenum, ALfloat) DECL_THUNK3(void, alAuxiliaryEffectSlotfv, ALuint, ALenum, const ALfloat*) DECL_THUNK3(void, alAuxiliaryEffectSloti, ALuint, ALenum, ALint) DECL_THUNK3(void, alAuxiliaryEffectSlotiv, ALuint, ALenum, const ALint*) DECL_THUNK3(void, alGetAuxiliaryEffectSlotf, ALuint, ALenum, ALfloat*) DECL_THUNK3(void, alGetAuxiliaryEffectSlotfv, ALuint, ALenum, ALfloat*) DECL_THUNK3(void, alGetAuxiliaryEffectSloti, ALuint, ALenum, ALint*) DECL_THUNK3(void, alGetAuxiliaryEffectSlotiv, ALuint, ALenum, ALint*) openal-soft-1.24.2/router/alc.cpp000066400000000000000000000711541474041540300166240ustar00rootroot00000000000000 #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include "AL/alc.h" #include "almalloc.h" #include "router.h" #include "strutils.h" namespace { using namespace std::string_view_literals; std::once_flag InitOnce; void LoadDrivers() { std::call_once(InitOnce, []{ LoadDriverList(); }); } struct FuncExportEntry { const char *funcName; void *address; }; #define DECL(x) FuncExportEntry{ #x, reinterpret_cast(x) } const std::array alcFunctions{ DECL(alcCreateContext), DECL(alcMakeContextCurrent), DECL(alcProcessContext), DECL(alcSuspendContext), DECL(alcDestroyContext), DECL(alcGetCurrentContext), DECL(alcGetContextsDevice), DECL(alcOpenDevice), DECL(alcCloseDevice), DECL(alcGetError), DECL(alcIsExtensionPresent), DECL(alcGetProcAddress), DECL(alcGetEnumValue), DECL(alcGetString), DECL(alcGetIntegerv), DECL(alcCaptureOpenDevice), DECL(alcCaptureCloseDevice), DECL(alcCaptureStart), DECL(alcCaptureStop), DECL(alcCaptureSamples), DECL(alcSetThreadContext), DECL(alcGetThreadContext), DECL(alEnable), DECL(alDisable), DECL(alIsEnabled), DECL(alGetString), DECL(alGetBooleanv), DECL(alGetIntegerv), DECL(alGetFloatv), DECL(alGetDoublev), DECL(alGetBoolean), DECL(alGetInteger), DECL(alGetFloat), DECL(alGetDouble), DECL(alGetError), DECL(alIsExtensionPresent), DECL(alGetProcAddress), DECL(alGetEnumValue), DECL(alListenerf), DECL(alListener3f), DECL(alListenerfv), DECL(alListeneri), DECL(alListener3i), DECL(alListeneriv), DECL(alGetListenerf), DECL(alGetListener3f), DECL(alGetListenerfv), DECL(alGetListeneri), DECL(alGetListener3i), DECL(alGetListeneriv), DECL(alGenSources), DECL(alDeleteSources), DECL(alIsSource), DECL(alSourcef), DECL(alSource3f), DECL(alSourcefv), DECL(alSourcei), DECL(alSource3i), DECL(alSourceiv), DECL(alGetSourcef), DECL(alGetSource3f), DECL(alGetSourcefv), DECL(alGetSourcei), DECL(alGetSource3i), DECL(alGetSourceiv), DECL(alSourcePlayv), DECL(alSourceStopv), DECL(alSourceRewindv), DECL(alSourcePausev), DECL(alSourcePlay), DECL(alSourceStop), DECL(alSourceRewind), DECL(alSourcePause), DECL(alSourceQueueBuffers), DECL(alSourceUnqueueBuffers), DECL(alGenBuffers), DECL(alDeleteBuffers), DECL(alIsBuffer), DECL(alBufferData), DECL(alBufferf), DECL(alBuffer3f), DECL(alBufferfv), DECL(alBufferi), DECL(alBuffer3i), DECL(alBufferiv), DECL(alGetBufferf), DECL(alGetBuffer3f), DECL(alGetBufferfv), DECL(alGetBufferi), DECL(alGetBuffer3i), DECL(alGetBufferiv), DECL(alDopplerFactor), DECL(alDopplerVelocity), DECL(alSpeedOfSound), DECL(alDistanceModel), /* EFX 1.0 */ DECL(alGenFilters), DECL(alDeleteFilters), DECL(alIsFilter), DECL(alFilterf), DECL(alFilterfv), DECL(alFilteri), DECL(alFilteriv), DECL(alGetFilterf), DECL(alGetFilterfv), DECL(alGetFilteri), DECL(alGetFilteriv), DECL(alGenEffects), DECL(alDeleteEffects), DECL(alIsEffect), DECL(alEffectf), DECL(alEffectfv), DECL(alEffecti), DECL(alEffectiv), DECL(alGetEffectf), DECL(alGetEffectfv), DECL(alGetEffecti), DECL(alGetEffectiv), DECL(alGenAuxiliaryEffectSlots), DECL(alDeleteAuxiliaryEffectSlots), DECL(alIsAuxiliaryEffectSlot), DECL(alAuxiliaryEffectSlotf), DECL(alAuxiliaryEffectSlotfv), DECL(alAuxiliaryEffectSloti), DECL(alAuxiliaryEffectSlotiv), DECL(alGetAuxiliaryEffectSlotf), DECL(alGetAuxiliaryEffectSlotfv), DECL(alGetAuxiliaryEffectSloti), DECL(alGetAuxiliaryEffectSlotiv), }; #undef DECL struct EnumExportEntry { const ALCchar *enumName; ALCenum value; }; #define DECL(x) EnumExportEntry{ #x, (x) } const std::array alcEnumerations{ DECL(ALC_INVALID), DECL(ALC_FALSE), DECL(ALC_TRUE), DECL(ALC_MAJOR_VERSION), DECL(ALC_MINOR_VERSION), DECL(ALC_ATTRIBUTES_SIZE), DECL(ALC_ALL_ATTRIBUTES), DECL(ALC_DEFAULT_DEVICE_SPECIFIER), DECL(ALC_DEVICE_SPECIFIER), DECL(ALC_ALL_DEVICES_SPECIFIER), DECL(ALC_DEFAULT_ALL_DEVICES_SPECIFIER), DECL(ALC_EXTENSIONS), DECL(ALC_FREQUENCY), DECL(ALC_REFRESH), DECL(ALC_SYNC), DECL(ALC_MONO_SOURCES), DECL(ALC_STEREO_SOURCES), DECL(ALC_CAPTURE_DEVICE_SPECIFIER), DECL(ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER), DECL(ALC_CAPTURE_SAMPLES), DECL(ALC_NO_ERROR), DECL(ALC_INVALID_DEVICE), DECL(ALC_INVALID_CONTEXT), DECL(ALC_INVALID_ENUM), DECL(ALC_INVALID_VALUE), DECL(ALC_OUT_OF_MEMORY), DECL(AL_INVALID), DECL(AL_NONE), DECL(AL_FALSE), DECL(AL_TRUE), DECL(AL_SOURCE_RELATIVE), DECL(AL_CONE_INNER_ANGLE), DECL(AL_CONE_OUTER_ANGLE), DECL(AL_PITCH), DECL(AL_POSITION), DECL(AL_DIRECTION), DECL(AL_VELOCITY), DECL(AL_LOOPING), DECL(AL_BUFFER), DECL(AL_GAIN), DECL(AL_MIN_GAIN), DECL(AL_MAX_GAIN), DECL(AL_ORIENTATION), DECL(AL_REFERENCE_DISTANCE), DECL(AL_ROLLOFF_FACTOR), DECL(AL_CONE_OUTER_GAIN), DECL(AL_MAX_DISTANCE), DECL(AL_SEC_OFFSET), DECL(AL_SAMPLE_OFFSET), DECL(AL_BYTE_OFFSET), DECL(AL_SOURCE_TYPE), DECL(AL_STATIC), DECL(AL_STREAMING), DECL(AL_UNDETERMINED), DECL(AL_SOURCE_STATE), DECL(AL_INITIAL), DECL(AL_PLAYING), DECL(AL_PAUSED), DECL(AL_STOPPED), DECL(AL_BUFFERS_QUEUED), DECL(AL_BUFFERS_PROCESSED), DECL(AL_FORMAT_MONO8), DECL(AL_FORMAT_MONO16), DECL(AL_FORMAT_STEREO8), DECL(AL_FORMAT_STEREO16), DECL(AL_FREQUENCY), DECL(AL_BITS), DECL(AL_CHANNELS), DECL(AL_SIZE), DECL(AL_UNUSED), DECL(AL_PENDING), DECL(AL_PROCESSED), DECL(AL_NO_ERROR), DECL(AL_INVALID_NAME), DECL(AL_INVALID_ENUM), DECL(AL_INVALID_VALUE), DECL(AL_INVALID_OPERATION), DECL(AL_OUT_OF_MEMORY), DECL(AL_VENDOR), DECL(AL_VERSION), DECL(AL_RENDERER), DECL(AL_EXTENSIONS), DECL(AL_DOPPLER_FACTOR), DECL(AL_DOPPLER_VELOCITY), DECL(AL_DISTANCE_MODEL), DECL(AL_SPEED_OF_SOUND), DECL(AL_INVERSE_DISTANCE), DECL(AL_INVERSE_DISTANCE_CLAMPED), DECL(AL_LINEAR_DISTANCE), DECL(AL_LINEAR_DISTANCE_CLAMPED), DECL(AL_EXPONENT_DISTANCE), DECL(AL_EXPONENT_DISTANCE_CLAMPED), }; #undef DECL [[nodiscard]] constexpr auto GetNoErrorString() noexcept { return "No Error"; } [[nodiscard]] constexpr auto GetInvalidDeviceString() noexcept { return "Invalid Device"; } [[nodiscard]] constexpr auto GetInvalidContextString() noexcept { return "Invalid Context"; } [[nodiscard]] constexpr auto GetInvalidEnumString() noexcept { return "Invalid Enum"; } [[nodiscard]] constexpr auto GetInvalidValueString() noexcept { return "Invalid Value"; } [[nodiscard]] constexpr auto GetOutOfMemoryString() noexcept { return "Out of Memory"; } [[nodiscard]] constexpr auto GetExtensionList() noexcept -> std::string_view { return "ALC_ENUMERATE_ALL_EXT ALC_ENUMERATION_EXT ALC_EXT_CAPTURE " "ALC_EXT_thread_local_context"sv; } constexpr ALCint alcMajorVersion = 1; constexpr ALCint alcMinorVersion = 1; std::recursive_mutex EnumerationLock; std::mutex ContextSwitchLock; std::atomic LastError{ALC_NO_ERROR}; std::unordered_map DeviceIfaceMap; std::unordered_map ContextIfaceMap; template auto maybe_get(std::unordered_map &list, V&& key) -> std::optional { auto iter = list.find(std::forward(key)); if(iter != list.end()) return iter->second; return std::nullopt; } struct EnumeratedList { std::vector Names; std::vector Indicies; void clear() { Names.clear(); Indicies.clear(); } void AppendDeviceList(const ALCchar *names, ALCuint idx); [[nodiscard]] auto GetDriverIndexForName(const std::string_view name) const -> std::optional; }; EnumeratedList DevicesList; EnumeratedList AllDevicesList; EnumeratedList CaptureDevicesList; void EnumeratedList::AppendDeviceList(const ALCchar* names, ALCuint idx) { const ALCchar *name_end = names; if(!name_end) return; size_t count{0}; while(*name_end) { TRACE("Enumerated \"{}\", driver {}", name_end, idx); ++count; name_end += strlen(name_end)+1; /* NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) */ } if(names == name_end) return; Names.reserve(Names.size() + static_cast(name_end - names) + 1); Names.insert(Names.cend(), names, name_end); Indicies.reserve(Indicies.size() + count); Indicies.insert(Indicies.cend(), count, idx); } auto EnumeratedList::GetDriverIndexForName(const std::string_view name) const -> std::optional { auto devnames = Names.cbegin(); auto index = Indicies.cbegin(); while(devnames != Names.cend() && *devnames) { const auto devname = std::string_view{al::to_address(devnames)}; if(name == devname) return *index; devnames += ptrdiff_t(devname.size()+1); ++index; } return std::nullopt; } void InitCtxFuncs(DriverIface &iface) { ALCdevice *device{iface.alcGetContextsDevice(iface.alcGetCurrentContext())}; #define LOAD_PROC(x) do { \ iface.x = reinterpret_cast(iface.alGetProcAddress(#x));\ if(!iface.x) \ ERR("Failed to find entry point for {} in {}", #x, \ wstr_to_utf8(iface.Name)); \ } while(0) if(iface.alcIsExtensionPresent(device, "ALC_EXT_EFX")) { LOAD_PROC(alGenFilters); LOAD_PROC(alDeleteFilters); LOAD_PROC(alIsFilter); LOAD_PROC(alFilterf); LOAD_PROC(alFilterfv); LOAD_PROC(alFilteri); LOAD_PROC(alFilteriv); LOAD_PROC(alGetFilterf); LOAD_PROC(alGetFilterfv); LOAD_PROC(alGetFilteri); LOAD_PROC(alGetFilteriv); LOAD_PROC(alGenEffects); LOAD_PROC(alDeleteEffects); LOAD_PROC(alIsEffect); LOAD_PROC(alEffectf); LOAD_PROC(alEffectfv); LOAD_PROC(alEffecti); LOAD_PROC(alEffectiv); LOAD_PROC(alGetEffectf); LOAD_PROC(alGetEffectfv); LOAD_PROC(alGetEffecti); LOAD_PROC(alGetEffectiv); LOAD_PROC(alGenAuxiliaryEffectSlots); LOAD_PROC(alDeleteAuxiliaryEffectSlots); LOAD_PROC(alIsAuxiliaryEffectSlot); LOAD_PROC(alAuxiliaryEffectSlotf); LOAD_PROC(alAuxiliaryEffectSlotfv); LOAD_PROC(alAuxiliaryEffectSloti); LOAD_PROC(alAuxiliaryEffectSlotiv); LOAD_PROC(alGetAuxiliaryEffectSlotf); LOAD_PROC(alGetAuxiliaryEffectSlotfv); LOAD_PROC(alGetAuxiliaryEffectSloti); LOAD_PROC(alGetAuxiliaryEffectSlotiv); } #undef LOAD_PROC } } /* namespace */ ALC_API ALCdevice* ALC_APIENTRY alcOpenDevice(const ALCchar *devicename) noexcept { LoadDrivers(); ALCdevice *device{nullptr}; std::optional idx; /* Prior to the enumeration extension, apps would hardcode these names as a * quality hint for the wrapper driver. Ignore them since there's no sane * way to map them. */ if(devicename && *devicename != '\0' && devicename != "DirectSound3D"sv && devicename != "DirectSound"sv && devicename != "MMSYSTEM"sv) { { std::lock_guard enumlock{EnumerationLock}; if(DevicesList.Names.empty()) std::ignore = alcGetString(nullptr, ALC_DEVICE_SPECIFIER); idx = DevicesList.GetDriverIndexForName(devicename); if(!idx) { if(AllDevicesList.Names.empty()) std::ignore = alcGetString(nullptr, ALC_ALL_DEVICES_SPECIFIER); idx = AllDevicesList.GetDriverIndexForName(devicename); } } if(!idx) { LastError.store(ALC_INVALID_VALUE); TRACE("Failed to find driver for name \"{}\"", devicename); return nullptr; } TRACE("Found driver {} for name \"{}\"", *idx, devicename); device = DriverList[*idx]->alcOpenDevice(devicename); } else { ALCuint drvidx{0}; for(const auto &drv : DriverList) { if(drv->ALCVer >= MakeALCVer(1, 1) || drv->alcIsExtensionPresent(nullptr, "ALC_ENUMERATION_EXT")) { TRACE("Using default device from driver {}", drvidx); device = drv->alcOpenDevice(nullptr); idx = drvidx; break; } ++drvidx; } } if(device) { try { DeviceIfaceMap.emplace(device, idx.value()); } catch(...) { DriverList[idx.value()]->alcCloseDevice(device); device = nullptr; } } return device; } ALC_API ALCboolean ALC_APIENTRY alcCloseDevice(ALCdevice *device) noexcept { if(const auto idx = maybe_get(DeviceIfaceMap, device)) { if(!DriverList[*idx]->alcCloseDevice(device)) return ALC_FALSE; DeviceIfaceMap.erase(device); return ALC_TRUE; } LastError.store(ALC_INVALID_DEVICE); return ALC_FALSE; } ALC_API ALCcontext* ALC_APIENTRY alcCreateContext(ALCdevice *device, const ALCint *attrlist) noexcept { const auto idx = maybe_get(DeviceIfaceMap, device); if(!idx) { LastError.store(ALC_INVALID_DEVICE); return nullptr; } ALCcontext *context{DriverList[*idx]->alcCreateContext(device, attrlist)}; if(context) { try { ContextIfaceMap.emplace(context, *idx); } catch(...) { DriverList[*idx]->alcDestroyContext(context); context = nullptr; } } return context; } ALC_API ALCboolean ALC_APIENTRY alcMakeContextCurrent(ALCcontext *context) noexcept { std::lock_guard ctxlock{ContextSwitchLock}; std::optional idx; if(context) { idx = maybe_get(ContextIfaceMap, context); if(!idx) { LastError.store(ALC_INVALID_CONTEXT); return ALC_FALSE; } if(!DriverList[*idx]->alcMakeContextCurrent(context)) return ALC_FALSE; std::call_once(DriverList[*idx]->InitOnceCtx, [idx]{ InitCtxFuncs(*DriverList[*idx]); }); } /* Unset the context from the old driver if it's different from the new * current one. */ if(!idx) { DriverIface *oldiface{GetThreadDriver()}; if(oldiface) oldiface->alcSetThreadContext(nullptr); oldiface = CurrentCtxDriver.exchange(nullptr); if(oldiface) oldiface->alcMakeContextCurrent(nullptr); } else { DriverIface *oldiface{GetThreadDriver()}; if(oldiface && oldiface != DriverList[*idx].get()) oldiface->alcSetThreadContext(nullptr); oldiface = CurrentCtxDriver.exchange(DriverList[*idx].get()); if(oldiface && oldiface != DriverList[*idx].get()) oldiface->alcMakeContextCurrent(nullptr); } SetThreadDriver(nullptr); return ALC_TRUE; } ALC_API void ALC_APIENTRY alcProcessContext(ALCcontext *context) noexcept { if(const auto idx = maybe_get(ContextIfaceMap, context)) return DriverList[*idx]->alcProcessContext(context); LastError.store(ALC_INVALID_CONTEXT); } ALC_API void ALC_APIENTRY alcSuspendContext(ALCcontext *context) noexcept { if(const auto idx = maybe_get(ContextIfaceMap, context)) return DriverList[*idx]->alcSuspendContext(context); LastError.store(ALC_INVALID_CONTEXT); } ALC_API void ALC_APIENTRY alcDestroyContext(ALCcontext *context) noexcept { if(const auto idx = maybe_get(ContextIfaceMap, context)) { DriverList[*idx]->alcDestroyContext(context); ContextIfaceMap.erase(context); return; } LastError.store(ALC_INVALID_CONTEXT); } ALC_API ALCcontext* ALC_APIENTRY alcGetCurrentContext() noexcept { DriverIface *iface{GetThreadDriver()}; if(!iface) iface = CurrentCtxDriver.load(); return iface ? iface->alcGetCurrentContext() : nullptr; } ALC_API ALCdevice* ALC_APIENTRY alcGetContextsDevice(ALCcontext *context) noexcept { if(const auto idx = maybe_get(ContextIfaceMap, context)) return DriverList[*idx]->alcGetContextsDevice(context); LastError.store(ALC_INVALID_CONTEXT); return nullptr; } ALC_API ALCenum ALC_APIENTRY alcGetError(ALCdevice *device) noexcept { if(device) { if(const auto idx = maybe_get(DeviceIfaceMap, device)) return DriverList[*idx]->alcGetError(device); return ALC_INVALID_DEVICE; } return LastError.exchange(ALC_NO_ERROR); } ALC_API ALCboolean ALC_APIENTRY alcIsExtensionPresent(ALCdevice *device, const ALCchar *extname) noexcept { if(device) { if(const auto idx = maybe_get(DeviceIfaceMap, device)) return DriverList[*idx]->alcIsExtensionPresent(device, extname); LastError.store(ALC_INVALID_DEVICE); return ALC_FALSE; } const auto tofind = std::string_view{extname}; const auto extlist = GetExtensionList(); auto matchpos = extlist.find(tofind); while(matchpos != std::string_view::npos) { const auto endpos = matchpos + tofind.size(); if((matchpos == 0 || std::isspace(extlist[matchpos-1])) && (endpos == extlist.size() || std::isspace(extlist[endpos]))) return ALC_TRUE; matchpos = extlist.find(tofind, matchpos+1); } return ALC_FALSE; } ALC_API void* ALC_APIENTRY alcGetProcAddress(ALCdevice *device, const ALCchar *funcname) noexcept { if(device) { if(const auto idx = maybe_get(DeviceIfaceMap, device)) return DriverList[*idx]->alcGetProcAddress(device, funcname); LastError.store(ALC_INVALID_DEVICE); return nullptr; } auto iter = std::find_if(alcFunctions.cbegin(), alcFunctions.cend(), [funcname](const FuncExportEntry &entry) -> bool { return strcmp(funcname, entry.funcName) == 0; } ); return (iter != alcFunctions.cend()) ? iter->address : nullptr; } ALC_API ALCenum ALC_APIENTRY alcGetEnumValue(ALCdevice *device, const ALCchar *enumname) noexcept { if(device) { if(const auto idx = maybe_get(DeviceIfaceMap, device)) return DriverList[*idx]->alcGetEnumValue(device, enumname); LastError.store(ALC_INVALID_DEVICE); return 0; } auto iter = std::find_if(alcEnumerations.cbegin(), alcEnumerations.cend(), [enumname](const EnumExportEntry &entry) -> bool { return strcmp(enumname, entry.enumName) == 0; } ); return (iter != alcEnumerations.cend()) ? iter->value : 0; } ALC_API const ALCchar* ALC_APIENTRY alcGetString(ALCdevice *device, ALCenum param) noexcept { LoadDrivers(); if(device) { if(const auto idx = maybe_get(DeviceIfaceMap, device)) return DriverList[*idx]->alcGetString(device, param); LastError.store(ALC_INVALID_DEVICE); return nullptr; } switch(param) { case ALC_NO_ERROR: return GetNoErrorString(); case ALC_INVALID_ENUM: return GetInvalidEnumString(); case ALC_INVALID_VALUE: return GetInvalidValueString(); case ALC_INVALID_DEVICE: return GetInvalidDeviceString(); case ALC_INVALID_CONTEXT: return GetInvalidContextString(); case ALC_OUT_OF_MEMORY: return GetOutOfMemoryString(); case ALC_EXTENSIONS: return GetExtensionList().data(); case ALC_DEVICE_SPECIFIER: { std::lock_guard enumlock{EnumerationLock}; DevicesList.clear(); ALCuint idx{0}; for(const auto &drv : DriverList) { /* Only enumerate names from drivers that support it. */ if(drv->ALCVer >= MakeALCVer(1, 1) || drv->alcIsExtensionPresent(nullptr, "ALC_ENUMERATION_EXT")) DevicesList.AppendDeviceList(drv->alcGetString(nullptr,ALC_DEVICE_SPECIFIER), idx); ++idx; } /* Ensure the list is double-null termianted. */ if(DevicesList.Names.empty()) DevicesList.Names.emplace_back('\0'); DevicesList.Names.emplace_back('\0'); return DevicesList.Names.data(); } case ALC_ALL_DEVICES_SPECIFIER: { std::lock_guard enumlock{EnumerationLock}; AllDevicesList.clear(); ALCuint idx{0}; for(const auto &drv : DriverList) { /* If the driver doesn't support ALC_ENUMERATE_ALL_EXT, substitute * standard enumeration. */ if(drv->alcIsExtensionPresent(nullptr, "ALC_ENUMERATE_ALL_EXT")) AllDevicesList.AppendDeviceList( drv->alcGetString(nullptr, ALC_ALL_DEVICES_SPECIFIER), idx); else if(drv->ALCVer >= MakeALCVer(1, 1) || drv->alcIsExtensionPresent(nullptr, "ALC_ENUMERATION_EXT")) AllDevicesList.AppendDeviceList( drv->alcGetString(nullptr, ALC_DEVICE_SPECIFIER), idx); ++idx; } /* Ensure the list is double-null termianted. */ if(AllDevicesList.Names.empty()) AllDevicesList.Names.emplace_back('\0'); AllDevicesList.Names.emplace_back('\0'); return AllDevicesList.Names.data(); } case ALC_CAPTURE_DEVICE_SPECIFIER: { std::lock_guard enumlock{EnumerationLock}; CaptureDevicesList.clear(); ALCuint idx{0}; for(const auto &drv : DriverList) { if(drv->ALCVer >= MakeALCVer(1, 1) || drv->alcIsExtensionPresent(nullptr, "ALC_EXT_CAPTURE")) CaptureDevicesList.AppendDeviceList( drv->alcGetString(nullptr, ALC_CAPTURE_DEVICE_SPECIFIER), idx); ++idx; } /* Ensure the list is double-null termianted. */ if(CaptureDevicesList.Names.empty()) CaptureDevicesList.Names.emplace_back('\0'); CaptureDevicesList.Names.emplace_back('\0'); return CaptureDevicesList.Names.data(); } case ALC_DEFAULT_DEVICE_SPECIFIER: { for(const auto &drv : DriverList) { if(drv->ALCVer >= MakeALCVer(1, 1) || drv->alcIsExtensionPresent(nullptr, "ALC_ENUMERATION_EXT")) return drv->alcGetString(nullptr, ALC_DEFAULT_DEVICE_SPECIFIER); } return ""; } case ALC_DEFAULT_ALL_DEVICES_SPECIFIER: { for(const auto &drv : DriverList) { if(drv->alcIsExtensionPresent(nullptr, "ALC_ENUMERATE_ALL_EXT") != ALC_FALSE) return drv->alcGetString(nullptr, ALC_DEFAULT_ALL_DEVICES_SPECIFIER); } return ""; } case ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER: { for(const auto &drv : DriverList) { if(drv->ALCVer >= MakeALCVer(1, 1) || drv->alcIsExtensionPresent(nullptr, "ALC_EXT_CAPTURE")) return drv->alcGetString(nullptr, ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER); } return ""; } default: LastError.store(ALC_INVALID_ENUM); break; } return nullptr; } ALC_API void ALC_APIENTRY alcGetIntegerv(ALCdevice *device, ALCenum param, ALCsizei size, ALCint *values) noexcept { if(device) { if(const auto idx = maybe_get(DeviceIfaceMap, device)) return DriverList[*idx]->alcGetIntegerv(device, param, size, values); LastError.store(ALC_INVALID_DEVICE); return; } if(size <= 0 || values == nullptr) { LastError.store(ALC_INVALID_VALUE); return; } switch(param) { case ALC_MAJOR_VERSION: if(size >= 1) { *values = alcMajorVersion; return; } LastError.store(ALC_INVALID_VALUE); return; case ALC_MINOR_VERSION: if(size >= 1) { *values = alcMinorVersion; return; } LastError.store(ALC_INVALID_VALUE); return; case ALC_ATTRIBUTES_SIZE: case ALC_ALL_ATTRIBUTES: case ALC_FREQUENCY: case ALC_REFRESH: case ALC_SYNC: case ALC_MONO_SOURCES: case ALC_STEREO_SOURCES: case ALC_CAPTURE_SAMPLES: LastError.store(ALC_INVALID_DEVICE); return; default: LastError.store(ALC_INVALID_ENUM); return; } } ALC_API ALCdevice* ALC_APIENTRY alcCaptureOpenDevice(const ALCchar *devicename, ALCuint frequency, ALCenum format, ALCsizei buffersize) noexcept { LoadDrivers(); ALCdevice *device{nullptr}; std::optional idx; if(devicename && *devicename != '\0') { { std::lock_guard enumlock{EnumerationLock}; if(CaptureDevicesList.Names.empty()) std::ignore = alcGetString(nullptr, ALC_CAPTURE_DEVICE_SPECIFIER); idx = CaptureDevicesList.GetDriverIndexForName(devicename); } if(!idx) { LastError.store(ALC_INVALID_VALUE); TRACE("Failed to find driver for name \"{}\"", devicename); return nullptr; } TRACE("Found driver {} for name \"{}\"", *idx, devicename); device = DriverList[*idx]->alcCaptureOpenDevice(devicename, frequency, format, buffersize); } else { ALCuint drvidx{0}; for(const auto &drv : DriverList) { if(drv->ALCVer >= MakeALCVer(1, 1) || drv->alcIsExtensionPresent(nullptr, "ALC_EXT_CAPTURE")) { TRACE("Using default capture device from driver {}", drvidx); device = drv->alcCaptureOpenDevice(nullptr, frequency, format, buffersize); idx = drvidx; break; } ++drvidx; } } if(device) { try { DeviceIfaceMap.emplace(device, idx.value()); } catch(...) { DriverList[idx.value()]->alcCaptureCloseDevice(device); device = nullptr; } } return device; } ALC_API ALCboolean ALC_APIENTRY alcCaptureCloseDevice(ALCdevice *device) noexcept { if(const auto idx = maybe_get(DeviceIfaceMap, device)) { if(!DriverList[*idx]->alcCaptureCloseDevice(device)) return ALC_FALSE; DeviceIfaceMap.erase(device); return ALC_TRUE; } LastError.store(ALC_INVALID_DEVICE); return ALC_FALSE; } ALC_API void ALC_APIENTRY alcCaptureStart(ALCdevice *device) noexcept { if(const auto idx = maybe_get(DeviceIfaceMap, device)) return DriverList[*idx]->alcCaptureStart(device); LastError.store(ALC_INVALID_DEVICE); } ALC_API void ALC_APIENTRY alcCaptureStop(ALCdevice *device) noexcept { if(const auto idx = maybe_get(DeviceIfaceMap, device)) return DriverList[*idx]->alcCaptureStop(device); LastError.store(ALC_INVALID_DEVICE); } ALC_API void ALC_APIENTRY alcCaptureSamples(ALCdevice *device, ALCvoid *buffer, ALCsizei samples) noexcept { if(const auto idx = maybe_get(DeviceIfaceMap, device)) return DriverList[*idx]->alcCaptureSamples(device, buffer, samples); LastError.store(ALC_INVALID_DEVICE); } ALC_API ALCboolean ALC_APIENTRY alcSetThreadContext(ALCcontext *context) noexcept { if(!context) { DriverIface *oldiface{GetThreadDriver()}; if(oldiface && !oldiface->alcSetThreadContext(nullptr)) return ALC_FALSE; SetThreadDriver(nullptr); return ALC_TRUE; } ALCenum err{ALC_INVALID_CONTEXT}; if(const auto idx = maybe_get(ContextIfaceMap, context)) { if(DriverList[*idx]->alcSetThreadContext(context)) { std::call_once(DriverList[*idx]->InitOnceCtx, [idx]{InitCtxFuncs(*DriverList[*idx]);}); DriverIface *oldiface{GetThreadDriver()}; if(oldiface != DriverList[*idx].get()) { SetThreadDriver(DriverList[*idx].get()); if(oldiface) oldiface->alcSetThreadContext(nullptr); } return ALC_TRUE; } err = DriverList[*idx]->alcGetError(nullptr); } LastError.store(err); return ALC_FALSE; } ALC_API ALCcontext* ALC_APIENTRY alcGetThreadContext() noexcept { if(DriverIface *iface{GetThreadDriver()}) return iface->alcGetThreadContext(); return nullptr; } openal-soft-1.24.2/router/router.cpp000066400000000000000000000341741474041540300174060ustar00rootroot00000000000000 #include "config.h" #include "router.h" #include #include #include #include #include #include #include #include #include "AL/alc.h" #include "AL/al.h" #include "albit.h" #include "alstring.h" #include "opthelpers.h" #include "strutils.h" #include "version.h" eLogLevel LogLevel{eLogLevel::Error}; gsl::owner LogFile; namespace { std::vector gAcceptList; std::vector gRejectList; void AddModule(HMODULE module, const std::wstring_view name) { for(auto &drv : DriverList) { if(drv->Module == module) { TRACE("Skipping already-loaded module {}", decltype(std::declval()){module}); FreeLibrary(module); return; } if(drv->Name == name) { TRACE("Skipping similarly-named module {}", wstr_to_utf8(name)); FreeLibrary(module); return; } } if(!gAcceptList.empty()) { auto iter = std::find_if(gAcceptList.cbegin(), gAcceptList.cend(), [name](const std::wstring_view accept) { return al::case_compare(name, accept) == 0; }); if(iter == gAcceptList.cend()) { TRACE("{} not found in ALROUTER_ACCEPT, skipping", wstr_to_utf8(name)); FreeLibrary(module); return; } } if(!gRejectList.empty()) { auto iter = std::find_if(gRejectList.cbegin(), gRejectList.cend(), [name](const std::wstring_view accept) { return al::case_compare(name, accept) == 0; }); if(iter != gRejectList.cend()) { TRACE("{} found in ALROUTER_REJECT, skipping", wstr_to_utf8(name)); FreeLibrary(module); return; } } DriverIface &newdrv = *DriverList.emplace_back(std::make_unique(name, module)); /* Load required functions. */ bool loadok{true}; auto do_load = [module,name](auto &func, const char *fname) -> bool { using func_t = std::remove_reference_t; auto ptr = GetProcAddress(module, fname); if(!ptr) { ERR("Failed to find entry point for {} in {}", fname, wstr_to_utf8(name)); return false; } func = al::bit_cast(ptr); return true; }; #define LOAD_PROC(x) loadok &= do_load(newdrv.x, #x) LOAD_PROC(alcCreateContext); LOAD_PROC(alcMakeContextCurrent); LOAD_PROC(alcProcessContext); LOAD_PROC(alcSuspendContext); LOAD_PROC(alcDestroyContext); LOAD_PROC(alcGetCurrentContext); LOAD_PROC(alcGetContextsDevice); LOAD_PROC(alcOpenDevice); LOAD_PROC(alcCloseDevice); LOAD_PROC(alcGetError); LOAD_PROC(alcIsExtensionPresent); LOAD_PROC(alcGetProcAddress); LOAD_PROC(alcGetEnumValue); LOAD_PROC(alcGetString); LOAD_PROC(alcGetIntegerv); LOAD_PROC(alcCaptureOpenDevice); LOAD_PROC(alcCaptureCloseDevice); LOAD_PROC(alcCaptureStart); LOAD_PROC(alcCaptureStop); LOAD_PROC(alcCaptureSamples); LOAD_PROC(alEnable); LOAD_PROC(alDisable); LOAD_PROC(alIsEnabled); LOAD_PROC(alGetString); LOAD_PROC(alGetBooleanv); LOAD_PROC(alGetIntegerv); LOAD_PROC(alGetFloatv); LOAD_PROC(alGetDoublev); LOAD_PROC(alGetBoolean); LOAD_PROC(alGetInteger); LOAD_PROC(alGetFloat); LOAD_PROC(alGetDouble); LOAD_PROC(alGetError); LOAD_PROC(alIsExtensionPresent); LOAD_PROC(alGetProcAddress); LOAD_PROC(alGetEnumValue); LOAD_PROC(alListenerf); LOAD_PROC(alListener3f); LOAD_PROC(alListenerfv); LOAD_PROC(alListeneri); LOAD_PROC(alListener3i); LOAD_PROC(alListeneriv); LOAD_PROC(alGetListenerf); LOAD_PROC(alGetListener3f); LOAD_PROC(alGetListenerfv); LOAD_PROC(alGetListeneri); LOAD_PROC(alGetListener3i); LOAD_PROC(alGetListeneriv); LOAD_PROC(alGenSources); LOAD_PROC(alDeleteSources); LOAD_PROC(alIsSource); LOAD_PROC(alSourcef); LOAD_PROC(alSource3f); LOAD_PROC(alSourcefv); LOAD_PROC(alSourcei); LOAD_PROC(alSource3i); LOAD_PROC(alSourceiv); LOAD_PROC(alGetSourcef); LOAD_PROC(alGetSource3f); LOAD_PROC(alGetSourcefv); LOAD_PROC(alGetSourcei); LOAD_PROC(alGetSource3i); LOAD_PROC(alGetSourceiv); LOAD_PROC(alSourcePlayv); LOAD_PROC(alSourceStopv); LOAD_PROC(alSourceRewindv); LOAD_PROC(alSourcePausev); LOAD_PROC(alSourcePlay); LOAD_PROC(alSourceStop); LOAD_PROC(alSourceRewind); LOAD_PROC(alSourcePause); LOAD_PROC(alSourceQueueBuffers); LOAD_PROC(alSourceUnqueueBuffers); LOAD_PROC(alGenBuffers); LOAD_PROC(alDeleteBuffers); LOAD_PROC(alIsBuffer); LOAD_PROC(alBufferData); LOAD_PROC(alDopplerFactor); LOAD_PROC(alDopplerVelocity); LOAD_PROC(alSpeedOfSound); LOAD_PROC(alDistanceModel); #undef LOAD_PROC if(loadok) { std::array alc_ver{0, 0}; newdrv.alcGetIntegerv(nullptr, ALC_MAJOR_VERSION, 1, &alc_ver[0]); newdrv.alcGetIntegerv(nullptr, ALC_MINOR_VERSION, 1, &alc_ver[1]); if(newdrv.alcGetError(nullptr) == ALC_NO_ERROR) newdrv.ALCVer = MakeALCVer(alc_ver[0], alc_ver[1]); else { WARN("Failed to query ALC version for {}, assuming 1.0", wstr_to_utf8(name)); newdrv.ALCVer = MakeALCVer(1, 0); } auto do_load2 = [module,name](auto &func, const char *fname) -> void { using func_t = std::remove_reference_t; auto ptr = GetProcAddress(module, fname); if(!ptr) WARN("Failed to find optional entry point for {} in {}", fname, wstr_to_utf8(name)); else func = al::bit_cast(ptr); }; #define LOAD_PROC(x) do_load2(newdrv.x, #x) LOAD_PROC(alBufferf); LOAD_PROC(alBuffer3f); LOAD_PROC(alBufferfv); LOAD_PROC(alBufferi); LOAD_PROC(alBuffer3i); LOAD_PROC(alBufferiv); LOAD_PROC(alGetBufferf); LOAD_PROC(alGetBuffer3f); LOAD_PROC(alGetBufferfv); LOAD_PROC(alGetBufferi); LOAD_PROC(alGetBuffer3i); LOAD_PROC(alGetBufferiv); #undef LOAD_PROC auto do_load3 = [name,&newdrv](auto &func, const char *fname) -> bool { using func_t = std::remove_reference_t; auto ptr = newdrv.alcGetProcAddress(nullptr, fname); if(!ptr) { ERR("Failed to find entry point for {} in {}", fname, wstr_to_utf8(name)); return false; } func = reinterpret_cast(ptr); return true; }; #define LOAD_PROC(x) loadok &= do_load3(newdrv.x, #x) if(newdrv.alcIsExtensionPresent(nullptr, "ALC_EXT_thread_local_context")) { LOAD_PROC(alcSetThreadContext); LOAD_PROC(alcGetThreadContext); } #undef LOAD_PROC } if(!loadok) { DriverList.pop_back(); return; } TRACE("Loaded module {}, {}, ALC {}.{}", decltype(std::declval()){module}, wstr_to_utf8(name), newdrv.ALCVer>>8, newdrv.ALCVer&255); } void SearchDrivers(const std::wstring_view path) { TRACE("Searching for drivers in {}...", wstr_to_utf8(path)); std::wstring srchPath{path}; srchPath += L"\\*oal.dll"; WIN32_FIND_DATAW fdata{}; HANDLE srchHdl{FindFirstFileW(srchPath.c_str(), &fdata)}; if(srchHdl == INVALID_HANDLE_VALUE) return; do { srchPath = path; srchPath += L"\\"; srchPath += std::data(fdata.cFileName); TRACE("Found {}", wstr_to_utf8(srchPath)); HMODULE mod{LoadLibraryW(srchPath.c_str())}; if(!mod) WARN("Could not load {}", wstr_to_utf8(srchPath)); else AddModule(mod, std::data(fdata.cFileName)); } while(FindNextFileW(srchHdl, &fdata)); FindClose(srchHdl); } bool GetLoadedModuleDirectory(const WCHAR *name, std::wstring *moddir) { HMODULE module{nullptr}; if(name) { module = GetModuleHandleW(name); if(!module) return false; } moddir->assign(256, '\0'); DWORD res{GetModuleFileNameW(module, moddir->data(), static_cast(moddir->size()))}; if(res >= moddir->size()) { do { moddir->append(256, '\0'); res = GetModuleFileNameW(module, moddir->data(), static_cast(moddir->size())); } while(res >= moddir->size()); } moddir->resize(res); auto sep0 = moddir->rfind('/'); auto sep1 = moddir->rfind('\\'); if(sep0 < moddir->size() && sep1 < moddir->size()) moddir->resize(std::max(sep0, sep1)); else if(sep0 < moddir->size()) moddir->resize(sep0); else if(sep1 < moddir->size()) moddir->resize(sep1); else moddir->resize(0); return !moddir->empty(); } } // namespace void LoadDriverList() { TRACE("Initializing router v0.1-{} {}", ALSOFT_GIT_COMMIT_HASH, ALSOFT_GIT_BRANCH); if(auto list = al::getenv(L"ALROUTER_ACCEPT")) { std::wstring_view namelist{*list}; while(!namelist.empty()) { auto seppos = namelist.find(','); if(seppos > 0) gAcceptList.emplace_back(namelist.substr(0, seppos)); if(seppos < namelist.size()) namelist.remove_prefix(seppos+1); else namelist.remove_prefix(namelist.size()); } } if(auto list = al::getenv(L"ALROUTER_REJECT")) { std::wstring_view namelist{*list}; while(!namelist.empty()) { auto seppos = namelist.find(','); if(seppos > 0) gRejectList.emplace_back(namelist.substr(0, seppos)); if(seppos < namelist.size()) namelist.remove_prefix(seppos+1); else namelist.remove_prefix(namelist.size()); } } std::wstring dll_path; if(GetLoadedModuleDirectory(L"OpenAL32.dll", &dll_path)) TRACE("Got DLL path {}", wstr_to_utf8(dll_path)); std::wstring cwd_path; if(DWORD pathlen{GetCurrentDirectoryW(0, nullptr)}) { do { cwd_path.resize(pathlen); pathlen = GetCurrentDirectoryW(pathlen, cwd_path.data()); } while(pathlen >= cwd_path.size()); cwd_path.resize(pathlen); } if(!cwd_path.empty() && (cwd_path.back() == '\\' || cwd_path.back() == '/')) cwd_path.pop_back(); if(!cwd_path.empty()) TRACE("Got current working directory {}", wstr_to_utf8(cwd_path)); std::wstring proc_path; if(GetLoadedModuleDirectory(nullptr, &proc_path)) TRACE("Got proc path {}", wstr_to_utf8(proc_path)); std::wstring sys_path; if(UINT pathlen{GetSystemDirectoryW(nullptr, 0)}) { do { sys_path.resize(pathlen); pathlen = GetSystemDirectoryW(sys_path.data(), pathlen); } while(pathlen >= sys_path.size()); sys_path.resize(pathlen); } if(!sys_path.empty() && (sys_path.back() == '\\' || sys_path.back() == '/')) sys_path.pop_back(); if(!sys_path.empty()) TRACE("Got system path {}", wstr_to_utf8(sys_path)); /* Don't search the DLL's path if it is the same as the current working * directory, app's path, or system path (don't want to do duplicate * searches, or increase the priority of the app or system path). */ if(!dll_path.empty() && (cwd_path.empty() || dll_path != cwd_path) && (proc_path.empty() || dll_path != proc_path) && (sys_path.empty() || dll_path != sys_path)) SearchDrivers(dll_path); if(!cwd_path.empty() && (proc_path.empty() || cwd_path != proc_path) && (sys_path.empty() || cwd_path != sys_path)) SearchDrivers(cwd_path); if(!proc_path.empty() && (sys_path.empty() || proc_path != sys_path)) SearchDrivers(proc_path); if(!sys_path.empty()) SearchDrivers(sys_path); /* Sort drivers that can enumerate device names to the front. */ static constexpr auto is_enumerable = [](DriverIfacePtr &drv) { return drv->ALCVer >= MakeALCVer(1, 1) || drv->alcIsExtensionPresent(nullptr, "ALC_ENUMERATE_ALL_EXT") || drv->alcIsExtensionPresent(nullptr, "ALC_ENUMERATION_EXT"); }; std::stable_partition(DriverList.begin(), DriverList.end(), is_enumerable); /* HACK: rapture3d_oal.dll isn't likely to work if it's one distributed for * specific games licensed to use it. It will enumerate a Rapture3D device * but fail to open. This isn't much of a problem, the device just won't * work for users not allowed to use it. But if it's the first in the list * where it gets used for the default device, the default device will fail * to open. Move it down so it's not used for the default device. */ if(DriverList.size() > 1 && al::case_compare(DriverList.front()->Name, L"rapture3d_oal.dll") == 0) std::swap(*DriverList.begin(), *(DriverList.begin()+1)); } BOOL APIENTRY DllMain(HINSTANCE, DWORD reason, void*) { switch(reason) { case DLL_PROCESS_ATTACH: if(auto logfname = al::getenv(L"ALROUTER_LOGFILE")) { gsl::owner f{_wfopen(logfname->c_str(), L"w")}; if(f == nullptr) ERR("Could not open log file: {}", wstr_to_utf8(*logfname)); else LogFile = f; } if(auto loglev = al::getenv("ALROUTER_LOGLEVEL")) { char *end = nullptr; long l{strtol(loglev->c_str(), &end, 0)}; if(!end || *end != '\0') ERR("Invalid log level value: {}", *loglev); else if(l < al::to_underlying(eLogLevel::None) || l > al::to_underlying(eLogLevel::Trace)) ERR("Log level out of range: {}", *loglev); else LogLevel = static_cast(l); } break; case DLL_THREAD_ATTACH: break; case DLL_THREAD_DETACH: break; case DLL_PROCESS_DETACH: DriverList.clear(); if(LogFile) fclose(LogFile); LogFile = nullptr; break; } return TRUE; } openal-soft-1.24.2/router/router.h000066400000000000000000000212041474041540300170410ustar00rootroot00000000000000#ifndef ROUTER_ROUTER_H #define ROUTER_ROUTER_H #define WIN32_LEAN_AND_MEAN #include #include #include #include #include #include #include #include #include #include "AL/alc.h" #include "AL/al.h" #include "AL/alext.h" #include "almalloc.h" #include "fmt/core.h" constexpr auto MakeALCVer(int major, int minor) noexcept -> int { return (major<<8) | minor; } struct DriverIface { LPALCCREATECONTEXT alcCreateContext{nullptr}; LPALCMAKECONTEXTCURRENT alcMakeContextCurrent{nullptr}; LPALCPROCESSCONTEXT alcProcessContext{nullptr}; LPALCSUSPENDCONTEXT alcSuspendContext{nullptr}; LPALCDESTROYCONTEXT alcDestroyContext{nullptr}; LPALCGETCURRENTCONTEXT alcGetCurrentContext{nullptr}; LPALCGETCONTEXTSDEVICE alcGetContextsDevice{nullptr}; LPALCOPENDEVICE alcOpenDevice{nullptr}; LPALCCLOSEDEVICE alcCloseDevice{nullptr}; LPALCGETERROR alcGetError{nullptr}; LPALCISEXTENSIONPRESENT alcIsExtensionPresent{nullptr}; LPALCGETPROCADDRESS alcGetProcAddress{nullptr}; LPALCGETENUMVALUE alcGetEnumValue{nullptr}; LPALCGETSTRING alcGetString{nullptr}; LPALCGETINTEGERV alcGetIntegerv{nullptr}; LPALCCAPTUREOPENDEVICE alcCaptureOpenDevice{nullptr}; LPALCCAPTURECLOSEDEVICE alcCaptureCloseDevice{nullptr}; LPALCCAPTURESTART alcCaptureStart{nullptr}; LPALCCAPTURESTOP alcCaptureStop{nullptr}; LPALCCAPTURESAMPLES alcCaptureSamples{nullptr}; PFNALCSETTHREADCONTEXTPROC alcSetThreadContext{nullptr}; PFNALCGETTHREADCONTEXTPROC alcGetThreadContext{nullptr}; LPALENABLE alEnable{nullptr}; LPALDISABLE alDisable{nullptr}; LPALISENABLED alIsEnabled{nullptr}; LPALGETSTRING alGetString{nullptr}; LPALGETBOOLEANV alGetBooleanv{nullptr}; LPALGETINTEGERV alGetIntegerv{nullptr}; LPALGETFLOATV alGetFloatv{nullptr}; LPALGETDOUBLEV alGetDoublev{nullptr}; LPALGETBOOLEAN alGetBoolean{nullptr}; LPALGETINTEGER alGetInteger{nullptr}; LPALGETFLOAT alGetFloat{nullptr}; LPALGETDOUBLE alGetDouble{nullptr}; LPALGETERROR alGetError{nullptr}; LPALISEXTENSIONPRESENT alIsExtensionPresent{nullptr}; LPALGETPROCADDRESS alGetProcAddress{nullptr}; LPALGETENUMVALUE alGetEnumValue{nullptr}; LPALLISTENERF alListenerf{nullptr}; LPALLISTENER3F alListener3f{nullptr}; LPALLISTENERFV alListenerfv{nullptr}; LPALLISTENERI alListeneri{nullptr}; LPALLISTENER3I alListener3i{nullptr}; LPALLISTENERIV alListeneriv{nullptr}; LPALGETLISTENERF alGetListenerf{nullptr}; LPALGETLISTENER3F alGetListener3f{nullptr}; LPALGETLISTENERFV alGetListenerfv{nullptr}; LPALGETLISTENERI alGetListeneri{nullptr}; LPALGETLISTENER3I alGetListener3i{nullptr}; LPALGETLISTENERIV alGetListeneriv{nullptr}; LPALGENSOURCES alGenSources{nullptr}; LPALDELETESOURCES alDeleteSources{nullptr}; LPALISSOURCE alIsSource{nullptr}; LPALSOURCEF alSourcef{nullptr}; LPALSOURCE3F alSource3f{nullptr}; LPALSOURCEFV alSourcefv{nullptr}; LPALSOURCEI alSourcei{nullptr}; LPALSOURCE3I alSource3i{nullptr}; LPALSOURCEIV alSourceiv{nullptr}; LPALGETSOURCEF alGetSourcef{nullptr}; LPALGETSOURCE3F alGetSource3f{nullptr}; LPALGETSOURCEFV alGetSourcefv{nullptr}; LPALGETSOURCEI alGetSourcei{nullptr}; LPALGETSOURCE3I alGetSource3i{nullptr}; LPALGETSOURCEIV alGetSourceiv{nullptr}; LPALSOURCEPLAYV alSourcePlayv{nullptr}; LPALSOURCESTOPV alSourceStopv{nullptr}; LPALSOURCEREWINDV alSourceRewindv{nullptr}; LPALSOURCEPAUSEV alSourcePausev{nullptr}; LPALSOURCEPLAY alSourcePlay{nullptr}; LPALSOURCESTOP alSourceStop{nullptr}; LPALSOURCEREWIND alSourceRewind{nullptr}; LPALSOURCEPAUSE alSourcePause{nullptr}; LPALSOURCEQUEUEBUFFERS alSourceQueueBuffers{nullptr}; LPALSOURCEUNQUEUEBUFFERS alSourceUnqueueBuffers{nullptr}; LPALGENBUFFERS alGenBuffers{nullptr}; LPALDELETEBUFFERS alDeleteBuffers{nullptr}; LPALISBUFFER alIsBuffer{nullptr}; LPALBUFFERF alBufferf{nullptr}; LPALBUFFER3F alBuffer3f{nullptr}; LPALBUFFERFV alBufferfv{nullptr}; LPALBUFFERI alBufferi{nullptr}; LPALBUFFER3I alBuffer3i{nullptr}; LPALBUFFERIV alBufferiv{nullptr}; LPALGETBUFFERF alGetBufferf{nullptr}; LPALGETBUFFER3F alGetBuffer3f{nullptr}; LPALGETBUFFERFV alGetBufferfv{nullptr}; LPALGETBUFFERI alGetBufferi{nullptr}; LPALGETBUFFER3I alGetBuffer3i{nullptr}; LPALGETBUFFERIV alGetBufferiv{nullptr}; LPALBUFFERDATA alBufferData{nullptr}; LPALDOPPLERFACTOR alDopplerFactor{nullptr}; LPALDOPPLERVELOCITY alDopplerVelocity{nullptr}; LPALSPEEDOFSOUND alSpeedOfSound{nullptr}; LPALDISTANCEMODEL alDistanceModel{nullptr}; /* Functions to load after first context creation. */ LPALGENFILTERS alGenFilters{nullptr}; LPALDELETEFILTERS alDeleteFilters{nullptr}; LPALISFILTER alIsFilter{nullptr}; LPALFILTERF alFilterf{nullptr}; LPALFILTERFV alFilterfv{nullptr}; LPALFILTERI alFilteri{nullptr}; LPALFILTERIV alFilteriv{nullptr}; LPALGETFILTERF alGetFilterf{nullptr}; LPALGETFILTERFV alGetFilterfv{nullptr}; LPALGETFILTERI alGetFilteri{nullptr}; LPALGETFILTERIV alGetFilteriv{nullptr}; LPALGENEFFECTS alGenEffects{nullptr}; LPALDELETEEFFECTS alDeleteEffects{nullptr}; LPALISEFFECT alIsEffect{nullptr}; LPALEFFECTF alEffectf{nullptr}; LPALEFFECTFV alEffectfv{nullptr}; LPALEFFECTI alEffecti{nullptr}; LPALEFFECTIV alEffectiv{nullptr}; LPALGETEFFECTF alGetEffectf{nullptr}; LPALGETEFFECTFV alGetEffectfv{nullptr}; LPALGETEFFECTI alGetEffecti{nullptr}; LPALGETEFFECTIV alGetEffectiv{nullptr}; LPALGENAUXILIARYEFFECTSLOTS alGenAuxiliaryEffectSlots{nullptr}; LPALDELETEAUXILIARYEFFECTSLOTS alDeleteAuxiliaryEffectSlots{nullptr}; LPALISAUXILIARYEFFECTSLOT alIsAuxiliaryEffectSlot{nullptr}; LPALAUXILIARYEFFECTSLOTF alAuxiliaryEffectSlotf{nullptr}; LPALAUXILIARYEFFECTSLOTFV alAuxiliaryEffectSlotfv{nullptr}; LPALAUXILIARYEFFECTSLOTI alAuxiliaryEffectSloti{nullptr}; LPALAUXILIARYEFFECTSLOTIV alAuxiliaryEffectSlotiv{nullptr}; LPALGETAUXILIARYEFFECTSLOTF alGetAuxiliaryEffectSlotf{nullptr}; LPALGETAUXILIARYEFFECTSLOTFV alGetAuxiliaryEffectSlotfv{nullptr}; LPALGETAUXILIARYEFFECTSLOTI alGetAuxiliaryEffectSloti{nullptr}; LPALGETAUXILIARYEFFECTSLOTIV alGetAuxiliaryEffectSlotiv{nullptr}; std::wstring Name; HMODULE Module{nullptr}; int ALCVer{0}; std::once_flag InitOnceCtx; template DriverIface(T&& name, HMODULE mod) : Name(std::forward(name)), Module(mod) { } ~DriverIface() { if(Module) FreeLibrary(Module); } DriverIface(const DriverIface&) = delete; DriverIface(DriverIface&&) = delete; DriverIface& operator=(const DriverIface&) = delete; DriverIface& operator=(DriverIface&&) = delete; }; using DriverIfacePtr = std::unique_ptr; inline std::vector DriverList; inline thread_local DriverIface *ThreadCtxDriver{}; inline std::atomic CurrentCtxDriver{}; inline DriverIface *GetThreadDriver() noexcept { return ThreadCtxDriver; } inline void SetThreadDriver(DriverIface *driver) noexcept { ThreadCtxDriver = driver; } enum class eLogLevel { None = 0, Error = 1, Warn = 2, Trace = 3, }; extern eLogLevel LogLevel; extern gsl::owner LogFile; #define TRACE(...) do { \ if(LogLevel >= eLogLevel::Trace) \ { \ std::FILE *file{LogFile ? LogFile : stderr}; \ fmt::println(file, "AL Router (II): " __VA_ARGS__); \ fflush(file); \ } \ } while(0) #define WARN(...) do { \ if(LogLevel >= eLogLevel::Warn) \ { \ std::FILE *file{LogFile ? LogFile : stderr}; \ fmt::println(file, "AL Router (WW): " __VA_ARGS__); \ fflush(file); \ } \ } while(0) #define ERR(...) do { \ if(LogLevel >= eLogLevel::Error) \ { \ std::FILE *file{LogFile ? LogFile : stderr}; \ fmt::println(file, "AL Router (EE): " __VA_ARGS__); \ fflush(file); \ } \ } while(0) void LoadDriverList(); #endif /* ROUTER_ROUTER_H */ openal-soft-1.24.2/tests/000077500000000000000000000000001474041540300151735ustar00rootroot00000000000000openal-soft-1.24.2/tests/CMakeLists.txt000066400000000000000000000010731474041540300177340ustar00rootroot00000000000000add_executable(OpenAL_Tests) include(FetchContent) FetchContent_Declare( googletest GIT_REPOSITORY https://github.com/google/googletest.git GIT_TAG main ) # For Windows: Prevent overriding the parent project's compiler/linker settings set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) FetchContent_MakeAvailable(googletest) target_link_libraries(OpenAL_Tests PRIVATE OpenAL GTest::gtest_main ) target_sources(OpenAL_Tests PRIVATE example.t.cpp ) # This needs to come last include(GoogleTest) gtest_discover_tests(OpenAL_Tests) openal-soft-1.24.2/tests/example.t.cpp000066400000000000000000000003641474041540300175770ustar00rootroot00000000000000#include #include class ExampleTest : public ::testing::Test { }; TEST_F(ExampleTest, Basic) { // just making sure we compile ALuint source, buffer; ALfloat offset; ALenum state; } openal-soft-1.24.2/utils/000077500000000000000000000000001474041540300151715ustar00rootroot00000000000000openal-soft-1.24.2/utils/CIAIR.def000066400000000000000000007320201474041540300165040ustar00rootroot00000000000000# This is a makemhr HRIR definition file. It is used to define the layout and # source data to be processed into an OpenAL Soft compatible HRTF. # # This definition is used to transform the left and right ear HRIRs from a # data set used in several papers and articles by Fumitada Itakura, Kazuya # Takeda, Mikio Ikeda, Shoji Kajita, and Takanori Nishino. # # The data (data02.tgz) can be obtained from The Database of Head Related # Transfer Functions hosted by the Takeda Laboratory at Nagoya University: # # http://www.sp.m.is.nagoya-u.ac.jp/HRTF/database.html # # It is copyright 1999 by Itakura Laboratory and the Center for Integrated # Acoustic Information Research (CIAIR) of Nagoya University and provided # free of charge with no restrictions on use so long as the authors (above) # are cited. rate = 44100 # The CIAIR set is stereo because it provides both ear HRIRs. type = stereo points = 512 # No head radius was provided. Just use the average radius of 9 cm. radius = 0.09 # The CIAIR set is composed of a single field with an unknown distance # between the source and the listener, so a guess of 1.5 meters is used. distance = 1.5 # This set has a uniform number of azimuths for all but the poles (-90 and 90 # degree elevation). azimuths = 1, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 1 # The CIAIR source azimuth is counter-clockwise, so it needs to be flipped. # The extension of the source data may be misleading, they're ASCII text # lists of floating point values (one per line). Left and right ear HRIRs # (from the respective files) are used to create a stereo HRTF. [ 9, 0 ] = ascii (fp) : "./hrtfs/elev-45/L-45e000a.dat" left + ascii (fp) : "./hrtfs/elev-45/R-45e000a.dat" right [ 9, 1 ] = ascii (fp) : "./hrtfs/elev-45/L-45e355a.dat" left + ascii (fp) : "./hrtfs/elev-45/R-45e355a.dat" right [ 9, 2 ] = ascii (fp) : "./hrtfs/elev-45/L-45e350a.dat" left + ascii (fp) : "./hrtfs/elev-45/R-45e350a.dat" right [ 9, 3 ] = ascii (fp) : "./hrtfs/elev-45/L-45e345a.dat" left + ascii (fp) : "./hrtfs/elev-45/R-45e345a.dat" right [ 9, 4 ] = ascii (fp) : "./hrtfs/elev-45/L-45e340a.dat" left + ascii (fp) : "./hrtfs/elev-45/R-45e340a.dat" right [ 9, 5 ] = ascii (fp) : "./hrtfs/elev-45/L-45e335a.dat" left + ascii (fp) : "./hrtfs/elev-45/R-45e335a.dat" right [ 9, 6 ] = ascii (fp) : "./hrtfs/elev-45/L-45e330a.dat" left + ascii (fp) : "./hrtfs/elev-45/R-45e330a.dat" right [ 9, 7 ] = ascii (fp) : "./hrtfs/elev-45/L-45e325a.dat" left + ascii (fp) : "./hrtfs/elev-45/R-45e325a.dat" right [ 9, 8 ] = ascii (fp) : "./hrtfs/elev-45/L-45e320a.dat" left + ascii (fp) : "./hrtfs/elev-45/R-45e320a.dat" right [ 9, 9 ] = ascii (fp) : "./hrtfs/elev-45/L-45e315a.dat" left + ascii (fp) : "./hrtfs/elev-45/R-45e315a.dat" right [ 9, 10 ] = ascii (fp) : "./hrtfs/elev-45/L-45e310a.dat" left + ascii (fp) : "./hrtfs/elev-45/R-45e310a.dat" right [ 9, 11 ] = ascii (fp) : "./hrtfs/elev-45/L-45e305a.dat" left + ascii (fp) : "./hrtfs/elev-45/R-45e305a.dat" right [ 9, 12 ] = ascii (fp) : "./hrtfs/elev-45/L-45e300a.dat" left + ascii (fp) : "./hrtfs/elev-45/R-45e300a.dat" right [ 9, 13 ] = ascii (fp) : "./hrtfs/elev-45/L-45e295a.dat" left + ascii (fp) : "./hrtfs/elev-45/R-45e295a.dat" right [ 9, 14 ] = ascii (fp) : "./hrtfs/elev-45/L-45e290a.dat" left + ascii (fp) : "./hrtfs/elev-45/R-45e290a.dat" right [ 9, 15 ] = ascii (fp) : "./hrtfs/elev-45/L-45e285a.dat" left + ascii (fp) : "./hrtfs/elev-45/R-45e285a.dat" right [ 9, 16 ] = ascii (fp) : "./hrtfs/elev-45/L-45e280a.dat" left + ascii (fp) : "./hrtfs/elev-45/R-45e280a.dat" right [ 9, 17 ] = ascii (fp) : "./hrtfs/elev-45/L-45e275a.dat" left + ascii (fp) : "./hrtfs/elev-45/R-45e275a.dat" right [ 9, 18 ] = ascii (fp) : "./hrtfs/elev-45/L-45e270a.dat" left + ascii (fp) : "./hrtfs/elev-45/R-45e270a.dat" right [ 9, 19 ] = ascii (fp) : "./hrtfs/elev-45/L-45e265a.dat" left + ascii (fp) : "./hrtfs/elev-45/R-45e265a.dat" right [ 9, 20 ] = ascii (fp) : "./hrtfs/elev-45/L-45e260a.dat" left + ascii (fp) : "./hrtfs/elev-45/R-45e260a.dat" right [ 9, 21 ] = ascii (fp) : "./hrtfs/elev-45/L-45e255a.dat" left + ascii (fp) : "./hrtfs/elev-45/R-45e255a.dat" right [ 9, 22 ] = ascii (fp) : "./hrtfs/elev-45/L-45e250a.dat" left + ascii (fp) : "./hrtfs/elev-45/R-45e250a.dat" right [ 9, 23 ] = ascii (fp) : "./hrtfs/elev-45/L-45e245a.dat" left + ascii (fp) : "./hrtfs/elev-45/R-45e245a.dat" right [ 9, 24 ] = ascii (fp) : "./hrtfs/elev-45/L-45e240a.dat" left + ascii (fp) : "./hrtfs/elev-45/R-45e240a.dat" right [ 9, 25 ] = ascii (fp) : "./hrtfs/elev-45/L-45e235a.dat" left + ascii (fp) : "./hrtfs/elev-45/R-45e235a.dat" right [ 9, 26 ] = ascii (fp) : "./hrtfs/elev-45/L-45e230a.dat" left + ascii (fp) : "./hrtfs/elev-45/R-45e230a.dat" right [ 9, 27 ] = ascii (fp) : "./hrtfs/elev-45/L-45e225a.dat" left + ascii (fp) : "./hrtfs/elev-45/R-45e225a.dat" right [ 9, 28 ] = ascii (fp) : "./hrtfs/elev-45/L-45e220a.dat" left + ascii (fp) : "./hrtfs/elev-45/R-45e220a.dat" right [ 9, 29 ] = ascii (fp) : "./hrtfs/elev-45/L-45e215a.dat" left + ascii (fp) : "./hrtfs/elev-45/R-45e215a.dat" right [ 9, 30 ] = ascii (fp) : "./hrtfs/elev-45/L-45e210a.dat" left + ascii (fp) : "./hrtfs/elev-45/R-45e210a.dat" right [ 9, 31 ] = ascii (fp) : "./hrtfs/elev-45/L-45e205a.dat" left + ascii (fp) : "./hrtfs/elev-45/R-45e205a.dat" right [ 9, 32 ] = ascii (fp) : "./hrtfs/elev-45/L-45e200a.dat" left + ascii (fp) : "./hrtfs/elev-45/R-45e200a.dat" right [ 9, 33 ] = ascii (fp) : "./hrtfs/elev-45/L-45e195a.dat" left + ascii (fp) : "./hrtfs/elev-45/R-45e195a.dat" right [ 9, 34 ] = ascii (fp) : "./hrtfs/elev-45/L-45e190a.dat" left + ascii (fp) : "./hrtfs/elev-45/R-45e190a.dat" right [ 9, 35 ] = ascii (fp) : "./hrtfs/elev-45/L-45e185a.dat" left + ascii (fp) : "./hrtfs/elev-45/R-45e185a.dat" right [ 9, 36 ] = ascii (fp) : "./hrtfs/elev-45/L-45e180a.dat" left + ascii (fp) : "./hrtfs/elev-45/R-45e180a.dat" right [ 9, 37 ] = ascii (fp) : "./hrtfs/elev-45/L-45e175a.dat" left + ascii (fp) : "./hrtfs/elev-45/R-45e175a.dat" right [ 9, 38 ] = ascii (fp) : "./hrtfs/elev-45/L-45e170a.dat" left + ascii (fp) : "./hrtfs/elev-45/R-45e170a.dat" right [ 9, 39 ] = ascii (fp) : "./hrtfs/elev-45/L-45e165a.dat" left + ascii (fp) : "./hrtfs/elev-45/R-45e165a.dat" right [ 9, 40 ] = ascii (fp) : "./hrtfs/elev-45/L-45e160a.dat" left + ascii (fp) : "./hrtfs/elev-45/R-45e160a.dat" right [ 9, 41 ] = ascii (fp) : "./hrtfs/elev-45/L-45e155a.dat" left + ascii (fp) : "./hrtfs/elev-45/R-45e155a.dat" right [ 9, 42 ] = ascii (fp) : "./hrtfs/elev-45/L-45e150a.dat" left + ascii (fp) : "./hrtfs/elev-45/R-45e150a.dat" right [ 9, 43 ] = ascii (fp) : "./hrtfs/elev-45/L-45e145a.dat" left + ascii (fp) : "./hrtfs/elev-45/R-45e145a.dat" right [ 9, 44 ] = ascii (fp) : "./hrtfs/elev-45/L-45e140a.dat" left + ascii (fp) : "./hrtfs/elev-45/R-45e140a.dat" right [ 9, 45 ] = ascii (fp) : "./hrtfs/elev-45/L-45e135a.dat" left + ascii (fp) : "./hrtfs/elev-45/R-45e135a.dat" right [ 9, 46 ] = ascii (fp) : "./hrtfs/elev-45/L-45e130a.dat" left + ascii (fp) : "./hrtfs/elev-45/R-45e130a.dat" right [ 9, 47 ] = ascii (fp) : "./hrtfs/elev-45/L-45e125a.dat" left + ascii (fp) : "./hrtfs/elev-45/R-45e125a.dat" right [ 9, 48 ] = ascii (fp) : "./hrtfs/elev-45/L-45e120a.dat" left + ascii (fp) : "./hrtfs/elev-45/R-45e120a.dat" right [ 9, 49 ] = ascii (fp) : "./hrtfs/elev-45/L-45e115a.dat" left + ascii (fp) : "./hrtfs/elev-45/R-45e115a.dat" right [ 9, 50 ] = ascii (fp) : "./hrtfs/elev-45/L-45e110a.dat" left + ascii (fp) : "./hrtfs/elev-45/R-45e110a.dat" right [ 9, 51 ] = ascii (fp) : "./hrtfs/elev-45/L-45e105a.dat" left + ascii (fp) : "./hrtfs/elev-45/R-45e105a.dat" right [ 9, 52 ] = ascii (fp) : "./hrtfs/elev-45/L-45e100a.dat" left + ascii (fp) : "./hrtfs/elev-45/R-45e100a.dat" right [ 9, 53 ] = ascii (fp) : "./hrtfs/elev-45/L-45e095a.dat" left + ascii (fp) : "./hrtfs/elev-45/R-45e095a.dat" right [ 9, 54 ] = ascii (fp) : "./hrtfs/elev-45/L-45e090a.dat" left + ascii (fp) : "./hrtfs/elev-45/R-45e090a.dat" right [ 9, 55 ] = ascii (fp) : "./hrtfs/elev-45/L-45e085a.dat" left + ascii (fp) : "./hrtfs/elev-45/R-45e085a.dat" right [ 9, 56 ] = ascii (fp) : "./hrtfs/elev-45/L-45e080a.dat" left + ascii (fp) : "./hrtfs/elev-45/R-45e080a.dat" right [ 9, 57 ] = ascii (fp) : "./hrtfs/elev-45/L-45e075a.dat" left + ascii (fp) : "./hrtfs/elev-45/R-45e075a.dat" right [ 9, 58 ] = ascii (fp) : "./hrtfs/elev-45/L-45e070a.dat" left + ascii (fp) : "./hrtfs/elev-45/R-45e070a.dat" right [ 9, 59 ] = ascii (fp) : "./hrtfs/elev-45/L-45e065a.dat" left + ascii (fp) : "./hrtfs/elev-45/R-45e065a.dat" right [ 9, 60 ] = ascii (fp) : "./hrtfs/elev-45/L-45e060a.dat" left + ascii (fp) : "./hrtfs/elev-45/R-45e060a.dat" right [ 9, 61 ] = ascii (fp) : "./hrtfs/elev-45/L-45e055a.dat" left + ascii (fp) : "./hrtfs/elev-45/R-45e055a.dat" right [ 9, 62 ] = ascii (fp) : "./hrtfs/elev-45/L-45e050a.dat" left + ascii (fp) : "./hrtfs/elev-45/R-45e050a.dat" right [ 9, 63 ] = ascii (fp) : "./hrtfs/elev-45/L-45e045a.dat" left + ascii (fp) : "./hrtfs/elev-45/R-45e045a.dat" right [ 9, 64 ] = ascii (fp) : "./hrtfs/elev-45/L-45e040a.dat" left + ascii (fp) : "./hrtfs/elev-45/R-45e040a.dat" right [ 9, 65 ] = ascii (fp) : "./hrtfs/elev-45/L-45e035a.dat" left + ascii (fp) : "./hrtfs/elev-45/R-45e035a.dat" right [ 9, 66 ] = ascii (fp) : "./hrtfs/elev-45/L-45e030a.dat" left + ascii (fp) : "./hrtfs/elev-45/R-45e030a.dat" right [ 9, 67 ] = ascii (fp) : "./hrtfs/elev-45/L-45e025a.dat" left + ascii (fp) : "./hrtfs/elev-45/R-45e025a.dat" right [ 9, 68 ] = ascii (fp) : "./hrtfs/elev-45/L-45e020a.dat" left + ascii (fp) : "./hrtfs/elev-45/R-45e020a.dat" right [ 9, 69 ] = ascii (fp) : "./hrtfs/elev-45/L-45e015a.dat" left + ascii (fp) : "./hrtfs/elev-45/R-45e015a.dat" right [ 9, 70 ] = ascii (fp) : "./hrtfs/elev-45/L-45e010a.dat" left + ascii (fp) : "./hrtfs/elev-45/R-45e010a.dat" right [ 9, 71 ] = ascii (fp) : "./hrtfs/elev-45/L-45e005a.dat" left + ascii (fp) : "./hrtfs/elev-45/R-45e005a.dat" right [ 10, 0 ] = ascii (fp) : "./hrtfs/elev-40/L-40e000a.dat" left + ascii (fp) : "./hrtfs/elev-40/R-40e000a.dat" right [ 10, 1 ] = ascii (fp) : "./hrtfs/elev-40/L-40e355a.dat" left + ascii (fp) : "./hrtfs/elev-40/R-40e355a.dat" right [ 10, 2 ] = ascii (fp) : "./hrtfs/elev-40/L-40e350a.dat" left + ascii (fp) : "./hrtfs/elev-40/R-40e350a.dat" right [ 10, 3 ] = ascii (fp) : "./hrtfs/elev-40/L-40e345a.dat" left + ascii (fp) : "./hrtfs/elev-40/R-40e345a.dat" right [ 10, 4 ] = ascii (fp) : "./hrtfs/elev-40/L-40e340a.dat" left + ascii (fp) : "./hrtfs/elev-40/R-40e340a.dat" right [ 10, 5 ] = ascii (fp) : "./hrtfs/elev-40/L-40e335a.dat" left + ascii (fp) : "./hrtfs/elev-40/R-40e335a.dat" right [ 10, 6 ] = ascii (fp) : "./hrtfs/elev-40/L-40e330a.dat" left + ascii (fp) : "./hrtfs/elev-40/R-40e330a.dat" right [ 10, 7 ] = ascii (fp) : "./hrtfs/elev-40/L-40e325a.dat" left + ascii (fp) : "./hrtfs/elev-40/R-40e325a.dat" right [ 10, 8 ] = ascii (fp) : "./hrtfs/elev-40/L-40e320a.dat" left + ascii (fp) : "./hrtfs/elev-40/R-40e320a.dat" right [ 10, 9 ] = ascii (fp) : "./hrtfs/elev-40/L-40e315a.dat" left + ascii (fp) : "./hrtfs/elev-40/R-40e315a.dat" right [ 10, 10 ] = ascii (fp) : "./hrtfs/elev-40/L-40e310a.dat" left + ascii (fp) : "./hrtfs/elev-40/R-40e310a.dat" right [ 10, 11 ] = ascii (fp) : "./hrtfs/elev-40/L-40e305a.dat" left + ascii (fp) : "./hrtfs/elev-40/R-40e305a.dat" right [ 10, 12 ] = ascii (fp) : "./hrtfs/elev-40/L-40e300a.dat" left + ascii (fp) : "./hrtfs/elev-40/R-40e300a.dat" right [ 10, 13 ] = ascii (fp) : "./hrtfs/elev-40/L-40e295a.dat" left + ascii (fp) : "./hrtfs/elev-40/R-40e295a.dat" right [ 10, 14 ] = ascii (fp) : "./hrtfs/elev-40/L-40e290a.dat" left + ascii (fp) : "./hrtfs/elev-40/R-40e290a.dat" right [ 10, 15 ] = ascii (fp) : "./hrtfs/elev-40/L-40e285a.dat" left + ascii (fp) : "./hrtfs/elev-40/R-40e285a.dat" right [ 10, 16 ] = ascii (fp) : "./hrtfs/elev-40/L-40e280a.dat" left + ascii (fp) : "./hrtfs/elev-40/R-40e280a.dat" right [ 10, 17 ] = ascii (fp) : "./hrtfs/elev-40/L-40e275a.dat" left + ascii (fp) : "./hrtfs/elev-40/R-40e275a.dat" right [ 10, 18 ] = ascii (fp) : "./hrtfs/elev-40/L-40e270a.dat" left + ascii (fp) : "./hrtfs/elev-40/R-40e270a.dat" right [ 10, 19 ] = ascii (fp) : "./hrtfs/elev-40/L-40e265a.dat" left + ascii (fp) : "./hrtfs/elev-40/R-40e265a.dat" right [ 10, 20 ] = ascii (fp) : "./hrtfs/elev-40/L-40e260a.dat" left + ascii (fp) : "./hrtfs/elev-40/R-40e260a.dat" right [ 10, 21 ] = ascii (fp) : "./hrtfs/elev-40/L-40e255a.dat" left + ascii (fp) : "./hrtfs/elev-40/R-40e255a.dat" right [ 10, 22 ] = ascii (fp) : "./hrtfs/elev-40/L-40e250a.dat" left + ascii (fp) : "./hrtfs/elev-40/R-40e250a.dat" right [ 10, 23 ] = ascii (fp) : "./hrtfs/elev-40/L-40e245a.dat" left + ascii (fp) : "./hrtfs/elev-40/R-40e245a.dat" right [ 10, 24 ] = ascii (fp) : "./hrtfs/elev-40/L-40e240a.dat" left + ascii (fp) : "./hrtfs/elev-40/R-40e240a.dat" right [ 10, 25 ] = ascii (fp) : "./hrtfs/elev-40/L-40e235a.dat" left + ascii (fp) : "./hrtfs/elev-40/R-40e235a.dat" right [ 10, 26 ] = ascii (fp) : "./hrtfs/elev-40/L-40e230a.dat" left + ascii (fp) : "./hrtfs/elev-40/R-40e230a.dat" right [ 10, 27 ] = ascii (fp) : "./hrtfs/elev-40/L-40e225a.dat" left + ascii (fp) : "./hrtfs/elev-40/R-40e225a.dat" right [ 10, 28 ] = ascii (fp) : "./hrtfs/elev-40/L-40e220a.dat" left + ascii (fp) : "./hrtfs/elev-40/R-40e220a.dat" right [ 10, 29 ] = ascii (fp) : "./hrtfs/elev-40/L-40e215a.dat" left + ascii (fp) : "./hrtfs/elev-40/R-40e215a.dat" right [ 10, 30 ] = ascii (fp) : "./hrtfs/elev-40/L-40e210a.dat" left + ascii (fp) : "./hrtfs/elev-40/R-40e210a.dat" right [ 10, 31 ] = ascii (fp) : "./hrtfs/elev-40/L-40e205a.dat" left + ascii (fp) : "./hrtfs/elev-40/R-40e205a.dat" right [ 10, 32 ] = ascii (fp) : "./hrtfs/elev-40/L-40e200a.dat" left + ascii (fp) : "./hrtfs/elev-40/R-40e200a.dat" right [ 10, 33 ] = ascii (fp) : "./hrtfs/elev-40/L-40e195a.dat" left + ascii (fp) : "./hrtfs/elev-40/R-40e195a.dat" right [ 10, 34 ] = ascii (fp) : "./hrtfs/elev-40/L-40e190a.dat" left + ascii (fp) : "./hrtfs/elev-40/R-40e190a.dat" right [ 10, 35 ] = ascii (fp) : "./hrtfs/elev-40/L-40e185a.dat" left + ascii (fp) : "./hrtfs/elev-40/R-40e185a.dat" right [ 10, 36 ] = ascii (fp) : "./hrtfs/elev-40/L-40e180a.dat" left + ascii (fp) : "./hrtfs/elev-40/R-40e180a.dat" right [ 10, 37 ] = ascii (fp) : "./hrtfs/elev-40/L-40e175a.dat" left + ascii (fp) : "./hrtfs/elev-40/R-40e175a.dat" right [ 10, 38 ] = ascii (fp) : "./hrtfs/elev-40/L-40e170a.dat" left + ascii (fp) : "./hrtfs/elev-40/R-40e170a.dat" right [ 10, 39 ] = ascii (fp) : "./hrtfs/elev-40/L-40e165a.dat" left + ascii (fp) : "./hrtfs/elev-40/R-40e165a.dat" right [ 10, 40 ] = ascii (fp) : "./hrtfs/elev-40/L-40e160a.dat" left + ascii (fp) : "./hrtfs/elev-40/R-40e160a.dat" right [ 10, 41 ] = ascii (fp) : "./hrtfs/elev-40/L-40e155a.dat" left + ascii (fp) : "./hrtfs/elev-40/R-40e155a.dat" right [ 10, 42 ] = ascii (fp) : "./hrtfs/elev-40/L-40e150a.dat" left + ascii (fp) : "./hrtfs/elev-40/R-40e150a.dat" right [ 10, 43 ] = ascii (fp) : "./hrtfs/elev-40/L-40e145a.dat" left + ascii (fp) : "./hrtfs/elev-40/R-40e145a.dat" right [ 10, 44 ] = ascii (fp) : "./hrtfs/elev-40/L-40e140a.dat" left + ascii (fp) : "./hrtfs/elev-40/R-40e140a.dat" right [ 10, 45 ] = ascii (fp) : "./hrtfs/elev-40/L-40e135a.dat" left + ascii (fp) : "./hrtfs/elev-40/R-40e135a.dat" right [ 10, 46 ] = ascii (fp) : "./hrtfs/elev-40/L-40e130a.dat" left + ascii (fp) : "./hrtfs/elev-40/R-40e130a.dat" right [ 10, 47 ] = ascii (fp) : "./hrtfs/elev-40/L-40e125a.dat" left + ascii (fp) : "./hrtfs/elev-40/R-40e125a.dat" right [ 10, 48 ] = ascii (fp) : "./hrtfs/elev-40/L-40e120a.dat" left + ascii (fp) : "./hrtfs/elev-40/R-40e120a.dat" right [ 10, 49 ] = ascii (fp) : "./hrtfs/elev-40/L-40e115a.dat" left + ascii (fp) : "./hrtfs/elev-40/R-40e115a.dat" right [ 10, 50 ] = ascii (fp) : "./hrtfs/elev-40/L-40e110a.dat" left + ascii (fp) : "./hrtfs/elev-40/R-40e110a.dat" right [ 10, 51 ] = ascii (fp) : "./hrtfs/elev-40/L-40e105a.dat" left + ascii (fp) : "./hrtfs/elev-40/R-40e105a.dat" right [ 10, 52 ] = ascii (fp) : "./hrtfs/elev-40/L-40e100a.dat" left + ascii (fp) : "./hrtfs/elev-40/R-40e100a.dat" right [ 10, 53 ] = ascii (fp) : "./hrtfs/elev-40/L-40e095a.dat" left + ascii (fp) : "./hrtfs/elev-40/R-40e095a.dat" right [ 10, 54 ] = ascii (fp) : "./hrtfs/elev-40/L-40e090a.dat" left + ascii (fp) : "./hrtfs/elev-40/R-40e090a.dat" right [ 10, 55 ] = ascii (fp) : "./hrtfs/elev-40/L-40e085a.dat" left + ascii (fp) : "./hrtfs/elev-40/R-40e085a.dat" right [ 10, 56 ] = ascii (fp) : "./hrtfs/elev-40/L-40e080a.dat" left + ascii (fp) : "./hrtfs/elev-40/R-40e080a.dat" right [ 10, 57 ] = ascii (fp) : "./hrtfs/elev-40/L-40e075a.dat" left + ascii (fp) : "./hrtfs/elev-40/R-40e075a.dat" right [ 10, 58 ] = ascii (fp) : "./hrtfs/elev-40/L-40e070a.dat" left + ascii (fp) : "./hrtfs/elev-40/R-40e070a.dat" right [ 10, 59 ] = ascii (fp) : "./hrtfs/elev-40/L-40e065a.dat" left + ascii (fp) : "./hrtfs/elev-40/R-40e065a.dat" right [ 10, 60 ] = ascii (fp) : "./hrtfs/elev-40/L-40e060a.dat" left + ascii (fp) : "./hrtfs/elev-40/R-40e060a.dat" right [ 10, 61 ] = ascii (fp) : "./hrtfs/elev-40/L-40e055a.dat" left + ascii (fp) : "./hrtfs/elev-40/R-40e055a.dat" right [ 10, 62 ] = ascii (fp) : "./hrtfs/elev-40/L-40e050a.dat" left + ascii (fp) : "./hrtfs/elev-40/R-40e050a.dat" right [ 10, 63 ] = ascii (fp) : "./hrtfs/elev-40/L-40e045a.dat" left + ascii (fp) : "./hrtfs/elev-40/R-40e045a.dat" right [ 10, 64 ] = ascii (fp) : "./hrtfs/elev-40/L-40e040a.dat" left + ascii (fp) : "./hrtfs/elev-40/R-40e040a.dat" right [ 10, 65 ] = ascii (fp) : "./hrtfs/elev-40/L-40e035a.dat" left + ascii (fp) : "./hrtfs/elev-40/R-40e035a.dat" right [ 10, 66 ] = ascii (fp) : "./hrtfs/elev-40/L-40e030a.dat" left + ascii (fp) : "./hrtfs/elev-40/R-40e030a.dat" right [ 10, 67 ] = ascii (fp) : "./hrtfs/elev-40/L-40e025a.dat" left + ascii (fp) : "./hrtfs/elev-40/R-40e025a.dat" right [ 10, 68 ] = ascii (fp) : "./hrtfs/elev-40/L-40e020a.dat" left + ascii (fp) : "./hrtfs/elev-40/R-40e020a.dat" right [ 10, 69 ] = ascii (fp) : "./hrtfs/elev-40/L-40e015a.dat" left + ascii (fp) : "./hrtfs/elev-40/R-40e015a.dat" right [ 10, 70 ] = ascii (fp) : "./hrtfs/elev-40/L-40e010a.dat" left + ascii (fp) : "./hrtfs/elev-40/R-40e010a.dat" right [ 10, 71 ] = ascii (fp) : "./hrtfs/elev-40/L-40e005a.dat" left + ascii (fp) : "./hrtfs/elev-40/R-40e005a.dat" right [ 11, 0 ] = ascii (fp) : "./hrtfs/elev-35/L-35e000a.dat" left + ascii (fp) : "./hrtfs/elev-35/R-35e000a.dat" right [ 11, 1 ] = ascii (fp) : "./hrtfs/elev-35/L-35e355a.dat" left + ascii (fp) : "./hrtfs/elev-35/R-35e355a.dat" right [ 11, 2 ] = ascii (fp) : "./hrtfs/elev-35/L-35e350a.dat" left + ascii (fp) : "./hrtfs/elev-35/R-35e350a.dat" right [ 11, 3 ] = ascii (fp) : "./hrtfs/elev-35/L-35e345a.dat" left + ascii (fp) : "./hrtfs/elev-35/R-35e345a.dat" right [ 11, 4 ] = ascii (fp) : "./hrtfs/elev-35/L-35e340a.dat" left + ascii (fp) : "./hrtfs/elev-35/R-35e340a.dat" right [ 11, 5 ] = ascii (fp) : "./hrtfs/elev-35/L-35e335a.dat" left + ascii (fp) : "./hrtfs/elev-35/R-35e335a.dat" right [ 11, 6 ] = ascii (fp) : "./hrtfs/elev-35/L-35e330a.dat" left + ascii (fp) : "./hrtfs/elev-35/R-35e330a.dat" right [ 11, 7 ] = ascii (fp) : "./hrtfs/elev-35/L-35e325a.dat" left + ascii (fp) : "./hrtfs/elev-35/R-35e325a.dat" right [ 11, 8 ] = ascii (fp) : "./hrtfs/elev-35/L-35e320a.dat" left + ascii (fp) : "./hrtfs/elev-35/R-35e320a.dat" right [ 11, 9 ] = ascii (fp) : "./hrtfs/elev-35/L-35e315a.dat" left + ascii (fp) : "./hrtfs/elev-35/R-35e315a.dat" right [ 11, 10 ] = ascii (fp) : "./hrtfs/elev-35/L-35e310a.dat" left + ascii (fp) : "./hrtfs/elev-35/R-35e310a.dat" right [ 11, 11 ] = ascii (fp) : "./hrtfs/elev-35/L-35e305a.dat" left + ascii (fp) : "./hrtfs/elev-35/R-35e305a.dat" right [ 11, 12 ] = ascii (fp) : "./hrtfs/elev-35/L-35e300a.dat" left + ascii (fp) : "./hrtfs/elev-35/R-35e300a.dat" right [ 11, 13 ] = ascii (fp) : "./hrtfs/elev-35/L-35e295a.dat" left + ascii (fp) : "./hrtfs/elev-35/R-35e295a.dat" right [ 11, 14 ] = ascii (fp) : "./hrtfs/elev-35/L-35e290a.dat" left + ascii (fp) : "./hrtfs/elev-35/R-35e290a.dat" right [ 11, 15 ] = ascii (fp) : "./hrtfs/elev-35/L-35e285a.dat" left + ascii (fp) : "./hrtfs/elev-35/R-35e285a.dat" right [ 11, 16 ] = ascii (fp) : "./hrtfs/elev-35/L-35e280a.dat" left + ascii (fp) : "./hrtfs/elev-35/R-35e280a.dat" right [ 11, 17 ] = ascii (fp) : "./hrtfs/elev-35/L-35e275a.dat" left + ascii (fp) : "./hrtfs/elev-35/R-35e275a.dat" right [ 11, 18 ] = ascii (fp) : "./hrtfs/elev-35/L-35e270a.dat" left + ascii (fp) : "./hrtfs/elev-35/R-35e270a.dat" right [ 11, 19 ] = ascii (fp) : "./hrtfs/elev-35/L-35e265a.dat" left + ascii (fp) : "./hrtfs/elev-35/R-35e265a.dat" right [ 11, 20 ] = ascii (fp) : "./hrtfs/elev-35/L-35e260a.dat" left + ascii (fp) : "./hrtfs/elev-35/R-35e260a.dat" right [ 11, 21 ] = ascii (fp) : "./hrtfs/elev-35/L-35e255a.dat" left + ascii (fp) : "./hrtfs/elev-35/R-35e255a.dat" right [ 11, 22 ] = ascii (fp) : "./hrtfs/elev-35/L-35e250a.dat" left + ascii (fp) : "./hrtfs/elev-35/R-35e250a.dat" right [ 11, 23 ] = ascii (fp) : "./hrtfs/elev-35/L-35e245a.dat" left + ascii (fp) : "./hrtfs/elev-35/R-35e245a.dat" right [ 11, 24 ] = ascii (fp) : "./hrtfs/elev-35/L-35e240a.dat" left + ascii (fp) : "./hrtfs/elev-35/R-35e240a.dat" right [ 11, 25 ] = ascii (fp) : "./hrtfs/elev-35/L-35e235a.dat" left + ascii (fp) : "./hrtfs/elev-35/R-35e235a.dat" right [ 11, 26 ] = ascii (fp) : "./hrtfs/elev-35/L-35e230a.dat" left + ascii (fp) : "./hrtfs/elev-35/R-35e230a.dat" right [ 11, 27 ] = ascii (fp) : "./hrtfs/elev-35/L-35e225a.dat" left + ascii (fp) : "./hrtfs/elev-35/R-35e225a.dat" right [ 11, 28 ] = ascii (fp) : "./hrtfs/elev-35/L-35e220a.dat" left + ascii (fp) : "./hrtfs/elev-35/R-35e220a.dat" right [ 11, 29 ] = ascii (fp) : "./hrtfs/elev-35/L-35e215a.dat" left + ascii (fp) : "./hrtfs/elev-35/R-35e215a.dat" right [ 11, 30 ] = ascii (fp) : "./hrtfs/elev-35/L-35e210a.dat" left + ascii (fp) : "./hrtfs/elev-35/R-35e210a.dat" right [ 11, 31 ] = ascii (fp) : "./hrtfs/elev-35/L-35e205a.dat" left + ascii (fp) : "./hrtfs/elev-35/R-35e205a.dat" right [ 11, 32 ] = ascii (fp) : "./hrtfs/elev-35/L-35e200a.dat" left + ascii (fp) : "./hrtfs/elev-35/R-35e200a.dat" right [ 11, 33 ] = ascii (fp) : "./hrtfs/elev-35/L-35e195a.dat" left + ascii (fp) : "./hrtfs/elev-35/R-35e195a.dat" right [ 11, 34 ] = ascii (fp) : "./hrtfs/elev-35/L-35e190a.dat" left + ascii (fp) : "./hrtfs/elev-35/R-35e190a.dat" right [ 11, 35 ] = ascii (fp) : "./hrtfs/elev-35/L-35e185a.dat" left + ascii (fp) : "./hrtfs/elev-35/R-35e185a.dat" right [ 11, 36 ] = ascii (fp) : "./hrtfs/elev-35/L-35e180a.dat" left + ascii (fp) : "./hrtfs/elev-35/R-35e180a.dat" right [ 11, 37 ] = ascii (fp) : "./hrtfs/elev-35/L-35e175a.dat" left + ascii (fp) : "./hrtfs/elev-35/R-35e175a.dat" right [ 11, 38 ] = ascii (fp) : "./hrtfs/elev-35/L-35e170a.dat" left + ascii (fp) : "./hrtfs/elev-35/R-35e170a.dat" right [ 11, 39 ] = ascii (fp) : "./hrtfs/elev-35/L-35e165a.dat" left + ascii (fp) : "./hrtfs/elev-35/R-35e165a.dat" right [ 11, 40 ] = ascii (fp) : "./hrtfs/elev-35/L-35e160a.dat" left + ascii (fp) : "./hrtfs/elev-35/R-35e160a.dat" right [ 11, 41 ] = ascii (fp) : "./hrtfs/elev-35/L-35e155a.dat" left + ascii (fp) : "./hrtfs/elev-35/R-35e155a.dat" right [ 11, 42 ] = ascii (fp) : "./hrtfs/elev-35/L-35e150a.dat" left + ascii (fp) : "./hrtfs/elev-35/R-35e150a.dat" right [ 11, 43 ] = ascii (fp) : "./hrtfs/elev-35/L-35e145a.dat" left + ascii (fp) : "./hrtfs/elev-35/R-35e145a.dat" right [ 11, 44 ] = ascii (fp) : "./hrtfs/elev-35/L-35e140a.dat" left + ascii (fp) : "./hrtfs/elev-35/R-35e140a.dat" right [ 11, 45 ] = ascii (fp) : "./hrtfs/elev-35/L-35e135a.dat" left + ascii (fp) : "./hrtfs/elev-35/R-35e135a.dat" right [ 11, 46 ] = ascii (fp) : "./hrtfs/elev-35/L-35e130a.dat" left + ascii (fp) : "./hrtfs/elev-35/R-35e130a.dat" right [ 11, 47 ] = ascii (fp) : "./hrtfs/elev-35/L-35e125a.dat" left + ascii (fp) : "./hrtfs/elev-35/R-35e125a.dat" right [ 11, 48 ] = ascii (fp) : "./hrtfs/elev-35/L-35e120a.dat" left + ascii (fp) : "./hrtfs/elev-35/R-35e120a.dat" right [ 11, 49 ] = ascii (fp) : "./hrtfs/elev-35/L-35e115a.dat" left + ascii (fp) : "./hrtfs/elev-35/R-35e115a.dat" right [ 11, 50 ] = ascii (fp) : "./hrtfs/elev-35/L-35e110a.dat" left + ascii (fp) : "./hrtfs/elev-35/R-35e110a.dat" right [ 11, 51 ] = ascii (fp) : "./hrtfs/elev-35/L-35e105a.dat" left + ascii (fp) : "./hrtfs/elev-35/R-35e105a.dat" right [ 11, 52 ] = ascii (fp) : "./hrtfs/elev-35/L-35e100a.dat" left + ascii (fp) : "./hrtfs/elev-35/R-35e100a.dat" right [ 11, 53 ] = ascii (fp) : "./hrtfs/elev-35/L-35e095a.dat" left + ascii (fp) : "./hrtfs/elev-35/R-35e095a.dat" right [ 11, 54 ] = ascii (fp) : "./hrtfs/elev-35/L-35e090a.dat" left + ascii (fp) : "./hrtfs/elev-35/R-35e090a.dat" right [ 11, 55 ] = ascii (fp) : "./hrtfs/elev-35/L-35e085a.dat" left + ascii (fp) : "./hrtfs/elev-35/R-35e085a.dat" right [ 11, 56 ] = ascii (fp) : "./hrtfs/elev-35/L-35e080a.dat" left + ascii (fp) : "./hrtfs/elev-35/R-35e080a.dat" right [ 11, 57 ] = ascii (fp) : "./hrtfs/elev-35/L-35e075a.dat" left + ascii (fp) : "./hrtfs/elev-35/R-35e075a.dat" right [ 11, 58 ] = ascii (fp) : "./hrtfs/elev-35/L-35e070a.dat" left + ascii (fp) : "./hrtfs/elev-35/R-35e070a.dat" right [ 11, 59 ] = ascii (fp) : "./hrtfs/elev-35/L-35e065a.dat" left + ascii (fp) : "./hrtfs/elev-35/R-35e065a.dat" right [ 11, 60 ] = ascii (fp) : "./hrtfs/elev-35/L-35e060a.dat" left + ascii (fp) : "./hrtfs/elev-35/R-35e060a.dat" right [ 11, 61 ] = ascii (fp) : "./hrtfs/elev-35/L-35e055a.dat" left + ascii (fp) : "./hrtfs/elev-35/R-35e055a.dat" right [ 11, 62 ] = ascii (fp) : "./hrtfs/elev-35/L-35e050a.dat" left + ascii (fp) : "./hrtfs/elev-35/R-35e050a.dat" right [ 11, 63 ] = ascii (fp) : "./hrtfs/elev-35/L-35e045a.dat" left + ascii (fp) : "./hrtfs/elev-35/R-35e045a.dat" right [ 11, 64 ] = ascii (fp) : "./hrtfs/elev-35/L-35e040a.dat" left + ascii (fp) : "./hrtfs/elev-35/R-35e040a.dat" right [ 11, 65 ] = ascii (fp) : "./hrtfs/elev-35/L-35e035a.dat" left + ascii (fp) : "./hrtfs/elev-35/R-35e035a.dat" right [ 11, 66 ] = ascii (fp) : "./hrtfs/elev-35/L-35e030a.dat" left + ascii (fp) : "./hrtfs/elev-35/R-35e030a.dat" right [ 11, 67 ] = ascii (fp) : "./hrtfs/elev-35/L-35e025a.dat" left + ascii (fp) : "./hrtfs/elev-35/R-35e025a.dat" right [ 11, 68 ] = ascii (fp) : "./hrtfs/elev-35/L-35e020a.dat" left + ascii (fp) : "./hrtfs/elev-35/R-35e020a.dat" right [ 11, 69 ] = ascii (fp) : "./hrtfs/elev-35/L-35e015a.dat" left + ascii (fp) : "./hrtfs/elev-35/R-35e015a.dat" right [ 11, 70 ] = ascii (fp) : "./hrtfs/elev-35/L-35e010a.dat" left + ascii (fp) : "./hrtfs/elev-35/R-35e010a.dat" right [ 11, 71 ] = ascii (fp) : "./hrtfs/elev-35/L-35e005a.dat" left + ascii (fp) : "./hrtfs/elev-35/R-35e005a.dat" right [ 12, 0 ] = ascii (fp) : "./hrtfs/elev-30/L-30e000a.dat" left + ascii (fp) : "./hrtfs/elev-30/R-30e000a.dat" right [ 12, 1 ] = ascii (fp) : "./hrtfs/elev-30/L-30e355a.dat" left + ascii (fp) : "./hrtfs/elev-30/R-30e355a.dat" right [ 12, 2 ] = ascii (fp) : "./hrtfs/elev-30/L-30e350a.dat" left + ascii (fp) : "./hrtfs/elev-30/R-30e350a.dat" right [ 12, 3 ] = ascii (fp) : "./hrtfs/elev-30/L-30e345a.dat" left + ascii (fp) : "./hrtfs/elev-30/R-30e345a.dat" right [ 12, 4 ] = ascii (fp) : "./hrtfs/elev-30/L-30e340a.dat" left + ascii (fp) : "./hrtfs/elev-30/R-30e340a.dat" right [ 12, 5 ] = ascii (fp) : "./hrtfs/elev-30/L-30e335a.dat" left + ascii (fp) : "./hrtfs/elev-30/R-30e335a.dat" right [ 12, 6 ] = ascii (fp) : "./hrtfs/elev-30/L-30e330a.dat" left + ascii (fp) : "./hrtfs/elev-30/R-30e330a.dat" right [ 12, 7 ] = ascii (fp) : "./hrtfs/elev-30/L-30e325a.dat" left + ascii (fp) : "./hrtfs/elev-30/R-30e325a.dat" right [ 12, 8 ] = ascii (fp) : "./hrtfs/elev-30/L-30e320a.dat" left + ascii (fp) : "./hrtfs/elev-30/R-30e320a.dat" right [ 12, 9 ] = ascii (fp) : "./hrtfs/elev-30/L-30e315a.dat" left + ascii (fp) : "./hrtfs/elev-30/R-30e315a.dat" right [ 12, 10 ] = ascii (fp) : "./hrtfs/elev-30/L-30e310a.dat" left + ascii (fp) : "./hrtfs/elev-30/R-30e310a.dat" right [ 12, 11 ] = ascii (fp) : "./hrtfs/elev-30/L-30e305a.dat" left + ascii (fp) : "./hrtfs/elev-30/R-30e305a.dat" right [ 12, 12 ] = ascii (fp) : "./hrtfs/elev-30/L-30e300a.dat" left + ascii (fp) : "./hrtfs/elev-30/R-30e300a.dat" right [ 12, 13 ] = ascii (fp) : "./hrtfs/elev-30/L-30e295a.dat" left + ascii (fp) : "./hrtfs/elev-30/R-30e295a.dat" right [ 12, 14 ] = ascii (fp) : "./hrtfs/elev-30/L-30e290a.dat" left + ascii (fp) : "./hrtfs/elev-30/R-30e290a.dat" right [ 12, 15 ] = ascii (fp) : "./hrtfs/elev-30/L-30e285a.dat" left + ascii (fp) : "./hrtfs/elev-30/R-30e285a.dat" right [ 12, 16 ] = ascii (fp) : "./hrtfs/elev-30/L-30e280a.dat" left + ascii (fp) : "./hrtfs/elev-30/R-30e280a.dat" right [ 12, 17 ] = ascii (fp) : "./hrtfs/elev-30/L-30e275a.dat" left + ascii (fp) : "./hrtfs/elev-30/R-30e275a.dat" right [ 12, 18 ] = ascii (fp) : "./hrtfs/elev-30/L-30e270a.dat" left + ascii (fp) : "./hrtfs/elev-30/R-30e270a.dat" right [ 12, 19 ] = ascii (fp) : "./hrtfs/elev-30/L-30e265a.dat" left + ascii (fp) : "./hrtfs/elev-30/R-30e265a.dat" right [ 12, 20 ] = ascii (fp) : "./hrtfs/elev-30/L-30e260a.dat" left + ascii (fp) : "./hrtfs/elev-30/R-30e260a.dat" right [ 12, 21 ] = ascii (fp) : "./hrtfs/elev-30/L-30e255a.dat" left + ascii (fp) : "./hrtfs/elev-30/R-30e255a.dat" right [ 12, 22 ] = ascii (fp) : "./hrtfs/elev-30/L-30e250a.dat" left + ascii (fp) : "./hrtfs/elev-30/R-30e250a.dat" right [ 12, 23 ] = ascii (fp) : "./hrtfs/elev-30/L-30e245a.dat" left + ascii (fp) : "./hrtfs/elev-30/R-30e245a.dat" right [ 12, 24 ] = ascii (fp) : "./hrtfs/elev-30/L-30e240a.dat" left + ascii (fp) : "./hrtfs/elev-30/R-30e240a.dat" right [ 12, 25 ] = ascii (fp) : "./hrtfs/elev-30/L-30e235a.dat" left + ascii (fp) : "./hrtfs/elev-30/R-30e235a.dat" right [ 12, 26 ] = ascii (fp) : "./hrtfs/elev-30/L-30e230a.dat" left + ascii (fp) : "./hrtfs/elev-30/R-30e230a.dat" right [ 12, 27 ] = ascii (fp) : "./hrtfs/elev-30/L-30e225a.dat" left + ascii (fp) : "./hrtfs/elev-30/R-30e225a.dat" right [ 12, 28 ] = ascii (fp) : "./hrtfs/elev-30/L-30e220a.dat" left + ascii (fp) : "./hrtfs/elev-30/R-30e220a.dat" right [ 12, 29 ] = ascii (fp) : "./hrtfs/elev-30/L-30e215a.dat" left + ascii (fp) : "./hrtfs/elev-30/R-30e215a.dat" right [ 12, 30 ] = ascii (fp) : "./hrtfs/elev-30/L-30e210a.dat" left + ascii (fp) : "./hrtfs/elev-30/R-30e210a.dat" right [ 12, 31 ] = ascii (fp) : "./hrtfs/elev-30/L-30e205a.dat" left + ascii (fp) : "./hrtfs/elev-30/R-30e205a.dat" right [ 12, 32 ] = ascii (fp) : "./hrtfs/elev-30/L-30e200a.dat" left + ascii (fp) : "./hrtfs/elev-30/R-30e200a.dat" right [ 12, 33 ] = ascii (fp) : "./hrtfs/elev-30/L-30e195a.dat" left + ascii (fp) : "./hrtfs/elev-30/R-30e195a.dat" right [ 12, 34 ] = ascii (fp) : "./hrtfs/elev-30/L-30e190a.dat" left + ascii (fp) : "./hrtfs/elev-30/R-30e190a.dat" right [ 12, 35 ] = ascii (fp) : "./hrtfs/elev-30/L-30e185a.dat" left + ascii (fp) : "./hrtfs/elev-30/R-30e185a.dat" right [ 12, 36 ] = ascii (fp) : "./hrtfs/elev-30/L-30e180a.dat" left + ascii (fp) : "./hrtfs/elev-30/R-30e180a.dat" right [ 12, 37 ] = ascii (fp) : "./hrtfs/elev-30/L-30e175a.dat" left + ascii (fp) : "./hrtfs/elev-30/R-30e175a.dat" right [ 12, 38 ] = ascii (fp) : "./hrtfs/elev-30/L-30e170a.dat" left + ascii (fp) : "./hrtfs/elev-30/R-30e170a.dat" right [ 12, 39 ] = ascii (fp) : "./hrtfs/elev-30/L-30e165a.dat" left + ascii (fp) : "./hrtfs/elev-30/R-30e165a.dat" right [ 12, 40 ] = ascii (fp) : "./hrtfs/elev-30/L-30e160a.dat" left + ascii (fp) : "./hrtfs/elev-30/R-30e160a.dat" right [ 12, 41 ] = ascii (fp) : "./hrtfs/elev-30/L-30e155a.dat" left + ascii (fp) : "./hrtfs/elev-30/R-30e155a.dat" right [ 12, 42 ] = ascii (fp) : "./hrtfs/elev-30/L-30e150a.dat" left + ascii (fp) : "./hrtfs/elev-30/R-30e150a.dat" right [ 12, 43 ] = ascii (fp) : "./hrtfs/elev-30/L-30e145a.dat" left + ascii (fp) : "./hrtfs/elev-30/R-30e145a.dat" right [ 12, 44 ] = ascii (fp) : "./hrtfs/elev-30/L-30e140a.dat" left + ascii (fp) : "./hrtfs/elev-30/R-30e140a.dat" right [ 12, 45 ] = ascii (fp) : "./hrtfs/elev-30/L-30e135a.dat" left + ascii (fp) : "./hrtfs/elev-30/R-30e135a.dat" right [ 12, 46 ] = ascii (fp) : "./hrtfs/elev-30/L-30e130a.dat" left + ascii (fp) : "./hrtfs/elev-30/R-30e130a.dat" right [ 12, 47 ] = ascii (fp) : "./hrtfs/elev-30/L-30e125a.dat" left + ascii (fp) : "./hrtfs/elev-30/R-30e125a.dat" right [ 12, 48 ] = ascii (fp) : "./hrtfs/elev-30/L-30e120a.dat" left + ascii (fp) : "./hrtfs/elev-30/R-30e120a.dat" right [ 12, 49 ] = ascii (fp) : "./hrtfs/elev-30/L-30e115a.dat" left + ascii (fp) : "./hrtfs/elev-30/R-30e115a.dat" right [ 12, 50 ] = ascii (fp) : "./hrtfs/elev-30/L-30e110a.dat" left + ascii (fp) : "./hrtfs/elev-30/R-30e110a.dat" right [ 12, 51 ] = ascii (fp) : "./hrtfs/elev-30/L-30e105a.dat" left + ascii (fp) : "./hrtfs/elev-30/R-30e105a.dat" right [ 12, 52 ] = ascii (fp) : "./hrtfs/elev-30/L-30e100a.dat" left + ascii (fp) : "./hrtfs/elev-30/R-30e100a.dat" right [ 12, 53 ] = ascii (fp) : "./hrtfs/elev-30/L-30e095a.dat" left + ascii (fp) : "./hrtfs/elev-30/R-30e095a.dat" right [ 12, 54 ] = ascii (fp) : "./hrtfs/elev-30/L-30e090a.dat" left + ascii (fp) : "./hrtfs/elev-30/R-30e090a.dat" right [ 12, 55 ] = ascii (fp) : "./hrtfs/elev-30/L-30e085a.dat" left + ascii (fp) : "./hrtfs/elev-30/R-30e085a.dat" right [ 12, 56 ] = ascii (fp) : "./hrtfs/elev-30/L-30e080a.dat" left + ascii (fp) : "./hrtfs/elev-30/R-30e080a.dat" right [ 12, 57 ] = ascii (fp) : "./hrtfs/elev-30/L-30e075a.dat" left + ascii (fp) : "./hrtfs/elev-30/R-30e075a.dat" right [ 12, 58 ] = ascii (fp) : "./hrtfs/elev-30/L-30e070a.dat" left + ascii (fp) : "./hrtfs/elev-30/R-30e070a.dat" right [ 12, 59 ] = ascii (fp) : "./hrtfs/elev-30/L-30e065a.dat" left + ascii (fp) : "./hrtfs/elev-30/R-30e065a.dat" right [ 12, 60 ] = ascii (fp) : "./hrtfs/elev-30/L-30e060a.dat" left + ascii (fp) : "./hrtfs/elev-30/R-30e060a.dat" right [ 12, 61 ] = ascii (fp) : "./hrtfs/elev-30/L-30e055a.dat" left + ascii (fp) : "./hrtfs/elev-30/R-30e055a.dat" right [ 12, 62 ] = ascii (fp) : "./hrtfs/elev-30/L-30e050a.dat" left + ascii (fp) : "./hrtfs/elev-30/R-30e050a.dat" right [ 12, 63 ] = ascii (fp) : "./hrtfs/elev-30/L-30e045a.dat" left + ascii (fp) : "./hrtfs/elev-30/R-30e045a.dat" right [ 12, 64 ] = ascii (fp) : "./hrtfs/elev-30/L-30e040a.dat" left + ascii (fp) : "./hrtfs/elev-30/R-30e040a.dat" right [ 12, 65 ] = ascii (fp) : "./hrtfs/elev-30/L-30e035a.dat" left + ascii (fp) : "./hrtfs/elev-30/R-30e035a.dat" right [ 12, 66 ] = ascii (fp) : "./hrtfs/elev-30/L-30e030a.dat" left + ascii (fp) : "./hrtfs/elev-30/R-30e030a.dat" right [ 12, 67 ] = ascii (fp) : "./hrtfs/elev-30/L-30e025a.dat" left + ascii (fp) : "./hrtfs/elev-30/R-30e025a.dat" right [ 12, 68 ] = ascii (fp) : "./hrtfs/elev-30/L-30e020a.dat" left + ascii (fp) : "./hrtfs/elev-30/R-30e020a.dat" right [ 12, 69 ] = ascii (fp) : "./hrtfs/elev-30/L-30e015a.dat" left + ascii (fp) : "./hrtfs/elev-30/R-30e015a.dat" right [ 12, 70 ] = ascii (fp) : "./hrtfs/elev-30/L-30e010a.dat" left + ascii (fp) : "./hrtfs/elev-30/R-30e010a.dat" right [ 12, 71 ] = ascii (fp) : "./hrtfs/elev-30/L-30e005a.dat" left + ascii (fp) : "./hrtfs/elev-30/R-30e005a.dat" right [ 13, 0 ] = ascii (fp) : "./hrtfs/elev-25/L-25e000a.dat" left + ascii (fp) : "./hrtfs/elev-25/R-25e000a.dat" right [ 13, 1 ] = ascii (fp) : "./hrtfs/elev-25/L-25e355a.dat" left + ascii (fp) : "./hrtfs/elev-25/R-25e355a.dat" right [ 13, 2 ] = ascii (fp) : "./hrtfs/elev-25/L-25e350a.dat" left + ascii (fp) : "./hrtfs/elev-25/R-25e350a.dat" right [ 13, 3 ] = ascii (fp) : "./hrtfs/elev-25/L-25e345a.dat" left + ascii (fp) : "./hrtfs/elev-25/R-25e345a.dat" right [ 13, 4 ] = ascii (fp) : "./hrtfs/elev-25/L-25e340a.dat" left + ascii (fp) : "./hrtfs/elev-25/R-25e340a.dat" right [ 13, 5 ] = ascii (fp) : "./hrtfs/elev-25/L-25e335a.dat" left + ascii (fp) : "./hrtfs/elev-25/R-25e335a.dat" right [ 13, 6 ] = ascii (fp) : "./hrtfs/elev-25/L-25e330a.dat" left + ascii (fp) : "./hrtfs/elev-25/R-25e330a.dat" right [ 13, 7 ] = ascii (fp) : "./hrtfs/elev-25/L-25e325a.dat" left + ascii (fp) : "./hrtfs/elev-25/R-25e325a.dat" right [ 13, 8 ] = ascii (fp) : "./hrtfs/elev-25/L-25e320a.dat" left + ascii (fp) : "./hrtfs/elev-25/R-25e320a.dat" right [ 13, 9 ] = ascii (fp) : "./hrtfs/elev-25/L-25e315a.dat" left + ascii (fp) : "./hrtfs/elev-25/R-25e315a.dat" right [ 13, 10 ] = ascii (fp) : "./hrtfs/elev-25/L-25e310a.dat" left + ascii (fp) : "./hrtfs/elev-25/R-25e310a.dat" right [ 13, 11 ] = ascii (fp) : "./hrtfs/elev-25/L-25e305a.dat" left + ascii (fp) : "./hrtfs/elev-25/R-25e305a.dat" right [ 13, 12 ] = ascii (fp) : "./hrtfs/elev-25/L-25e300a.dat" left + ascii (fp) : "./hrtfs/elev-25/R-25e300a.dat" right [ 13, 13 ] = ascii (fp) : "./hrtfs/elev-25/L-25e295a.dat" left + ascii (fp) : "./hrtfs/elev-25/R-25e295a.dat" right [ 13, 14 ] = ascii (fp) : "./hrtfs/elev-25/L-25e290a.dat" left + ascii (fp) : "./hrtfs/elev-25/R-25e290a.dat" right [ 13, 15 ] = ascii (fp) : "./hrtfs/elev-25/L-25e285a.dat" left + ascii (fp) : "./hrtfs/elev-25/R-25e285a.dat" right [ 13, 16 ] = ascii (fp) : "./hrtfs/elev-25/L-25e280a.dat" left + ascii (fp) : "./hrtfs/elev-25/R-25e280a.dat" right [ 13, 17 ] = ascii (fp) : "./hrtfs/elev-25/L-25e275a.dat" left + ascii (fp) : "./hrtfs/elev-25/R-25e275a.dat" right [ 13, 18 ] = ascii (fp) : "./hrtfs/elev-25/L-25e270a.dat" left + ascii (fp) : "./hrtfs/elev-25/R-25e270a.dat" right [ 13, 19 ] = ascii (fp) : "./hrtfs/elev-25/L-25e265a.dat" left + ascii (fp) : "./hrtfs/elev-25/R-25e265a.dat" right [ 13, 20 ] = ascii (fp) : "./hrtfs/elev-25/L-25e260a.dat" left + ascii (fp) : "./hrtfs/elev-25/R-25e260a.dat" right [ 13, 21 ] = ascii (fp) : "./hrtfs/elev-25/L-25e255a.dat" left + ascii (fp) : "./hrtfs/elev-25/R-25e255a.dat" right [ 13, 22 ] = ascii (fp) : "./hrtfs/elev-25/L-25e250a.dat" left + ascii (fp) : "./hrtfs/elev-25/R-25e250a.dat" right [ 13, 23 ] = ascii (fp) : "./hrtfs/elev-25/L-25e245a.dat" left + ascii (fp) : "./hrtfs/elev-25/R-25e245a.dat" right [ 13, 24 ] = ascii (fp) : "./hrtfs/elev-25/L-25e240a.dat" left + ascii (fp) : "./hrtfs/elev-25/R-25e240a.dat" right [ 13, 25 ] = ascii (fp) : "./hrtfs/elev-25/L-25e235a.dat" left + ascii (fp) : "./hrtfs/elev-25/R-25e235a.dat" right [ 13, 26 ] = ascii (fp) : "./hrtfs/elev-25/L-25e230a.dat" left + ascii (fp) : "./hrtfs/elev-25/R-25e230a.dat" right [ 13, 27 ] = ascii (fp) : "./hrtfs/elev-25/L-25e225a.dat" left + ascii (fp) : "./hrtfs/elev-25/R-25e225a.dat" right [ 13, 28 ] = ascii (fp) : "./hrtfs/elev-25/L-25e220a.dat" left + ascii (fp) : "./hrtfs/elev-25/R-25e220a.dat" right [ 13, 29 ] = ascii (fp) : "./hrtfs/elev-25/L-25e215a.dat" left + ascii (fp) : "./hrtfs/elev-25/R-25e215a.dat" right [ 13, 30 ] = ascii (fp) : "./hrtfs/elev-25/L-25e210a.dat" left + ascii (fp) : "./hrtfs/elev-25/R-25e210a.dat" right [ 13, 31 ] = ascii (fp) : "./hrtfs/elev-25/L-25e205a.dat" left + ascii (fp) : "./hrtfs/elev-25/R-25e205a.dat" right [ 13, 32 ] = ascii (fp) : "./hrtfs/elev-25/L-25e200a.dat" left + ascii (fp) : "./hrtfs/elev-25/R-25e200a.dat" right [ 13, 33 ] = ascii (fp) : "./hrtfs/elev-25/L-25e195a.dat" left + ascii (fp) : "./hrtfs/elev-25/R-25e195a.dat" right [ 13, 34 ] = ascii (fp) : "./hrtfs/elev-25/L-25e190a.dat" left + ascii (fp) : "./hrtfs/elev-25/R-25e190a.dat" right [ 13, 35 ] = ascii (fp) : "./hrtfs/elev-25/L-25e185a.dat" left + ascii (fp) : "./hrtfs/elev-25/R-25e185a.dat" right [ 13, 36 ] = ascii (fp) : "./hrtfs/elev-25/L-25e180a.dat" left + ascii (fp) : "./hrtfs/elev-25/R-25e180a.dat" right [ 13, 37 ] = ascii (fp) : "./hrtfs/elev-25/L-25e175a.dat" left + ascii (fp) : "./hrtfs/elev-25/R-25e175a.dat" right [ 13, 38 ] = ascii (fp) : "./hrtfs/elev-25/L-25e170a.dat" left + ascii (fp) : "./hrtfs/elev-25/R-25e170a.dat" right [ 13, 39 ] = ascii (fp) : "./hrtfs/elev-25/L-25e165a.dat" left + ascii (fp) : "./hrtfs/elev-25/R-25e165a.dat" right [ 13, 40 ] = ascii (fp) : "./hrtfs/elev-25/L-25e160a.dat" left + ascii (fp) : "./hrtfs/elev-25/R-25e160a.dat" right [ 13, 41 ] = ascii (fp) : "./hrtfs/elev-25/L-25e155a.dat" left + ascii (fp) : "./hrtfs/elev-25/R-25e155a.dat" right [ 13, 42 ] = ascii (fp) : "./hrtfs/elev-25/L-25e150a.dat" left + ascii (fp) : "./hrtfs/elev-25/R-25e150a.dat" right [ 13, 43 ] = ascii (fp) : "./hrtfs/elev-25/L-25e145a.dat" left + ascii (fp) : "./hrtfs/elev-25/R-25e145a.dat" right [ 13, 44 ] = ascii (fp) : "./hrtfs/elev-25/L-25e140a.dat" left + ascii (fp) : "./hrtfs/elev-25/R-25e140a.dat" right [ 13, 45 ] = ascii (fp) : "./hrtfs/elev-25/L-25e135a.dat" left + ascii (fp) : "./hrtfs/elev-25/R-25e135a.dat" right [ 13, 46 ] = ascii (fp) : "./hrtfs/elev-25/L-25e130a.dat" left + ascii (fp) : "./hrtfs/elev-25/R-25e130a.dat" right [ 13, 47 ] = ascii (fp) : "./hrtfs/elev-25/L-25e125a.dat" left + ascii (fp) : "./hrtfs/elev-25/R-25e125a.dat" right [ 13, 48 ] = ascii (fp) : "./hrtfs/elev-25/L-25e120a.dat" left + ascii (fp) : "./hrtfs/elev-25/R-25e120a.dat" right [ 13, 49 ] = ascii (fp) : "./hrtfs/elev-25/L-25e115a.dat" left + ascii (fp) : "./hrtfs/elev-25/R-25e115a.dat" right [ 13, 50 ] = ascii (fp) : "./hrtfs/elev-25/L-25e110a.dat" left + ascii (fp) : "./hrtfs/elev-25/R-25e110a.dat" right [ 13, 51 ] = ascii (fp) : "./hrtfs/elev-25/L-25e105a.dat" left + ascii (fp) : "./hrtfs/elev-25/R-25e105a.dat" right [ 13, 52 ] = ascii (fp) : "./hrtfs/elev-25/L-25e100a.dat" left + ascii (fp) : "./hrtfs/elev-25/R-25e100a.dat" right [ 13, 53 ] = ascii (fp) : "./hrtfs/elev-25/L-25e095a.dat" left + ascii (fp) : "./hrtfs/elev-25/R-25e095a.dat" right [ 13, 54 ] = ascii (fp) : "./hrtfs/elev-25/L-25e090a.dat" left + ascii (fp) : "./hrtfs/elev-25/R-25e090a.dat" right [ 13, 55 ] = ascii (fp) : "./hrtfs/elev-25/L-25e085a.dat" left + ascii (fp) : "./hrtfs/elev-25/R-25e085a.dat" right [ 13, 56 ] = ascii (fp) : "./hrtfs/elev-25/L-25e080a.dat" left + ascii (fp) : "./hrtfs/elev-25/R-25e080a.dat" right [ 13, 57 ] = ascii (fp) : "./hrtfs/elev-25/L-25e075a.dat" left + ascii (fp) : "./hrtfs/elev-25/R-25e075a.dat" right [ 13, 58 ] = ascii (fp) : "./hrtfs/elev-25/L-25e070a.dat" left + ascii (fp) : "./hrtfs/elev-25/R-25e070a.dat" right [ 13, 59 ] = ascii (fp) : "./hrtfs/elev-25/L-25e065a.dat" left + ascii (fp) : "./hrtfs/elev-25/R-25e065a.dat" right [ 13, 60 ] = ascii (fp) : "./hrtfs/elev-25/L-25e060a.dat" left + ascii (fp) : "./hrtfs/elev-25/R-25e060a.dat" right [ 13, 61 ] = ascii (fp) : "./hrtfs/elev-25/L-25e055a.dat" left + ascii (fp) : "./hrtfs/elev-25/R-25e055a.dat" right [ 13, 62 ] = ascii (fp) : "./hrtfs/elev-25/L-25e050a.dat" left + ascii (fp) : "./hrtfs/elev-25/R-25e050a.dat" right [ 13, 63 ] = ascii (fp) : "./hrtfs/elev-25/L-25e045a.dat" left + ascii (fp) : "./hrtfs/elev-25/R-25e045a.dat" right [ 13, 64 ] = ascii (fp) : "./hrtfs/elev-25/L-25e040a.dat" left + ascii (fp) : "./hrtfs/elev-25/R-25e040a.dat" right [ 13, 65 ] = ascii (fp) : "./hrtfs/elev-25/L-25e035a.dat" left + ascii (fp) : "./hrtfs/elev-25/R-25e035a.dat" right [ 13, 66 ] = ascii (fp) : "./hrtfs/elev-25/L-25e030a.dat" left + ascii (fp) : "./hrtfs/elev-25/R-25e030a.dat" right [ 13, 67 ] = ascii (fp) : "./hrtfs/elev-25/L-25e025a.dat" left + ascii (fp) : "./hrtfs/elev-25/R-25e025a.dat" right [ 13, 68 ] = ascii (fp) : "./hrtfs/elev-25/L-25e020a.dat" left + ascii (fp) : "./hrtfs/elev-25/R-25e020a.dat" right [ 13, 69 ] = ascii (fp) : "./hrtfs/elev-25/L-25e015a.dat" left + ascii (fp) : "./hrtfs/elev-25/R-25e015a.dat" right [ 13, 70 ] = ascii (fp) : "./hrtfs/elev-25/L-25e010a.dat" left + ascii (fp) : "./hrtfs/elev-25/R-25e010a.dat" right [ 13, 71 ] = ascii (fp) : "./hrtfs/elev-25/L-25e005a.dat" left + ascii (fp) : "./hrtfs/elev-25/R-25e005a.dat" right [ 14, 0 ] = ascii (fp) : "./hrtfs/elev-20/L-20e000a.dat" left + ascii (fp) : "./hrtfs/elev-20/R-20e000a.dat" right [ 14, 1 ] = ascii (fp) : "./hrtfs/elev-20/L-20e355a.dat" left + ascii (fp) : "./hrtfs/elev-20/R-20e355a.dat" right [ 14, 2 ] = ascii (fp) : "./hrtfs/elev-20/L-20e350a.dat" left + ascii (fp) : "./hrtfs/elev-20/R-20e350a.dat" right [ 14, 3 ] = ascii (fp) : "./hrtfs/elev-20/L-20e345a.dat" left + ascii (fp) : "./hrtfs/elev-20/R-20e345a.dat" right [ 14, 4 ] = ascii (fp) : "./hrtfs/elev-20/L-20e340a.dat" left + ascii (fp) : "./hrtfs/elev-20/R-20e340a.dat" right [ 14, 5 ] = ascii (fp) : "./hrtfs/elev-20/L-20e335a.dat" left + ascii (fp) : "./hrtfs/elev-20/R-20e335a.dat" right [ 14, 6 ] = ascii (fp) : "./hrtfs/elev-20/L-20e330a.dat" left + ascii (fp) : "./hrtfs/elev-20/R-20e330a.dat" right [ 14, 7 ] = ascii (fp) : "./hrtfs/elev-20/L-20e325a.dat" left + ascii (fp) : "./hrtfs/elev-20/R-20e325a.dat" right [ 14, 8 ] = ascii (fp) : "./hrtfs/elev-20/L-20e320a.dat" left + ascii (fp) : "./hrtfs/elev-20/R-20e320a.dat" right [ 14, 9 ] = ascii (fp) : "./hrtfs/elev-20/L-20e315a.dat" left + ascii (fp) : "./hrtfs/elev-20/R-20e315a.dat" right [ 14, 10 ] = ascii (fp) : "./hrtfs/elev-20/L-20e310a.dat" left + ascii (fp) : "./hrtfs/elev-20/R-20e310a.dat" right [ 14, 11 ] = ascii (fp) : "./hrtfs/elev-20/L-20e305a.dat" left + ascii (fp) : "./hrtfs/elev-20/R-20e305a.dat" right [ 14, 12 ] = ascii (fp) : "./hrtfs/elev-20/L-20e300a.dat" left + ascii (fp) : "./hrtfs/elev-20/R-20e300a.dat" right [ 14, 13 ] = ascii (fp) : "./hrtfs/elev-20/L-20e295a.dat" left + ascii (fp) : "./hrtfs/elev-20/R-20e295a.dat" right [ 14, 14 ] = ascii (fp) : "./hrtfs/elev-20/L-20e290a.dat" left + ascii (fp) : "./hrtfs/elev-20/R-20e290a.dat" right [ 14, 15 ] = ascii (fp) : "./hrtfs/elev-20/L-20e285a.dat" left + ascii (fp) : "./hrtfs/elev-20/R-20e285a.dat" right [ 14, 16 ] = ascii (fp) : "./hrtfs/elev-20/L-20e280a.dat" left + ascii (fp) : "./hrtfs/elev-20/R-20e280a.dat" right [ 14, 17 ] = ascii (fp) : "./hrtfs/elev-20/L-20e275a.dat" left + ascii (fp) : "./hrtfs/elev-20/R-20e275a.dat" right [ 14, 18 ] = ascii (fp) : "./hrtfs/elev-20/L-20e270a.dat" left + ascii (fp) : "./hrtfs/elev-20/R-20e270a.dat" right [ 14, 19 ] = ascii (fp) : "./hrtfs/elev-20/L-20e265a.dat" left + ascii (fp) : "./hrtfs/elev-20/R-20e265a.dat" right [ 14, 20 ] = ascii (fp) : "./hrtfs/elev-20/L-20e260a.dat" left + ascii (fp) : "./hrtfs/elev-20/R-20e260a.dat" right [ 14, 21 ] = ascii (fp) : "./hrtfs/elev-20/L-20e255a.dat" left + ascii (fp) : "./hrtfs/elev-20/R-20e255a.dat" right [ 14, 22 ] = ascii (fp) : "./hrtfs/elev-20/L-20e250a.dat" left + ascii (fp) : "./hrtfs/elev-20/R-20e250a.dat" right [ 14, 23 ] = ascii (fp) : "./hrtfs/elev-20/L-20e245a.dat" left + ascii (fp) : "./hrtfs/elev-20/R-20e245a.dat" right [ 14, 24 ] = ascii (fp) : "./hrtfs/elev-20/L-20e240a.dat" left + ascii (fp) : "./hrtfs/elev-20/R-20e240a.dat" right [ 14, 25 ] = ascii (fp) : "./hrtfs/elev-20/L-20e235a.dat" left + ascii (fp) : "./hrtfs/elev-20/R-20e235a.dat" right [ 14, 26 ] = ascii (fp) : "./hrtfs/elev-20/L-20e230a.dat" left + ascii (fp) : "./hrtfs/elev-20/R-20e230a.dat" right [ 14, 27 ] = ascii (fp) : "./hrtfs/elev-20/L-20e225a.dat" left + ascii (fp) : "./hrtfs/elev-20/R-20e225a.dat" right [ 14, 28 ] = ascii (fp) : "./hrtfs/elev-20/L-20e220a.dat" left + ascii (fp) : "./hrtfs/elev-20/R-20e220a.dat" right [ 14, 29 ] = ascii (fp) : "./hrtfs/elev-20/L-20e215a.dat" left + ascii (fp) : "./hrtfs/elev-20/R-20e215a.dat" right [ 14, 30 ] = ascii (fp) : "./hrtfs/elev-20/L-20e210a.dat" left + ascii (fp) : "./hrtfs/elev-20/R-20e210a.dat" right [ 14, 31 ] = ascii (fp) : "./hrtfs/elev-20/L-20e205a.dat" left + ascii (fp) : "./hrtfs/elev-20/R-20e205a.dat" right [ 14, 32 ] = ascii (fp) : "./hrtfs/elev-20/L-20e200a.dat" left + ascii (fp) : "./hrtfs/elev-20/R-20e200a.dat" right [ 14, 33 ] = ascii (fp) : "./hrtfs/elev-20/L-20e195a.dat" left + ascii (fp) : "./hrtfs/elev-20/R-20e195a.dat" right [ 14, 34 ] = ascii (fp) : "./hrtfs/elev-20/L-20e190a.dat" left + ascii (fp) : "./hrtfs/elev-20/R-20e190a.dat" right [ 14, 35 ] = ascii (fp) : "./hrtfs/elev-20/L-20e185a.dat" left + ascii (fp) : "./hrtfs/elev-20/R-20e185a.dat" right [ 14, 36 ] = ascii (fp) : "./hrtfs/elev-20/L-20e180a.dat" left + ascii (fp) : "./hrtfs/elev-20/R-20e180a.dat" right [ 14, 37 ] = ascii (fp) : "./hrtfs/elev-20/L-20e175a.dat" left + ascii (fp) : "./hrtfs/elev-20/R-20e175a.dat" right [ 14, 38 ] = ascii (fp) : "./hrtfs/elev-20/L-20e170a.dat" left + ascii (fp) : "./hrtfs/elev-20/R-20e170a.dat" right [ 14, 39 ] = ascii (fp) : "./hrtfs/elev-20/L-20e165a.dat" left + ascii (fp) : "./hrtfs/elev-20/R-20e165a.dat" right [ 14, 40 ] = ascii (fp) : "./hrtfs/elev-20/L-20e160a.dat" left + ascii (fp) : "./hrtfs/elev-20/R-20e160a.dat" right [ 14, 41 ] = ascii (fp) : "./hrtfs/elev-20/L-20e155a.dat" left + ascii (fp) : "./hrtfs/elev-20/R-20e155a.dat" right [ 14, 42 ] = ascii (fp) : "./hrtfs/elev-20/L-20e150a.dat" left + ascii (fp) : "./hrtfs/elev-20/R-20e150a.dat" right [ 14, 43 ] = ascii (fp) : "./hrtfs/elev-20/L-20e145a.dat" left + ascii (fp) : "./hrtfs/elev-20/R-20e145a.dat" right [ 14, 44 ] = ascii (fp) : "./hrtfs/elev-20/L-20e140a.dat" left + ascii (fp) : "./hrtfs/elev-20/R-20e140a.dat" right [ 14, 45 ] = ascii (fp) : "./hrtfs/elev-20/L-20e135a.dat" left + ascii (fp) : "./hrtfs/elev-20/R-20e135a.dat" right [ 14, 46 ] = ascii (fp) : "./hrtfs/elev-20/L-20e130a.dat" left + ascii (fp) : "./hrtfs/elev-20/R-20e130a.dat" right [ 14, 47 ] = ascii (fp) : "./hrtfs/elev-20/L-20e125a.dat" left + ascii (fp) : "./hrtfs/elev-20/R-20e125a.dat" right [ 14, 48 ] = ascii (fp) : "./hrtfs/elev-20/L-20e120a.dat" left + ascii (fp) : "./hrtfs/elev-20/R-20e120a.dat" right [ 14, 49 ] = ascii (fp) : "./hrtfs/elev-20/L-20e115a.dat" left + ascii (fp) : "./hrtfs/elev-20/R-20e115a.dat" right [ 14, 50 ] = ascii (fp) : "./hrtfs/elev-20/L-20e110a.dat" left + ascii (fp) : "./hrtfs/elev-20/R-20e110a.dat" right [ 14, 51 ] = ascii (fp) : "./hrtfs/elev-20/L-20e105a.dat" left + ascii (fp) : "./hrtfs/elev-20/R-20e105a.dat" right [ 14, 52 ] = ascii (fp) : "./hrtfs/elev-20/L-20e100a.dat" left + ascii (fp) : "./hrtfs/elev-20/R-20e100a.dat" right [ 14, 53 ] = ascii (fp) : "./hrtfs/elev-20/L-20e095a.dat" left + ascii (fp) : "./hrtfs/elev-20/R-20e095a.dat" right [ 14, 54 ] = ascii (fp) : "./hrtfs/elev-20/L-20e090a.dat" left + ascii (fp) : "./hrtfs/elev-20/R-20e090a.dat" right [ 14, 55 ] = ascii (fp) : "./hrtfs/elev-20/L-20e085a.dat" left + ascii (fp) : "./hrtfs/elev-20/R-20e085a.dat" right [ 14, 56 ] = ascii (fp) : "./hrtfs/elev-20/L-20e080a.dat" left + ascii (fp) : "./hrtfs/elev-20/R-20e080a.dat" right [ 14, 57 ] = ascii (fp) : "./hrtfs/elev-20/L-20e075a.dat" left + ascii (fp) : "./hrtfs/elev-20/R-20e075a.dat" right [ 14, 58 ] = ascii (fp) : "./hrtfs/elev-20/L-20e070a.dat" left + ascii (fp) : "./hrtfs/elev-20/R-20e070a.dat" right [ 14, 59 ] = ascii (fp) : "./hrtfs/elev-20/L-20e065a.dat" left + ascii (fp) : "./hrtfs/elev-20/R-20e065a.dat" right [ 14, 60 ] = ascii (fp) : "./hrtfs/elev-20/L-20e060a.dat" left + ascii (fp) : "./hrtfs/elev-20/R-20e060a.dat" right [ 14, 61 ] = ascii (fp) : "./hrtfs/elev-20/L-20e055a.dat" left + ascii (fp) : "./hrtfs/elev-20/R-20e055a.dat" right [ 14, 62 ] = ascii (fp) : "./hrtfs/elev-20/L-20e050a.dat" left + ascii (fp) : "./hrtfs/elev-20/R-20e050a.dat" right [ 14, 63 ] = ascii (fp) : "./hrtfs/elev-20/L-20e045a.dat" left + ascii (fp) : "./hrtfs/elev-20/R-20e045a.dat" right [ 14, 64 ] = ascii (fp) : "./hrtfs/elev-20/L-20e040a.dat" left + ascii (fp) : "./hrtfs/elev-20/R-20e040a.dat" right [ 14, 65 ] = ascii (fp) : "./hrtfs/elev-20/L-20e035a.dat" left + ascii (fp) : "./hrtfs/elev-20/R-20e035a.dat" right [ 14, 66 ] = ascii (fp) : "./hrtfs/elev-20/L-20e030a.dat" left + ascii (fp) : "./hrtfs/elev-20/R-20e030a.dat" right [ 14, 67 ] = ascii (fp) : "./hrtfs/elev-20/L-20e025a.dat" left + ascii (fp) : "./hrtfs/elev-20/R-20e025a.dat" right [ 14, 68 ] = ascii (fp) : "./hrtfs/elev-20/L-20e020a.dat" left + ascii (fp) : "./hrtfs/elev-20/R-20e020a.dat" right [ 14, 69 ] = ascii (fp) : "./hrtfs/elev-20/L-20e015a.dat" left + ascii (fp) : "./hrtfs/elev-20/R-20e015a.dat" right [ 14, 70 ] = ascii (fp) : "./hrtfs/elev-20/L-20e010a.dat" left + ascii (fp) : "./hrtfs/elev-20/R-20e010a.dat" right [ 14, 71 ] = ascii (fp) : "./hrtfs/elev-20/L-20e005a.dat" left + ascii (fp) : "./hrtfs/elev-20/R-20e005a.dat" right [ 15, 0 ] = ascii (fp) : "./hrtfs/elev-15/L-15e000a.dat" left + ascii (fp) : "./hrtfs/elev-15/R-15e000a.dat" right [ 15, 1 ] = ascii (fp) : "./hrtfs/elev-15/L-15e355a.dat" left + ascii (fp) : "./hrtfs/elev-15/R-15e355a.dat" right [ 15, 2 ] = ascii (fp) : "./hrtfs/elev-15/L-15e350a.dat" left + ascii (fp) : "./hrtfs/elev-15/R-15e350a.dat" right [ 15, 3 ] = ascii (fp) : "./hrtfs/elev-15/L-15e345a.dat" left + ascii (fp) : "./hrtfs/elev-15/R-15e345a.dat" right [ 15, 4 ] = ascii (fp) : "./hrtfs/elev-15/L-15e340a.dat" left + ascii (fp) : "./hrtfs/elev-15/R-15e340a.dat" right [ 15, 5 ] = ascii (fp) : "./hrtfs/elev-15/L-15e335a.dat" left + ascii (fp) : "./hrtfs/elev-15/R-15e335a.dat" right [ 15, 6 ] = ascii (fp) : "./hrtfs/elev-15/L-15e330a.dat" left + ascii (fp) : "./hrtfs/elev-15/R-15e330a.dat" right [ 15, 7 ] = ascii (fp) : "./hrtfs/elev-15/L-15e325a.dat" left + ascii (fp) : "./hrtfs/elev-15/R-15e325a.dat" right [ 15, 8 ] = ascii (fp) : "./hrtfs/elev-15/L-15e320a.dat" left + ascii (fp) : "./hrtfs/elev-15/R-15e320a.dat" right [ 15, 9 ] = ascii (fp) : "./hrtfs/elev-15/L-15e315a.dat" left + ascii (fp) : "./hrtfs/elev-15/R-15e315a.dat" right [ 15, 10 ] = ascii (fp) : "./hrtfs/elev-15/L-15e310a.dat" left + ascii (fp) : "./hrtfs/elev-15/R-15e310a.dat" right [ 15, 11 ] = ascii (fp) : "./hrtfs/elev-15/L-15e305a.dat" left + ascii (fp) : "./hrtfs/elev-15/R-15e305a.dat" right [ 15, 12 ] = ascii (fp) : "./hrtfs/elev-15/L-15e300a.dat" left + ascii (fp) : "./hrtfs/elev-15/R-15e300a.dat" right [ 15, 13 ] = ascii (fp) : "./hrtfs/elev-15/L-15e295a.dat" left + ascii (fp) : "./hrtfs/elev-15/R-15e295a.dat" right [ 15, 14 ] = ascii (fp) : "./hrtfs/elev-15/L-15e290a.dat" left + ascii (fp) : "./hrtfs/elev-15/R-15e290a.dat" right [ 15, 15 ] = ascii (fp) : "./hrtfs/elev-15/L-15e285a.dat" left + ascii (fp) : "./hrtfs/elev-15/R-15e285a.dat" right [ 15, 16 ] = ascii (fp) : "./hrtfs/elev-15/L-15e280a.dat" left + ascii (fp) : "./hrtfs/elev-15/R-15e280a.dat" right [ 15, 17 ] = ascii (fp) : "./hrtfs/elev-15/L-15e275a.dat" left + ascii (fp) : "./hrtfs/elev-15/R-15e275a.dat" right [ 15, 18 ] = ascii (fp) : "./hrtfs/elev-15/L-15e270a.dat" left + ascii (fp) : "./hrtfs/elev-15/R-15e270a.dat" right [ 15, 19 ] = ascii (fp) : "./hrtfs/elev-15/L-15e265a.dat" left + ascii (fp) : "./hrtfs/elev-15/R-15e265a.dat" right [ 15, 20 ] = ascii (fp) : "./hrtfs/elev-15/L-15e260a.dat" left + ascii (fp) : "./hrtfs/elev-15/R-15e260a.dat" right [ 15, 21 ] = ascii (fp) : "./hrtfs/elev-15/L-15e255a.dat" left + ascii (fp) : "./hrtfs/elev-15/R-15e255a.dat" right [ 15, 22 ] = ascii (fp) : "./hrtfs/elev-15/L-15e250a.dat" left + ascii (fp) : "./hrtfs/elev-15/R-15e250a.dat" right [ 15, 23 ] = ascii (fp) : "./hrtfs/elev-15/L-15e245a.dat" left + ascii (fp) : "./hrtfs/elev-15/R-15e245a.dat" right [ 15, 24 ] = ascii (fp) : "./hrtfs/elev-15/L-15e240a.dat" left + ascii (fp) : "./hrtfs/elev-15/R-15e240a.dat" right [ 15, 25 ] = ascii (fp) : "./hrtfs/elev-15/L-15e235a.dat" left + ascii (fp) : "./hrtfs/elev-15/R-15e235a.dat" right [ 15, 26 ] = ascii (fp) : "./hrtfs/elev-15/L-15e230a.dat" left + ascii (fp) : "./hrtfs/elev-15/R-15e230a.dat" right [ 15, 27 ] = ascii (fp) : "./hrtfs/elev-15/L-15e225a.dat" left + ascii (fp) : "./hrtfs/elev-15/R-15e225a.dat" right [ 15, 28 ] = ascii (fp) : "./hrtfs/elev-15/L-15e220a.dat" left + ascii (fp) : "./hrtfs/elev-15/R-15e220a.dat" right [ 15, 29 ] = ascii (fp) : "./hrtfs/elev-15/L-15e215a.dat" left + ascii (fp) : "./hrtfs/elev-15/R-15e215a.dat" right [ 15, 30 ] = ascii (fp) : "./hrtfs/elev-15/L-15e210a.dat" left + ascii (fp) : "./hrtfs/elev-15/R-15e210a.dat" right [ 15, 31 ] = ascii (fp) : "./hrtfs/elev-15/L-15e205a.dat" left + ascii (fp) : "./hrtfs/elev-15/R-15e205a.dat" right [ 15, 32 ] = ascii (fp) : "./hrtfs/elev-15/L-15e200a.dat" left + ascii (fp) : "./hrtfs/elev-15/R-15e200a.dat" right [ 15, 33 ] = ascii (fp) : "./hrtfs/elev-15/L-15e195a.dat" left + ascii (fp) : "./hrtfs/elev-15/R-15e195a.dat" right [ 15, 34 ] = ascii (fp) : "./hrtfs/elev-15/L-15e190a.dat" left + ascii (fp) : "./hrtfs/elev-15/R-15e190a.dat" right [ 15, 35 ] = ascii (fp) : "./hrtfs/elev-15/L-15e185a.dat" left + ascii (fp) : "./hrtfs/elev-15/R-15e185a.dat" right [ 15, 36 ] = ascii (fp) : "./hrtfs/elev-15/L-15e180a.dat" left + ascii (fp) : "./hrtfs/elev-15/R-15e180a.dat" right [ 15, 37 ] = ascii (fp) : "./hrtfs/elev-15/L-15e175a.dat" left + ascii (fp) : "./hrtfs/elev-15/R-15e175a.dat" right [ 15, 38 ] = ascii (fp) : "./hrtfs/elev-15/L-15e170a.dat" left + ascii (fp) : "./hrtfs/elev-15/R-15e170a.dat" right [ 15, 39 ] = ascii (fp) : "./hrtfs/elev-15/L-15e165a.dat" left + ascii (fp) : "./hrtfs/elev-15/R-15e165a.dat" right [ 15, 40 ] = ascii (fp) : "./hrtfs/elev-15/L-15e160a.dat" left + ascii (fp) : "./hrtfs/elev-15/R-15e160a.dat" right [ 15, 41 ] = ascii (fp) : "./hrtfs/elev-15/L-15e155a.dat" left + ascii (fp) : "./hrtfs/elev-15/R-15e155a.dat" right [ 15, 42 ] = ascii (fp) : "./hrtfs/elev-15/L-15e150a.dat" left + ascii (fp) : "./hrtfs/elev-15/R-15e150a.dat" right [ 15, 43 ] = ascii (fp) : "./hrtfs/elev-15/L-15e145a.dat" left + ascii (fp) : "./hrtfs/elev-15/R-15e145a.dat" right [ 15, 44 ] = ascii (fp) : "./hrtfs/elev-15/L-15e140a.dat" left + ascii (fp) : "./hrtfs/elev-15/R-15e140a.dat" right [ 15, 45 ] = ascii (fp) : "./hrtfs/elev-15/L-15e135a.dat" left + ascii (fp) : "./hrtfs/elev-15/R-15e135a.dat" right [ 15, 46 ] = ascii (fp) : "./hrtfs/elev-15/L-15e130a.dat" left + ascii (fp) : "./hrtfs/elev-15/R-15e130a.dat" right [ 15, 47 ] = ascii (fp) : "./hrtfs/elev-15/L-15e125a.dat" left + ascii (fp) : "./hrtfs/elev-15/R-15e125a.dat" right [ 15, 48 ] = ascii (fp) : "./hrtfs/elev-15/L-15e120a.dat" left + ascii (fp) : "./hrtfs/elev-15/R-15e120a.dat" right [ 15, 49 ] = ascii (fp) : "./hrtfs/elev-15/L-15e115a.dat" left + ascii (fp) : "./hrtfs/elev-15/R-15e115a.dat" right [ 15, 50 ] = ascii (fp) : "./hrtfs/elev-15/L-15e110a.dat" left + ascii (fp) : "./hrtfs/elev-15/R-15e110a.dat" right [ 15, 51 ] = ascii (fp) : "./hrtfs/elev-15/L-15e105a.dat" left + ascii (fp) : "./hrtfs/elev-15/R-15e105a.dat" right [ 15, 52 ] = ascii (fp) : "./hrtfs/elev-15/L-15e100a.dat" left + ascii (fp) : "./hrtfs/elev-15/R-15e100a.dat" right [ 15, 53 ] = ascii (fp) : "./hrtfs/elev-15/L-15e095a.dat" left + ascii (fp) : "./hrtfs/elev-15/R-15e095a.dat" right [ 15, 54 ] = ascii (fp) : "./hrtfs/elev-15/L-15e090a.dat" left + ascii (fp) : "./hrtfs/elev-15/R-15e090a.dat" right [ 15, 55 ] = ascii (fp) : "./hrtfs/elev-15/L-15e085a.dat" left + ascii (fp) : "./hrtfs/elev-15/R-15e085a.dat" right [ 15, 56 ] = ascii (fp) : "./hrtfs/elev-15/L-15e080a.dat" left + ascii (fp) : "./hrtfs/elev-15/R-15e080a.dat" right [ 15, 57 ] = ascii (fp) : "./hrtfs/elev-15/L-15e075a.dat" left + ascii (fp) : "./hrtfs/elev-15/R-15e075a.dat" right [ 15, 58 ] = ascii (fp) : "./hrtfs/elev-15/L-15e070a.dat" left + ascii (fp) : "./hrtfs/elev-15/R-15e070a.dat" right [ 15, 59 ] = ascii (fp) : "./hrtfs/elev-15/L-15e065a.dat" left + ascii (fp) : "./hrtfs/elev-15/R-15e065a.dat" right [ 15, 60 ] = ascii (fp) : "./hrtfs/elev-15/L-15e060a.dat" left + ascii (fp) : "./hrtfs/elev-15/R-15e060a.dat" right [ 15, 61 ] = ascii (fp) : "./hrtfs/elev-15/L-15e055a.dat" left + ascii (fp) : "./hrtfs/elev-15/R-15e055a.dat" right [ 15, 62 ] = ascii (fp) : "./hrtfs/elev-15/L-15e050a.dat" left + ascii (fp) : "./hrtfs/elev-15/R-15e050a.dat" right [ 15, 63 ] = ascii (fp) : "./hrtfs/elev-15/L-15e045a.dat" left + ascii (fp) : "./hrtfs/elev-15/R-15e045a.dat" right [ 15, 64 ] = ascii (fp) : "./hrtfs/elev-15/L-15e040a.dat" left + ascii (fp) : "./hrtfs/elev-15/R-15e040a.dat" right [ 15, 65 ] = ascii (fp) : "./hrtfs/elev-15/L-15e035a.dat" left + ascii (fp) : "./hrtfs/elev-15/R-15e035a.dat" right [ 15, 66 ] = ascii (fp) : "./hrtfs/elev-15/L-15e030a.dat" left + ascii (fp) : "./hrtfs/elev-15/R-15e030a.dat" right [ 15, 67 ] = ascii (fp) : "./hrtfs/elev-15/L-15e025a.dat" left + ascii (fp) : "./hrtfs/elev-15/R-15e025a.dat" right [ 15, 68 ] = ascii (fp) : "./hrtfs/elev-15/L-15e020a.dat" left + ascii (fp) : "./hrtfs/elev-15/R-15e020a.dat" right [ 15, 69 ] = ascii (fp) : "./hrtfs/elev-15/L-15e015a.dat" left + ascii (fp) : "./hrtfs/elev-15/R-15e015a.dat" right [ 15, 70 ] = ascii (fp) : "./hrtfs/elev-15/L-15e010a.dat" left + ascii (fp) : "./hrtfs/elev-15/R-15e010a.dat" right [ 15, 71 ] = ascii (fp) : "./hrtfs/elev-15/L-15e005a.dat" left + ascii (fp) : "./hrtfs/elev-15/R-15e005a.dat" right [ 16, 0 ] = ascii (fp) : "./hrtfs/elev-10/L-10e000a.dat" left + ascii (fp) : "./hrtfs/elev-10/R-10e000a.dat" right [ 16, 1 ] = ascii (fp) : "./hrtfs/elev-10/L-10e355a.dat" left + ascii (fp) : "./hrtfs/elev-10/R-10e355a.dat" right [ 16, 2 ] = ascii (fp) : "./hrtfs/elev-10/L-10e350a.dat" left + ascii (fp) : "./hrtfs/elev-10/R-10e350a.dat" right [ 16, 3 ] = ascii (fp) : "./hrtfs/elev-10/L-10e345a.dat" left + ascii (fp) : "./hrtfs/elev-10/R-10e345a.dat" right [ 16, 4 ] = ascii (fp) : "./hrtfs/elev-10/L-10e340a.dat" left + ascii (fp) : "./hrtfs/elev-10/R-10e340a.dat" right [ 16, 5 ] = ascii (fp) : "./hrtfs/elev-10/L-10e335a.dat" left + ascii (fp) : "./hrtfs/elev-10/R-10e335a.dat" right [ 16, 6 ] = ascii (fp) : "./hrtfs/elev-10/L-10e330a.dat" left + ascii (fp) : "./hrtfs/elev-10/R-10e330a.dat" right [ 16, 7 ] = ascii (fp) : "./hrtfs/elev-10/L-10e325a.dat" left + ascii (fp) : "./hrtfs/elev-10/R-10e325a.dat" right [ 16, 8 ] = ascii (fp) : "./hrtfs/elev-10/L-10e320a.dat" left + ascii (fp) : "./hrtfs/elev-10/R-10e320a.dat" right [ 16, 9 ] = ascii (fp) : "./hrtfs/elev-10/L-10e315a.dat" left + ascii (fp) : "./hrtfs/elev-10/R-10e315a.dat" right [ 16, 10 ] = ascii (fp) : "./hrtfs/elev-10/L-10e310a.dat" left + ascii (fp) : "./hrtfs/elev-10/R-10e310a.dat" right [ 16, 11 ] = ascii (fp) : "./hrtfs/elev-10/L-10e305a.dat" left + ascii (fp) : "./hrtfs/elev-10/R-10e305a.dat" right [ 16, 12 ] = ascii (fp) : "./hrtfs/elev-10/L-10e300a.dat" left + ascii (fp) : "./hrtfs/elev-10/R-10e300a.dat" right [ 16, 13 ] = ascii (fp) : "./hrtfs/elev-10/L-10e295a.dat" left + ascii (fp) : "./hrtfs/elev-10/R-10e295a.dat" right [ 16, 14 ] = ascii (fp) : "./hrtfs/elev-10/L-10e290a.dat" left + ascii (fp) : "./hrtfs/elev-10/R-10e290a.dat" right [ 16, 15 ] = ascii (fp) : "./hrtfs/elev-10/L-10e285a.dat" left + ascii (fp) : "./hrtfs/elev-10/R-10e285a.dat" right [ 16, 16 ] = ascii (fp) : "./hrtfs/elev-10/L-10e280a.dat" left + ascii (fp) : "./hrtfs/elev-10/R-10e280a.dat" right [ 16, 17 ] = ascii (fp) : "./hrtfs/elev-10/L-10e275a.dat" left + ascii (fp) : "./hrtfs/elev-10/R-10e275a.dat" right [ 16, 18 ] = ascii (fp) : "./hrtfs/elev-10/L-10e270a.dat" left + ascii (fp) : "./hrtfs/elev-10/R-10e270a.dat" right [ 16, 19 ] = ascii (fp) : "./hrtfs/elev-10/L-10e265a.dat" left + ascii (fp) : "./hrtfs/elev-10/R-10e265a.dat" right [ 16, 20 ] = ascii (fp) : "./hrtfs/elev-10/L-10e260a.dat" left + ascii (fp) : "./hrtfs/elev-10/R-10e260a.dat" right [ 16, 21 ] = ascii (fp) : "./hrtfs/elev-10/L-10e255a.dat" left + ascii (fp) : "./hrtfs/elev-10/R-10e255a.dat" right [ 16, 22 ] = ascii (fp) : "./hrtfs/elev-10/L-10e250a.dat" left + ascii (fp) : "./hrtfs/elev-10/R-10e250a.dat" right [ 16, 23 ] = ascii (fp) : "./hrtfs/elev-10/L-10e245a.dat" left + ascii (fp) : "./hrtfs/elev-10/R-10e245a.dat" right [ 16, 24 ] = ascii (fp) : "./hrtfs/elev-10/L-10e240a.dat" left + ascii (fp) : "./hrtfs/elev-10/R-10e240a.dat" right [ 16, 25 ] = ascii (fp) : "./hrtfs/elev-10/L-10e235a.dat" left + ascii (fp) : "./hrtfs/elev-10/R-10e235a.dat" right [ 16, 26 ] = ascii (fp) : "./hrtfs/elev-10/L-10e230a.dat" left + ascii (fp) : "./hrtfs/elev-10/R-10e230a.dat" right [ 16, 27 ] = ascii (fp) : "./hrtfs/elev-10/L-10e225a.dat" left + ascii (fp) : "./hrtfs/elev-10/R-10e225a.dat" right [ 16, 28 ] = ascii (fp) : "./hrtfs/elev-10/L-10e220a.dat" left + ascii (fp) : "./hrtfs/elev-10/R-10e220a.dat" right [ 16, 29 ] = ascii (fp) : "./hrtfs/elev-10/L-10e215a.dat" left + ascii (fp) : "./hrtfs/elev-10/R-10e215a.dat" right [ 16, 30 ] = ascii (fp) : "./hrtfs/elev-10/L-10e210a.dat" left + ascii (fp) : "./hrtfs/elev-10/R-10e210a.dat" right [ 16, 31 ] = ascii (fp) : "./hrtfs/elev-10/L-10e205a.dat" left + ascii (fp) : "./hrtfs/elev-10/R-10e205a.dat" right [ 16, 32 ] = ascii (fp) : "./hrtfs/elev-10/L-10e200a.dat" left + ascii (fp) : "./hrtfs/elev-10/R-10e200a.dat" right [ 16, 33 ] = ascii (fp) : "./hrtfs/elev-10/L-10e195a.dat" left + ascii (fp) : "./hrtfs/elev-10/R-10e195a.dat" right [ 16, 34 ] = ascii (fp) : "./hrtfs/elev-10/L-10e190a.dat" left + ascii (fp) : "./hrtfs/elev-10/R-10e190a.dat" right [ 16, 35 ] = ascii (fp) : "./hrtfs/elev-10/L-10e185a.dat" left + ascii (fp) : "./hrtfs/elev-10/R-10e185a.dat" right [ 16, 36 ] = ascii (fp) : "./hrtfs/elev-10/L-10e180a.dat" left + ascii (fp) : "./hrtfs/elev-10/R-10e180a.dat" right [ 16, 37 ] = ascii (fp) : "./hrtfs/elev-10/L-10e175a.dat" left + ascii (fp) : "./hrtfs/elev-10/R-10e175a.dat" right [ 16, 38 ] = ascii (fp) : "./hrtfs/elev-10/L-10e170a.dat" left + ascii (fp) : "./hrtfs/elev-10/R-10e170a.dat" right [ 16, 39 ] = ascii (fp) : "./hrtfs/elev-10/L-10e165a.dat" left + ascii (fp) : "./hrtfs/elev-10/R-10e165a.dat" right [ 16, 40 ] = ascii (fp) : "./hrtfs/elev-10/L-10e160a.dat" left + ascii (fp) : "./hrtfs/elev-10/R-10e160a.dat" right [ 16, 41 ] = ascii (fp) : "./hrtfs/elev-10/L-10e155a.dat" left + ascii (fp) : "./hrtfs/elev-10/R-10e155a.dat" right [ 16, 42 ] = ascii (fp) : "./hrtfs/elev-10/L-10e150a.dat" left + ascii (fp) : "./hrtfs/elev-10/R-10e150a.dat" right [ 16, 43 ] = ascii (fp) : "./hrtfs/elev-10/L-10e145a.dat" left + ascii (fp) : "./hrtfs/elev-10/R-10e145a.dat" right [ 16, 44 ] = ascii (fp) : "./hrtfs/elev-10/L-10e140a.dat" left + ascii (fp) : "./hrtfs/elev-10/R-10e140a.dat" right [ 16, 45 ] = ascii (fp) : "./hrtfs/elev-10/L-10e135a.dat" left + ascii (fp) : "./hrtfs/elev-10/R-10e135a.dat" right [ 16, 46 ] = ascii (fp) : "./hrtfs/elev-10/L-10e130a.dat" left + ascii (fp) : "./hrtfs/elev-10/R-10e130a.dat" right [ 16, 47 ] = ascii (fp) : "./hrtfs/elev-10/L-10e125a.dat" left + ascii (fp) : "./hrtfs/elev-10/R-10e125a.dat" right [ 16, 48 ] = ascii (fp) : "./hrtfs/elev-10/L-10e120a.dat" left + ascii (fp) : "./hrtfs/elev-10/R-10e120a.dat" right [ 16, 49 ] = ascii (fp) : "./hrtfs/elev-10/L-10e115a.dat" left + ascii (fp) : "./hrtfs/elev-10/R-10e115a.dat" right [ 16, 50 ] = ascii (fp) : "./hrtfs/elev-10/L-10e110a.dat" left + ascii (fp) : "./hrtfs/elev-10/R-10e110a.dat" right [ 16, 51 ] = ascii (fp) : "./hrtfs/elev-10/L-10e105a.dat" left + ascii (fp) : "./hrtfs/elev-10/R-10e105a.dat" right [ 16, 52 ] = ascii (fp) : "./hrtfs/elev-10/L-10e100a.dat" left + ascii (fp) : "./hrtfs/elev-10/R-10e100a.dat" right [ 16, 53 ] = ascii (fp) : "./hrtfs/elev-10/L-10e095a.dat" left + ascii (fp) : "./hrtfs/elev-10/R-10e095a.dat" right [ 16, 54 ] = ascii (fp) : "./hrtfs/elev-10/L-10e090a.dat" left + ascii (fp) : "./hrtfs/elev-10/R-10e090a.dat" right [ 16, 55 ] = ascii (fp) : "./hrtfs/elev-10/L-10e085a.dat" left + ascii (fp) : "./hrtfs/elev-10/R-10e085a.dat" right [ 16, 56 ] = ascii (fp) : "./hrtfs/elev-10/L-10e080a.dat" left + ascii (fp) : "./hrtfs/elev-10/R-10e080a.dat" right [ 16, 57 ] = ascii (fp) : "./hrtfs/elev-10/L-10e075a.dat" left + ascii (fp) : "./hrtfs/elev-10/R-10e075a.dat" right [ 16, 58 ] = ascii (fp) : "./hrtfs/elev-10/L-10e070a.dat" left + ascii (fp) : "./hrtfs/elev-10/R-10e070a.dat" right [ 16, 59 ] = ascii (fp) : "./hrtfs/elev-10/L-10e065a.dat" left + ascii (fp) : "./hrtfs/elev-10/R-10e065a.dat" right [ 16, 60 ] = ascii (fp) : "./hrtfs/elev-10/L-10e060a.dat" left + ascii (fp) : "./hrtfs/elev-10/R-10e060a.dat" right [ 16, 61 ] = ascii (fp) : "./hrtfs/elev-10/L-10e055a.dat" left + ascii (fp) : "./hrtfs/elev-10/R-10e055a.dat" right [ 16, 62 ] = ascii (fp) : "./hrtfs/elev-10/L-10e050a.dat" left + ascii (fp) : "./hrtfs/elev-10/R-10e050a.dat" right [ 16, 63 ] = ascii (fp) : "./hrtfs/elev-10/L-10e045a.dat" left + ascii (fp) : "./hrtfs/elev-10/R-10e045a.dat" right [ 16, 64 ] = ascii (fp) : "./hrtfs/elev-10/L-10e040a.dat" left + ascii (fp) : "./hrtfs/elev-10/R-10e040a.dat" right [ 16, 65 ] = ascii (fp) : "./hrtfs/elev-10/L-10e035a.dat" left + ascii (fp) : "./hrtfs/elev-10/R-10e035a.dat" right [ 16, 66 ] = ascii (fp) : "./hrtfs/elev-10/L-10e030a.dat" left + ascii (fp) : "./hrtfs/elev-10/R-10e030a.dat" right [ 16, 67 ] = ascii (fp) : "./hrtfs/elev-10/L-10e025a.dat" left + ascii (fp) : "./hrtfs/elev-10/R-10e025a.dat" right [ 16, 68 ] = ascii (fp) : "./hrtfs/elev-10/L-10e020a.dat" left + ascii (fp) : "./hrtfs/elev-10/R-10e020a.dat" right [ 16, 69 ] = ascii (fp) : "./hrtfs/elev-10/L-10e015a.dat" left + ascii (fp) : "./hrtfs/elev-10/R-10e015a.dat" right [ 16, 70 ] = ascii (fp) : "./hrtfs/elev-10/L-10e010a.dat" left + ascii (fp) : "./hrtfs/elev-10/R-10e010a.dat" right [ 16, 71 ] = ascii (fp) : "./hrtfs/elev-10/L-10e005a.dat" left + ascii (fp) : "./hrtfs/elev-10/R-10e005a.dat" right [ 17, 0 ] = ascii (fp) : "./hrtfs/elev-5/L-5e000a.dat" left + ascii (fp) : "./hrtfs/elev-5/R-5e000a.dat" right [ 17, 1 ] = ascii (fp) : "./hrtfs/elev-5/L-5e355a.dat" left + ascii (fp) : "./hrtfs/elev-5/R-5e355a.dat" right [ 17, 2 ] = ascii (fp) : "./hrtfs/elev-5/L-5e350a.dat" left + ascii (fp) : "./hrtfs/elev-5/R-5e350a.dat" right [ 17, 3 ] = ascii (fp) : "./hrtfs/elev-5/L-5e345a.dat" left + ascii (fp) : "./hrtfs/elev-5/R-5e345a.dat" right [ 17, 4 ] = ascii (fp) : "./hrtfs/elev-5/L-5e340a.dat" left + ascii (fp) : "./hrtfs/elev-5/R-5e340a.dat" right [ 17, 5 ] = ascii (fp) : "./hrtfs/elev-5/L-5e335a.dat" left + ascii (fp) : "./hrtfs/elev-5/R-5e335a.dat" right [ 17, 6 ] = ascii (fp) : "./hrtfs/elev-5/L-5e330a.dat" left + ascii (fp) : "./hrtfs/elev-5/R-5e330a.dat" right [ 17, 7 ] = ascii (fp) : "./hrtfs/elev-5/L-5e325a.dat" left + ascii (fp) : "./hrtfs/elev-5/R-5e325a.dat" right [ 17, 8 ] = ascii (fp) : "./hrtfs/elev-5/L-5e320a.dat" left + ascii (fp) : "./hrtfs/elev-5/R-5e320a.dat" right [ 17, 9 ] = ascii (fp) : "./hrtfs/elev-5/L-5e315a.dat" left + ascii (fp) : "./hrtfs/elev-5/R-5e315a.dat" right [ 17, 10 ] = ascii (fp) : "./hrtfs/elev-5/L-5e310a.dat" left + ascii (fp) : "./hrtfs/elev-5/R-5e310a.dat" right [ 17, 11 ] = ascii (fp) : "./hrtfs/elev-5/L-5e305a.dat" left + ascii (fp) : "./hrtfs/elev-5/R-5e305a.dat" right [ 17, 12 ] = ascii (fp) : "./hrtfs/elev-5/L-5e300a.dat" left + ascii (fp) : "./hrtfs/elev-5/R-5e300a.dat" right [ 17, 13 ] = ascii (fp) : "./hrtfs/elev-5/L-5e295a.dat" left + ascii (fp) : "./hrtfs/elev-5/R-5e295a.dat" right [ 17, 14 ] = ascii (fp) : "./hrtfs/elev-5/L-5e290a.dat" left + ascii (fp) : "./hrtfs/elev-5/R-5e290a.dat" right [ 17, 15 ] = ascii (fp) : "./hrtfs/elev-5/L-5e285a.dat" left + ascii (fp) : "./hrtfs/elev-5/R-5e285a.dat" right [ 17, 16 ] = ascii (fp) : "./hrtfs/elev-5/L-5e280a.dat" left + ascii (fp) : "./hrtfs/elev-5/R-5e280a.dat" right [ 17, 17 ] = ascii (fp) : "./hrtfs/elev-5/L-5e275a.dat" left + ascii (fp) : "./hrtfs/elev-5/R-5e275a.dat" right [ 17, 18 ] = ascii (fp) : "./hrtfs/elev-5/L-5e270a.dat" left + ascii (fp) : "./hrtfs/elev-5/R-5e270a.dat" right [ 17, 19 ] = ascii (fp) : "./hrtfs/elev-5/L-5e265a.dat" left + ascii (fp) : "./hrtfs/elev-5/R-5e265a.dat" right [ 17, 20 ] = ascii (fp) : "./hrtfs/elev-5/L-5e260a.dat" left + ascii (fp) : "./hrtfs/elev-5/R-5e260a.dat" right [ 17, 21 ] = ascii (fp) : "./hrtfs/elev-5/L-5e255a.dat" left + ascii (fp) : "./hrtfs/elev-5/R-5e255a.dat" right [ 17, 22 ] = ascii (fp) : "./hrtfs/elev-5/L-5e250a.dat" left + ascii (fp) : "./hrtfs/elev-5/R-5e250a.dat" right [ 17, 23 ] = ascii (fp) : "./hrtfs/elev-5/L-5e245a.dat" left + ascii (fp) : "./hrtfs/elev-5/R-5e245a.dat" right [ 17, 24 ] = ascii (fp) : "./hrtfs/elev-5/L-5e240a.dat" left + ascii (fp) : "./hrtfs/elev-5/R-5e240a.dat" right [ 17, 25 ] = ascii (fp) : "./hrtfs/elev-5/L-5e235a.dat" left + ascii (fp) : "./hrtfs/elev-5/R-5e235a.dat" right [ 17, 26 ] = ascii (fp) : "./hrtfs/elev-5/L-5e230a.dat" left + ascii (fp) : "./hrtfs/elev-5/R-5e230a.dat" right [ 17, 27 ] = ascii (fp) : "./hrtfs/elev-5/L-5e225a.dat" left + ascii (fp) : "./hrtfs/elev-5/R-5e225a.dat" right [ 17, 28 ] = ascii (fp) : "./hrtfs/elev-5/L-5e220a.dat" left + ascii (fp) : "./hrtfs/elev-5/R-5e220a.dat" right [ 17, 29 ] = ascii (fp) : "./hrtfs/elev-5/L-5e215a.dat" left + ascii (fp) : "./hrtfs/elev-5/R-5e215a.dat" right [ 17, 30 ] = ascii (fp) : "./hrtfs/elev-5/L-5e210a.dat" left + ascii (fp) : "./hrtfs/elev-5/R-5e210a.dat" right [ 17, 31 ] = ascii (fp) : "./hrtfs/elev-5/L-5e205a.dat" left + ascii (fp) : "./hrtfs/elev-5/R-5e205a.dat" right [ 17, 32 ] = ascii (fp) : "./hrtfs/elev-5/L-5e200a.dat" left + ascii (fp) : "./hrtfs/elev-5/R-5e200a.dat" right [ 17, 33 ] = ascii (fp) : "./hrtfs/elev-5/L-5e195a.dat" left + ascii (fp) : "./hrtfs/elev-5/R-5e195a.dat" right [ 17, 34 ] = ascii (fp) : "./hrtfs/elev-5/L-5e190a.dat" left + ascii (fp) : "./hrtfs/elev-5/R-5e190a.dat" right [ 17, 35 ] = ascii (fp) : "./hrtfs/elev-5/L-5e185a.dat" left + ascii (fp) : "./hrtfs/elev-5/R-5e185a.dat" right [ 17, 36 ] = ascii (fp) : "./hrtfs/elev-5/L-5e180a.dat" left + ascii (fp) : "./hrtfs/elev-5/R-5e180a.dat" right [ 17, 37 ] = ascii (fp) : "./hrtfs/elev-5/L-5e175a.dat" left + ascii (fp) : "./hrtfs/elev-5/R-5e175a.dat" right [ 17, 38 ] = ascii (fp) : "./hrtfs/elev-5/L-5e170a.dat" left + ascii (fp) : "./hrtfs/elev-5/R-5e170a.dat" right [ 17, 39 ] = ascii (fp) : "./hrtfs/elev-5/L-5e165a.dat" left + ascii (fp) : "./hrtfs/elev-5/R-5e165a.dat" right [ 17, 40 ] = ascii (fp) : "./hrtfs/elev-5/L-5e160a.dat" left + ascii (fp) : "./hrtfs/elev-5/R-5e160a.dat" right [ 17, 41 ] = ascii (fp) : "./hrtfs/elev-5/L-5e155a.dat" left + ascii (fp) : "./hrtfs/elev-5/R-5e155a.dat" right [ 17, 42 ] = ascii (fp) : "./hrtfs/elev-5/L-5e150a.dat" left + ascii (fp) : "./hrtfs/elev-5/R-5e150a.dat" right [ 17, 43 ] = ascii (fp) : "./hrtfs/elev-5/L-5e145a.dat" left + ascii (fp) : "./hrtfs/elev-5/R-5e145a.dat" right [ 17, 44 ] = ascii (fp) : "./hrtfs/elev-5/L-5e140a.dat" left + ascii (fp) : "./hrtfs/elev-5/R-5e140a.dat" right [ 17, 45 ] = ascii (fp) : "./hrtfs/elev-5/L-5e135a.dat" left + ascii (fp) : "./hrtfs/elev-5/R-5e135a.dat" right [ 17, 46 ] = ascii (fp) : "./hrtfs/elev-5/L-5e130a.dat" left + ascii (fp) : "./hrtfs/elev-5/R-5e130a.dat" right [ 17, 47 ] = ascii (fp) : "./hrtfs/elev-5/L-5e125a.dat" left + ascii (fp) : "./hrtfs/elev-5/R-5e125a.dat" right [ 17, 48 ] = ascii (fp) : "./hrtfs/elev-5/L-5e120a.dat" left + ascii (fp) : "./hrtfs/elev-5/R-5e120a.dat" right [ 17, 49 ] = ascii (fp) : "./hrtfs/elev-5/L-5e115a.dat" left + ascii (fp) : "./hrtfs/elev-5/R-5e115a.dat" right [ 17, 50 ] = ascii (fp) : "./hrtfs/elev-5/L-5e110a.dat" left + ascii (fp) : "./hrtfs/elev-5/R-5e110a.dat" right [ 17, 51 ] = ascii (fp) : "./hrtfs/elev-5/L-5e105a.dat" left + ascii (fp) : "./hrtfs/elev-5/R-5e105a.dat" right [ 17, 52 ] = ascii (fp) : "./hrtfs/elev-5/L-5e100a.dat" left + ascii (fp) : "./hrtfs/elev-5/R-5e100a.dat" right [ 17, 53 ] = ascii (fp) : "./hrtfs/elev-5/L-5e095a.dat" left + ascii (fp) : "./hrtfs/elev-5/R-5e095a.dat" right [ 17, 54 ] = ascii (fp) : "./hrtfs/elev-5/L-5e090a.dat" left + ascii (fp) : "./hrtfs/elev-5/R-5e090a.dat" right [ 17, 55 ] = ascii (fp) : "./hrtfs/elev-5/L-5e085a.dat" left + ascii (fp) : "./hrtfs/elev-5/R-5e085a.dat" right [ 17, 56 ] = ascii (fp) : "./hrtfs/elev-5/L-5e080a.dat" left + ascii (fp) : "./hrtfs/elev-5/R-5e080a.dat" right [ 17, 57 ] = ascii (fp) : "./hrtfs/elev-5/L-5e075a.dat" left + ascii (fp) : "./hrtfs/elev-5/R-5e075a.dat" right [ 17, 58 ] = ascii (fp) : "./hrtfs/elev-5/L-5e070a.dat" left + ascii (fp) : "./hrtfs/elev-5/R-5e070a.dat" right [ 17, 59 ] = ascii (fp) : "./hrtfs/elev-5/L-5e065a.dat" left + ascii (fp) : "./hrtfs/elev-5/R-5e065a.dat" right [ 17, 60 ] = ascii (fp) : "./hrtfs/elev-5/L-5e060a.dat" left + ascii (fp) : "./hrtfs/elev-5/R-5e060a.dat" right [ 17, 61 ] = ascii (fp) : "./hrtfs/elev-5/L-5e055a.dat" left + ascii (fp) : "./hrtfs/elev-5/R-5e055a.dat" right [ 17, 62 ] = ascii (fp) : "./hrtfs/elev-5/L-5e050a.dat" left + ascii (fp) : "./hrtfs/elev-5/R-5e050a.dat" right [ 17, 63 ] = ascii (fp) : "./hrtfs/elev-5/L-5e045a.dat" left + ascii (fp) : "./hrtfs/elev-5/R-5e045a.dat" right [ 17, 64 ] = ascii (fp) : "./hrtfs/elev-5/L-5e040a.dat" left + ascii (fp) : "./hrtfs/elev-5/R-5e040a.dat" right [ 17, 65 ] = ascii (fp) : "./hrtfs/elev-5/L-5e035a.dat" left + ascii (fp) : "./hrtfs/elev-5/R-5e035a.dat" right [ 17, 66 ] = ascii (fp) : "./hrtfs/elev-5/L-5e030a.dat" left + ascii (fp) : "./hrtfs/elev-5/R-5e030a.dat" right [ 17, 67 ] = ascii (fp) : "./hrtfs/elev-5/L-5e025a.dat" left + ascii (fp) : "./hrtfs/elev-5/R-5e025a.dat" right [ 17, 68 ] = ascii (fp) : "./hrtfs/elev-5/L-5e020a.dat" left + ascii (fp) : "./hrtfs/elev-5/R-5e020a.dat" right [ 17, 69 ] = ascii (fp) : "./hrtfs/elev-5/L-5e015a.dat" left + ascii (fp) : "./hrtfs/elev-5/R-5e015a.dat" right [ 17, 70 ] = ascii (fp) : "./hrtfs/elev-5/L-5e010a.dat" left + ascii (fp) : "./hrtfs/elev-5/R-5e010a.dat" right [ 17, 71 ] = ascii (fp) : "./hrtfs/elev-5/L-5e005a.dat" left + ascii (fp) : "./hrtfs/elev-5/R-5e005a.dat" right [ 18, 0 ] = ascii (fp) : "./hrtfs/elev0/L0e000a.dat" left + ascii (fp) : "./hrtfs/elev0/R0e000a.dat" right [ 18, 1 ] = ascii (fp) : "./hrtfs/elev0/L0e355a.dat" left + ascii (fp) : "./hrtfs/elev0/R0e355a.dat" right [ 18, 2 ] = ascii (fp) : "./hrtfs/elev0/L0e350a.dat" left + ascii (fp) : "./hrtfs/elev0/R0e350a.dat" right [ 18, 3 ] = ascii (fp) : "./hrtfs/elev0/L0e345a.dat" left + ascii (fp) : "./hrtfs/elev0/R0e345a.dat" right [ 18, 4 ] = ascii (fp) : "./hrtfs/elev0/L0e340a.dat" left + ascii (fp) : "./hrtfs/elev0/R0e340a.dat" right [ 18, 5 ] = ascii (fp) : "./hrtfs/elev0/L0e335a.dat" left + ascii (fp) : "./hrtfs/elev0/R0e335a.dat" right [ 18, 6 ] = ascii (fp) : "./hrtfs/elev0/L0e330a.dat" left + ascii (fp) : "./hrtfs/elev0/R0e330a.dat" right [ 18, 7 ] = ascii (fp) : "./hrtfs/elev0/L0e325a.dat" left + ascii (fp) : "./hrtfs/elev0/R0e325a.dat" right [ 18, 8 ] = ascii (fp) : "./hrtfs/elev0/L0e320a.dat" left + ascii (fp) : "./hrtfs/elev0/R0e320a.dat" right [ 18, 9 ] = ascii (fp) : "./hrtfs/elev0/L0e315a.dat" left + ascii (fp) : "./hrtfs/elev0/R0e315a.dat" right [ 18, 10 ] = ascii (fp) : "./hrtfs/elev0/L0e310a.dat" left + ascii (fp) : "./hrtfs/elev0/R0e310a.dat" right [ 18, 11 ] = ascii (fp) : "./hrtfs/elev0/L0e305a.dat" left + ascii (fp) : "./hrtfs/elev0/R0e305a.dat" right [ 18, 12 ] = ascii (fp) : "./hrtfs/elev0/L0e300a.dat" left + ascii (fp) : "./hrtfs/elev0/R0e300a.dat" right [ 18, 13 ] = ascii (fp) : "./hrtfs/elev0/L0e295a.dat" left + ascii (fp) : "./hrtfs/elev0/R0e295a.dat" right [ 18, 14 ] = ascii (fp) : "./hrtfs/elev0/L0e290a.dat" left + ascii (fp) : "./hrtfs/elev0/R0e290a.dat" right [ 18, 15 ] = ascii (fp) : "./hrtfs/elev0/L0e285a.dat" left + ascii (fp) : "./hrtfs/elev0/R0e285a.dat" right [ 18, 16 ] = ascii (fp) : "./hrtfs/elev0/L0e280a.dat" left + ascii (fp) : "./hrtfs/elev0/R0e280a.dat" right [ 18, 17 ] = ascii (fp) : "./hrtfs/elev0/L0e275a.dat" left + ascii (fp) : "./hrtfs/elev0/R0e275a.dat" right [ 18, 18 ] = ascii (fp) : "./hrtfs/elev0/L0e270a.dat" left + ascii (fp) : "./hrtfs/elev0/R0e270a.dat" right [ 18, 19 ] = ascii (fp) : "./hrtfs/elev0/L0e265a.dat" left + ascii (fp) : "./hrtfs/elev0/R0e265a.dat" right [ 18, 20 ] = ascii (fp) : "./hrtfs/elev0/L0e260a.dat" left + ascii (fp) : "./hrtfs/elev0/R0e260a.dat" right [ 18, 21 ] = ascii (fp) : "./hrtfs/elev0/L0e255a.dat" left + ascii (fp) : "./hrtfs/elev0/R0e255a.dat" right [ 18, 22 ] = ascii (fp) : "./hrtfs/elev0/L0e250a.dat" left + ascii (fp) : "./hrtfs/elev0/R0e250a.dat" right [ 18, 23 ] = ascii (fp) : "./hrtfs/elev0/L0e245a.dat" left + ascii (fp) : "./hrtfs/elev0/R0e245a.dat" right [ 18, 24 ] = ascii (fp) : "./hrtfs/elev0/L0e240a.dat" left + ascii (fp) : "./hrtfs/elev0/R0e240a.dat" right [ 18, 25 ] = ascii (fp) : "./hrtfs/elev0/L0e235a.dat" left + ascii (fp) : "./hrtfs/elev0/R0e235a.dat" right [ 18, 26 ] = ascii (fp) : "./hrtfs/elev0/L0e230a.dat" left + ascii (fp) : "./hrtfs/elev0/R0e230a.dat" right [ 18, 27 ] = ascii (fp) : "./hrtfs/elev0/L0e225a.dat" left + ascii (fp) : "./hrtfs/elev0/R0e225a.dat" right [ 18, 28 ] = ascii (fp) : "./hrtfs/elev0/L0e220a.dat" left + ascii (fp) : "./hrtfs/elev0/R0e220a.dat" right [ 18, 29 ] = ascii (fp) : "./hrtfs/elev0/L0e215a.dat" left + ascii (fp) : "./hrtfs/elev0/R0e215a.dat" right [ 18, 30 ] = ascii (fp) : "./hrtfs/elev0/L0e210a.dat" left + ascii (fp) : "./hrtfs/elev0/R0e210a.dat" right [ 18, 31 ] = ascii (fp) : "./hrtfs/elev0/L0e205a.dat" left + ascii (fp) : "./hrtfs/elev0/R0e205a.dat" right [ 18, 32 ] = ascii (fp) : "./hrtfs/elev0/L0e200a.dat" left + ascii (fp) : "./hrtfs/elev0/R0e200a.dat" right [ 18, 33 ] = ascii (fp) : "./hrtfs/elev0/L0e195a.dat" left + ascii (fp) : "./hrtfs/elev0/R0e195a.dat" right [ 18, 34 ] = ascii (fp) : "./hrtfs/elev0/L0e190a.dat" left + ascii (fp) : "./hrtfs/elev0/R0e190a.dat" right [ 18, 35 ] = ascii (fp) : "./hrtfs/elev0/L0e185a.dat" left + ascii (fp) : "./hrtfs/elev0/R0e185a.dat" right [ 18, 36 ] = ascii (fp) : "./hrtfs/elev0/L0e180a.dat" left + ascii (fp) : "./hrtfs/elev0/R0e180a.dat" right [ 18, 37 ] = ascii (fp) : "./hrtfs/elev0/L0e175a.dat" left + ascii (fp) : "./hrtfs/elev0/R0e175a.dat" right [ 18, 38 ] = ascii (fp) : "./hrtfs/elev0/L0e170a.dat" left + ascii (fp) : "./hrtfs/elev0/R0e170a.dat" right [ 18, 39 ] = ascii (fp) : "./hrtfs/elev0/L0e165a.dat" left + ascii (fp) : "./hrtfs/elev0/R0e165a.dat" right [ 18, 40 ] = ascii (fp) : "./hrtfs/elev0/L0e160a.dat" left + ascii (fp) : "./hrtfs/elev0/R0e160a.dat" right [ 18, 41 ] = ascii (fp) : "./hrtfs/elev0/L0e155a.dat" left + ascii (fp) : "./hrtfs/elev0/R0e155a.dat" right [ 18, 42 ] = ascii (fp) : "./hrtfs/elev0/L0e150a.dat" left + ascii (fp) : "./hrtfs/elev0/R0e150a.dat" right [ 18, 43 ] = ascii (fp) : "./hrtfs/elev0/L0e145a.dat" left + ascii (fp) : "./hrtfs/elev0/R0e145a.dat" right [ 18, 44 ] = ascii (fp) : "./hrtfs/elev0/L0e140a.dat" left + ascii (fp) : "./hrtfs/elev0/R0e140a.dat" right [ 18, 45 ] = ascii (fp) : "./hrtfs/elev0/L0e135a.dat" left + ascii (fp) : "./hrtfs/elev0/R0e135a.dat" right [ 18, 46 ] = ascii (fp) : "./hrtfs/elev0/L0e130a.dat" left + ascii (fp) : "./hrtfs/elev0/R0e130a.dat" right [ 18, 47 ] = ascii (fp) : "./hrtfs/elev0/L0e125a.dat" left + ascii (fp) : "./hrtfs/elev0/R0e125a.dat" right [ 18, 48 ] = ascii (fp) : "./hrtfs/elev0/L0e120a.dat" left + ascii (fp) : "./hrtfs/elev0/R0e120a.dat" right [ 18, 49 ] = ascii (fp) : "./hrtfs/elev0/L0e115a.dat" left + ascii (fp) : "./hrtfs/elev0/R0e115a.dat" right [ 18, 50 ] = ascii (fp) : "./hrtfs/elev0/L0e110a.dat" left + ascii (fp) : "./hrtfs/elev0/R0e110a.dat" right [ 18, 51 ] = ascii (fp) : "./hrtfs/elev0/L0e105a.dat" left + ascii (fp) : "./hrtfs/elev0/R0e105a.dat" right [ 18, 52 ] = ascii (fp) : "./hrtfs/elev0/L0e100a.dat" left + ascii (fp) : "./hrtfs/elev0/R0e100a.dat" right [ 18, 53 ] = ascii (fp) : "./hrtfs/elev0/L0e095a.dat" left + ascii (fp) : "./hrtfs/elev0/R0e095a.dat" right [ 18, 54 ] = ascii (fp) : "./hrtfs/elev0/L0e090a.dat" left + ascii (fp) : "./hrtfs/elev0/R0e090a.dat" right [ 18, 55 ] = ascii (fp) : "./hrtfs/elev0/L0e085a.dat" left + ascii (fp) : "./hrtfs/elev0/R0e085a.dat" right [ 18, 56 ] = ascii (fp) : "./hrtfs/elev0/L0e080a.dat" left + ascii (fp) : "./hrtfs/elev0/R0e080a.dat" right [ 18, 57 ] = ascii (fp) : "./hrtfs/elev0/L0e075a.dat" left + ascii (fp) : "./hrtfs/elev0/R0e075a.dat" right [ 18, 58 ] = ascii (fp) : "./hrtfs/elev0/L0e070a.dat" left + ascii (fp) : "./hrtfs/elev0/R0e070a.dat" right [ 18, 59 ] = ascii (fp) : "./hrtfs/elev0/L0e065a.dat" left + ascii (fp) : "./hrtfs/elev0/R0e065a.dat" right [ 18, 60 ] = ascii (fp) : "./hrtfs/elev0/L0e060a.dat" left + ascii (fp) : "./hrtfs/elev0/R0e060a.dat" right [ 18, 61 ] = ascii (fp) : "./hrtfs/elev0/L0e055a.dat" left + ascii (fp) : "./hrtfs/elev0/R0e055a.dat" right [ 18, 62 ] = ascii (fp) : "./hrtfs/elev0/L0e050a.dat" left + ascii (fp) : "./hrtfs/elev0/R0e050a.dat" right [ 18, 63 ] = ascii (fp) : "./hrtfs/elev0/L0e045a.dat" left + ascii (fp) : "./hrtfs/elev0/R0e045a.dat" right [ 18, 64 ] = ascii (fp) : "./hrtfs/elev0/L0e040a.dat" left + ascii (fp) : "./hrtfs/elev0/R0e040a.dat" right [ 18, 65 ] = ascii (fp) : "./hrtfs/elev0/L0e035a.dat" left + ascii (fp) : "./hrtfs/elev0/R0e035a.dat" right [ 18, 66 ] = ascii (fp) : "./hrtfs/elev0/L0e030a.dat" left + ascii (fp) : "./hrtfs/elev0/R0e030a.dat" right [ 18, 67 ] = ascii (fp) : "./hrtfs/elev0/L0e025a.dat" left + ascii (fp) : "./hrtfs/elev0/R0e025a.dat" right [ 18, 68 ] = ascii (fp) : "./hrtfs/elev0/L0e020a.dat" left + ascii (fp) : "./hrtfs/elev0/R0e020a.dat" right [ 18, 69 ] = ascii (fp) : "./hrtfs/elev0/L0e015a.dat" left + ascii (fp) : "./hrtfs/elev0/R0e015a.dat" right [ 18, 70 ] = ascii (fp) : "./hrtfs/elev0/L0e010a.dat" left + ascii (fp) : "./hrtfs/elev0/R0e010a.dat" right [ 18, 71 ] = ascii (fp) : "./hrtfs/elev0/L0e005a.dat" left + ascii (fp) : "./hrtfs/elev0/R0e005a.dat" right [ 19, 0 ] = ascii (fp) : "./hrtfs/elev5/L5e000a.dat" left + ascii (fp) : "./hrtfs/elev5/R5e000a.dat" right [ 19, 1 ] = ascii (fp) : "./hrtfs/elev5/L5e355a.dat" left + ascii (fp) : "./hrtfs/elev5/R5e355a.dat" right [ 19, 2 ] = ascii (fp) : "./hrtfs/elev5/L5e350a.dat" left + ascii (fp) : "./hrtfs/elev5/R5e350a.dat" right [ 19, 3 ] = ascii (fp) : "./hrtfs/elev5/L5e345a.dat" left + ascii (fp) : "./hrtfs/elev5/R5e345a.dat" right [ 19, 4 ] = ascii (fp) : "./hrtfs/elev5/L5e340a.dat" left + ascii (fp) : "./hrtfs/elev5/R5e340a.dat" right [ 19, 5 ] = ascii (fp) : "./hrtfs/elev5/L5e335a.dat" left + ascii (fp) : "./hrtfs/elev5/R5e335a.dat" right [ 19, 6 ] = ascii (fp) : "./hrtfs/elev5/L5e330a.dat" left + ascii (fp) : "./hrtfs/elev5/R5e330a.dat" right [ 19, 7 ] = ascii (fp) : "./hrtfs/elev5/L5e325a.dat" left + ascii (fp) : "./hrtfs/elev5/R5e325a.dat" right [ 19, 8 ] = ascii (fp) : "./hrtfs/elev5/L5e320a.dat" left + ascii (fp) : "./hrtfs/elev5/R5e320a.dat" right [ 19, 9 ] = ascii (fp) : "./hrtfs/elev5/L5e315a.dat" left + ascii (fp) : "./hrtfs/elev5/R5e315a.dat" right [ 19, 10 ] = ascii (fp) : "./hrtfs/elev5/L5e310a.dat" left + ascii (fp) : "./hrtfs/elev5/R5e310a.dat" right [ 19, 11 ] = ascii (fp) : "./hrtfs/elev5/L5e305a.dat" left + ascii (fp) : "./hrtfs/elev5/R5e305a.dat" right [ 19, 12 ] = ascii (fp) : "./hrtfs/elev5/L5e300a.dat" left + ascii (fp) : "./hrtfs/elev5/R5e300a.dat" right [ 19, 13 ] = ascii (fp) : "./hrtfs/elev5/L5e295a.dat" left + ascii (fp) : "./hrtfs/elev5/R5e295a.dat" right [ 19, 14 ] = ascii (fp) : "./hrtfs/elev5/L5e290a.dat" left + ascii (fp) : "./hrtfs/elev5/R5e290a.dat" right [ 19, 15 ] = ascii (fp) : "./hrtfs/elev5/L5e285a.dat" left + ascii (fp) : "./hrtfs/elev5/R5e285a.dat" right [ 19, 16 ] = ascii (fp) : "./hrtfs/elev5/L5e280a.dat" left + ascii (fp) : "./hrtfs/elev5/R5e280a.dat" right [ 19, 17 ] = ascii (fp) : "./hrtfs/elev5/L5e275a.dat" left + ascii (fp) : "./hrtfs/elev5/R5e275a.dat" right [ 19, 18 ] = ascii (fp) : "./hrtfs/elev5/L5e270a.dat" left + ascii (fp) : "./hrtfs/elev5/R5e270a.dat" right [ 19, 19 ] = ascii (fp) : "./hrtfs/elev5/L5e265a.dat" left + ascii (fp) : "./hrtfs/elev5/R5e265a.dat" right [ 19, 20 ] = ascii (fp) : "./hrtfs/elev5/L5e260a.dat" left + ascii (fp) : "./hrtfs/elev5/R5e260a.dat" right [ 19, 21 ] = ascii (fp) : "./hrtfs/elev5/L5e255a.dat" left + ascii (fp) : "./hrtfs/elev5/R5e255a.dat" right [ 19, 22 ] = ascii (fp) : "./hrtfs/elev5/L5e250a.dat" left + ascii (fp) : "./hrtfs/elev5/R5e250a.dat" right [ 19, 23 ] = ascii (fp) : "./hrtfs/elev5/L5e245a.dat" left + ascii (fp) : "./hrtfs/elev5/R5e245a.dat" right [ 19, 24 ] = ascii (fp) : "./hrtfs/elev5/L5e240a.dat" left + ascii (fp) : "./hrtfs/elev5/R5e240a.dat" right [ 19, 25 ] = ascii (fp) : "./hrtfs/elev5/L5e235a.dat" left + ascii (fp) : "./hrtfs/elev5/R5e235a.dat" right [ 19, 26 ] = ascii (fp) : "./hrtfs/elev5/L5e230a.dat" left + ascii (fp) : "./hrtfs/elev5/R5e230a.dat" right [ 19, 27 ] = ascii (fp) : "./hrtfs/elev5/L5e225a.dat" left + ascii (fp) : "./hrtfs/elev5/R5e225a.dat" right [ 19, 28 ] = ascii (fp) : "./hrtfs/elev5/L5e220a.dat" left + ascii (fp) : "./hrtfs/elev5/R5e220a.dat" right [ 19, 29 ] = ascii (fp) : "./hrtfs/elev5/L5e215a.dat" left + ascii (fp) : "./hrtfs/elev5/R5e215a.dat" right [ 19, 30 ] = ascii (fp) : "./hrtfs/elev5/L5e210a.dat" left + ascii (fp) : "./hrtfs/elev5/R5e210a.dat" right [ 19, 31 ] = ascii (fp) : "./hrtfs/elev5/L5e205a.dat" left + ascii (fp) : "./hrtfs/elev5/R5e205a.dat" right [ 19, 32 ] = ascii (fp) : "./hrtfs/elev5/L5e200a.dat" left + ascii (fp) : "./hrtfs/elev5/R5e200a.dat" right [ 19, 33 ] = ascii (fp) : "./hrtfs/elev5/L5e195a.dat" left + ascii (fp) : "./hrtfs/elev5/R5e195a.dat" right [ 19, 34 ] = ascii (fp) : "./hrtfs/elev5/L5e190a.dat" left + ascii (fp) : "./hrtfs/elev5/R5e190a.dat" right [ 19, 35 ] = ascii (fp) : "./hrtfs/elev5/L5e185a.dat" left + ascii (fp) : "./hrtfs/elev5/R5e185a.dat" right [ 19, 36 ] = ascii (fp) : "./hrtfs/elev5/L5e180a.dat" left + ascii (fp) : "./hrtfs/elev5/R5e180a.dat" right [ 19, 37 ] = ascii (fp) : "./hrtfs/elev5/L5e175a.dat" left + ascii (fp) : "./hrtfs/elev5/R5e175a.dat" right [ 19, 38 ] = ascii (fp) : "./hrtfs/elev5/L5e170a.dat" left + ascii (fp) : "./hrtfs/elev5/R5e170a.dat" right [ 19, 39 ] = ascii (fp) : "./hrtfs/elev5/L5e165a.dat" left + ascii (fp) : "./hrtfs/elev5/R5e165a.dat" right [ 19, 40 ] = ascii (fp) : "./hrtfs/elev5/L5e160a.dat" left + ascii (fp) : "./hrtfs/elev5/R5e160a.dat" right [ 19, 41 ] = ascii (fp) : "./hrtfs/elev5/L5e155a.dat" left + ascii (fp) : "./hrtfs/elev5/R5e155a.dat" right [ 19, 42 ] = ascii (fp) : "./hrtfs/elev5/L5e150a.dat" left + ascii (fp) : "./hrtfs/elev5/R5e150a.dat" right [ 19, 43 ] = ascii (fp) : "./hrtfs/elev5/L5e145a.dat" left + ascii (fp) : "./hrtfs/elev5/R5e145a.dat" right [ 19, 44 ] = ascii (fp) : "./hrtfs/elev5/L5e140a.dat" left + ascii (fp) : "./hrtfs/elev5/R5e140a.dat" right [ 19, 45 ] = ascii (fp) : "./hrtfs/elev5/L5e135a.dat" left + ascii (fp) : "./hrtfs/elev5/R5e135a.dat" right [ 19, 46 ] = ascii (fp) : "./hrtfs/elev5/L5e130a.dat" left + ascii (fp) : "./hrtfs/elev5/R5e130a.dat" right [ 19, 47 ] = ascii (fp) : "./hrtfs/elev5/L5e125a.dat" left + ascii (fp) : "./hrtfs/elev5/R5e125a.dat" right [ 19, 48 ] = ascii (fp) : "./hrtfs/elev5/L5e120a.dat" left + ascii (fp) : "./hrtfs/elev5/R5e120a.dat" right [ 19, 49 ] = ascii (fp) : "./hrtfs/elev5/L5e115a.dat" left + ascii (fp) : "./hrtfs/elev5/R5e115a.dat" right [ 19, 50 ] = ascii (fp) : "./hrtfs/elev5/L5e110a.dat" left + ascii (fp) : "./hrtfs/elev5/R5e110a.dat" right [ 19, 51 ] = ascii (fp) : "./hrtfs/elev5/L5e105a.dat" left + ascii (fp) : "./hrtfs/elev5/R5e105a.dat" right [ 19, 52 ] = ascii (fp) : "./hrtfs/elev5/L5e100a.dat" left + ascii (fp) : "./hrtfs/elev5/R5e100a.dat" right [ 19, 53 ] = ascii (fp) : "./hrtfs/elev5/L5e095a.dat" left + ascii (fp) : "./hrtfs/elev5/R5e095a.dat" right [ 19, 54 ] = ascii (fp) : "./hrtfs/elev5/L5e090a.dat" left + ascii (fp) : "./hrtfs/elev5/R5e090a.dat" right [ 19, 55 ] = ascii (fp) : "./hrtfs/elev5/L5e085a.dat" left + ascii (fp) : "./hrtfs/elev5/R5e085a.dat" right [ 19, 56 ] = ascii (fp) : "./hrtfs/elev5/L5e080a.dat" left + ascii (fp) : "./hrtfs/elev5/R5e080a.dat" right [ 19, 57 ] = ascii (fp) : "./hrtfs/elev5/L5e075a.dat" left + ascii (fp) : "./hrtfs/elev5/R5e075a.dat" right [ 19, 58 ] = ascii (fp) : "./hrtfs/elev5/L5e070a.dat" left + ascii (fp) : "./hrtfs/elev5/R5e070a.dat" right [ 19, 59 ] = ascii (fp) : "./hrtfs/elev5/L5e065a.dat" left + ascii (fp) : "./hrtfs/elev5/R5e065a.dat" right [ 19, 60 ] = ascii (fp) : "./hrtfs/elev5/L5e060a.dat" left + ascii (fp) : "./hrtfs/elev5/R5e060a.dat" right [ 19, 61 ] = ascii (fp) : "./hrtfs/elev5/L5e055a.dat" left + ascii (fp) : "./hrtfs/elev5/R5e055a.dat" right [ 19, 62 ] = ascii (fp) : "./hrtfs/elev5/L5e050a.dat" left + ascii (fp) : "./hrtfs/elev5/R5e050a.dat" right [ 19, 63 ] = ascii (fp) : "./hrtfs/elev5/L5e045a.dat" left + ascii (fp) : "./hrtfs/elev5/R5e045a.dat" right [ 19, 64 ] = ascii (fp) : "./hrtfs/elev5/L5e040a.dat" left + ascii (fp) : "./hrtfs/elev5/R5e040a.dat" right [ 19, 65 ] = ascii (fp) : "./hrtfs/elev5/L5e035a.dat" left + ascii (fp) : "./hrtfs/elev5/R5e035a.dat" right [ 19, 66 ] = ascii (fp) : "./hrtfs/elev5/L5e030a.dat" left + ascii (fp) : "./hrtfs/elev5/R5e030a.dat" right [ 19, 67 ] = ascii (fp) : "./hrtfs/elev5/L5e025a.dat" left + ascii (fp) : "./hrtfs/elev5/R5e025a.dat" right [ 19, 68 ] = ascii (fp) : "./hrtfs/elev5/L5e020a.dat" left + ascii (fp) : "./hrtfs/elev5/R5e020a.dat" right [ 19, 69 ] = ascii (fp) : "./hrtfs/elev5/L5e015a.dat" left + ascii (fp) : "./hrtfs/elev5/R5e015a.dat" right [ 19, 70 ] = ascii (fp) : "./hrtfs/elev5/L5e010a.dat" left + ascii (fp) : "./hrtfs/elev5/R5e010a.dat" right [ 19, 71 ] = ascii (fp) : "./hrtfs/elev5/L5e005a.dat" left + ascii (fp) : "./hrtfs/elev5/R5e005a.dat" right [ 20, 0 ] = ascii (fp) : "./hrtfs/elev10/L10e000a.dat" left + ascii (fp) : "./hrtfs/elev10/R10e000a.dat" right [ 20, 1 ] = ascii (fp) : "./hrtfs/elev10/L10e355a.dat" left + ascii (fp) : "./hrtfs/elev10/R10e355a.dat" right [ 20, 2 ] = ascii (fp) : "./hrtfs/elev10/L10e350a.dat" left + ascii (fp) : "./hrtfs/elev10/R10e350a.dat" right [ 20, 3 ] = ascii (fp) : "./hrtfs/elev10/L10e345a.dat" left + ascii (fp) : "./hrtfs/elev10/R10e345a.dat" right [ 20, 4 ] = ascii (fp) : "./hrtfs/elev10/L10e340a.dat" left + ascii (fp) : "./hrtfs/elev10/R10e340a.dat" right [ 20, 5 ] = ascii (fp) : "./hrtfs/elev10/L10e335a.dat" left + ascii (fp) : "./hrtfs/elev10/R10e335a.dat" right [ 20, 6 ] = ascii (fp) : "./hrtfs/elev10/L10e330a.dat" left + ascii (fp) : "./hrtfs/elev10/R10e330a.dat" right [ 20, 7 ] = ascii (fp) : "./hrtfs/elev10/L10e325a.dat" left + ascii (fp) : "./hrtfs/elev10/R10e325a.dat" right [ 20, 8 ] = ascii (fp) : "./hrtfs/elev10/L10e320a.dat" left + ascii (fp) : "./hrtfs/elev10/R10e320a.dat" right [ 20, 9 ] = ascii (fp) : "./hrtfs/elev10/L10e315a.dat" left + ascii (fp) : "./hrtfs/elev10/R10e315a.dat" right [ 20, 10 ] = ascii (fp) : "./hrtfs/elev10/L10e310a.dat" left + ascii (fp) : "./hrtfs/elev10/R10e310a.dat" right [ 20, 11 ] = ascii (fp) : "./hrtfs/elev10/L10e305a.dat" left + ascii (fp) : "./hrtfs/elev10/R10e305a.dat" right [ 20, 12 ] = ascii (fp) : "./hrtfs/elev10/L10e300a.dat" left + ascii (fp) : "./hrtfs/elev10/R10e300a.dat" right [ 20, 13 ] = ascii (fp) : "./hrtfs/elev10/L10e295a.dat" left + ascii (fp) : "./hrtfs/elev10/R10e295a.dat" right [ 20, 14 ] = ascii (fp) : "./hrtfs/elev10/L10e290a.dat" left + ascii (fp) : "./hrtfs/elev10/R10e290a.dat" right [ 20, 15 ] = ascii (fp) : "./hrtfs/elev10/L10e285a.dat" left + ascii (fp) : "./hrtfs/elev10/R10e285a.dat" right [ 20, 16 ] = ascii (fp) : "./hrtfs/elev10/L10e280a.dat" left + ascii (fp) : "./hrtfs/elev10/R10e280a.dat" right [ 20, 17 ] = ascii (fp) : "./hrtfs/elev10/L10e275a.dat" left + ascii (fp) : "./hrtfs/elev10/R10e275a.dat" right [ 20, 18 ] = ascii (fp) : "./hrtfs/elev10/L10e270a.dat" left + ascii (fp) : "./hrtfs/elev10/R10e270a.dat" right [ 20, 19 ] = ascii (fp) : "./hrtfs/elev10/L10e265a.dat" left + ascii (fp) : "./hrtfs/elev10/R10e265a.dat" right [ 20, 20 ] = ascii (fp) : "./hrtfs/elev10/L10e260a.dat" left + ascii (fp) : "./hrtfs/elev10/R10e260a.dat" right [ 20, 21 ] = ascii (fp) : "./hrtfs/elev10/L10e255a.dat" left + ascii (fp) : "./hrtfs/elev10/R10e255a.dat" right [ 20, 22 ] = ascii (fp) : "./hrtfs/elev10/L10e250a.dat" left + ascii (fp) : "./hrtfs/elev10/R10e250a.dat" right [ 20, 23 ] = ascii (fp) : "./hrtfs/elev10/L10e245a.dat" left + ascii (fp) : "./hrtfs/elev10/R10e245a.dat" right [ 20, 24 ] = ascii (fp) : "./hrtfs/elev10/L10e240a.dat" left + ascii (fp) : "./hrtfs/elev10/R10e240a.dat" right [ 20, 25 ] = ascii (fp) : "./hrtfs/elev10/L10e235a.dat" left + ascii (fp) : "./hrtfs/elev10/R10e235a.dat" right [ 20, 26 ] = ascii (fp) : "./hrtfs/elev10/L10e230a.dat" left + ascii (fp) : "./hrtfs/elev10/R10e230a.dat" right [ 20, 27 ] = ascii (fp) : "./hrtfs/elev10/L10e225a.dat" left + ascii (fp) : "./hrtfs/elev10/R10e225a.dat" right [ 20, 28 ] = ascii (fp) : "./hrtfs/elev10/L10e220a.dat" left + ascii (fp) : "./hrtfs/elev10/R10e220a.dat" right [ 20, 29 ] = ascii (fp) : "./hrtfs/elev10/L10e215a.dat" left + ascii (fp) : "./hrtfs/elev10/R10e215a.dat" right [ 20, 30 ] = ascii (fp) : "./hrtfs/elev10/L10e210a.dat" left + ascii (fp) : "./hrtfs/elev10/R10e210a.dat" right [ 20, 31 ] = ascii (fp) : "./hrtfs/elev10/L10e205a.dat" left + ascii (fp) : "./hrtfs/elev10/R10e205a.dat" right [ 20, 32 ] = ascii (fp) : "./hrtfs/elev10/L10e200a.dat" left + ascii (fp) : "./hrtfs/elev10/R10e200a.dat" right [ 20, 33 ] = ascii (fp) : "./hrtfs/elev10/L10e195a.dat" left + ascii (fp) : "./hrtfs/elev10/R10e195a.dat" right [ 20, 34 ] = ascii (fp) : "./hrtfs/elev10/L10e190a.dat" left + ascii (fp) : "./hrtfs/elev10/R10e190a.dat" right [ 20, 35 ] = ascii (fp) : "./hrtfs/elev10/L10e185a.dat" left + ascii (fp) : "./hrtfs/elev10/R10e185a.dat" right [ 20, 36 ] = ascii (fp) : "./hrtfs/elev10/L10e180a.dat" left + ascii (fp) : "./hrtfs/elev10/R10e180a.dat" right [ 20, 37 ] = ascii (fp) : "./hrtfs/elev10/L10e175a.dat" left + ascii (fp) : "./hrtfs/elev10/R10e175a.dat" right [ 20, 38 ] = ascii (fp) : "./hrtfs/elev10/L10e170a.dat" left + ascii (fp) : "./hrtfs/elev10/R10e170a.dat" right [ 20, 39 ] = ascii (fp) : "./hrtfs/elev10/L10e165a.dat" left + ascii (fp) : "./hrtfs/elev10/R10e165a.dat" right [ 20, 40 ] = ascii (fp) : "./hrtfs/elev10/L10e160a.dat" left + ascii (fp) : "./hrtfs/elev10/R10e160a.dat" right [ 20, 41 ] = ascii (fp) : "./hrtfs/elev10/L10e155a.dat" left + ascii (fp) : "./hrtfs/elev10/R10e155a.dat" right [ 20, 42 ] = ascii (fp) : "./hrtfs/elev10/L10e150a.dat" left + ascii (fp) : "./hrtfs/elev10/R10e150a.dat" right [ 20, 43 ] = ascii (fp) : "./hrtfs/elev10/L10e145a.dat" left + ascii (fp) : "./hrtfs/elev10/R10e145a.dat" right [ 20, 44 ] = ascii (fp) : "./hrtfs/elev10/L10e140a.dat" left + ascii (fp) : "./hrtfs/elev10/R10e140a.dat" right [ 20, 45 ] = ascii (fp) : "./hrtfs/elev10/L10e135a.dat" left + ascii (fp) : "./hrtfs/elev10/R10e135a.dat" right [ 20, 46 ] = ascii (fp) : "./hrtfs/elev10/L10e130a.dat" left + ascii (fp) : "./hrtfs/elev10/R10e130a.dat" right [ 20, 47 ] = ascii (fp) : "./hrtfs/elev10/L10e125a.dat" left + ascii (fp) : "./hrtfs/elev10/R10e125a.dat" right [ 20, 48 ] = ascii (fp) : "./hrtfs/elev10/L10e120a.dat" left + ascii (fp) : "./hrtfs/elev10/R10e120a.dat" right [ 20, 49 ] = ascii (fp) : "./hrtfs/elev10/L10e115a.dat" left + ascii (fp) : "./hrtfs/elev10/R10e115a.dat" right [ 20, 50 ] = ascii (fp) : "./hrtfs/elev10/L10e110a.dat" left + ascii (fp) : "./hrtfs/elev10/R10e110a.dat" right [ 20, 51 ] = ascii (fp) : "./hrtfs/elev10/L10e105a.dat" left + ascii (fp) : "./hrtfs/elev10/R10e105a.dat" right [ 20, 52 ] = ascii (fp) : "./hrtfs/elev10/L10e100a.dat" left + ascii (fp) : "./hrtfs/elev10/R10e100a.dat" right [ 20, 53 ] = ascii (fp) : "./hrtfs/elev10/L10e095a.dat" left + ascii (fp) : "./hrtfs/elev10/R10e095a.dat" right [ 20, 54 ] = ascii (fp) : "./hrtfs/elev10/L10e090a.dat" left + ascii (fp) : "./hrtfs/elev10/R10e090a.dat" right [ 20, 55 ] = ascii (fp) : "./hrtfs/elev10/L10e085a.dat" left + ascii (fp) : "./hrtfs/elev10/R10e085a.dat" right [ 20, 56 ] = ascii (fp) : "./hrtfs/elev10/L10e080a.dat" left + ascii (fp) : "./hrtfs/elev10/R10e080a.dat" right [ 20, 57 ] = ascii (fp) : "./hrtfs/elev10/L10e075a.dat" left + ascii (fp) : "./hrtfs/elev10/R10e075a.dat" right [ 20, 58 ] = ascii (fp) : "./hrtfs/elev10/L10e070a.dat" left + ascii (fp) : "./hrtfs/elev10/R10e070a.dat" right [ 20, 59 ] = ascii (fp) : "./hrtfs/elev10/L10e065a.dat" left + ascii (fp) : "./hrtfs/elev10/R10e065a.dat" right [ 20, 60 ] = ascii (fp) : "./hrtfs/elev10/L10e060a.dat" left + ascii (fp) : "./hrtfs/elev10/R10e060a.dat" right [ 20, 61 ] = ascii (fp) : "./hrtfs/elev10/L10e055a.dat" left + ascii (fp) : "./hrtfs/elev10/R10e055a.dat" right [ 20, 62 ] = ascii (fp) : "./hrtfs/elev10/L10e050a.dat" left + ascii (fp) : "./hrtfs/elev10/R10e050a.dat" right [ 20, 63 ] = ascii (fp) : "./hrtfs/elev10/L10e045a.dat" left + ascii (fp) : "./hrtfs/elev10/R10e045a.dat" right [ 20, 64 ] = ascii (fp) : "./hrtfs/elev10/L10e040a.dat" left + ascii (fp) : "./hrtfs/elev10/R10e040a.dat" right [ 20, 65 ] = ascii (fp) : "./hrtfs/elev10/L10e035a.dat" left + ascii (fp) : "./hrtfs/elev10/R10e035a.dat" right [ 20, 66 ] = ascii (fp) : "./hrtfs/elev10/L10e030a.dat" left + ascii (fp) : "./hrtfs/elev10/R10e030a.dat" right [ 20, 67 ] = ascii (fp) : "./hrtfs/elev10/L10e025a.dat" left + ascii (fp) : "./hrtfs/elev10/R10e025a.dat" right [ 20, 68 ] = ascii (fp) : "./hrtfs/elev10/L10e020a.dat" left + ascii (fp) : "./hrtfs/elev10/R10e020a.dat" right [ 20, 69 ] = ascii (fp) : "./hrtfs/elev10/L10e015a.dat" left + ascii (fp) : "./hrtfs/elev10/R10e015a.dat" right [ 20, 70 ] = ascii (fp) : "./hrtfs/elev10/L10e010a.dat" left + ascii (fp) : "./hrtfs/elev10/R10e010a.dat" right [ 20, 71 ] = ascii (fp) : "./hrtfs/elev10/L10e005a.dat" left + ascii (fp) : "./hrtfs/elev10/R10e005a.dat" right [ 21, 0 ] = ascii (fp) : "./hrtfs/elev15/L15e000a.dat" left + ascii (fp) : "./hrtfs/elev15/R15e000a.dat" right [ 21, 1 ] = ascii (fp) : "./hrtfs/elev15/L15e355a.dat" left + ascii (fp) : "./hrtfs/elev15/R15e355a.dat" right [ 21, 2 ] = ascii (fp) : "./hrtfs/elev15/L15e350a.dat" left + ascii (fp) : "./hrtfs/elev15/R15e350a.dat" right [ 21, 3 ] = ascii (fp) : "./hrtfs/elev15/L15e345a.dat" left + ascii (fp) : "./hrtfs/elev15/R15e345a.dat" right [ 21, 4 ] = ascii (fp) : "./hrtfs/elev15/L15e340a.dat" left + ascii (fp) : "./hrtfs/elev15/R15e340a.dat" right [ 21, 5 ] = ascii (fp) : "./hrtfs/elev15/L15e335a.dat" left + ascii (fp) : "./hrtfs/elev15/R15e335a.dat" right [ 21, 6 ] = ascii (fp) : "./hrtfs/elev15/L15e330a.dat" left + ascii (fp) : "./hrtfs/elev15/R15e330a.dat" right [ 21, 7 ] = ascii (fp) : "./hrtfs/elev15/L15e325a.dat" left + ascii (fp) : "./hrtfs/elev15/R15e325a.dat" right [ 21, 8 ] = ascii (fp) : "./hrtfs/elev15/L15e320a.dat" left + ascii (fp) : "./hrtfs/elev15/R15e320a.dat" right [ 21, 9 ] = ascii (fp) : "./hrtfs/elev15/L15e315a.dat" left + ascii (fp) : "./hrtfs/elev15/R15e315a.dat" right [ 21, 10 ] = ascii (fp) : "./hrtfs/elev15/L15e310a.dat" left + ascii (fp) : "./hrtfs/elev15/R15e310a.dat" right [ 21, 11 ] = ascii (fp) : "./hrtfs/elev15/L15e305a.dat" left + ascii (fp) : "./hrtfs/elev15/R15e305a.dat" right [ 21, 12 ] = ascii (fp) : "./hrtfs/elev15/L15e300a.dat" left + ascii (fp) : "./hrtfs/elev15/R15e300a.dat" right [ 21, 13 ] = ascii (fp) : "./hrtfs/elev15/L15e295a.dat" left + ascii (fp) : "./hrtfs/elev15/R15e295a.dat" right [ 21, 14 ] = ascii (fp) : "./hrtfs/elev15/L15e290a.dat" left + ascii (fp) : "./hrtfs/elev15/R15e290a.dat" right [ 21, 15 ] = ascii (fp) : "./hrtfs/elev15/L15e285a.dat" left + ascii (fp) : "./hrtfs/elev15/R15e285a.dat" right [ 21, 16 ] = ascii (fp) : "./hrtfs/elev15/L15e280a.dat" left + ascii (fp) : "./hrtfs/elev15/R15e280a.dat" right [ 21, 17 ] = ascii (fp) : "./hrtfs/elev15/L15e275a.dat" left + ascii (fp) : "./hrtfs/elev15/R15e275a.dat" right [ 21, 18 ] = ascii (fp) : "./hrtfs/elev15/L15e270a.dat" left + ascii (fp) : "./hrtfs/elev15/R15e270a.dat" right [ 21, 19 ] = ascii (fp) : "./hrtfs/elev15/L15e265a.dat" left + ascii (fp) : "./hrtfs/elev15/R15e265a.dat" right [ 21, 20 ] = ascii (fp) : "./hrtfs/elev15/L15e260a.dat" left + ascii (fp) : "./hrtfs/elev15/R15e260a.dat" right [ 21, 21 ] = ascii (fp) : "./hrtfs/elev15/L15e255a.dat" left + ascii (fp) : "./hrtfs/elev15/R15e255a.dat" right [ 21, 22 ] = ascii (fp) : "./hrtfs/elev15/L15e250a.dat" left + ascii (fp) : "./hrtfs/elev15/R15e250a.dat" right [ 21, 23 ] = ascii (fp) : "./hrtfs/elev15/L15e245a.dat" left + ascii (fp) : "./hrtfs/elev15/R15e245a.dat" right [ 21, 24 ] = ascii (fp) : "./hrtfs/elev15/L15e240a.dat" left + ascii (fp) : "./hrtfs/elev15/R15e240a.dat" right [ 21, 25 ] = ascii (fp) : "./hrtfs/elev15/L15e235a.dat" left + ascii (fp) : "./hrtfs/elev15/R15e235a.dat" right [ 21, 26 ] = ascii (fp) : "./hrtfs/elev15/L15e230a.dat" left + ascii (fp) : "./hrtfs/elev15/R15e230a.dat" right [ 21, 27 ] = ascii (fp) : "./hrtfs/elev15/L15e225a.dat" left + ascii (fp) : "./hrtfs/elev15/R15e225a.dat" right [ 21, 28 ] = ascii (fp) : "./hrtfs/elev15/L15e220a.dat" left + ascii (fp) : "./hrtfs/elev15/R15e220a.dat" right [ 21, 29 ] = ascii (fp) : "./hrtfs/elev15/L15e215a.dat" left + ascii (fp) : "./hrtfs/elev15/R15e215a.dat" right [ 21, 30 ] = ascii (fp) : "./hrtfs/elev15/L15e210a.dat" left + ascii (fp) : "./hrtfs/elev15/R15e210a.dat" right [ 21, 31 ] = ascii (fp) : "./hrtfs/elev15/L15e205a.dat" left + ascii (fp) : "./hrtfs/elev15/R15e205a.dat" right [ 21, 32 ] = ascii (fp) : "./hrtfs/elev15/L15e200a.dat" left + ascii (fp) : "./hrtfs/elev15/R15e200a.dat" right [ 21, 33 ] = ascii (fp) : "./hrtfs/elev15/L15e195a.dat" left + ascii (fp) : "./hrtfs/elev15/R15e195a.dat" right [ 21, 34 ] = ascii (fp) : "./hrtfs/elev15/L15e190a.dat" left + ascii (fp) : "./hrtfs/elev15/R15e190a.dat" right [ 21, 35 ] = ascii (fp) : "./hrtfs/elev15/L15e185a.dat" left + ascii (fp) : "./hrtfs/elev15/R15e185a.dat" right [ 21, 36 ] = ascii (fp) : "./hrtfs/elev15/L15e180a.dat" left + ascii (fp) : "./hrtfs/elev15/R15e180a.dat" right [ 21, 37 ] = ascii (fp) : "./hrtfs/elev15/L15e175a.dat" left + ascii (fp) : "./hrtfs/elev15/R15e175a.dat" right [ 21, 38 ] = ascii (fp) : "./hrtfs/elev15/L15e170a.dat" left + ascii (fp) : "./hrtfs/elev15/R15e170a.dat" right [ 21, 39 ] = ascii (fp) : "./hrtfs/elev15/L15e165a.dat" left + ascii (fp) : "./hrtfs/elev15/R15e165a.dat" right [ 21, 40 ] = ascii (fp) : "./hrtfs/elev15/L15e160a.dat" left + ascii (fp) : "./hrtfs/elev15/R15e160a.dat" right [ 21, 41 ] = ascii (fp) : "./hrtfs/elev15/L15e155a.dat" left + ascii (fp) : "./hrtfs/elev15/R15e155a.dat" right [ 21, 42 ] = ascii (fp) : "./hrtfs/elev15/L15e150a.dat" left + ascii (fp) : "./hrtfs/elev15/R15e150a.dat" right [ 21, 43 ] = ascii (fp) : "./hrtfs/elev15/L15e145a.dat" left + ascii (fp) : "./hrtfs/elev15/R15e145a.dat" right [ 21, 44 ] = ascii (fp) : "./hrtfs/elev15/L15e140a.dat" left + ascii (fp) : "./hrtfs/elev15/R15e140a.dat" right [ 21, 45 ] = ascii (fp) : "./hrtfs/elev15/L15e135a.dat" left + ascii (fp) : "./hrtfs/elev15/R15e135a.dat" right [ 21, 46 ] = ascii (fp) : "./hrtfs/elev15/L15e130a.dat" left + ascii (fp) : "./hrtfs/elev15/R15e130a.dat" right [ 21, 47 ] = ascii (fp) : "./hrtfs/elev15/L15e125a.dat" left + ascii (fp) : "./hrtfs/elev15/R15e125a.dat" right [ 21, 48 ] = ascii (fp) : "./hrtfs/elev15/L15e120a.dat" left + ascii (fp) : "./hrtfs/elev15/R15e120a.dat" right [ 21, 49 ] = ascii (fp) : "./hrtfs/elev15/L15e115a.dat" left + ascii (fp) : "./hrtfs/elev15/R15e115a.dat" right [ 21, 50 ] = ascii (fp) : "./hrtfs/elev15/L15e110a.dat" left + ascii (fp) : "./hrtfs/elev15/R15e110a.dat" right [ 21, 51 ] = ascii (fp) : "./hrtfs/elev15/L15e105a.dat" left + ascii (fp) : "./hrtfs/elev15/R15e105a.dat" right [ 21, 52 ] = ascii (fp) : "./hrtfs/elev15/L15e100a.dat" left + ascii (fp) : "./hrtfs/elev15/R15e100a.dat" right [ 21, 53 ] = ascii (fp) : "./hrtfs/elev15/L15e095a.dat" left + ascii (fp) : "./hrtfs/elev15/R15e095a.dat" right [ 21, 54 ] = ascii (fp) : "./hrtfs/elev15/L15e090a.dat" left + ascii (fp) : "./hrtfs/elev15/R15e090a.dat" right [ 21, 55 ] = ascii (fp) : "./hrtfs/elev15/L15e085a.dat" left + ascii (fp) : "./hrtfs/elev15/R15e085a.dat" right [ 21, 56 ] = ascii (fp) : "./hrtfs/elev15/L15e080a.dat" left + ascii (fp) : "./hrtfs/elev15/R15e080a.dat" right [ 21, 57 ] = ascii (fp) : "./hrtfs/elev15/L15e075a.dat" left + ascii (fp) : "./hrtfs/elev15/R15e075a.dat" right [ 21, 58 ] = ascii (fp) : "./hrtfs/elev15/L15e070a.dat" left + ascii (fp) : "./hrtfs/elev15/R15e070a.dat" right [ 21, 59 ] = ascii (fp) : "./hrtfs/elev15/L15e065a.dat" left + ascii (fp) : "./hrtfs/elev15/R15e065a.dat" right [ 21, 60 ] = ascii (fp) : "./hrtfs/elev15/L15e060a.dat" left + ascii (fp) : "./hrtfs/elev15/R15e060a.dat" right [ 21, 61 ] = ascii (fp) : "./hrtfs/elev15/L15e055a.dat" left + ascii (fp) : "./hrtfs/elev15/R15e055a.dat" right [ 21, 62 ] = ascii (fp) : "./hrtfs/elev15/L15e050a.dat" left + ascii (fp) : "./hrtfs/elev15/R15e050a.dat" right [ 21, 63 ] = ascii (fp) : "./hrtfs/elev15/L15e045a.dat" left + ascii (fp) : "./hrtfs/elev15/R15e045a.dat" right [ 21, 64 ] = ascii (fp) : "./hrtfs/elev15/L15e040a.dat" left + ascii (fp) : "./hrtfs/elev15/R15e040a.dat" right [ 21, 65 ] = ascii (fp) : "./hrtfs/elev15/L15e035a.dat" left + ascii (fp) : "./hrtfs/elev15/R15e035a.dat" right [ 21, 66 ] = ascii (fp) : "./hrtfs/elev15/L15e030a.dat" left + ascii (fp) : "./hrtfs/elev15/R15e030a.dat" right [ 21, 67 ] = ascii (fp) : "./hrtfs/elev15/L15e025a.dat" left + ascii (fp) : "./hrtfs/elev15/R15e025a.dat" right [ 21, 68 ] = ascii (fp) : "./hrtfs/elev15/L15e020a.dat" left + ascii (fp) : "./hrtfs/elev15/R15e020a.dat" right [ 21, 69 ] = ascii (fp) : "./hrtfs/elev15/L15e015a.dat" left + ascii (fp) : "./hrtfs/elev15/R15e015a.dat" right [ 21, 70 ] = ascii (fp) : "./hrtfs/elev15/L15e010a.dat" left + ascii (fp) : "./hrtfs/elev15/R15e010a.dat" right [ 21, 71 ] = ascii (fp) : "./hrtfs/elev15/L15e005a.dat" left + ascii (fp) : "./hrtfs/elev15/R15e005a.dat" right [ 22, 0 ] = ascii (fp) : "./hrtfs/elev20/L20e000a.dat" left + ascii (fp) : "./hrtfs/elev20/R20e000a.dat" right [ 22, 1 ] = ascii (fp) : "./hrtfs/elev20/L20e355a.dat" left + ascii (fp) : "./hrtfs/elev20/R20e355a.dat" right [ 22, 2 ] = ascii (fp) : "./hrtfs/elev20/L20e350a.dat" left + ascii (fp) : "./hrtfs/elev20/R20e350a.dat" right [ 22, 3 ] = ascii (fp) : "./hrtfs/elev20/L20e345a.dat" left + ascii (fp) : "./hrtfs/elev20/R20e345a.dat" right [ 22, 4 ] = ascii (fp) : "./hrtfs/elev20/L20e340a.dat" left + ascii (fp) : "./hrtfs/elev20/R20e340a.dat" right [ 22, 5 ] = ascii (fp) : "./hrtfs/elev20/L20e335a.dat" left + ascii (fp) : "./hrtfs/elev20/R20e335a.dat" right [ 22, 6 ] = ascii (fp) : "./hrtfs/elev20/L20e330a.dat" left + ascii (fp) : "./hrtfs/elev20/R20e330a.dat" right [ 22, 7 ] = ascii (fp) : "./hrtfs/elev20/L20e325a.dat" left + ascii (fp) : "./hrtfs/elev20/R20e325a.dat" right [ 22, 8 ] = ascii (fp) : "./hrtfs/elev20/L20e320a.dat" left + ascii (fp) : "./hrtfs/elev20/R20e320a.dat" right [ 22, 9 ] = ascii (fp) : "./hrtfs/elev20/L20e315a.dat" left + ascii (fp) : "./hrtfs/elev20/R20e315a.dat" right [ 22, 10 ] = ascii (fp) : "./hrtfs/elev20/L20e310a.dat" left + ascii (fp) : "./hrtfs/elev20/R20e310a.dat" right [ 22, 11 ] = ascii (fp) : "./hrtfs/elev20/L20e305a.dat" left + ascii (fp) : "./hrtfs/elev20/R20e305a.dat" right [ 22, 12 ] = ascii (fp) : "./hrtfs/elev20/L20e300a.dat" left + ascii (fp) : "./hrtfs/elev20/R20e300a.dat" right [ 22, 13 ] = ascii (fp) : "./hrtfs/elev20/L20e295a.dat" left + ascii (fp) : "./hrtfs/elev20/R20e295a.dat" right [ 22, 14 ] = ascii (fp) : "./hrtfs/elev20/L20e290a.dat" left + ascii (fp) : "./hrtfs/elev20/R20e290a.dat" right [ 22, 15 ] = ascii (fp) : "./hrtfs/elev20/L20e285a.dat" left + ascii (fp) : "./hrtfs/elev20/R20e285a.dat" right [ 22, 16 ] = ascii (fp) : "./hrtfs/elev20/L20e280a.dat" left + ascii (fp) : "./hrtfs/elev20/R20e280a.dat" right [ 22, 17 ] = ascii (fp) : "./hrtfs/elev20/L20e275a.dat" left + ascii (fp) : "./hrtfs/elev20/R20e275a.dat" right [ 22, 18 ] = ascii (fp) : "./hrtfs/elev20/L20e270a.dat" left + ascii (fp) : "./hrtfs/elev20/R20e270a.dat" right [ 22, 19 ] = ascii (fp) : "./hrtfs/elev20/L20e265a.dat" left + ascii (fp) : "./hrtfs/elev20/R20e265a.dat" right [ 22, 20 ] = ascii (fp) : "./hrtfs/elev20/L20e260a.dat" left + ascii (fp) : "./hrtfs/elev20/R20e260a.dat" right [ 22, 21 ] = ascii (fp) : "./hrtfs/elev20/L20e255a.dat" left + ascii (fp) : "./hrtfs/elev20/R20e255a.dat" right [ 22, 22 ] = ascii (fp) : "./hrtfs/elev20/L20e250a.dat" left + ascii (fp) : "./hrtfs/elev20/R20e250a.dat" right [ 22, 23 ] = ascii (fp) : "./hrtfs/elev20/L20e245a.dat" left + ascii (fp) : "./hrtfs/elev20/R20e245a.dat" right [ 22, 24 ] = ascii (fp) : "./hrtfs/elev20/L20e240a.dat" left + ascii (fp) : "./hrtfs/elev20/R20e240a.dat" right [ 22, 25 ] = ascii (fp) : "./hrtfs/elev20/L20e235a.dat" left + ascii (fp) : "./hrtfs/elev20/R20e235a.dat" right [ 22, 26 ] = ascii (fp) : "./hrtfs/elev20/L20e230a.dat" left + ascii (fp) : "./hrtfs/elev20/R20e230a.dat" right [ 22, 27 ] = ascii (fp) : "./hrtfs/elev20/L20e225a.dat" left + ascii (fp) : "./hrtfs/elev20/R20e225a.dat" right [ 22, 28 ] = ascii (fp) : "./hrtfs/elev20/L20e220a.dat" left + ascii (fp) : "./hrtfs/elev20/R20e220a.dat" right [ 22, 29 ] = ascii (fp) : "./hrtfs/elev20/L20e215a.dat" left + ascii (fp) : "./hrtfs/elev20/R20e215a.dat" right [ 22, 30 ] = ascii (fp) : "./hrtfs/elev20/L20e210a.dat" left + ascii (fp) : "./hrtfs/elev20/R20e210a.dat" right [ 22, 31 ] = ascii (fp) : "./hrtfs/elev20/L20e205a.dat" left + ascii (fp) : "./hrtfs/elev20/R20e205a.dat" right [ 22, 32 ] = ascii (fp) : "./hrtfs/elev20/L20e200a.dat" left + ascii (fp) : "./hrtfs/elev20/R20e200a.dat" right [ 22, 33 ] = ascii (fp) : "./hrtfs/elev20/L20e195a.dat" left + ascii (fp) : "./hrtfs/elev20/R20e195a.dat" right [ 22, 34 ] = ascii (fp) : "./hrtfs/elev20/L20e190a.dat" left + ascii (fp) : "./hrtfs/elev20/R20e190a.dat" right [ 22, 35 ] = ascii (fp) : "./hrtfs/elev20/L20e185a.dat" left + ascii (fp) : "./hrtfs/elev20/R20e185a.dat" right [ 22, 36 ] = ascii (fp) : "./hrtfs/elev20/L20e180a.dat" left + ascii (fp) : "./hrtfs/elev20/R20e180a.dat" right [ 22, 37 ] = ascii (fp) : "./hrtfs/elev20/L20e175a.dat" left + ascii (fp) : "./hrtfs/elev20/R20e175a.dat" right [ 22, 38 ] = ascii (fp) : "./hrtfs/elev20/L20e170a.dat" left + ascii (fp) : "./hrtfs/elev20/R20e170a.dat" right [ 22, 39 ] = ascii (fp) : "./hrtfs/elev20/L20e165a.dat" left + ascii (fp) : "./hrtfs/elev20/R20e165a.dat" right [ 22, 40 ] = ascii (fp) : "./hrtfs/elev20/L20e160a.dat" left + ascii (fp) : "./hrtfs/elev20/R20e160a.dat" right [ 22, 41 ] = ascii (fp) : "./hrtfs/elev20/L20e155a.dat" left + ascii (fp) : "./hrtfs/elev20/R20e155a.dat" right [ 22, 42 ] = ascii (fp) : "./hrtfs/elev20/L20e150a.dat" left + ascii (fp) : "./hrtfs/elev20/R20e150a.dat" right [ 22, 43 ] = ascii (fp) : "./hrtfs/elev20/L20e145a.dat" left + ascii (fp) : "./hrtfs/elev20/R20e145a.dat" right [ 22, 44 ] = ascii (fp) : "./hrtfs/elev20/L20e140a.dat" left + ascii (fp) : "./hrtfs/elev20/R20e140a.dat" right [ 22, 45 ] = ascii (fp) : "./hrtfs/elev20/L20e135a.dat" left + ascii (fp) : "./hrtfs/elev20/R20e135a.dat" right [ 22, 46 ] = ascii (fp) : "./hrtfs/elev20/L20e130a.dat" left + ascii (fp) : "./hrtfs/elev20/R20e130a.dat" right [ 22, 47 ] = ascii (fp) : "./hrtfs/elev20/L20e125a.dat" left + ascii (fp) : "./hrtfs/elev20/R20e125a.dat" right [ 22, 48 ] = ascii (fp) : "./hrtfs/elev20/L20e120a.dat" left + ascii (fp) : "./hrtfs/elev20/R20e120a.dat" right [ 22, 49 ] = ascii (fp) : "./hrtfs/elev20/L20e115a.dat" left + ascii (fp) : "./hrtfs/elev20/R20e115a.dat" right [ 22, 50 ] = ascii (fp) : "./hrtfs/elev20/L20e110a.dat" left + ascii (fp) : "./hrtfs/elev20/R20e110a.dat" right [ 22, 51 ] = ascii (fp) : "./hrtfs/elev20/L20e105a.dat" left + ascii (fp) : "./hrtfs/elev20/R20e105a.dat" right [ 22, 52 ] = ascii (fp) : "./hrtfs/elev20/L20e100a.dat" left + ascii (fp) : "./hrtfs/elev20/R20e100a.dat" right [ 22, 53 ] = ascii (fp) : "./hrtfs/elev20/L20e095a.dat" left + ascii (fp) : "./hrtfs/elev20/R20e095a.dat" right [ 22, 54 ] = ascii (fp) : "./hrtfs/elev20/L20e090a.dat" left + ascii (fp) : "./hrtfs/elev20/R20e090a.dat" right [ 22, 55 ] = ascii (fp) : "./hrtfs/elev20/L20e085a.dat" left + ascii (fp) : "./hrtfs/elev20/R20e085a.dat" right [ 22, 56 ] = ascii (fp) : "./hrtfs/elev20/L20e080a.dat" left + ascii (fp) : "./hrtfs/elev20/R20e080a.dat" right [ 22, 57 ] = ascii (fp) : "./hrtfs/elev20/L20e075a.dat" left + ascii (fp) : "./hrtfs/elev20/R20e075a.dat" right [ 22, 58 ] = ascii (fp) : "./hrtfs/elev20/L20e070a.dat" left + ascii (fp) : "./hrtfs/elev20/R20e070a.dat" right [ 22, 59 ] = ascii (fp) : "./hrtfs/elev20/L20e065a.dat" left + ascii (fp) : "./hrtfs/elev20/R20e065a.dat" right [ 22, 60 ] = ascii (fp) : "./hrtfs/elev20/L20e060a.dat" left + ascii (fp) : "./hrtfs/elev20/R20e060a.dat" right [ 22, 61 ] = ascii (fp) : "./hrtfs/elev20/L20e055a.dat" left + ascii (fp) : "./hrtfs/elev20/R20e055a.dat" right [ 22, 62 ] = ascii (fp) : "./hrtfs/elev20/L20e050a.dat" left + ascii (fp) : "./hrtfs/elev20/R20e050a.dat" right [ 22, 63 ] = ascii (fp) : "./hrtfs/elev20/L20e045a.dat" left + ascii (fp) : "./hrtfs/elev20/R20e045a.dat" right [ 22, 64 ] = ascii (fp) : "./hrtfs/elev20/L20e040a.dat" left + ascii (fp) : "./hrtfs/elev20/R20e040a.dat" right [ 22, 65 ] = ascii (fp) : "./hrtfs/elev20/L20e035a.dat" left + ascii (fp) : "./hrtfs/elev20/R20e035a.dat" right [ 22, 66 ] = ascii (fp) : "./hrtfs/elev20/L20e030a.dat" left + ascii (fp) : "./hrtfs/elev20/R20e030a.dat" right [ 22, 67 ] = ascii (fp) : "./hrtfs/elev20/L20e025a.dat" left + ascii (fp) : "./hrtfs/elev20/R20e025a.dat" right [ 22, 68 ] = ascii (fp) : "./hrtfs/elev20/L20e020a.dat" left + ascii (fp) : "./hrtfs/elev20/R20e020a.dat" right [ 22, 69 ] = ascii (fp) : "./hrtfs/elev20/L20e015a.dat" left + ascii (fp) : "./hrtfs/elev20/R20e015a.dat" right [ 22, 70 ] = ascii (fp) : "./hrtfs/elev20/L20e010a.dat" left + ascii (fp) : "./hrtfs/elev20/R20e010a.dat" right [ 22, 71 ] = ascii (fp) : "./hrtfs/elev20/L20e005a.dat" left + ascii (fp) : "./hrtfs/elev20/R20e005a.dat" right [ 23, 0 ] = ascii (fp) : "./hrtfs/elev25/L25e000a.dat" left + ascii (fp) : "./hrtfs/elev25/R25e000a.dat" right [ 23, 1 ] = ascii (fp) : "./hrtfs/elev25/L25e355a.dat" left + ascii (fp) : "./hrtfs/elev25/R25e355a.dat" right [ 23, 2 ] = ascii (fp) : "./hrtfs/elev25/L25e350a.dat" left + ascii (fp) : "./hrtfs/elev25/R25e350a.dat" right [ 23, 3 ] = ascii (fp) : "./hrtfs/elev25/L25e345a.dat" left + ascii (fp) : "./hrtfs/elev25/R25e345a.dat" right [ 23, 4 ] = ascii (fp) : "./hrtfs/elev25/L25e340a.dat" left + ascii (fp) : "./hrtfs/elev25/R25e340a.dat" right [ 23, 5 ] = ascii (fp) : "./hrtfs/elev25/L25e335a.dat" left + ascii (fp) : "./hrtfs/elev25/R25e335a.dat" right [ 23, 6 ] = ascii (fp) : "./hrtfs/elev25/L25e330a.dat" left + ascii (fp) : "./hrtfs/elev25/R25e330a.dat" right [ 23, 7 ] = ascii (fp) : "./hrtfs/elev25/L25e325a.dat" left + ascii (fp) : "./hrtfs/elev25/R25e325a.dat" right [ 23, 8 ] = ascii (fp) : "./hrtfs/elev25/L25e320a.dat" left + ascii (fp) : "./hrtfs/elev25/R25e320a.dat" right [ 23, 9 ] = ascii (fp) : "./hrtfs/elev25/L25e315a.dat" left + ascii (fp) : "./hrtfs/elev25/R25e315a.dat" right [ 23, 10 ] = ascii (fp) : "./hrtfs/elev25/L25e310a.dat" left + ascii (fp) : "./hrtfs/elev25/R25e310a.dat" right [ 23, 11 ] = ascii (fp) : "./hrtfs/elev25/L25e305a.dat" left + ascii (fp) : "./hrtfs/elev25/R25e305a.dat" right [ 23, 12 ] = ascii (fp) : "./hrtfs/elev25/L25e300a.dat" left + ascii (fp) : "./hrtfs/elev25/R25e300a.dat" right [ 23, 13 ] = ascii (fp) : "./hrtfs/elev25/L25e295a.dat" left + ascii (fp) : "./hrtfs/elev25/R25e295a.dat" right [ 23, 14 ] = ascii (fp) : "./hrtfs/elev25/L25e290a.dat" left + ascii (fp) : "./hrtfs/elev25/R25e290a.dat" right [ 23, 15 ] = ascii (fp) : "./hrtfs/elev25/L25e285a.dat" left + ascii (fp) : "./hrtfs/elev25/R25e285a.dat" right [ 23, 16 ] = ascii (fp) : "./hrtfs/elev25/L25e280a.dat" left + ascii (fp) : "./hrtfs/elev25/R25e280a.dat" right [ 23, 17 ] = ascii (fp) : "./hrtfs/elev25/L25e275a.dat" left + ascii (fp) : "./hrtfs/elev25/R25e275a.dat" right [ 23, 18 ] = ascii (fp) : "./hrtfs/elev25/L25e270a.dat" left + ascii (fp) : "./hrtfs/elev25/R25e270a.dat" right [ 23, 19 ] = ascii (fp) : "./hrtfs/elev25/L25e265a.dat" left + ascii (fp) : "./hrtfs/elev25/R25e265a.dat" right [ 23, 20 ] = ascii (fp) : "./hrtfs/elev25/L25e260a.dat" left + ascii (fp) : "./hrtfs/elev25/R25e260a.dat" right [ 23, 21 ] = ascii (fp) : "./hrtfs/elev25/L25e255a.dat" left + ascii (fp) : "./hrtfs/elev25/R25e255a.dat" right [ 23, 22 ] = ascii (fp) : "./hrtfs/elev25/L25e250a.dat" left + ascii (fp) : "./hrtfs/elev25/R25e250a.dat" right [ 23, 23 ] = ascii (fp) : "./hrtfs/elev25/L25e245a.dat" left + ascii (fp) : "./hrtfs/elev25/R25e245a.dat" right [ 23, 24 ] = ascii (fp) : "./hrtfs/elev25/L25e240a.dat" left + ascii (fp) : "./hrtfs/elev25/R25e240a.dat" right [ 23, 25 ] = ascii (fp) : "./hrtfs/elev25/L25e235a.dat" left + ascii (fp) : "./hrtfs/elev25/R25e235a.dat" right [ 23, 26 ] = ascii (fp) : "./hrtfs/elev25/L25e230a.dat" left + ascii (fp) : "./hrtfs/elev25/R25e230a.dat" right [ 23, 27 ] = ascii (fp) : "./hrtfs/elev25/L25e225a.dat" left + ascii (fp) : "./hrtfs/elev25/R25e225a.dat" right [ 23, 28 ] = ascii (fp) : "./hrtfs/elev25/L25e220a.dat" left + ascii (fp) : "./hrtfs/elev25/R25e220a.dat" right [ 23, 29 ] = ascii (fp) : "./hrtfs/elev25/L25e215a.dat" left + ascii (fp) : "./hrtfs/elev25/R25e215a.dat" right [ 23, 30 ] = ascii (fp) : "./hrtfs/elev25/L25e210a.dat" left + ascii (fp) : "./hrtfs/elev25/R25e210a.dat" right [ 23, 31 ] = ascii (fp) : "./hrtfs/elev25/L25e205a.dat" left + ascii (fp) : "./hrtfs/elev25/R25e205a.dat" right [ 23, 32 ] = ascii (fp) : "./hrtfs/elev25/L25e200a.dat" left + ascii (fp) : "./hrtfs/elev25/R25e200a.dat" right [ 23, 33 ] = ascii (fp) : "./hrtfs/elev25/L25e195a.dat" left + ascii (fp) : "./hrtfs/elev25/R25e195a.dat" right [ 23, 34 ] = ascii (fp) : "./hrtfs/elev25/L25e190a.dat" left + ascii (fp) : "./hrtfs/elev25/R25e190a.dat" right [ 23, 35 ] = ascii (fp) : "./hrtfs/elev25/L25e185a.dat" left + ascii (fp) : "./hrtfs/elev25/R25e185a.dat" right [ 23, 36 ] = ascii (fp) : "./hrtfs/elev25/L25e180a.dat" left + ascii (fp) : "./hrtfs/elev25/R25e180a.dat" right [ 23, 37 ] = ascii (fp) : "./hrtfs/elev25/L25e175a.dat" left + ascii (fp) : "./hrtfs/elev25/R25e175a.dat" right [ 23, 38 ] = ascii (fp) : "./hrtfs/elev25/L25e170a.dat" left + ascii (fp) : "./hrtfs/elev25/R25e170a.dat" right [ 23, 39 ] = ascii (fp) : "./hrtfs/elev25/L25e165a.dat" left + ascii (fp) : "./hrtfs/elev25/R25e165a.dat" right [ 23, 40 ] = ascii (fp) : "./hrtfs/elev25/L25e160a.dat" left + ascii (fp) : "./hrtfs/elev25/R25e160a.dat" right [ 23, 41 ] = ascii (fp) : "./hrtfs/elev25/L25e155a.dat" left + ascii (fp) : "./hrtfs/elev25/R25e155a.dat" right [ 23, 42 ] = ascii (fp) : "./hrtfs/elev25/L25e150a.dat" left + ascii (fp) : "./hrtfs/elev25/R25e150a.dat" right [ 23, 43 ] = ascii (fp) : "./hrtfs/elev25/L25e145a.dat" left + ascii (fp) : "./hrtfs/elev25/R25e145a.dat" right [ 23, 44 ] = ascii (fp) : "./hrtfs/elev25/L25e140a.dat" left + ascii (fp) : "./hrtfs/elev25/R25e140a.dat" right [ 23, 45 ] = ascii (fp) : "./hrtfs/elev25/L25e135a.dat" left + ascii (fp) : "./hrtfs/elev25/R25e135a.dat" right [ 23, 46 ] = ascii (fp) : "./hrtfs/elev25/L25e130a.dat" left + ascii (fp) : "./hrtfs/elev25/R25e130a.dat" right [ 23, 47 ] = ascii (fp) : "./hrtfs/elev25/L25e125a.dat" left + ascii (fp) : "./hrtfs/elev25/R25e125a.dat" right [ 23, 48 ] = ascii (fp) : "./hrtfs/elev25/L25e120a.dat" left + ascii (fp) : "./hrtfs/elev25/R25e120a.dat" right [ 23, 49 ] = ascii (fp) : "./hrtfs/elev25/L25e115a.dat" left + ascii (fp) : "./hrtfs/elev25/R25e115a.dat" right [ 23, 50 ] = ascii (fp) : "./hrtfs/elev25/L25e110a.dat" left + ascii (fp) : "./hrtfs/elev25/R25e110a.dat" right [ 23, 51 ] = ascii (fp) : "./hrtfs/elev25/L25e105a.dat" left + ascii (fp) : "./hrtfs/elev25/R25e105a.dat" right [ 23, 52 ] = ascii (fp) : "./hrtfs/elev25/L25e100a.dat" left + ascii (fp) : "./hrtfs/elev25/R25e100a.dat" right [ 23, 53 ] = ascii (fp) : "./hrtfs/elev25/L25e095a.dat" left + ascii (fp) : "./hrtfs/elev25/R25e095a.dat" right [ 23, 54 ] = ascii (fp) : "./hrtfs/elev25/L25e090a.dat" left + ascii (fp) : "./hrtfs/elev25/R25e090a.dat" right [ 23, 55 ] = ascii (fp) : "./hrtfs/elev25/L25e085a.dat" left + ascii (fp) : "./hrtfs/elev25/R25e085a.dat" right [ 23, 56 ] = ascii (fp) : "./hrtfs/elev25/L25e080a.dat" left + ascii (fp) : "./hrtfs/elev25/R25e080a.dat" right [ 23, 57 ] = ascii (fp) : "./hrtfs/elev25/L25e075a.dat" left + ascii (fp) : "./hrtfs/elev25/R25e075a.dat" right [ 23, 58 ] = ascii (fp) : "./hrtfs/elev25/L25e070a.dat" left + ascii (fp) : "./hrtfs/elev25/R25e070a.dat" right [ 23, 59 ] = ascii (fp) : "./hrtfs/elev25/L25e065a.dat" left + ascii (fp) : "./hrtfs/elev25/R25e065a.dat" right [ 23, 60 ] = ascii (fp) : "./hrtfs/elev25/L25e060a.dat" left + ascii (fp) : "./hrtfs/elev25/R25e060a.dat" right [ 23, 61 ] = ascii (fp) : "./hrtfs/elev25/L25e055a.dat" left + ascii (fp) : "./hrtfs/elev25/R25e055a.dat" right [ 23, 62 ] = ascii (fp) : "./hrtfs/elev25/L25e050a.dat" left + ascii (fp) : "./hrtfs/elev25/R25e050a.dat" right [ 23, 63 ] = ascii (fp) : "./hrtfs/elev25/L25e045a.dat" left + ascii (fp) : "./hrtfs/elev25/R25e045a.dat" right [ 23, 64 ] = ascii (fp) : "./hrtfs/elev25/L25e040a.dat" left + ascii (fp) : "./hrtfs/elev25/R25e040a.dat" right [ 23, 65 ] = ascii (fp) : "./hrtfs/elev25/L25e035a.dat" left + ascii (fp) : "./hrtfs/elev25/R25e035a.dat" right [ 23, 66 ] = ascii (fp) : "./hrtfs/elev25/L25e030a.dat" left + ascii (fp) : "./hrtfs/elev25/R25e030a.dat" right [ 23, 67 ] = ascii (fp) : "./hrtfs/elev25/L25e025a.dat" left + ascii (fp) : "./hrtfs/elev25/R25e025a.dat" right [ 23, 68 ] = ascii (fp) : "./hrtfs/elev25/L25e020a.dat" left + ascii (fp) : "./hrtfs/elev25/R25e020a.dat" right [ 23, 69 ] = ascii (fp) : "./hrtfs/elev25/L25e015a.dat" left + ascii (fp) : "./hrtfs/elev25/R25e015a.dat" right [ 23, 70 ] = ascii (fp) : "./hrtfs/elev25/L25e010a.dat" left + ascii (fp) : "./hrtfs/elev25/R25e010a.dat" right [ 23, 71 ] = ascii (fp) : "./hrtfs/elev25/L25e005a.dat" left + ascii (fp) : "./hrtfs/elev25/R25e005a.dat" right [ 24, 0 ] = ascii (fp) : "./hrtfs/elev30/L30e000a.dat" left + ascii (fp) : "./hrtfs/elev30/R30e000a.dat" right [ 24, 1 ] = ascii (fp) : "./hrtfs/elev30/L30e355a.dat" left + ascii (fp) : "./hrtfs/elev30/R30e355a.dat" right [ 24, 2 ] = ascii (fp) : "./hrtfs/elev30/L30e350a.dat" left + ascii (fp) : "./hrtfs/elev30/R30e350a.dat" right [ 24, 3 ] = ascii (fp) : "./hrtfs/elev30/L30e345a.dat" left + ascii (fp) : "./hrtfs/elev30/R30e345a.dat" right [ 24, 4 ] = ascii (fp) : "./hrtfs/elev30/L30e340a.dat" left + ascii (fp) : "./hrtfs/elev30/R30e340a.dat" right [ 24, 5 ] = ascii (fp) : "./hrtfs/elev30/L30e335a.dat" left + ascii (fp) : "./hrtfs/elev30/R30e335a.dat" right [ 24, 6 ] = ascii (fp) : "./hrtfs/elev30/L30e330a.dat" left + ascii (fp) : "./hrtfs/elev30/R30e330a.dat" right [ 24, 7 ] = ascii (fp) : "./hrtfs/elev30/L30e325a.dat" left + ascii (fp) : "./hrtfs/elev30/R30e325a.dat" right [ 24, 8 ] = ascii (fp) : "./hrtfs/elev30/L30e320a.dat" left + ascii (fp) : "./hrtfs/elev30/R30e320a.dat" right [ 24, 9 ] = ascii (fp) : "./hrtfs/elev30/L30e315a.dat" left + ascii (fp) : "./hrtfs/elev30/R30e315a.dat" right [ 24, 10 ] = ascii (fp) : "./hrtfs/elev30/L30e310a.dat" left + ascii (fp) : "./hrtfs/elev30/R30e310a.dat" right [ 24, 11 ] = ascii (fp) : "./hrtfs/elev30/L30e305a.dat" left + ascii (fp) : "./hrtfs/elev30/R30e305a.dat" right [ 24, 12 ] = ascii (fp) : "./hrtfs/elev30/L30e300a.dat" left + ascii (fp) : "./hrtfs/elev30/R30e300a.dat" right [ 24, 13 ] = ascii (fp) : "./hrtfs/elev30/L30e295a.dat" left + ascii (fp) : "./hrtfs/elev30/R30e295a.dat" right [ 24, 14 ] = ascii (fp) : "./hrtfs/elev30/L30e290a.dat" left + ascii (fp) : "./hrtfs/elev30/R30e290a.dat" right [ 24, 15 ] = ascii (fp) : "./hrtfs/elev30/L30e285a.dat" left + ascii (fp) : "./hrtfs/elev30/R30e285a.dat" right [ 24, 16 ] = ascii (fp) : "./hrtfs/elev30/L30e280a.dat" left + ascii (fp) : "./hrtfs/elev30/R30e280a.dat" right [ 24, 17 ] = ascii (fp) : "./hrtfs/elev30/L30e275a.dat" left + ascii (fp) : "./hrtfs/elev30/R30e275a.dat" right [ 24, 18 ] = ascii (fp) : "./hrtfs/elev30/L30e270a.dat" left + ascii (fp) : "./hrtfs/elev30/R30e270a.dat" right [ 24, 19 ] = ascii (fp) : "./hrtfs/elev30/L30e265a.dat" left + ascii (fp) : "./hrtfs/elev30/R30e265a.dat" right [ 24, 20 ] = ascii (fp) : "./hrtfs/elev30/L30e260a.dat" left + ascii (fp) : "./hrtfs/elev30/R30e260a.dat" right [ 24, 21 ] = ascii (fp) : "./hrtfs/elev30/L30e255a.dat" left + ascii (fp) : "./hrtfs/elev30/R30e255a.dat" right [ 24, 22 ] = ascii (fp) : "./hrtfs/elev30/L30e250a.dat" left + ascii (fp) : "./hrtfs/elev30/R30e250a.dat" right [ 24, 23 ] = ascii (fp) : "./hrtfs/elev30/L30e245a.dat" left + ascii (fp) : "./hrtfs/elev30/R30e245a.dat" right [ 24, 24 ] = ascii (fp) : "./hrtfs/elev30/L30e240a.dat" left + ascii (fp) : "./hrtfs/elev30/R30e240a.dat" right [ 24, 25 ] = ascii (fp) : "./hrtfs/elev30/L30e235a.dat" left + ascii (fp) : "./hrtfs/elev30/R30e235a.dat" right [ 24, 26 ] = ascii (fp) : "./hrtfs/elev30/L30e230a.dat" left + ascii (fp) : "./hrtfs/elev30/R30e230a.dat" right [ 24, 27 ] = ascii (fp) : "./hrtfs/elev30/L30e225a.dat" left + ascii (fp) : "./hrtfs/elev30/R30e225a.dat" right [ 24, 28 ] = ascii (fp) : "./hrtfs/elev30/L30e220a.dat" left + ascii (fp) : "./hrtfs/elev30/R30e220a.dat" right [ 24, 29 ] = ascii (fp) : "./hrtfs/elev30/L30e215a.dat" left + ascii (fp) : "./hrtfs/elev30/R30e215a.dat" right [ 24, 30 ] = ascii (fp) : "./hrtfs/elev30/L30e210a.dat" left + ascii (fp) : "./hrtfs/elev30/R30e210a.dat" right [ 24, 31 ] = ascii (fp) : "./hrtfs/elev30/L30e205a.dat" left + ascii (fp) : "./hrtfs/elev30/R30e205a.dat" right [ 24, 32 ] = ascii (fp) : "./hrtfs/elev30/L30e200a.dat" left + ascii (fp) : "./hrtfs/elev30/R30e200a.dat" right [ 24, 33 ] = ascii (fp) : "./hrtfs/elev30/L30e195a.dat" left + ascii (fp) : "./hrtfs/elev30/R30e195a.dat" right [ 24, 34 ] = ascii (fp) : "./hrtfs/elev30/L30e190a.dat" left + ascii (fp) : "./hrtfs/elev30/R30e190a.dat" right [ 24, 35 ] = ascii (fp) : "./hrtfs/elev30/L30e185a.dat" left + ascii (fp) : "./hrtfs/elev30/R30e185a.dat" right [ 24, 36 ] = ascii (fp) : "./hrtfs/elev30/L30e180a.dat" left + ascii (fp) : "./hrtfs/elev30/R30e180a.dat" right [ 24, 37 ] = ascii (fp) : "./hrtfs/elev30/L30e175a.dat" left + ascii (fp) : "./hrtfs/elev30/R30e175a.dat" right [ 24, 38 ] = ascii (fp) : "./hrtfs/elev30/L30e170a.dat" left + ascii (fp) : "./hrtfs/elev30/R30e170a.dat" right [ 24, 39 ] = ascii (fp) : "./hrtfs/elev30/L30e165a.dat" left + ascii (fp) : "./hrtfs/elev30/R30e165a.dat" right [ 24, 40 ] = ascii (fp) : "./hrtfs/elev30/L30e160a.dat" left + ascii (fp) : "./hrtfs/elev30/R30e160a.dat" right [ 24, 41 ] = ascii (fp) : "./hrtfs/elev30/L30e155a.dat" left + ascii (fp) : "./hrtfs/elev30/R30e155a.dat" right [ 24, 42 ] = ascii (fp) : "./hrtfs/elev30/L30e150a.dat" left + ascii (fp) : "./hrtfs/elev30/R30e150a.dat" right [ 24, 43 ] = ascii (fp) : "./hrtfs/elev30/L30e145a.dat" left + ascii (fp) : "./hrtfs/elev30/R30e145a.dat" right [ 24, 44 ] = ascii (fp) : "./hrtfs/elev30/L30e140a.dat" left + ascii (fp) : "./hrtfs/elev30/R30e140a.dat" right [ 24, 45 ] = ascii (fp) : "./hrtfs/elev30/L30e135a.dat" left + ascii (fp) : "./hrtfs/elev30/R30e135a.dat" right [ 24, 46 ] = ascii (fp) : "./hrtfs/elev30/L30e130a.dat" left + ascii (fp) : "./hrtfs/elev30/R30e130a.dat" right [ 24, 47 ] = ascii (fp) : "./hrtfs/elev30/L30e125a.dat" left + ascii (fp) : "./hrtfs/elev30/R30e125a.dat" right [ 24, 48 ] = ascii (fp) : "./hrtfs/elev30/L30e120a.dat" left + ascii (fp) : "./hrtfs/elev30/R30e120a.dat" right [ 24, 49 ] = ascii (fp) : "./hrtfs/elev30/L30e115a.dat" left + ascii (fp) : "./hrtfs/elev30/R30e115a.dat" right [ 24, 50 ] = ascii (fp) : "./hrtfs/elev30/L30e110a.dat" left + ascii (fp) : "./hrtfs/elev30/R30e110a.dat" right [ 24, 51 ] = ascii (fp) : "./hrtfs/elev30/L30e105a.dat" left + ascii (fp) : "./hrtfs/elev30/R30e105a.dat" right [ 24, 52 ] = ascii (fp) : "./hrtfs/elev30/L30e100a.dat" left + ascii (fp) : "./hrtfs/elev30/R30e100a.dat" right [ 24, 53 ] = ascii (fp) : "./hrtfs/elev30/L30e095a.dat" left + ascii (fp) : "./hrtfs/elev30/R30e095a.dat" right [ 24, 54 ] = ascii (fp) : "./hrtfs/elev30/L30e090a.dat" left + ascii (fp) : "./hrtfs/elev30/R30e090a.dat" right [ 24, 55 ] = ascii (fp) : "./hrtfs/elev30/L30e085a.dat" left + ascii (fp) : "./hrtfs/elev30/R30e085a.dat" right [ 24, 56 ] = ascii (fp) : "./hrtfs/elev30/L30e080a.dat" left + ascii (fp) : "./hrtfs/elev30/R30e080a.dat" right [ 24, 57 ] = ascii (fp) : "./hrtfs/elev30/L30e075a.dat" left + ascii (fp) : "./hrtfs/elev30/R30e075a.dat" right [ 24, 58 ] = ascii (fp) : "./hrtfs/elev30/L30e070a.dat" left + ascii (fp) : "./hrtfs/elev30/R30e070a.dat" right [ 24, 59 ] = ascii (fp) : "./hrtfs/elev30/L30e065a.dat" left + ascii (fp) : "./hrtfs/elev30/R30e065a.dat" right [ 24, 60 ] = ascii (fp) : "./hrtfs/elev30/L30e060a.dat" left + ascii (fp) : "./hrtfs/elev30/R30e060a.dat" right [ 24, 61 ] = ascii (fp) : "./hrtfs/elev30/L30e055a.dat" left + ascii (fp) : "./hrtfs/elev30/R30e055a.dat" right [ 24, 62 ] = ascii (fp) : "./hrtfs/elev30/L30e050a.dat" left + ascii (fp) : "./hrtfs/elev30/R30e050a.dat" right [ 24, 63 ] = ascii (fp) : "./hrtfs/elev30/L30e045a.dat" left + ascii (fp) : "./hrtfs/elev30/R30e045a.dat" right [ 24, 64 ] = ascii (fp) : "./hrtfs/elev30/L30e040a.dat" left + ascii (fp) : "./hrtfs/elev30/R30e040a.dat" right [ 24, 65 ] = ascii (fp) : "./hrtfs/elev30/L30e035a.dat" left + ascii (fp) : "./hrtfs/elev30/R30e035a.dat" right [ 24, 66 ] = ascii (fp) : "./hrtfs/elev30/L30e030a.dat" left + ascii (fp) : "./hrtfs/elev30/R30e030a.dat" right [ 24, 67 ] = ascii (fp) : "./hrtfs/elev30/L30e025a.dat" left + ascii (fp) : "./hrtfs/elev30/R30e025a.dat" right [ 24, 68 ] = ascii (fp) : "./hrtfs/elev30/L30e020a.dat" left + ascii (fp) : "./hrtfs/elev30/R30e020a.dat" right [ 24, 69 ] = ascii (fp) : "./hrtfs/elev30/L30e015a.dat" left + ascii (fp) : "./hrtfs/elev30/R30e015a.dat" right [ 24, 70 ] = ascii (fp) : "./hrtfs/elev30/L30e010a.dat" left + ascii (fp) : "./hrtfs/elev30/R30e010a.dat" right [ 24, 71 ] = ascii (fp) : "./hrtfs/elev30/L30e005a.dat" left + ascii (fp) : "./hrtfs/elev30/R30e005a.dat" right [ 25, 0 ] = ascii (fp) : "./hrtfs/elev35/L35e000a.dat" left + ascii (fp) : "./hrtfs/elev35/R35e000a.dat" right [ 25, 1 ] = ascii (fp) : "./hrtfs/elev35/L35e355a.dat" left + ascii (fp) : "./hrtfs/elev35/R35e355a.dat" right [ 25, 2 ] = ascii (fp) : "./hrtfs/elev35/L35e350a.dat" left + ascii (fp) : "./hrtfs/elev35/R35e350a.dat" right [ 25, 3 ] = ascii (fp) : "./hrtfs/elev35/L35e345a.dat" left + ascii (fp) : "./hrtfs/elev35/R35e345a.dat" right [ 25, 4 ] = ascii (fp) : "./hrtfs/elev35/L35e340a.dat" left + ascii (fp) : "./hrtfs/elev35/R35e340a.dat" right [ 25, 5 ] = ascii (fp) : "./hrtfs/elev35/L35e335a.dat" left + ascii (fp) : "./hrtfs/elev35/R35e335a.dat" right [ 25, 6 ] = ascii (fp) : "./hrtfs/elev35/L35e330a.dat" left + ascii (fp) : "./hrtfs/elev35/R35e330a.dat" right [ 25, 7 ] = ascii (fp) : "./hrtfs/elev35/L35e325a.dat" left + ascii (fp) : "./hrtfs/elev35/R35e325a.dat" right [ 25, 8 ] = ascii (fp) : "./hrtfs/elev35/L35e320a.dat" left + ascii (fp) : "./hrtfs/elev35/R35e320a.dat" right [ 25, 9 ] = ascii (fp) : "./hrtfs/elev35/L35e315a.dat" left + ascii (fp) : "./hrtfs/elev35/R35e315a.dat" right [ 25, 10 ] = ascii (fp) : "./hrtfs/elev35/L35e310a.dat" left + ascii (fp) : "./hrtfs/elev35/R35e310a.dat" right [ 25, 11 ] = ascii (fp) : "./hrtfs/elev35/L35e305a.dat" left + ascii (fp) : "./hrtfs/elev35/R35e305a.dat" right [ 25, 12 ] = ascii (fp) : "./hrtfs/elev35/L35e300a.dat" left + ascii (fp) : "./hrtfs/elev35/R35e300a.dat" right [ 25, 13 ] = ascii (fp) : "./hrtfs/elev35/L35e295a.dat" left + ascii (fp) : "./hrtfs/elev35/R35e295a.dat" right [ 25, 14 ] = ascii (fp) : "./hrtfs/elev35/L35e290a.dat" left + ascii (fp) : "./hrtfs/elev35/R35e290a.dat" right [ 25, 15 ] = ascii (fp) : "./hrtfs/elev35/L35e285a.dat" left + ascii (fp) : "./hrtfs/elev35/R35e285a.dat" right [ 25, 16 ] = ascii (fp) : "./hrtfs/elev35/L35e280a.dat" left + ascii (fp) : "./hrtfs/elev35/R35e280a.dat" right [ 25, 17 ] = ascii (fp) : "./hrtfs/elev35/L35e275a.dat" left + ascii (fp) : "./hrtfs/elev35/R35e275a.dat" right [ 25, 18 ] = ascii (fp) : "./hrtfs/elev35/L35e270a.dat" left + ascii (fp) : "./hrtfs/elev35/R35e270a.dat" right [ 25, 19 ] = ascii (fp) : "./hrtfs/elev35/L35e265a.dat" left + ascii (fp) : "./hrtfs/elev35/R35e265a.dat" right [ 25, 20 ] = ascii (fp) : "./hrtfs/elev35/L35e260a.dat" left + ascii (fp) : "./hrtfs/elev35/R35e260a.dat" right [ 25, 21 ] = ascii (fp) : "./hrtfs/elev35/L35e255a.dat" left + ascii (fp) : "./hrtfs/elev35/R35e255a.dat" right [ 25, 22 ] = ascii (fp) : "./hrtfs/elev35/L35e250a.dat" left + ascii (fp) : "./hrtfs/elev35/R35e250a.dat" right [ 25, 23 ] = ascii (fp) : "./hrtfs/elev35/L35e245a.dat" left + ascii (fp) : "./hrtfs/elev35/R35e245a.dat" right [ 25, 24 ] = ascii (fp) : "./hrtfs/elev35/L35e240a.dat" left + ascii (fp) : "./hrtfs/elev35/R35e240a.dat" right [ 25, 25 ] = ascii (fp) : "./hrtfs/elev35/L35e235a.dat" left + ascii (fp) : "./hrtfs/elev35/R35e235a.dat" right [ 25, 26 ] = ascii (fp) : "./hrtfs/elev35/L35e230a.dat" left + ascii (fp) : "./hrtfs/elev35/R35e230a.dat" right [ 25, 27 ] = ascii (fp) : "./hrtfs/elev35/L35e225a.dat" left + ascii (fp) : "./hrtfs/elev35/R35e225a.dat" right [ 25, 28 ] = ascii (fp) : "./hrtfs/elev35/L35e220a.dat" left + ascii (fp) : "./hrtfs/elev35/R35e220a.dat" right [ 25, 29 ] = ascii (fp) : "./hrtfs/elev35/L35e215a.dat" left + ascii (fp) : "./hrtfs/elev35/R35e215a.dat" right [ 25, 30 ] = ascii (fp) : "./hrtfs/elev35/L35e210a.dat" left + ascii (fp) : "./hrtfs/elev35/R35e210a.dat" right [ 25, 31 ] = ascii (fp) : "./hrtfs/elev35/L35e205a.dat" left + ascii (fp) : "./hrtfs/elev35/R35e205a.dat" right [ 25, 32 ] = ascii (fp) : "./hrtfs/elev35/L35e200a.dat" left + ascii (fp) : "./hrtfs/elev35/R35e200a.dat" right [ 25, 33 ] = ascii (fp) : "./hrtfs/elev35/L35e195a.dat" left + ascii (fp) : "./hrtfs/elev35/R35e195a.dat" right [ 25, 34 ] = ascii (fp) : "./hrtfs/elev35/L35e190a.dat" left + ascii (fp) : "./hrtfs/elev35/R35e190a.dat" right [ 25, 35 ] = ascii (fp) : "./hrtfs/elev35/L35e185a.dat" left + ascii (fp) : "./hrtfs/elev35/R35e185a.dat" right [ 25, 36 ] = ascii (fp) : "./hrtfs/elev35/L35e180a.dat" left + ascii (fp) : "./hrtfs/elev35/R35e180a.dat" right [ 25, 37 ] = ascii (fp) : "./hrtfs/elev35/L35e175a.dat" left + ascii (fp) : "./hrtfs/elev35/R35e175a.dat" right [ 25, 38 ] = ascii (fp) : "./hrtfs/elev35/L35e170a.dat" left + ascii (fp) : "./hrtfs/elev35/R35e170a.dat" right [ 25, 39 ] = ascii (fp) : "./hrtfs/elev35/L35e165a.dat" left + ascii (fp) : "./hrtfs/elev35/R35e165a.dat" right [ 25, 40 ] = ascii (fp) : "./hrtfs/elev35/L35e160a.dat" left + ascii (fp) : "./hrtfs/elev35/R35e160a.dat" right [ 25, 41 ] = ascii (fp) : "./hrtfs/elev35/L35e155a.dat" left + ascii (fp) : "./hrtfs/elev35/R35e155a.dat" right [ 25, 42 ] = ascii (fp) : "./hrtfs/elev35/L35e150a.dat" left + ascii (fp) : "./hrtfs/elev35/R35e150a.dat" right [ 25, 43 ] = ascii (fp) : "./hrtfs/elev35/L35e145a.dat" left + ascii (fp) : "./hrtfs/elev35/R35e145a.dat" right [ 25, 44 ] = ascii (fp) : "./hrtfs/elev35/L35e140a.dat" left + ascii (fp) : "./hrtfs/elev35/R35e140a.dat" right [ 25, 45 ] = ascii (fp) : "./hrtfs/elev35/L35e135a.dat" left + ascii (fp) : "./hrtfs/elev35/R35e135a.dat" right [ 25, 46 ] = ascii (fp) : "./hrtfs/elev35/L35e130a.dat" left + ascii (fp) : "./hrtfs/elev35/R35e130a.dat" right [ 25, 47 ] = ascii (fp) : "./hrtfs/elev35/L35e125a.dat" left + ascii (fp) : "./hrtfs/elev35/R35e125a.dat" right [ 25, 48 ] = ascii (fp) : "./hrtfs/elev35/L35e120a.dat" left + ascii (fp) : "./hrtfs/elev35/R35e120a.dat" right [ 25, 49 ] = ascii (fp) : "./hrtfs/elev35/L35e115a.dat" left + ascii (fp) : "./hrtfs/elev35/R35e115a.dat" right [ 25, 50 ] = ascii (fp) : "./hrtfs/elev35/L35e110a.dat" left + ascii (fp) : "./hrtfs/elev35/R35e110a.dat" right [ 25, 51 ] = ascii (fp) : "./hrtfs/elev35/L35e105a.dat" left + ascii (fp) : "./hrtfs/elev35/R35e105a.dat" right [ 25, 52 ] = ascii (fp) : "./hrtfs/elev35/L35e100a.dat" left + ascii (fp) : "./hrtfs/elev35/R35e100a.dat" right [ 25, 53 ] = ascii (fp) : "./hrtfs/elev35/L35e095a.dat" left + ascii (fp) : "./hrtfs/elev35/R35e095a.dat" right [ 25, 54 ] = ascii (fp) : "./hrtfs/elev35/L35e090a.dat" left + ascii (fp) : "./hrtfs/elev35/R35e090a.dat" right [ 25, 55 ] = ascii (fp) : "./hrtfs/elev35/L35e085a.dat" left + ascii (fp) : "./hrtfs/elev35/R35e085a.dat" right [ 25, 56 ] = ascii (fp) : "./hrtfs/elev35/L35e080a.dat" left + ascii (fp) : "./hrtfs/elev35/R35e080a.dat" right [ 25, 57 ] = ascii (fp) : "./hrtfs/elev35/L35e075a.dat" left + ascii (fp) : "./hrtfs/elev35/R35e075a.dat" right [ 25, 58 ] = ascii (fp) : "./hrtfs/elev35/L35e070a.dat" left + ascii (fp) : "./hrtfs/elev35/R35e070a.dat" right [ 25, 59 ] = ascii (fp) : "./hrtfs/elev35/L35e065a.dat" left + ascii (fp) : "./hrtfs/elev35/R35e065a.dat" right [ 25, 60 ] = ascii (fp) : "./hrtfs/elev35/L35e060a.dat" left + ascii (fp) : "./hrtfs/elev35/R35e060a.dat" right [ 25, 61 ] = ascii (fp) : "./hrtfs/elev35/L35e055a.dat" left + ascii (fp) : "./hrtfs/elev35/R35e055a.dat" right [ 25, 62 ] = ascii (fp) : "./hrtfs/elev35/L35e050a.dat" left + ascii (fp) : "./hrtfs/elev35/R35e050a.dat" right [ 25, 63 ] = ascii (fp) : "./hrtfs/elev35/L35e045a.dat" left + ascii (fp) : "./hrtfs/elev35/R35e045a.dat" right [ 25, 64 ] = ascii (fp) : "./hrtfs/elev35/L35e040a.dat" left + ascii (fp) : "./hrtfs/elev35/R35e040a.dat" right [ 25, 65 ] = ascii (fp) : "./hrtfs/elev35/L35e035a.dat" left + ascii (fp) : "./hrtfs/elev35/R35e035a.dat" right [ 25, 66 ] = ascii (fp) : "./hrtfs/elev35/L35e030a.dat" left + ascii (fp) : "./hrtfs/elev35/R35e030a.dat" right [ 25, 67 ] = ascii (fp) : "./hrtfs/elev35/L35e025a.dat" left + ascii (fp) : "./hrtfs/elev35/R35e025a.dat" right [ 25, 68 ] = ascii (fp) : "./hrtfs/elev35/L35e020a.dat" left + ascii (fp) : "./hrtfs/elev35/R35e020a.dat" right [ 25, 69 ] = ascii (fp) : "./hrtfs/elev35/L35e015a.dat" left + ascii (fp) : "./hrtfs/elev35/R35e015a.dat" right [ 25, 70 ] = ascii (fp) : "./hrtfs/elev35/L35e010a.dat" left + ascii (fp) : "./hrtfs/elev35/R35e010a.dat" right [ 25, 71 ] = ascii (fp) : "./hrtfs/elev35/L35e005a.dat" left + ascii (fp) : "./hrtfs/elev35/R35e005a.dat" right [ 26, 0 ] = ascii (fp) : "./hrtfs/elev40/L40e000a.dat" left + ascii (fp) : "./hrtfs/elev40/R40e000a.dat" right [ 26, 1 ] = ascii (fp) : "./hrtfs/elev40/L40e355a.dat" left + ascii (fp) : "./hrtfs/elev40/R40e355a.dat" right [ 26, 2 ] = ascii (fp) : "./hrtfs/elev40/L40e350a.dat" left + ascii (fp) : "./hrtfs/elev40/R40e350a.dat" right [ 26, 3 ] = ascii (fp) : "./hrtfs/elev40/L40e345a.dat" left + ascii (fp) : "./hrtfs/elev40/R40e345a.dat" right [ 26, 4 ] = ascii (fp) : "./hrtfs/elev40/L40e340a.dat" left + ascii (fp) : "./hrtfs/elev40/R40e340a.dat" right [ 26, 5 ] = ascii (fp) : "./hrtfs/elev40/L40e335a.dat" left + ascii (fp) : "./hrtfs/elev40/R40e335a.dat" right [ 26, 6 ] = ascii (fp) : "./hrtfs/elev40/L40e330a.dat" left + ascii (fp) : "./hrtfs/elev40/R40e330a.dat" right [ 26, 7 ] = ascii (fp) : "./hrtfs/elev40/L40e325a.dat" left + ascii (fp) : "./hrtfs/elev40/R40e325a.dat" right [ 26, 8 ] = ascii (fp) : "./hrtfs/elev40/L40e320a.dat" left + ascii (fp) : "./hrtfs/elev40/R40e320a.dat" right [ 26, 9 ] = ascii (fp) : "./hrtfs/elev40/L40e315a.dat" left + ascii (fp) : "./hrtfs/elev40/R40e315a.dat" right [ 26, 10 ] = ascii (fp) : "./hrtfs/elev40/L40e310a.dat" left + ascii (fp) : "./hrtfs/elev40/R40e310a.dat" right [ 26, 11 ] = ascii (fp) : "./hrtfs/elev40/L40e305a.dat" left + ascii (fp) : "./hrtfs/elev40/R40e305a.dat" right [ 26, 12 ] = ascii (fp) : "./hrtfs/elev40/L40e300a.dat" left + ascii (fp) : "./hrtfs/elev40/R40e300a.dat" right [ 26, 13 ] = ascii (fp) : "./hrtfs/elev40/L40e295a.dat" left + ascii (fp) : "./hrtfs/elev40/R40e295a.dat" right [ 26, 14 ] = ascii (fp) : "./hrtfs/elev40/L40e290a.dat" left + ascii (fp) : "./hrtfs/elev40/R40e290a.dat" right [ 26, 15 ] = ascii (fp) : "./hrtfs/elev40/L40e285a.dat" left + ascii (fp) : "./hrtfs/elev40/R40e285a.dat" right [ 26, 16 ] = ascii (fp) : "./hrtfs/elev40/L40e280a.dat" left + ascii (fp) : "./hrtfs/elev40/R40e280a.dat" right [ 26, 17 ] = ascii (fp) : "./hrtfs/elev40/L40e275a.dat" left + ascii (fp) : "./hrtfs/elev40/R40e275a.dat" right [ 26, 18 ] = ascii (fp) : "./hrtfs/elev40/L40e270a.dat" left + ascii (fp) : "./hrtfs/elev40/R40e270a.dat" right [ 26, 19 ] = ascii (fp) : "./hrtfs/elev40/L40e265a.dat" left + ascii (fp) : "./hrtfs/elev40/R40e265a.dat" right [ 26, 20 ] = ascii (fp) : "./hrtfs/elev40/L40e260a.dat" left + ascii (fp) : "./hrtfs/elev40/R40e260a.dat" right [ 26, 21 ] = ascii (fp) : "./hrtfs/elev40/L40e255a.dat" left + ascii (fp) : "./hrtfs/elev40/R40e255a.dat" right [ 26, 22 ] = ascii (fp) : "./hrtfs/elev40/L40e250a.dat" left + ascii (fp) : "./hrtfs/elev40/R40e250a.dat" right [ 26, 23 ] = ascii (fp) : "./hrtfs/elev40/L40e245a.dat" left + ascii (fp) : "./hrtfs/elev40/R40e245a.dat" right [ 26, 24 ] = ascii (fp) : "./hrtfs/elev40/L40e240a.dat" left + ascii (fp) : "./hrtfs/elev40/R40e240a.dat" right [ 26, 25 ] = ascii (fp) : "./hrtfs/elev40/L40e235a.dat" left + ascii (fp) : "./hrtfs/elev40/R40e235a.dat" right [ 26, 26 ] = ascii (fp) : "./hrtfs/elev40/L40e230a.dat" left + ascii (fp) : "./hrtfs/elev40/R40e230a.dat" right [ 26, 27 ] = ascii (fp) : "./hrtfs/elev40/L40e225a.dat" left + ascii (fp) : "./hrtfs/elev40/R40e225a.dat" right [ 26, 28 ] = ascii (fp) : "./hrtfs/elev40/L40e220a.dat" left + ascii (fp) : "./hrtfs/elev40/R40e220a.dat" right [ 26, 29 ] = ascii (fp) : "./hrtfs/elev40/L40e215a.dat" left + ascii (fp) : "./hrtfs/elev40/R40e215a.dat" right [ 26, 30 ] = ascii (fp) : "./hrtfs/elev40/L40e210a.dat" left + ascii (fp) : "./hrtfs/elev40/R40e210a.dat" right [ 26, 31 ] = ascii (fp) : "./hrtfs/elev40/L40e205a.dat" left + ascii (fp) : "./hrtfs/elev40/R40e205a.dat" right [ 26, 32 ] = ascii (fp) : "./hrtfs/elev40/L40e200a.dat" left + ascii (fp) : "./hrtfs/elev40/R40e200a.dat" right [ 26, 33 ] = ascii (fp) : "./hrtfs/elev40/L40e195a.dat" left + ascii (fp) : "./hrtfs/elev40/R40e195a.dat" right [ 26, 34 ] = ascii (fp) : "./hrtfs/elev40/L40e190a.dat" left + ascii (fp) : "./hrtfs/elev40/R40e190a.dat" right [ 26, 35 ] = ascii (fp) : "./hrtfs/elev40/L40e185a.dat" left + ascii (fp) : "./hrtfs/elev40/R40e185a.dat" right [ 26, 36 ] = ascii (fp) : "./hrtfs/elev40/L40e180a.dat" left + ascii (fp) : "./hrtfs/elev40/R40e180a.dat" right [ 26, 37 ] = ascii (fp) : "./hrtfs/elev40/L40e175a.dat" left + ascii (fp) : "./hrtfs/elev40/R40e175a.dat" right [ 26, 38 ] = ascii (fp) : "./hrtfs/elev40/L40e170a.dat" left + ascii (fp) : "./hrtfs/elev40/R40e170a.dat" right [ 26, 39 ] = ascii (fp) : "./hrtfs/elev40/L40e165a.dat" left + ascii (fp) : "./hrtfs/elev40/R40e165a.dat" right [ 26, 40 ] = ascii (fp) : "./hrtfs/elev40/L40e160a.dat" left + ascii (fp) : "./hrtfs/elev40/R40e160a.dat" right [ 26, 41 ] = ascii (fp) : "./hrtfs/elev40/L40e155a.dat" left + ascii (fp) : "./hrtfs/elev40/R40e155a.dat" right [ 26, 42 ] = ascii (fp) : "./hrtfs/elev40/L40e150a.dat" left + ascii (fp) : "./hrtfs/elev40/R40e150a.dat" right [ 26, 43 ] = ascii (fp) : "./hrtfs/elev40/L40e145a.dat" left + ascii (fp) : "./hrtfs/elev40/R40e145a.dat" right [ 26, 44 ] = ascii (fp) : "./hrtfs/elev40/L40e140a.dat" left + ascii (fp) : "./hrtfs/elev40/R40e140a.dat" right [ 26, 45 ] = ascii (fp) : "./hrtfs/elev40/L40e135a.dat" left + ascii (fp) : "./hrtfs/elev40/R40e135a.dat" right [ 26, 46 ] = ascii (fp) : "./hrtfs/elev40/L40e130a.dat" left + ascii (fp) : "./hrtfs/elev40/R40e130a.dat" right [ 26, 47 ] = ascii (fp) : "./hrtfs/elev40/L40e125a.dat" left + ascii (fp) : "./hrtfs/elev40/R40e125a.dat" right [ 26, 48 ] = ascii (fp) : "./hrtfs/elev40/L40e120a.dat" left + ascii (fp) : "./hrtfs/elev40/R40e120a.dat" right [ 26, 49 ] = ascii (fp) : "./hrtfs/elev40/L40e115a.dat" left + ascii (fp) : "./hrtfs/elev40/R40e115a.dat" right [ 26, 50 ] = ascii (fp) : "./hrtfs/elev40/L40e110a.dat" left + ascii (fp) : "./hrtfs/elev40/R40e110a.dat" right [ 26, 51 ] = ascii (fp) : "./hrtfs/elev40/L40e105a.dat" left + ascii (fp) : "./hrtfs/elev40/R40e105a.dat" right [ 26, 52 ] = ascii (fp) : "./hrtfs/elev40/L40e100a.dat" left + ascii (fp) : "./hrtfs/elev40/R40e100a.dat" right [ 26, 53 ] = ascii (fp) : "./hrtfs/elev40/L40e095a.dat" left + ascii (fp) : "./hrtfs/elev40/R40e095a.dat" right [ 26, 54 ] = ascii (fp) : "./hrtfs/elev40/L40e090a.dat" left + ascii (fp) : "./hrtfs/elev40/R40e090a.dat" right [ 26, 55 ] = ascii (fp) : "./hrtfs/elev40/L40e085a.dat" left + ascii (fp) : "./hrtfs/elev40/R40e085a.dat" right [ 26, 56 ] = ascii (fp) : "./hrtfs/elev40/L40e080a.dat" left + ascii (fp) : "./hrtfs/elev40/R40e080a.dat" right [ 26, 57 ] = ascii (fp) : "./hrtfs/elev40/L40e075a.dat" left + ascii (fp) : "./hrtfs/elev40/R40e075a.dat" right [ 26, 58 ] = ascii (fp) : "./hrtfs/elev40/L40e070a.dat" left + ascii (fp) : "./hrtfs/elev40/R40e070a.dat" right [ 26, 59 ] = ascii (fp) : "./hrtfs/elev40/L40e065a.dat" left + ascii (fp) : "./hrtfs/elev40/R40e065a.dat" right [ 26, 60 ] = ascii (fp) : "./hrtfs/elev40/L40e060a.dat" left + ascii (fp) : "./hrtfs/elev40/R40e060a.dat" right [ 26, 61 ] = ascii (fp) : "./hrtfs/elev40/L40e055a.dat" left + ascii (fp) : "./hrtfs/elev40/R40e055a.dat" right [ 26, 62 ] = ascii (fp) : "./hrtfs/elev40/L40e050a.dat" left + ascii (fp) : "./hrtfs/elev40/R40e050a.dat" right [ 26, 63 ] = ascii (fp) : "./hrtfs/elev40/L40e045a.dat" left + ascii (fp) : "./hrtfs/elev40/R40e045a.dat" right [ 26, 64 ] = ascii (fp) : "./hrtfs/elev40/L40e040a.dat" left + ascii (fp) : "./hrtfs/elev40/R40e040a.dat" right [ 26, 65 ] = ascii (fp) : "./hrtfs/elev40/L40e035a.dat" left + ascii (fp) : "./hrtfs/elev40/R40e035a.dat" right [ 26, 66 ] = ascii (fp) : "./hrtfs/elev40/L40e030a.dat" left + ascii (fp) : "./hrtfs/elev40/R40e030a.dat" right [ 26, 67 ] = ascii (fp) : "./hrtfs/elev40/L40e025a.dat" left + ascii (fp) : "./hrtfs/elev40/R40e025a.dat" right [ 26, 68 ] = ascii (fp) : "./hrtfs/elev40/L40e020a.dat" left + ascii (fp) : "./hrtfs/elev40/R40e020a.dat" right [ 26, 69 ] = ascii (fp) : "./hrtfs/elev40/L40e015a.dat" left + ascii (fp) : "./hrtfs/elev40/R40e015a.dat" right [ 26, 70 ] = ascii (fp) : "./hrtfs/elev40/L40e010a.dat" left + ascii (fp) : "./hrtfs/elev40/R40e010a.dat" right [ 26, 71 ] = ascii (fp) : "./hrtfs/elev40/L40e005a.dat" left + ascii (fp) : "./hrtfs/elev40/R40e005a.dat" right [ 27, 0 ] = ascii (fp) : "./hrtfs/elev45/L45e000a.dat" left + ascii (fp) : "./hrtfs/elev45/R45e000a.dat" right [ 27, 1 ] = ascii (fp) : "./hrtfs/elev45/L45e355a.dat" left + ascii (fp) : "./hrtfs/elev45/R45e355a.dat" right [ 27, 2 ] = ascii (fp) : "./hrtfs/elev45/L45e350a.dat" left + ascii (fp) : "./hrtfs/elev45/R45e350a.dat" right [ 27, 3 ] = ascii (fp) : "./hrtfs/elev45/L45e345a.dat" left + ascii (fp) : "./hrtfs/elev45/R45e345a.dat" right [ 27, 4 ] = ascii (fp) : "./hrtfs/elev45/L45e340a.dat" left + ascii (fp) : "./hrtfs/elev45/R45e340a.dat" right [ 27, 5 ] = ascii (fp) : "./hrtfs/elev45/L45e335a.dat" left + ascii (fp) : "./hrtfs/elev45/R45e335a.dat" right [ 27, 6 ] = ascii (fp) : "./hrtfs/elev45/L45e330a.dat" left + ascii (fp) : "./hrtfs/elev45/R45e330a.dat" right [ 27, 7 ] = ascii (fp) : "./hrtfs/elev45/L45e325a.dat" left + ascii (fp) : "./hrtfs/elev45/R45e325a.dat" right [ 27, 8 ] = ascii (fp) : "./hrtfs/elev45/L45e320a.dat" left + ascii (fp) : "./hrtfs/elev45/R45e320a.dat" right [ 27, 9 ] = ascii (fp) : "./hrtfs/elev45/L45e315a.dat" left + ascii (fp) : "./hrtfs/elev45/R45e315a.dat" right [ 27, 10 ] = ascii (fp) : "./hrtfs/elev45/L45e310a.dat" left + ascii (fp) : "./hrtfs/elev45/R45e310a.dat" right [ 27, 11 ] = ascii (fp) : "./hrtfs/elev45/L45e305a.dat" left + ascii (fp) : "./hrtfs/elev45/R45e305a.dat" right [ 27, 12 ] = ascii (fp) : "./hrtfs/elev45/L45e300a.dat" left + ascii (fp) : "./hrtfs/elev45/R45e300a.dat" right [ 27, 13 ] = ascii (fp) : "./hrtfs/elev45/L45e295a.dat" left + ascii (fp) : "./hrtfs/elev45/R45e295a.dat" right [ 27, 14 ] = ascii (fp) : "./hrtfs/elev45/L45e290a.dat" left + ascii (fp) : "./hrtfs/elev45/R45e290a.dat" right [ 27, 15 ] = ascii (fp) : "./hrtfs/elev45/L45e285a.dat" left + ascii (fp) : "./hrtfs/elev45/R45e285a.dat" right [ 27, 16 ] = ascii (fp) : "./hrtfs/elev45/L45e280a.dat" left + ascii (fp) : "./hrtfs/elev45/R45e280a.dat" right [ 27, 17 ] = ascii (fp) : "./hrtfs/elev45/L45e275a.dat" left + ascii (fp) : "./hrtfs/elev45/R45e275a.dat" right [ 27, 18 ] = ascii (fp) : "./hrtfs/elev45/L45e270a.dat" left + ascii (fp) : "./hrtfs/elev45/R45e270a.dat" right [ 27, 19 ] = ascii (fp) : "./hrtfs/elev45/L45e265a.dat" left + ascii (fp) : "./hrtfs/elev45/R45e265a.dat" right [ 27, 20 ] = ascii (fp) : "./hrtfs/elev45/L45e260a.dat" left + ascii (fp) : "./hrtfs/elev45/R45e260a.dat" right [ 27, 21 ] = ascii (fp) : "./hrtfs/elev45/L45e255a.dat" left + ascii (fp) : "./hrtfs/elev45/R45e255a.dat" right [ 27, 22 ] = ascii (fp) : "./hrtfs/elev45/L45e250a.dat" left + ascii (fp) : "./hrtfs/elev45/R45e250a.dat" right [ 27, 23 ] = ascii (fp) : "./hrtfs/elev45/L45e245a.dat" left + ascii (fp) : "./hrtfs/elev45/R45e245a.dat" right [ 27, 24 ] = ascii (fp) : "./hrtfs/elev45/L45e240a.dat" left + ascii (fp) : "./hrtfs/elev45/R45e240a.dat" right [ 27, 25 ] = ascii (fp) : "./hrtfs/elev45/L45e235a.dat" left + ascii (fp) : "./hrtfs/elev45/R45e235a.dat" right [ 27, 26 ] = ascii (fp) : "./hrtfs/elev45/L45e230a.dat" left + ascii (fp) : "./hrtfs/elev45/R45e230a.dat" right [ 27, 27 ] = ascii (fp) : "./hrtfs/elev45/L45e225a.dat" left + ascii (fp) : "./hrtfs/elev45/R45e225a.dat" right [ 27, 28 ] = ascii (fp) : "./hrtfs/elev45/L45e220a.dat" left + ascii (fp) : "./hrtfs/elev45/R45e220a.dat" right [ 27, 29 ] = ascii (fp) : "./hrtfs/elev45/L45e215a.dat" left + ascii (fp) : "./hrtfs/elev45/R45e215a.dat" right [ 27, 30 ] = ascii (fp) : "./hrtfs/elev45/L45e210a.dat" left + ascii (fp) : "./hrtfs/elev45/R45e210a.dat" right [ 27, 31 ] = ascii (fp) : "./hrtfs/elev45/L45e205a.dat" left + ascii (fp) : "./hrtfs/elev45/R45e205a.dat" right [ 27, 32 ] = ascii (fp) : "./hrtfs/elev45/L45e200a.dat" left + ascii (fp) : "./hrtfs/elev45/R45e200a.dat" right [ 27, 33 ] = ascii (fp) : "./hrtfs/elev45/L45e195a.dat" left + ascii (fp) : "./hrtfs/elev45/R45e195a.dat" right [ 27, 34 ] = ascii (fp) : "./hrtfs/elev45/L45e190a.dat" left + ascii (fp) : "./hrtfs/elev45/R45e190a.dat" right [ 27, 35 ] = ascii (fp) : "./hrtfs/elev45/L45e185a.dat" left + ascii (fp) : "./hrtfs/elev45/R45e185a.dat" right [ 27, 36 ] = ascii (fp) : "./hrtfs/elev45/L45e180a.dat" left + ascii (fp) : "./hrtfs/elev45/R45e180a.dat" right [ 27, 37 ] = ascii (fp) : "./hrtfs/elev45/L45e175a.dat" left + ascii (fp) : "./hrtfs/elev45/R45e175a.dat" right [ 27, 38 ] = ascii (fp) : "./hrtfs/elev45/L45e170a.dat" left + ascii (fp) : "./hrtfs/elev45/R45e170a.dat" right [ 27, 39 ] = ascii (fp) : "./hrtfs/elev45/L45e165a.dat" left + ascii (fp) : "./hrtfs/elev45/R45e165a.dat" right [ 27, 40 ] = ascii (fp) : "./hrtfs/elev45/L45e160a.dat" left + ascii (fp) : "./hrtfs/elev45/R45e160a.dat" right [ 27, 41 ] = ascii (fp) : "./hrtfs/elev45/L45e155a.dat" left + ascii (fp) : "./hrtfs/elev45/R45e155a.dat" right [ 27, 42 ] = ascii (fp) : "./hrtfs/elev45/L45e150a.dat" left + ascii (fp) : "./hrtfs/elev45/R45e150a.dat" right [ 27, 43 ] = ascii (fp) : "./hrtfs/elev45/L45e145a.dat" left + ascii (fp) : "./hrtfs/elev45/R45e145a.dat" right [ 27, 44 ] = ascii (fp) : "./hrtfs/elev45/L45e140a.dat" left + ascii (fp) : "./hrtfs/elev45/R45e140a.dat" right [ 27, 45 ] = ascii (fp) : "./hrtfs/elev45/L45e135a.dat" left + ascii (fp) : "./hrtfs/elev45/R45e135a.dat" right [ 27, 46 ] = ascii (fp) : "./hrtfs/elev45/L45e130a.dat" left + ascii (fp) : "./hrtfs/elev45/R45e130a.dat" right [ 27, 47 ] = ascii (fp) : "./hrtfs/elev45/L45e125a.dat" left + ascii (fp) : "./hrtfs/elev45/R45e125a.dat" right [ 27, 48 ] = ascii (fp) : "./hrtfs/elev45/L45e120a.dat" left + ascii (fp) : "./hrtfs/elev45/R45e120a.dat" right [ 27, 49 ] = ascii (fp) : "./hrtfs/elev45/L45e115a.dat" left + ascii (fp) : "./hrtfs/elev45/R45e115a.dat" right [ 27, 50 ] = ascii (fp) : "./hrtfs/elev45/L45e110a.dat" left + ascii (fp) : "./hrtfs/elev45/R45e110a.dat" right [ 27, 51 ] = ascii (fp) : "./hrtfs/elev45/L45e105a.dat" left + ascii (fp) : "./hrtfs/elev45/R45e105a.dat" right [ 27, 52 ] = ascii (fp) : "./hrtfs/elev45/L45e100a.dat" left + ascii (fp) : "./hrtfs/elev45/R45e100a.dat" right [ 27, 53 ] = ascii (fp) : "./hrtfs/elev45/L45e095a.dat" left + ascii (fp) : "./hrtfs/elev45/R45e095a.dat" right [ 27, 54 ] = ascii (fp) : "./hrtfs/elev45/L45e090a.dat" left + ascii (fp) : "./hrtfs/elev45/R45e090a.dat" right [ 27, 55 ] = ascii (fp) : "./hrtfs/elev45/L45e085a.dat" left + ascii (fp) : "./hrtfs/elev45/R45e085a.dat" right [ 27, 56 ] = ascii (fp) : "./hrtfs/elev45/L45e080a.dat" left + ascii (fp) : "./hrtfs/elev45/R45e080a.dat" right [ 27, 57 ] = ascii (fp) : "./hrtfs/elev45/L45e075a.dat" left + ascii (fp) : "./hrtfs/elev45/R45e075a.dat" right [ 27, 58 ] = ascii (fp) : "./hrtfs/elev45/L45e070a.dat" left + ascii (fp) : "./hrtfs/elev45/R45e070a.dat" right [ 27, 59 ] = ascii (fp) : "./hrtfs/elev45/L45e065a.dat" left + ascii (fp) : "./hrtfs/elev45/R45e065a.dat" right [ 27, 60 ] = ascii (fp) : "./hrtfs/elev45/L45e060a.dat" left + ascii (fp) : "./hrtfs/elev45/R45e060a.dat" right [ 27, 61 ] = ascii (fp) : "./hrtfs/elev45/L45e055a.dat" left + ascii (fp) : "./hrtfs/elev45/R45e055a.dat" right [ 27, 62 ] = ascii (fp) : "./hrtfs/elev45/L45e050a.dat" left + ascii (fp) : "./hrtfs/elev45/R45e050a.dat" right [ 27, 63 ] = ascii (fp) : "./hrtfs/elev45/L45e045a.dat" left + ascii (fp) : "./hrtfs/elev45/R45e045a.dat" right [ 27, 64 ] = ascii (fp) : "./hrtfs/elev45/L45e040a.dat" left + ascii (fp) : "./hrtfs/elev45/R45e040a.dat" right [ 27, 65 ] = ascii (fp) : "./hrtfs/elev45/L45e035a.dat" left + ascii (fp) : "./hrtfs/elev45/R45e035a.dat" right [ 27, 66 ] = ascii (fp) : "./hrtfs/elev45/L45e030a.dat" left + ascii (fp) : "./hrtfs/elev45/R45e030a.dat" right [ 27, 67 ] = ascii (fp) : "./hrtfs/elev45/L45e025a.dat" left + ascii (fp) : "./hrtfs/elev45/R45e025a.dat" right [ 27, 68 ] = ascii (fp) : "./hrtfs/elev45/L45e020a.dat" left + ascii (fp) : "./hrtfs/elev45/R45e020a.dat" right [ 27, 69 ] = ascii (fp) : "./hrtfs/elev45/L45e015a.dat" left + ascii (fp) : "./hrtfs/elev45/R45e015a.dat" right [ 27, 70 ] = ascii (fp) : "./hrtfs/elev45/L45e010a.dat" left + ascii (fp) : "./hrtfs/elev45/R45e010a.dat" right [ 27, 71 ] = ascii (fp) : "./hrtfs/elev45/L45e005a.dat" left + ascii (fp) : "./hrtfs/elev45/R45e005a.dat" right [ 28, 0 ] = ascii (fp) : "./hrtfs/elev50/L50e000a.dat" left + ascii (fp) : "./hrtfs/elev50/R50e000a.dat" right [ 28, 1 ] = ascii (fp) : "./hrtfs/elev50/L50e355a.dat" left + ascii (fp) : "./hrtfs/elev50/R50e355a.dat" right [ 28, 2 ] = ascii (fp) : "./hrtfs/elev50/L50e350a.dat" left + ascii (fp) : "./hrtfs/elev50/R50e350a.dat" right [ 28, 3 ] = ascii (fp) : "./hrtfs/elev50/L50e345a.dat" left + ascii (fp) : "./hrtfs/elev50/R50e345a.dat" right [ 28, 4 ] = ascii (fp) : "./hrtfs/elev50/L50e340a.dat" left + ascii (fp) : "./hrtfs/elev50/R50e340a.dat" right [ 28, 5 ] = ascii (fp) : "./hrtfs/elev50/L50e335a.dat" left + ascii (fp) : "./hrtfs/elev50/R50e335a.dat" right [ 28, 6 ] = ascii (fp) : "./hrtfs/elev50/L50e330a.dat" left + ascii (fp) : "./hrtfs/elev50/R50e330a.dat" right [ 28, 7 ] = ascii (fp) : "./hrtfs/elev50/L50e325a.dat" left + ascii (fp) : "./hrtfs/elev50/R50e325a.dat" right [ 28, 8 ] = ascii (fp) : "./hrtfs/elev50/L50e320a.dat" left + ascii (fp) : "./hrtfs/elev50/R50e320a.dat" right [ 28, 9 ] = ascii (fp) : "./hrtfs/elev50/L50e315a.dat" left + ascii (fp) : "./hrtfs/elev50/R50e315a.dat" right [ 28, 10 ] = ascii (fp) : "./hrtfs/elev50/L50e310a.dat" left + ascii (fp) : "./hrtfs/elev50/R50e310a.dat" right [ 28, 11 ] = ascii (fp) : "./hrtfs/elev50/L50e305a.dat" left + ascii (fp) : "./hrtfs/elev50/R50e305a.dat" right [ 28, 12 ] = ascii (fp) : "./hrtfs/elev50/L50e300a.dat" left + ascii (fp) : "./hrtfs/elev50/R50e300a.dat" right [ 28, 13 ] = ascii (fp) : "./hrtfs/elev50/L50e295a.dat" left + ascii (fp) : "./hrtfs/elev50/R50e295a.dat" right [ 28, 14 ] = ascii (fp) : "./hrtfs/elev50/L50e290a.dat" left + ascii (fp) : "./hrtfs/elev50/R50e290a.dat" right [ 28, 15 ] = ascii (fp) : "./hrtfs/elev50/L50e285a.dat" left + ascii (fp) : "./hrtfs/elev50/R50e285a.dat" right [ 28, 16 ] = ascii (fp) : "./hrtfs/elev50/L50e280a.dat" left + ascii (fp) : "./hrtfs/elev50/R50e280a.dat" right [ 28, 17 ] = ascii (fp) : "./hrtfs/elev50/L50e275a.dat" left + ascii (fp) : "./hrtfs/elev50/R50e275a.dat" right [ 28, 18 ] = ascii (fp) : "./hrtfs/elev50/L50e270a.dat" left + ascii (fp) : "./hrtfs/elev50/R50e270a.dat" right [ 28, 19 ] = ascii (fp) : "./hrtfs/elev50/L50e265a.dat" left + ascii (fp) : "./hrtfs/elev50/R50e265a.dat" right [ 28, 20 ] = ascii (fp) : "./hrtfs/elev50/L50e260a.dat" left + ascii (fp) : "./hrtfs/elev50/R50e260a.dat" right [ 28, 21 ] = ascii (fp) : "./hrtfs/elev50/L50e255a.dat" left + ascii (fp) : "./hrtfs/elev50/R50e255a.dat" right [ 28, 22 ] = ascii (fp) : "./hrtfs/elev50/L50e250a.dat" left + ascii (fp) : "./hrtfs/elev50/R50e250a.dat" right [ 28, 23 ] = ascii (fp) : "./hrtfs/elev50/L50e245a.dat" left + ascii (fp) : "./hrtfs/elev50/R50e245a.dat" right [ 28, 24 ] = ascii (fp) : "./hrtfs/elev50/L50e240a.dat" left + ascii (fp) : "./hrtfs/elev50/R50e240a.dat" right [ 28, 25 ] = ascii (fp) : "./hrtfs/elev50/L50e235a.dat" left + ascii (fp) : "./hrtfs/elev50/R50e235a.dat" right [ 28, 26 ] = ascii (fp) : "./hrtfs/elev50/L50e230a.dat" left + ascii (fp) : "./hrtfs/elev50/R50e230a.dat" right [ 28, 27 ] = ascii (fp) : "./hrtfs/elev50/L50e225a.dat" left + ascii (fp) : "./hrtfs/elev50/R50e225a.dat" right [ 28, 28 ] = ascii (fp) : "./hrtfs/elev50/L50e220a.dat" left + ascii (fp) : "./hrtfs/elev50/R50e220a.dat" right [ 28, 29 ] = ascii (fp) : "./hrtfs/elev50/L50e215a.dat" left + ascii (fp) : "./hrtfs/elev50/R50e215a.dat" right [ 28, 30 ] = ascii (fp) : "./hrtfs/elev50/L50e210a.dat" left + ascii (fp) : "./hrtfs/elev50/R50e210a.dat" right [ 28, 31 ] = ascii (fp) : "./hrtfs/elev50/L50e205a.dat" left + ascii (fp) : "./hrtfs/elev50/R50e205a.dat" right [ 28, 32 ] = ascii (fp) : "./hrtfs/elev50/L50e200a.dat" left + ascii (fp) : "./hrtfs/elev50/R50e200a.dat" right [ 28, 33 ] = ascii (fp) : "./hrtfs/elev50/L50e195a.dat" left + ascii (fp) : "./hrtfs/elev50/R50e195a.dat" right [ 28, 34 ] = ascii (fp) : "./hrtfs/elev50/L50e190a.dat" left + ascii (fp) : "./hrtfs/elev50/R50e190a.dat" right [ 28, 35 ] = ascii (fp) : "./hrtfs/elev50/L50e185a.dat" left + ascii (fp) : "./hrtfs/elev50/R50e185a.dat" right [ 28, 36 ] = ascii (fp) : "./hrtfs/elev50/L50e180a.dat" left + ascii (fp) : "./hrtfs/elev50/R50e180a.dat" right [ 28, 37 ] = ascii (fp) : "./hrtfs/elev50/L50e175a.dat" left + ascii (fp) : "./hrtfs/elev50/R50e175a.dat" right [ 28, 38 ] = ascii (fp) : "./hrtfs/elev50/L50e170a.dat" left + ascii (fp) : "./hrtfs/elev50/R50e170a.dat" right [ 28, 39 ] = ascii (fp) : "./hrtfs/elev50/L50e165a.dat" left + ascii (fp) : "./hrtfs/elev50/R50e165a.dat" right [ 28, 40 ] = ascii (fp) : "./hrtfs/elev50/L50e160a.dat" left + ascii (fp) : "./hrtfs/elev50/R50e160a.dat" right [ 28, 41 ] = ascii (fp) : "./hrtfs/elev50/L50e155a.dat" left + ascii (fp) : "./hrtfs/elev50/R50e155a.dat" right [ 28, 42 ] = ascii (fp) : "./hrtfs/elev50/L50e150a.dat" left + ascii (fp) : "./hrtfs/elev50/R50e150a.dat" right [ 28, 43 ] = ascii (fp) : "./hrtfs/elev50/L50e145a.dat" left + ascii (fp) : "./hrtfs/elev50/R50e145a.dat" right [ 28, 44 ] = ascii (fp) : "./hrtfs/elev50/L50e140a.dat" left + ascii (fp) : "./hrtfs/elev50/R50e140a.dat" right [ 28, 45 ] = ascii (fp) : "./hrtfs/elev50/L50e135a.dat" left + ascii (fp) : "./hrtfs/elev50/R50e135a.dat" right [ 28, 46 ] = ascii (fp) : "./hrtfs/elev50/L50e130a.dat" left + ascii (fp) : "./hrtfs/elev50/R50e130a.dat" right [ 28, 47 ] = ascii (fp) : "./hrtfs/elev50/L50e125a.dat" left + ascii (fp) : "./hrtfs/elev50/R50e125a.dat" right [ 28, 48 ] = ascii (fp) : "./hrtfs/elev50/L50e120a.dat" left + ascii (fp) : "./hrtfs/elev50/R50e120a.dat" right [ 28, 49 ] = ascii (fp) : "./hrtfs/elev50/L50e115a.dat" left + ascii (fp) : "./hrtfs/elev50/R50e115a.dat" right [ 28, 50 ] = ascii (fp) : "./hrtfs/elev50/L50e110a.dat" left + ascii (fp) : "./hrtfs/elev50/R50e110a.dat" right [ 28, 51 ] = ascii (fp) : "./hrtfs/elev50/L50e105a.dat" left + ascii (fp) : "./hrtfs/elev50/R50e105a.dat" right [ 28, 52 ] = ascii (fp) : "./hrtfs/elev50/L50e100a.dat" left + ascii (fp) : "./hrtfs/elev50/R50e100a.dat" right [ 28, 53 ] = ascii (fp) : "./hrtfs/elev50/L50e095a.dat" left + ascii (fp) : "./hrtfs/elev50/R50e095a.dat" right [ 28, 54 ] = ascii (fp) : "./hrtfs/elev50/L50e090a.dat" left + ascii (fp) : "./hrtfs/elev50/R50e090a.dat" right [ 28, 55 ] = ascii (fp) : "./hrtfs/elev50/L50e085a.dat" left + ascii (fp) : "./hrtfs/elev50/R50e085a.dat" right [ 28, 56 ] = ascii (fp) : "./hrtfs/elev50/L50e080a.dat" left + ascii (fp) : "./hrtfs/elev50/R50e080a.dat" right [ 28, 57 ] = ascii (fp) : "./hrtfs/elev50/L50e075a.dat" left + ascii (fp) : "./hrtfs/elev50/R50e075a.dat" right [ 28, 58 ] = ascii (fp) : "./hrtfs/elev50/L50e070a.dat" left + ascii (fp) : "./hrtfs/elev50/R50e070a.dat" right [ 28, 59 ] = ascii (fp) : "./hrtfs/elev50/L50e065a.dat" left + ascii (fp) : "./hrtfs/elev50/R50e065a.dat" right [ 28, 60 ] = ascii (fp) : "./hrtfs/elev50/L50e060a.dat" left + ascii (fp) : "./hrtfs/elev50/R50e060a.dat" right [ 28, 61 ] = ascii (fp) : "./hrtfs/elev50/L50e055a.dat" left + ascii (fp) : "./hrtfs/elev50/R50e055a.dat" right [ 28, 62 ] = ascii (fp) : "./hrtfs/elev50/L50e050a.dat" left + ascii (fp) : "./hrtfs/elev50/R50e050a.dat" right [ 28, 63 ] = ascii (fp) : "./hrtfs/elev50/L50e045a.dat" left + ascii (fp) : "./hrtfs/elev50/R50e045a.dat" right [ 28, 64 ] = ascii (fp) : "./hrtfs/elev50/L50e040a.dat" left + ascii (fp) : "./hrtfs/elev50/R50e040a.dat" right [ 28, 65 ] = ascii (fp) : "./hrtfs/elev50/L50e035a.dat" left + ascii (fp) : "./hrtfs/elev50/R50e035a.dat" right [ 28, 66 ] = ascii (fp) : "./hrtfs/elev50/L50e030a.dat" left + ascii (fp) : "./hrtfs/elev50/R50e030a.dat" right [ 28, 67 ] = ascii (fp) : "./hrtfs/elev50/L50e025a.dat" left + ascii (fp) : "./hrtfs/elev50/R50e025a.dat" right [ 28, 68 ] = ascii (fp) : "./hrtfs/elev50/L50e020a.dat" left + ascii (fp) : "./hrtfs/elev50/R50e020a.dat" right [ 28, 69 ] = ascii (fp) : "./hrtfs/elev50/L50e015a.dat" left + ascii (fp) : "./hrtfs/elev50/R50e015a.dat" right [ 28, 70 ] = ascii (fp) : "./hrtfs/elev50/L50e010a.dat" left + ascii (fp) : "./hrtfs/elev50/R50e010a.dat" right [ 28, 71 ] = ascii (fp) : "./hrtfs/elev50/L50e005a.dat" left + ascii (fp) : "./hrtfs/elev50/R50e005a.dat" right [ 29, 0 ] = ascii (fp) : "./hrtfs/elev55/L55e000a.dat" left + ascii (fp) : "./hrtfs/elev55/R55e000a.dat" right [ 29, 1 ] = ascii (fp) : "./hrtfs/elev55/L55e355a.dat" left + ascii (fp) : "./hrtfs/elev55/R55e355a.dat" right [ 29, 2 ] = ascii (fp) : "./hrtfs/elev55/L55e350a.dat" left + ascii (fp) : "./hrtfs/elev55/R55e350a.dat" right [ 29, 3 ] = ascii (fp) : "./hrtfs/elev55/L55e345a.dat" left + ascii (fp) : "./hrtfs/elev55/R55e345a.dat" right [ 29, 4 ] = ascii (fp) : "./hrtfs/elev55/L55e340a.dat" left + ascii (fp) : "./hrtfs/elev55/R55e340a.dat" right [ 29, 5 ] = ascii (fp) : "./hrtfs/elev55/L55e335a.dat" left + ascii (fp) : "./hrtfs/elev55/R55e335a.dat" right [ 29, 6 ] = ascii (fp) : "./hrtfs/elev55/L55e330a.dat" left + ascii (fp) : "./hrtfs/elev55/R55e330a.dat" right [ 29, 7 ] = ascii (fp) : "./hrtfs/elev55/L55e325a.dat" left + ascii (fp) : "./hrtfs/elev55/R55e325a.dat" right [ 29, 8 ] = ascii (fp) : "./hrtfs/elev55/L55e320a.dat" left + ascii (fp) : "./hrtfs/elev55/R55e320a.dat" right [ 29, 9 ] = ascii (fp) : "./hrtfs/elev55/L55e315a.dat" left + ascii (fp) : "./hrtfs/elev55/R55e315a.dat" right [ 29, 10 ] = ascii (fp) : "./hrtfs/elev55/L55e310a.dat" left + ascii (fp) : "./hrtfs/elev55/R55e310a.dat" right [ 29, 11 ] = ascii (fp) : "./hrtfs/elev55/L55e305a.dat" left + ascii (fp) : "./hrtfs/elev55/R55e305a.dat" right [ 29, 12 ] = ascii (fp) : "./hrtfs/elev55/L55e300a.dat" left + ascii (fp) : "./hrtfs/elev55/R55e300a.dat" right [ 29, 13 ] = ascii (fp) : "./hrtfs/elev55/L55e295a.dat" left + ascii (fp) : "./hrtfs/elev55/R55e295a.dat" right [ 29, 14 ] = ascii (fp) : "./hrtfs/elev55/L55e290a.dat" left + ascii (fp) : "./hrtfs/elev55/R55e290a.dat" right [ 29, 15 ] = ascii (fp) : "./hrtfs/elev55/L55e285a.dat" left + ascii (fp) : "./hrtfs/elev55/R55e285a.dat" right [ 29, 16 ] = ascii (fp) : "./hrtfs/elev55/L55e280a.dat" left + ascii (fp) : "./hrtfs/elev55/R55e280a.dat" right [ 29, 17 ] = ascii (fp) : "./hrtfs/elev55/L55e275a.dat" left + ascii (fp) : "./hrtfs/elev55/R55e275a.dat" right [ 29, 18 ] = ascii (fp) : "./hrtfs/elev55/L55e270a.dat" left + ascii (fp) : "./hrtfs/elev55/R55e270a.dat" right [ 29, 19 ] = ascii (fp) : "./hrtfs/elev55/L55e265a.dat" left + ascii (fp) : "./hrtfs/elev55/R55e265a.dat" right [ 29, 20 ] = ascii (fp) : "./hrtfs/elev55/L55e260a.dat" left + ascii (fp) : "./hrtfs/elev55/R55e260a.dat" right [ 29, 21 ] = ascii (fp) : "./hrtfs/elev55/L55e255a.dat" left + ascii (fp) : "./hrtfs/elev55/R55e255a.dat" right [ 29, 22 ] = ascii (fp) : "./hrtfs/elev55/L55e250a.dat" left + ascii (fp) : "./hrtfs/elev55/R55e250a.dat" right [ 29, 23 ] = ascii (fp) : "./hrtfs/elev55/L55e245a.dat" left + ascii (fp) : "./hrtfs/elev55/R55e245a.dat" right [ 29, 24 ] = ascii (fp) : "./hrtfs/elev55/L55e240a.dat" left + ascii (fp) : "./hrtfs/elev55/R55e240a.dat" right [ 29, 25 ] = ascii (fp) : "./hrtfs/elev55/L55e235a.dat" left + ascii (fp) : "./hrtfs/elev55/R55e235a.dat" right [ 29, 26 ] = ascii (fp) : "./hrtfs/elev55/L55e230a.dat" left + ascii (fp) : "./hrtfs/elev55/R55e230a.dat" right [ 29, 27 ] = ascii (fp) : "./hrtfs/elev55/L55e225a.dat" left + ascii (fp) : "./hrtfs/elev55/R55e225a.dat" right [ 29, 28 ] = ascii (fp) : "./hrtfs/elev55/L55e220a.dat" left + ascii (fp) : "./hrtfs/elev55/R55e220a.dat" right [ 29, 29 ] = ascii (fp) : "./hrtfs/elev55/L55e215a.dat" left + ascii (fp) : "./hrtfs/elev55/R55e215a.dat" right [ 29, 30 ] = ascii (fp) : "./hrtfs/elev55/L55e210a.dat" left + ascii (fp) : "./hrtfs/elev55/R55e210a.dat" right [ 29, 31 ] = ascii (fp) : "./hrtfs/elev55/L55e205a.dat" left + ascii (fp) : "./hrtfs/elev55/R55e205a.dat" right [ 29, 32 ] = ascii (fp) : "./hrtfs/elev55/L55e200a.dat" left + ascii (fp) : "./hrtfs/elev55/R55e200a.dat" right [ 29, 33 ] = ascii (fp) : "./hrtfs/elev55/L55e195a.dat" left + ascii (fp) : "./hrtfs/elev55/R55e195a.dat" right [ 29, 34 ] = ascii (fp) : "./hrtfs/elev55/L55e190a.dat" left + ascii (fp) : "./hrtfs/elev55/R55e190a.dat" right [ 29, 35 ] = ascii (fp) : "./hrtfs/elev55/L55e185a.dat" left + ascii (fp) : "./hrtfs/elev55/R55e185a.dat" right [ 29, 36 ] = ascii (fp) : "./hrtfs/elev55/L55e180a.dat" left + ascii (fp) : "./hrtfs/elev55/R55e180a.dat" right [ 29, 37 ] = ascii (fp) : "./hrtfs/elev55/L55e175a.dat" left + ascii (fp) : "./hrtfs/elev55/R55e175a.dat" right [ 29, 38 ] = ascii (fp) : "./hrtfs/elev55/L55e170a.dat" left + ascii (fp) : "./hrtfs/elev55/R55e170a.dat" right [ 29, 39 ] = ascii (fp) : "./hrtfs/elev55/L55e165a.dat" left + ascii (fp) : "./hrtfs/elev55/R55e165a.dat" right [ 29, 40 ] = ascii (fp) : "./hrtfs/elev55/L55e160a.dat" left + ascii (fp) : "./hrtfs/elev55/R55e160a.dat" right [ 29, 41 ] = ascii (fp) : "./hrtfs/elev55/L55e155a.dat" left + ascii (fp) : "./hrtfs/elev55/R55e155a.dat" right [ 29, 42 ] = ascii (fp) : "./hrtfs/elev55/L55e150a.dat" left + ascii (fp) : "./hrtfs/elev55/R55e150a.dat" right [ 29, 43 ] = ascii (fp) : "./hrtfs/elev55/L55e145a.dat" left + ascii (fp) : "./hrtfs/elev55/R55e145a.dat" right [ 29, 44 ] = ascii (fp) : "./hrtfs/elev55/L55e140a.dat" left + ascii (fp) : "./hrtfs/elev55/R55e140a.dat" right [ 29, 45 ] = ascii (fp) : "./hrtfs/elev55/L55e135a.dat" left + ascii (fp) : "./hrtfs/elev55/R55e135a.dat" right [ 29, 46 ] = ascii (fp) : "./hrtfs/elev55/L55e130a.dat" left + ascii (fp) : "./hrtfs/elev55/R55e130a.dat" right [ 29, 47 ] = ascii (fp) : "./hrtfs/elev55/L55e125a.dat" left + ascii (fp) : "./hrtfs/elev55/R55e125a.dat" right [ 29, 48 ] = ascii (fp) : "./hrtfs/elev55/L55e120a.dat" left + ascii (fp) : "./hrtfs/elev55/R55e120a.dat" right [ 29, 49 ] = ascii (fp) : "./hrtfs/elev55/L55e115a.dat" left + ascii (fp) : "./hrtfs/elev55/R55e115a.dat" right [ 29, 50 ] = ascii (fp) : "./hrtfs/elev55/L55e110a.dat" left + ascii (fp) : "./hrtfs/elev55/R55e110a.dat" right [ 29, 51 ] = ascii (fp) : "./hrtfs/elev55/L55e105a.dat" left + ascii (fp) : "./hrtfs/elev55/R55e105a.dat" right [ 29, 52 ] = ascii (fp) : "./hrtfs/elev55/L55e100a.dat" left + ascii (fp) : "./hrtfs/elev55/R55e100a.dat" right [ 29, 53 ] = ascii (fp) : "./hrtfs/elev55/L55e095a.dat" left + ascii (fp) : "./hrtfs/elev55/R55e095a.dat" right [ 29, 54 ] = ascii (fp) : "./hrtfs/elev55/L55e090a.dat" left + ascii (fp) : "./hrtfs/elev55/R55e090a.dat" right [ 29, 55 ] = ascii (fp) : "./hrtfs/elev55/L55e085a.dat" left + ascii (fp) : "./hrtfs/elev55/R55e085a.dat" right [ 29, 56 ] = ascii (fp) : "./hrtfs/elev55/L55e080a.dat" left + ascii (fp) : "./hrtfs/elev55/R55e080a.dat" right [ 29, 57 ] = ascii (fp) : "./hrtfs/elev55/L55e075a.dat" left + ascii (fp) : "./hrtfs/elev55/R55e075a.dat" right [ 29, 58 ] = ascii (fp) : "./hrtfs/elev55/L55e070a.dat" left + ascii (fp) : "./hrtfs/elev55/R55e070a.dat" right [ 29, 59 ] = ascii (fp) : "./hrtfs/elev55/L55e065a.dat" left + ascii (fp) : "./hrtfs/elev55/R55e065a.dat" right [ 29, 60 ] = ascii (fp) : "./hrtfs/elev55/L55e060a.dat" left + ascii (fp) : "./hrtfs/elev55/R55e060a.dat" right [ 29, 61 ] = ascii (fp) : "./hrtfs/elev55/L55e055a.dat" left + ascii (fp) : "./hrtfs/elev55/R55e055a.dat" right [ 29, 62 ] = ascii (fp) : "./hrtfs/elev55/L55e050a.dat" left + ascii (fp) : "./hrtfs/elev55/R55e050a.dat" right [ 29, 63 ] = ascii (fp) : "./hrtfs/elev55/L55e045a.dat" left + ascii (fp) : "./hrtfs/elev55/R55e045a.dat" right [ 29, 64 ] = ascii (fp) : "./hrtfs/elev55/L55e040a.dat" left + ascii (fp) : "./hrtfs/elev55/R55e040a.dat" right [ 29, 65 ] = ascii (fp) : "./hrtfs/elev55/L55e035a.dat" left + ascii (fp) : "./hrtfs/elev55/R55e035a.dat" right [ 29, 66 ] = ascii (fp) : "./hrtfs/elev55/L55e030a.dat" left + ascii (fp) : "./hrtfs/elev55/R55e030a.dat" right [ 29, 67 ] = ascii (fp) : "./hrtfs/elev55/L55e025a.dat" left + ascii (fp) : "./hrtfs/elev55/R55e025a.dat" right [ 29, 68 ] = ascii (fp) : "./hrtfs/elev55/L55e020a.dat" left + ascii (fp) : "./hrtfs/elev55/R55e020a.dat" right [ 29, 69 ] = ascii (fp) : "./hrtfs/elev55/L55e015a.dat" left + ascii (fp) : "./hrtfs/elev55/R55e015a.dat" right [ 29, 70 ] = ascii (fp) : "./hrtfs/elev55/L55e010a.dat" left + ascii (fp) : "./hrtfs/elev55/R55e010a.dat" right [ 29, 71 ] = ascii (fp) : "./hrtfs/elev55/L55e005a.dat" left + ascii (fp) : "./hrtfs/elev55/R55e005a.dat" right [ 30, 0 ] = ascii (fp) : "./hrtfs/elev60/L60e000a.dat" left + ascii (fp) : "./hrtfs/elev60/R60e000a.dat" right [ 30, 1 ] = ascii (fp) : "./hrtfs/elev60/L60e355a.dat" left + ascii (fp) : "./hrtfs/elev60/R60e355a.dat" right [ 30, 2 ] = ascii (fp) : "./hrtfs/elev60/L60e350a.dat" left + ascii (fp) : "./hrtfs/elev60/R60e350a.dat" right [ 30, 3 ] = ascii (fp) : "./hrtfs/elev60/L60e345a.dat" left + ascii (fp) : "./hrtfs/elev60/R60e345a.dat" right [ 30, 4 ] = ascii (fp) : "./hrtfs/elev60/L60e340a.dat" left + ascii (fp) : "./hrtfs/elev60/R60e340a.dat" right [ 30, 5 ] = ascii (fp) : "./hrtfs/elev60/L60e335a.dat" left + ascii (fp) : "./hrtfs/elev60/R60e335a.dat" right [ 30, 6 ] = ascii (fp) : "./hrtfs/elev60/L60e330a.dat" left + ascii (fp) : "./hrtfs/elev60/R60e330a.dat" right [ 30, 7 ] = ascii (fp) : "./hrtfs/elev60/L60e325a.dat" left + ascii (fp) : "./hrtfs/elev60/R60e325a.dat" right [ 30, 8 ] = ascii (fp) : "./hrtfs/elev60/L60e320a.dat" left + ascii (fp) : "./hrtfs/elev60/R60e320a.dat" right [ 30, 9 ] = ascii (fp) : "./hrtfs/elev60/L60e315a.dat" left + ascii (fp) : "./hrtfs/elev60/R60e315a.dat" right [ 30, 10 ] = ascii (fp) : "./hrtfs/elev60/L60e310a.dat" left + ascii (fp) : "./hrtfs/elev60/R60e310a.dat" right [ 30, 11 ] = ascii (fp) : "./hrtfs/elev60/L60e305a.dat" left + ascii (fp) : "./hrtfs/elev60/R60e305a.dat" right [ 30, 12 ] = ascii (fp) : "./hrtfs/elev60/L60e300a.dat" left + ascii (fp) : "./hrtfs/elev60/R60e300a.dat" right [ 30, 13 ] = ascii (fp) : "./hrtfs/elev60/L60e295a.dat" left + ascii (fp) : "./hrtfs/elev60/R60e295a.dat" right [ 30, 14 ] = ascii (fp) : "./hrtfs/elev60/L60e290a.dat" left + ascii (fp) : "./hrtfs/elev60/R60e290a.dat" right [ 30, 15 ] = ascii (fp) : "./hrtfs/elev60/L60e285a.dat" left + ascii (fp) : "./hrtfs/elev60/R60e285a.dat" right [ 30, 16 ] = ascii (fp) : "./hrtfs/elev60/L60e280a.dat" left + ascii (fp) : "./hrtfs/elev60/R60e280a.dat" right [ 30, 17 ] = ascii (fp) : "./hrtfs/elev60/L60e275a.dat" left + ascii (fp) : "./hrtfs/elev60/R60e275a.dat" right [ 30, 18 ] = ascii (fp) : "./hrtfs/elev60/L60e270a.dat" left + ascii (fp) : "./hrtfs/elev60/R60e270a.dat" right [ 30, 19 ] = ascii (fp) : "./hrtfs/elev60/L60e265a.dat" left + ascii (fp) : "./hrtfs/elev60/R60e265a.dat" right [ 30, 20 ] = ascii (fp) : "./hrtfs/elev60/L60e260a.dat" left + ascii (fp) : "./hrtfs/elev60/R60e260a.dat" right [ 30, 21 ] = ascii (fp) : "./hrtfs/elev60/L60e255a.dat" left + ascii (fp) : "./hrtfs/elev60/R60e255a.dat" right [ 30, 22 ] = ascii (fp) : "./hrtfs/elev60/L60e250a.dat" left + ascii (fp) : "./hrtfs/elev60/R60e250a.dat" right [ 30, 23 ] = ascii (fp) : "./hrtfs/elev60/L60e245a.dat" left + ascii (fp) : "./hrtfs/elev60/R60e245a.dat" right [ 30, 24 ] = ascii (fp) : "./hrtfs/elev60/L60e240a.dat" left + ascii (fp) : "./hrtfs/elev60/R60e240a.dat" right [ 30, 25 ] = ascii (fp) : "./hrtfs/elev60/L60e235a.dat" left + ascii (fp) : "./hrtfs/elev60/R60e235a.dat" right [ 30, 26 ] = ascii (fp) : "./hrtfs/elev60/L60e230a.dat" left + ascii (fp) : "./hrtfs/elev60/R60e230a.dat" right [ 30, 27 ] = ascii (fp) : "./hrtfs/elev60/L60e225a.dat" left + ascii (fp) : "./hrtfs/elev60/R60e225a.dat" right [ 30, 28 ] = ascii (fp) : "./hrtfs/elev60/L60e220a.dat" left + ascii (fp) : "./hrtfs/elev60/R60e220a.dat" right [ 30, 29 ] = ascii (fp) : "./hrtfs/elev60/L60e215a.dat" left + ascii (fp) : "./hrtfs/elev60/R60e215a.dat" right [ 30, 30 ] = ascii (fp) : "./hrtfs/elev60/L60e210a.dat" left + ascii (fp) : "./hrtfs/elev60/R60e210a.dat" right [ 30, 31 ] = ascii (fp) : "./hrtfs/elev60/L60e205a.dat" left + ascii (fp) : "./hrtfs/elev60/R60e205a.dat" right [ 30, 32 ] = ascii (fp) : "./hrtfs/elev60/L60e200a.dat" left + ascii (fp) : "./hrtfs/elev60/R60e200a.dat" right [ 30, 33 ] = ascii (fp) : "./hrtfs/elev60/L60e195a.dat" left + ascii (fp) : "./hrtfs/elev60/R60e195a.dat" right [ 30, 34 ] = ascii (fp) : "./hrtfs/elev60/L60e190a.dat" left + ascii (fp) : "./hrtfs/elev60/R60e190a.dat" right [ 30, 35 ] = ascii (fp) : "./hrtfs/elev60/L60e185a.dat" left + ascii (fp) : "./hrtfs/elev60/R60e185a.dat" right [ 30, 36 ] = ascii (fp) : "./hrtfs/elev60/L60e180a.dat" left + ascii (fp) : "./hrtfs/elev60/R60e180a.dat" right [ 30, 37 ] = ascii (fp) : "./hrtfs/elev60/L60e175a.dat" left + ascii (fp) : "./hrtfs/elev60/R60e175a.dat" right [ 30, 38 ] = ascii (fp) : "./hrtfs/elev60/L60e170a.dat" left + ascii (fp) : "./hrtfs/elev60/R60e170a.dat" right [ 30, 39 ] = ascii (fp) : "./hrtfs/elev60/L60e165a.dat" left + ascii (fp) : "./hrtfs/elev60/R60e165a.dat" right [ 30, 40 ] = ascii (fp) : "./hrtfs/elev60/L60e160a.dat" left + ascii (fp) : "./hrtfs/elev60/R60e160a.dat" right [ 30, 41 ] = ascii (fp) : "./hrtfs/elev60/L60e155a.dat" left + ascii (fp) : "./hrtfs/elev60/R60e155a.dat" right [ 30, 42 ] = ascii (fp) : "./hrtfs/elev60/L60e150a.dat" left + ascii (fp) : "./hrtfs/elev60/R60e150a.dat" right [ 30, 43 ] = ascii (fp) : "./hrtfs/elev60/L60e145a.dat" left + ascii (fp) : "./hrtfs/elev60/R60e145a.dat" right [ 30, 44 ] = ascii (fp) : "./hrtfs/elev60/L60e140a.dat" left + ascii (fp) : "./hrtfs/elev60/R60e140a.dat" right [ 30, 45 ] = ascii (fp) : "./hrtfs/elev60/L60e135a.dat" left + ascii (fp) : "./hrtfs/elev60/R60e135a.dat" right [ 30, 46 ] = ascii (fp) : "./hrtfs/elev60/L60e130a.dat" left + ascii (fp) : "./hrtfs/elev60/R60e130a.dat" right [ 30, 47 ] = ascii (fp) : "./hrtfs/elev60/L60e125a.dat" left + ascii (fp) : "./hrtfs/elev60/R60e125a.dat" right [ 30, 48 ] = ascii (fp) : "./hrtfs/elev60/L60e120a.dat" left + ascii (fp) : "./hrtfs/elev60/R60e120a.dat" right [ 30, 49 ] = ascii (fp) : "./hrtfs/elev60/L60e115a.dat" left + ascii (fp) : "./hrtfs/elev60/R60e115a.dat" right [ 30, 50 ] = ascii (fp) : "./hrtfs/elev60/L60e110a.dat" left + ascii (fp) : "./hrtfs/elev60/R60e110a.dat" right [ 30, 51 ] = ascii (fp) : "./hrtfs/elev60/L60e105a.dat" left + ascii (fp) : "./hrtfs/elev60/R60e105a.dat" right [ 30, 52 ] = ascii (fp) : "./hrtfs/elev60/L60e100a.dat" left + ascii (fp) : "./hrtfs/elev60/R60e100a.dat" right [ 30, 53 ] = ascii (fp) : "./hrtfs/elev60/L60e095a.dat" left + ascii (fp) : "./hrtfs/elev60/R60e095a.dat" right [ 30, 54 ] = ascii (fp) : "./hrtfs/elev60/L60e090a.dat" left + ascii (fp) : "./hrtfs/elev60/R60e090a.dat" right [ 30, 55 ] = ascii (fp) : "./hrtfs/elev60/L60e085a.dat" left + ascii (fp) : "./hrtfs/elev60/R60e085a.dat" right [ 30, 56 ] = ascii (fp) : "./hrtfs/elev60/L60e080a.dat" left + ascii (fp) : "./hrtfs/elev60/R60e080a.dat" right [ 30, 57 ] = ascii (fp) : "./hrtfs/elev60/L60e075a.dat" left + ascii (fp) : "./hrtfs/elev60/R60e075a.dat" right [ 30, 58 ] = ascii (fp) : "./hrtfs/elev60/L60e070a.dat" left + ascii (fp) : "./hrtfs/elev60/R60e070a.dat" right [ 30, 59 ] = ascii (fp) : "./hrtfs/elev60/L60e065a.dat" left + ascii (fp) : "./hrtfs/elev60/R60e065a.dat" right [ 30, 60 ] = ascii (fp) : "./hrtfs/elev60/L60e060a.dat" left + ascii (fp) : "./hrtfs/elev60/R60e060a.dat" right [ 30, 61 ] = ascii (fp) : "./hrtfs/elev60/L60e055a.dat" left + ascii (fp) : "./hrtfs/elev60/R60e055a.dat" right [ 30, 62 ] = ascii (fp) : "./hrtfs/elev60/L60e050a.dat" left + ascii (fp) : "./hrtfs/elev60/R60e050a.dat" right [ 30, 63 ] = ascii (fp) : "./hrtfs/elev60/L60e045a.dat" left + ascii (fp) : "./hrtfs/elev60/R60e045a.dat" right [ 30, 64 ] = ascii (fp) : "./hrtfs/elev60/L60e040a.dat" left + ascii (fp) : "./hrtfs/elev60/R60e040a.dat" right [ 30, 65 ] = ascii (fp) : "./hrtfs/elev60/L60e035a.dat" left + ascii (fp) : "./hrtfs/elev60/R60e035a.dat" right [ 30, 66 ] = ascii (fp) : "./hrtfs/elev60/L60e030a.dat" left + ascii (fp) : "./hrtfs/elev60/R60e030a.dat" right [ 30, 67 ] = ascii (fp) : "./hrtfs/elev60/L60e025a.dat" left + ascii (fp) : "./hrtfs/elev60/R60e025a.dat" right [ 30, 68 ] = ascii (fp) : "./hrtfs/elev60/L60e020a.dat" left + ascii (fp) : "./hrtfs/elev60/R60e020a.dat" right [ 30, 69 ] = ascii (fp) : "./hrtfs/elev60/L60e015a.dat" left + ascii (fp) : "./hrtfs/elev60/R60e015a.dat" right [ 30, 70 ] = ascii (fp) : "./hrtfs/elev60/L60e010a.dat" left + ascii (fp) : "./hrtfs/elev60/R60e010a.dat" right [ 30, 71 ] = ascii (fp) : "./hrtfs/elev60/L60e005a.dat" left + ascii (fp) : "./hrtfs/elev60/R60e005a.dat" right [ 31, 0 ] = ascii (fp) : "./hrtfs/elev65/L65e000a.dat" left + ascii (fp) : "./hrtfs/elev65/R65e000a.dat" right [ 31, 1 ] = ascii (fp) : "./hrtfs/elev65/L65e355a.dat" left + ascii (fp) : "./hrtfs/elev65/R65e355a.dat" right [ 31, 2 ] = ascii (fp) : "./hrtfs/elev65/L65e350a.dat" left + ascii (fp) : "./hrtfs/elev65/R65e350a.dat" right [ 31, 3 ] = ascii (fp) : "./hrtfs/elev65/L65e345a.dat" left + ascii (fp) : "./hrtfs/elev65/R65e345a.dat" right [ 31, 4 ] = ascii (fp) : "./hrtfs/elev65/L65e340a.dat" left + ascii (fp) : "./hrtfs/elev65/R65e340a.dat" right [ 31, 5 ] = ascii (fp) : "./hrtfs/elev65/L65e335a.dat" left + ascii (fp) : "./hrtfs/elev65/R65e335a.dat" right [ 31, 6 ] = ascii (fp) : "./hrtfs/elev65/L65e330a.dat" left + ascii (fp) : "./hrtfs/elev65/R65e330a.dat" right [ 31, 7 ] = ascii (fp) : "./hrtfs/elev65/L65e325a.dat" left + ascii (fp) : "./hrtfs/elev65/R65e325a.dat" right [ 31, 8 ] = ascii (fp) : "./hrtfs/elev65/L65e320a.dat" left + ascii (fp) : "./hrtfs/elev65/R65e320a.dat" right [ 31, 9 ] = ascii (fp) : "./hrtfs/elev65/L65e315a.dat" left + ascii (fp) : "./hrtfs/elev65/R65e315a.dat" right [ 31, 10 ] = ascii (fp) : "./hrtfs/elev65/L65e310a.dat" left + ascii (fp) : "./hrtfs/elev65/R65e310a.dat" right [ 31, 11 ] = ascii (fp) : "./hrtfs/elev65/L65e305a.dat" left + ascii (fp) : "./hrtfs/elev65/R65e305a.dat" right [ 31, 12 ] = ascii (fp) : "./hrtfs/elev65/L65e300a.dat" left + ascii (fp) : "./hrtfs/elev65/R65e300a.dat" right [ 31, 13 ] = ascii (fp) : "./hrtfs/elev65/L65e295a.dat" left + ascii (fp) : "./hrtfs/elev65/R65e295a.dat" right [ 31, 14 ] = ascii (fp) : "./hrtfs/elev65/L65e290a.dat" left + ascii (fp) : "./hrtfs/elev65/R65e290a.dat" right [ 31, 15 ] = ascii (fp) : "./hrtfs/elev65/L65e285a.dat" left + ascii (fp) : "./hrtfs/elev65/R65e285a.dat" right [ 31, 16 ] = ascii (fp) : "./hrtfs/elev65/L65e280a.dat" left + ascii (fp) : "./hrtfs/elev65/R65e280a.dat" right [ 31, 17 ] = ascii (fp) : "./hrtfs/elev65/L65e275a.dat" left + ascii (fp) : "./hrtfs/elev65/R65e275a.dat" right [ 31, 18 ] = ascii (fp) : "./hrtfs/elev65/L65e270a.dat" left + ascii (fp) : "./hrtfs/elev65/R65e270a.dat" right [ 31, 19 ] = ascii (fp) : "./hrtfs/elev65/L65e265a.dat" left + ascii (fp) : "./hrtfs/elev65/R65e265a.dat" right [ 31, 20 ] = ascii (fp) : "./hrtfs/elev65/L65e260a.dat" left + ascii (fp) : "./hrtfs/elev65/R65e260a.dat" right [ 31, 21 ] = ascii (fp) : "./hrtfs/elev65/L65e255a.dat" left + ascii (fp) : "./hrtfs/elev65/R65e255a.dat" right [ 31, 22 ] = ascii (fp) : "./hrtfs/elev65/L65e250a.dat" left + ascii (fp) : "./hrtfs/elev65/R65e250a.dat" right [ 31, 23 ] = ascii (fp) : "./hrtfs/elev65/L65e245a.dat" left + ascii (fp) : "./hrtfs/elev65/R65e245a.dat" right [ 31, 24 ] = ascii (fp) : "./hrtfs/elev65/L65e240a.dat" left + ascii (fp) : "./hrtfs/elev65/R65e240a.dat" right [ 31, 25 ] = ascii (fp) : "./hrtfs/elev65/L65e235a.dat" left + ascii (fp) : "./hrtfs/elev65/R65e235a.dat" right [ 31, 26 ] = ascii (fp) : "./hrtfs/elev65/L65e230a.dat" left + ascii (fp) : "./hrtfs/elev65/R65e230a.dat" right [ 31, 27 ] = ascii (fp) : "./hrtfs/elev65/L65e225a.dat" left + ascii (fp) : "./hrtfs/elev65/R65e225a.dat" right [ 31, 28 ] = ascii (fp) : "./hrtfs/elev65/L65e220a.dat" left + ascii (fp) : "./hrtfs/elev65/R65e220a.dat" right [ 31, 29 ] = ascii (fp) : "./hrtfs/elev65/L65e215a.dat" left + ascii (fp) : "./hrtfs/elev65/R65e215a.dat" right [ 31, 30 ] = ascii (fp) : "./hrtfs/elev65/L65e210a.dat" left + ascii (fp) : "./hrtfs/elev65/R65e210a.dat" right [ 31, 31 ] = ascii (fp) : "./hrtfs/elev65/L65e205a.dat" left + ascii (fp) : "./hrtfs/elev65/R65e205a.dat" right [ 31, 32 ] = ascii (fp) : "./hrtfs/elev65/L65e200a.dat" left + ascii (fp) : "./hrtfs/elev65/R65e200a.dat" right [ 31, 33 ] = ascii (fp) : "./hrtfs/elev65/L65e195a.dat" left + ascii (fp) : "./hrtfs/elev65/R65e195a.dat" right [ 31, 34 ] = ascii (fp) : "./hrtfs/elev65/L65e190a.dat" left + ascii (fp) : "./hrtfs/elev65/R65e190a.dat" right [ 31, 35 ] = ascii (fp) : "./hrtfs/elev65/L65e185a.dat" left + ascii (fp) : "./hrtfs/elev65/R65e185a.dat" right [ 31, 36 ] = ascii (fp) : "./hrtfs/elev65/L65e180a.dat" left + ascii (fp) : "./hrtfs/elev65/R65e180a.dat" right [ 31, 37 ] = ascii (fp) : "./hrtfs/elev65/L65e175a.dat" left + ascii (fp) : "./hrtfs/elev65/R65e175a.dat" right [ 31, 38 ] = ascii (fp) : "./hrtfs/elev65/L65e170a.dat" left + ascii (fp) : "./hrtfs/elev65/R65e170a.dat" right [ 31, 39 ] = ascii (fp) : "./hrtfs/elev65/L65e165a.dat" left + ascii (fp) : "./hrtfs/elev65/R65e165a.dat" right [ 31, 40 ] = ascii (fp) : "./hrtfs/elev65/L65e160a.dat" left + ascii (fp) : "./hrtfs/elev65/R65e160a.dat" right [ 31, 41 ] = ascii (fp) : "./hrtfs/elev65/L65e155a.dat" left + ascii (fp) : "./hrtfs/elev65/R65e155a.dat" right [ 31, 42 ] = ascii (fp) : "./hrtfs/elev65/L65e150a.dat" left + ascii (fp) : "./hrtfs/elev65/R65e150a.dat" right [ 31, 43 ] = ascii (fp) : "./hrtfs/elev65/L65e145a.dat" left + ascii (fp) : "./hrtfs/elev65/R65e145a.dat" right [ 31, 44 ] = ascii (fp) : "./hrtfs/elev65/L65e140a.dat" left + ascii (fp) : "./hrtfs/elev65/R65e140a.dat" right [ 31, 45 ] = ascii (fp) : "./hrtfs/elev65/L65e135a.dat" left + ascii (fp) : "./hrtfs/elev65/R65e135a.dat" right [ 31, 46 ] = ascii (fp) : "./hrtfs/elev65/L65e130a.dat" left + ascii (fp) : "./hrtfs/elev65/R65e130a.dat" right [ 31, 47 ] = ascii (fp) : "./hrtfs/elev65/L65e125a.dat" left + ascii (fp) : "./hrtfs/elev65/R65e125a.dat" right [ 31, 48 ] = ascii (fp) : "./hrtfs/elev65/L65e120a.dat" left + ascii (fp) : "./hrtfs/elev65/R65e120a.dat" right [ 31, 49 ] = ascii (fp) : "./hrtfs/elev65/L65e115a.dat" left + ascii (fp) : "./hrtfs/elev65/R65e115a.dat" right [ 31, 50 ] = ascii (fp) : "./hrtfs/elev65/L65e110a.dat" left + ascii (fp) : "./hrtfs/elev65/R65e110a.dat" right [ 31, 51 ] = ascii (fp) : "./hrtfs/elev65/L65e105a.dat" left + ascii (fp) : "./hrtfs/elev65/R65e105a.dat" right [ 31, 52 ] = ascii (fp) : "./hrtfs/elev65/L65e100a.dat" left + ascii (fp) : "./hrtfs/elev65/R65e100a.dat" right [ 31, 53 ] = ascii (fp) : "./hrtfs/elev65/L65e095a.dat" left + ascii (fp) : "./hrtfs/elev65/R65e095a.dat" right [ 31, 54 ] = ascii (fp) : "./hrtfs/elev65/L65e090a.dat" left + ascii (fp) : "./hrtfs/elev65/R65e090a.dat" right [ 31, 55 ] = ascii (fp) : "./hrtfs/elev65/L65e085a.dat" left + ascii (fp) : "./hrtfs/elev65/R65e085a.dat" right [ 31, 56 ] = ascii (fp) : "./hrtfs/elev65/L65e080a.dat" left + ascii (fp) : "./hrtfs/elev65/R65e080a.dat" right [ 31, 57 ] = ascii (fp) : "./hrtfs/elev65/L65e075a.dat" left + ascii (fp) : "./hrtfs/elev65/R65e075a.dat" right [ 31, 58 ] = ascii (fp) : "./hrtfs/elev65/L65e070a.dat" left + ascii (fp) : "./hrtfs/elev65/R65e070a.dat" right [ 31, 59 ] = ascii (fp) : "./hrtfs/elev65/L65e065a.dat" left + ascii (fp) : "./hrtfs/elev65/R65e065a.dat" right [ 31, 60 ] = ascii (fp) : "./hrtfs/elev65/L65e060a.dat" left + ascii (fp) : "./hrtfs/elev65/R65e060a.dat" right [ 31, 61 ] = ascii (fp) : "./hrtfs/elev65/L65e055a.dat" left + ascii (fp) : "./hrtfs/elev65/R65e055a.dat" right [ 31, 62 ] = ascii (fp) : "./hrtfs/elev65/L65e050a.dat" left + ascii (fp) : "./hrtfs/elev65/R65e050a.dat" right [ 31, 63 ] = ascii (fp) : "./hrtfs/elev65/L65e045a.dat" left + ascii (fp) : "./hrtfs/elev65/R65e045a.dat" right [ 31, 64 ] = ascii (fp) : "./hrtfs/elev65/L65e040a.dat" left + ascii (fp) : "./hrtfs/elev65/R65e040a.dat" right [ 31, 65 ] = ascii (fp) : "./hrtfs/elev65/L65e035a.dat" left + ascii (fp) : "./hrtfs/elev65/R65e035a.dat" right [ 31, 66 ] = ascii (fp) : "./hrtfs/elev65/L65e030a.dat" left + ascii (fp) : "./hrtfs/elev65/R65e030a.dat" right [ 31, 67 ] = ascii (fp) : "./hrtfs/elev65/L65e025a.dat" left + ascii (fp) : "./hrtfs/elev65/R65e025a.dat" right [ 31, 68 ] = ascii (fp) : "./hrtfs/elev65/L65e020a.dat" left + ascii (fp) : "./hrtfs/elev65/R65e020a.dat" right [ 31, 69 ] = ascii (fp) : "./hrtfs/elev65/L65e015a.dat" left + ascii (fp) : "./hrtfs/elev65/R65e015a.dat" right [ 31, 70 ] = ascii (fp) : "./hrtfs/elev65/L65e010a.dat" left + ascii (fp) : "./hrtfs/elev65/R65e010a.dat" right [ 31, 71 ] = ascii (fp) : "./hrtfs/elev65/L65e005a.dat" left + ascii (fp) : "./hrtfs/elev65/R65e005a.dat" right [ 32, 0 ] = ascii (fp) : "./hrtfs/elev70/L70e000a.dat" left + ascii (fp) : "./hrtfs/elev70/R70e000a.dat" right [ 32, 1 ] = ascii (fp) : "./hrtfs/elev70/L70e355a.dat" left + ascii (fp) : "./hrtfs/elev70/R70e355a.dat" right [ 32, 2 ] = ascii (fp) : "./hrtfs/elev70/L70e350a.dat" left + ascii (fp) : "./hrtfs/elev70/R70e350a.dat" right [ 32, 3 ] = ascii (fp) : "./hrtfs/elev70/L70e345a.dat" left + ascii (fp) : "./hrtfs/elev70/R70e345a.dat" right [ 32, 4 ] = ascii (fp) : "./hrtfs/elev70/L70e340a.dat" left + ascii (fp) : "./hrtfs/elev70/R70e340a.dat" right [ 32, 5 ] = ascii (fp) : "./hrtfs/elev70/L70e335a.dat" left + ascii (fp) : "./hrtfs/elev70/R70e335a.dat" right [ 32, 6 ] = ascii (fp) : "./hrtfs/elev70/L70e330a.dat" left + ascii (fp) : "./hrtfs/elev70/R70e330a.dat" right [ 32, 7 ] = ascii (fp) : "./hrtfs/elev70/L70e325a.dat" left + ascii (fp) : "./hrtfs/elev70/R70e325a.dat" right [ 32, 8 ] = ascii (fp) : "./hrtfs/elev70/L70e320a.dat" left + ascii (fp) : "./hrtfs/elev70/R70e320a.dat" right [ 32, 9 ] = ascii (fp) : "./hrtfs/elev70/L70e315a.dat" left + ascii (fp) : "./hrtfs/elev70/R70e315a.dat" right [ 32, 10 ] = ascii (fp) : "./hrtfs/elev70/L70e310a.dat" left + ascii (fp) : "./hrtfs/elev70/R70e310a.dat" right [ 32, 11 ] = ascii (fp) : "./hrtfs/elev70/L70e305a.dat" left + ascii (fp) : "./hrtfs/elev70/R70e305a.dat" right [ 32, 12 ] = ascii (fp) : "./hrtfs/elev70/L70e300a.dat" left + ascii (fp) : "./hrtfs/elev70/R70e300a.dat" right [ 32, 13 ] = ascii (fp) : "./hrtfs/elev70/L70e295a.dat" left + ascii (fp) : "./hrtfs/elev70/R70e295a.dat" right [ 32, 14 ] = ascii (fp) : "./hrtfs/elev70/L70e290a.dat" left + ascii (fp) : "./hrtfs/elev70/R70e290a.dat" right [ 32, 15 ] = ascii (fp) : "./hrtfs/elev70/L70e285a.dat" left + ascii (fp) : "./hrtfs/elev70/R70e285a.dat" right [ 32, 16 ] = ascii (fp) : "./hrtfs/elev70/L70e280a.dat" left + ascii (fp) : "./hrtfs/elev70/R70e280a.dat" right [ 32, 17 ] = ascii (fp) : "./hrtfs/elev70/L70e275a.dat" left + ascii (fp) : "./hrtfs/elev70/R70e275a.dat" right [ 32, 18 ] = ascii (fp) : "./hrtfs/elev70/L70e270a.dat" left + ascii (fp) : "./hrtfs/elev70/R70e270a.dat" right [ 32, 19 ] = ascii (fp) : "./hrtfs/elev70/L70e265a.dat" left + ascii (fp) : "./hrtfs/elev70/R70e265a.dat" right [ 32, 20 ] = ascii (fp) : "./hrtfs/elev70/L70e260a.dat" left + ascii (fp) : "./hrtfs/elev70/R70e260a.dat" right [ 32, 21 ] = ascii (fp) : "./hrtfs/elev70/L70e255a.dat" left + ascii (fp) : "./hrtfs/elev70/R70e255a.dat" right [ 32, 22 ] = ascii (fp) : "./hrtfs/elev70/L70e250a.dat" left + ascii (fp) : "./hrtfs/elev70/R70e250a.dat" right [ 32, 23 ] = ascii (fp) : "./hrtfs/elev70/L70e245a.dat" left + ascii (fp) : "./hrtfs/elev70/R70e245a.dat" right [ 32, 24 ] = ascii (fp) : "./hrtfs/elev70/L70e240a.dat" left + ascii (fp) : "./hrtfs/elev70/R70e240a.dat" right [ 32, 25 ] = ascii (fp) : "./hrtfs/elev70/L70e235a.dat" left + ascii (fp) : "./hrtfs/elev70/R70e235a.dat" right [ 32, 26 ] = ascii (fp) : "./hrtfs/elev70/L70e230a.dat" left + ascii (fp) : "./hrtfs/elev70/R70e230a.dat" right [ 32, 27 ] = ascii (fp) : "./hrtfs/elev70/L70e225a.dat" left + ascii (fp) : "./hrtfs/elev70/R70e225a.dat" right [ 32, 28 ] = ascii (fp) : "./hrtfs/elev70/L70e220a.dat" left + ascii (fp) : "./hrtfs/elev70/R70e220a.dat" right [ 32, 29 ] = ascii (fp) : "./hrtfs/elev70/L70e215a.dat" left + ascii (fp) : "./hrtfs/elev70/R70e215a.dat" right [ 32, 30 ] = ascii (fp) : "./hrtfs/elev70/L70e210a.dat" left + ascii (fp) : "./hrtfs/elev70/R70e210a.dat" right [ 32, 31 ] = ascii (fp) : "./hrtfs/elev70/L70e205a.dat" left + ascii (fp) : "./hrtfs/elev70/R70e205a.dat" right [ 32, 32 ] = ascii (fp) : "./hrtfs/elev70/L70e200a.dat" left + ascii (fp) : "./hrtfs/elev70/R70e200a.dat" right [ 32, 33 ] = ascii (fp) : "./hrtfs/elev70/L70e195a.dat" left + ascii (fp) : "./hrtfs/elev70/R70e195a.dat" right [ 32, 34 ] = ascii (fp) : "./hrtfs/elev70/L70e190a.dat" left + ascii (fp) : "./hrtfs/elev70/R70e190a.dat" right [ 32, 35 ] = ascii (fp) : "./hrtfs/elev70/L70e185a.dat" left + ascii (fp) : "./hrtfs/elev70/R70e185a.dat" right [ 32, 36 ] = ascii (fp) : "./hrtfs/elev70/L70e180a.dat" left + ascii (fp) : "./hrtfs/elev70/R70e180a.dat" right [ 32, 37 ] = ascii (fp) : "./hrtfs/elev70/L70e175a.dat" left + ascii (fp) : "./hrtfs/elev70/R70e175a.dat" right [ 32, 38 ] = ascii (fp) : "./hrtfs/elev70/L70e170a.dat" left + ascii (fp) : "./hrtfs/elev70/R70e170a.dat" right [ 32, 39 ] = ascii (fp) : "./hrtfs/elev70/L70e165a.dat" left + ascii (fp) : "./hrtfs/elev70/R70e165a.dat" right [ 32, 40 ] = ascii (fp) : "./hrtfs/elev70/L70e160a.dat" left + ascii (fp) : "./hrtfs/elev70/R70e160a.dat" right [ 32, 41 ] = ascii (fp) : "./hrtfs/elev70/L70e155a.dat" left + ascii (fp) : "./hrtfs/elev70/R70e155a.dat" right [ 32, 42 ] = ascii (fp) : "./hrtfs/elev70/L70e150a.dat" left + ascii (fp) : "./hrtfs/elev70/R70e150a.dat" right [ 32, 43 ] = ascii (fp) : "./hrtfs/elev70/L70e145a.dat" left + ascii (fp) : "./hrtfs/elev70/R70e145a.dat" right [ 32, 44 ] = ascii (fp) : "./hrtfs/elev70/L70e140a.dat" left + ascii (fp) : "./hrtfs/elev70/R70e140a.dat" right [ 32, 45 ] = ascii (fp) : "./hrtfs/elev70/L70e135a.dat" left + ascii (fp) : "./hrtfs/elev70/R70e135a.dat" right [ 32, 46 ] = ascii (fp) : "./hrtfs/elev70/L70e130a.dat" left + ascii (fp) : "./hrtfs/elev70/R70e130a.dat" right [ 32, 47 ] = ascii (fp) : "./hrtfs/elev70/L70e125a.dat" left + ascii (fp) : "./hrtfs/elev70/R70e125a.dat" right [ 32, 48 ] = ascii (fp) : "./hrtfs/elev70/L70e120a.dat" left + ascii (fp) : "./hrtfs/elev70/R70e120a.dat" right [ 32, 49 ] = ascii (fp) : "./hrtfs/elev70/L70e115a.dat" left + ascii (fp) : "./hrtfs/elev70/R70e115a.dat" right [ 32, 50 ] = ascii (fp) : "./hrtfs/elev70/L70e110a.dat" left + ascii (fp) : "./hrtfs/elev70/R70e110a.dat" right [ 32, 51 ] = ascii (fp) : "./hrtfs/elev70/L70e105a.dat" left + ascii (fp) : "./hrtfs/elev70/R70e105a.dat" right [ 32, 52 ] = ascii (fp) : "./hrtfs/elev70/L70e100a.dat" left + ascii (fp) : "./hrtfs/elev70/R70e100a.dat" right [ 32, 53 ] = ascii (fp) : "./hrtfs/elev70/L70e095a.dat" left + ascii (fp) : "./hrtfs/elev70/R70e095a.dat" right [ 32, 54 ] = ascii (fp) : "./hrtfs/elev70/L70e090a.dat" left + ascii (fp) : "./hrtfs/elev70/R70e090a.dat" right [ 32, 55 ] = ascii (fp) : "./hrtfs/elev70/L70e085a.dat" left + ascii (fp) : "./hrtfs/elev70/R70e085a.dat" right [ 32, 56 ] = ascii (fp) : "./hrtfs/elev70/L70e080a.dat" left + ascii (fp) : "./hrtfs/elev70/R70e080a.dat" right [ 32, 57 ] = ascii (fp) : "./hrtfs/elev70/L70e075a.dat" left + ascii (fp) : "./hrtfs/elev70/R70e075a.dat" right [ 32, 58 ] = ascii (fp) : "./hrtfs/elev70/L70e070a.dat" left + ascii (fp) : "./hrtfs/elev70/R70e070a.dat" right [ 32, 59 ] = ascii (fp) : "./hrtfs/elev70/L70e065a.dat" left + ascii (fp) : "./hrtfs/elev70/R70e065a.dat" right [ 32, 60 ] = ascii (fp) : "./hrtfs/elev70/L70e060a.dat" left + ascii (fp) : "./hrtfs/elev70/R70e060a.dat" right [ 32, 61 ] = ascii (fp) : "./hrtfs/elev70/L70e055a.dat" left + ascii (fp) : "./hrtfs/elev70/R70e055a.dat" right [ 32, 62 ] = ascii (fp) : "./hrtfs/elev70/L70e050a.dat" left + ascii (fp) : "./hrtfs/elev70/R70e050a.dat" right [ 32, 63 ] = ascii (fp) : "./hrtfs/elev70/L70e045a.dat" left + ascii (fp) : "./hrtfs/elev70/R70e045a.dat" right [ 32, 64 ] = ascii (fp) : "./hrtfs/elev70/L70e040a.dat" left + ascii (fp) : "./hrtfs/elev70/R70e040a.dat" right [ 32, 65 ] = ascii (fp) : "./hrtfs/elev70/L70e035a.dat" left + ascii (fp) : "./hrtfs/elev70/R70e035a.dat" right [ 32, 66 ] = ascii (fp) : "./hrtfs/elev70/L70e030a.dat" left + ascii (fp) : "./hrtfs/elev70/R70e030a.dat" right [ 32, 67 ] = ascii (fp) : "./hrtfs/elev70/L70e025a.dat" left + ascii (fp) : "./hrtfs/elev70/R70e025a.dat" right [ 32, 68 ] = ascii (fp) : "./hrtfs/elev70/L70e020a.dat" left + ascii (fp) : "./hrtfs/elev70/R70e020a.dat" right [ 32, 69 ] = ascii (fp) : "./hrtfs/elev70/L70e015a.dat" left + ascii (fp) : "./hrtfs/elev70/R70e015a.dat" right [ 32, 70 ] = ascii (fp) : "./hrtfs/elev70/L70e010a.dat" left + ascii (fp) : "./hrtfs/elev70/R70e010a.dat" right [ 32, 71 ] = ascii (fp) : "./hrtfs/elev70/L70e005a.dat" left + ascii (fp) : "./hrtfs/elev70/R70e005a.dat" right [ 33, 0 ] = ascii (fp) : "./hrtfs/elev75/L75e000a.dat" left + ascii (fp) : "./hrtfs/elev75/R75e000a.dat" right [ 33, 1 ] = ascii (fp) : "./hrtfs/elev75/L75e355a.dat" left + ascii (fp) : "./hrtfs/elev75/R75e355a.dat" right [ 33, 2 ] = ascii (fp) : "./hrtfs/elev75/L75e350a.dat" left + ascii (fp) : "./hrtfs/elev75/R75e350a.dat" right [ 33, 3 ] = ascii (fp) : "./hrtfs/elev75/L75e345a.dat" left + ascii (fp) : "./hrtfs/elev75/R75e345a.dat" right [ 33, 4 ] = ascii (fp) : "./hrtfs/elev75/L75e340a.dat" left + ascii (fp) : "./hrtfs/elev75/R75e340a.dat" right [ 33, 5 ] = ascii (fp) : "./hrtfs/elev75/L75e335a.dat" left + ascii (fp) : "./hrtfs/elev75/R75e335a.dat" right [ 33, 6 ] = ascii (fp) : "./hrtfs/elev75/L75e330a.dat" left + ascii (fp) : "./hrtfs/elev75/R75e330a.dat" right [ 33, 7 ] = ascii (fp) : "./hrtfs/elev75/L75e325a.dat" left + ascii (fp) : "./hrtfs/elev75/R75e325a.dat" right [ 33, 8 ] = ascii (fp) : "./hrtfs/elev75/L75e320a.dat" left + ascii (fp) : "./hrtfs/elev75/R75e320a.dat" right [ 33, 9 ] = ascii (fp) : "./hrtfs/elev75/L75e315a.dat" left + ascii (fp) : "./hrtfs/elev75/R75e315a.dat" right [ 33, 10 ] = ascii (fp) : "./hrtfs/elev75/L75e310a.dat" left + ascii (fp) : "./hrtfs/elev75/R75e310a.dat" right [ 33, 11 ] = ascii (fp) : "./hrtfs/elev75/L75e305a.dat" left + ascii (fp) : "./hrtfs/elev75/R75e305a.dat" right [ 33, 12 ] = ascii (fp) : "./hrtfs/elev75/L75e300a.dat" left + ascii (fp) : "./hrtfs/elev75/R75e300a.dat" right [ 33, 13 ] = ascii (fp) : "./hrtfs/elev75/L75e295a.dat" left + ascii (fp) : "./hrtfs/elev75/R75e295a.dat" right [ 33, 14 ] = ascii (fp) : "./hrtfs/elev75/L75e290a.dat" left + ascii (fp) : "./hrtfs/elev75/R75e290a.dat" right [ 33, 15 ] = ascii (fp) : "./hrtfs/elev75/L75e285a.dat" left + ascii (fp) : "./hrtfs/elev75/R75e285a.dat" right [ 33, 16 ] = ascii (fp) : "./hrtfs/elev75/L75e280a.dat" left + ascii (fp) : "./hrtfs/elev75/R75e280a.dat" right [ 33, 17 ] = ascii (fp) : "./hrtfs/elev75/L75e275a.dat" left + ascii (fp) : "./hrtfs/elev75/R75e275a.dat" right [ 33, 18 ] = ascii (fp) : "./hrtfs/elev75/L75e270a.dat" left + ascii (fp) : "./hrtfs/elev75/R75e270a.dat" right [ 33, 19 ] = ascii (fp) : "./hrtfs/elev75/L75e265a.dat" left + ascii (fp) : "./hrtfs/elev75/R75e265a.dat" right [ 33, 20 ] = ascii (fp) : "./hrtfs/elev75/L75e260a.dat" left + ascii (fp) : "./hrtfs/elev75/R75e260a.dat" right [ 33, 21 ] = ascii (fp) : "./hrtfs/elev75/L75e255a.dat" left + ascii (fp) : "./hrtfs/elev75/R75e255a.dat" right [ 33, 22 ] = ascii (fp) : "./hrtfs/elev75/L75e250a.dat" left + ascii (fp) : "./hrtfs/elev75/R75e250a.dat" right [ 33, 23 ] = ascii (fp) : "./hrtfs/elev75/L75e245a.dat" left + ascii (fp) : "./hrtfs/elev75/R75e245a.dat" right [ 33, 24 ] = ascii (fp) : "./hrtfs/elev75/L75e240a.dat" left + ascii (fp) : "./hrtfs/elev75/R75e240a.dat" right [ 33, 25 ] = ascii (fp) : "./hrtfs/elev75/L75e235a.dat" left + ascii (fp) : "./hrtfs/elev75/R75e235a.dat" right [ 33, 26 ] = ascii (fp) : "./hrtfs/elev75/L75e230a.dat" left + ascii (fp) : "./hrtfs/elev75/R75e230a.dat" right [ 33, 27 ] = ascii (fp) : "./hrtfs/elev75/L75e225a.dat" left + ascii (fp) : "./hrtfs/elev75/R75e225a.dat" right [ 33, 28 ] = ascii (fp) : "./hrtfs/elev75/L75e220a.dat" left + ascii (fp) : "./hrtfs/elev75/R75e220a.dat" right [ 33, 29 ] = ascii (fp) : "./hrtfs/elev75/L75e215a.dat" left + ascii (fp) : "./hrtfs/elev75/R75e215a.dat" right [ 33, 30 ] = ascii (fp) : "./hrtfs/elev75/L75e210a.dat" left + ascii (fp) : "./hrtfs/elev75/R75e210a.dat" right [ 33, 31 ] = ascii (fp) : "./hrtfs/elev75/L75e205a.dat" left + ascii (fp) : "./hrtfs/elev75/R75e205a.dat" right [ 33, 32 ] = ascii (fp) : "./hrtfs/elev75/L75e200a.dat" left + ascii (fp) : "./hrtfs/elev75/R75e200a.dat" right [ 33, 33 ] = ascii (fp) : "./hrtfs/elev75/L75e195a.dat" left + ascii (fp) : "./hrtfs/elev75/R75e195a.dat" right [ 33, 34 ] = ascii (fp) : "./hrtfs/elev75/L75e190a.dat" left + ascii (fp) : "./hrtfs/elev75/R75e190a.dat" right [ 33, 35 ] = ascii (fp) : "./hrtfs/elev75/L75e185a.dat" left + ascii (fp) : "./hrtfs/elev75/R75e185a.dat" right [ 33, 36 ] = ascii (fp) : "./hrtfs/elev75/L75e180a.dat" left + ascii (fp) : "./hrtfs/elev75/R75e180a.dat" right [ 33, 37 ] = ascii (fp) : "./hrtfs/elev75/L75e175a.dat" left + ascii (fp) : "./hrtfs/elev75/R75e175a.dat" right [ 33, 38 ] = ascii (fp) : "./hrtfs/elev75/L75e170a.dat" left + ascii (fp) : "./hrtfs/elev75/R75e170a.dat" right [ 33, 39 ] = ascii (fp) : "./hrtfs/elev75/L75e165a.dat" left + ascii (fp) : "./hrtfs/elev75/R75e165a.dat" right [ 33, 40 ] = ascii (fp) : "./hrtfs/elev75/L75e160a.dat" left + ascii (fp) : "./hrtfs/elev75/R75e160a.dat" right [ 33, 41 ] = ascii (fp) : "./hrtfs/elev75/L75e155a.dat" left + ascii (fp) : "./hrtfs/elev75/R75e155a.dat" right [ 33, 42 ] = ascii (fp) : "./hrtfs/elev75/L75e150a.dat" left + ascii (fp) : "./hrtfs/elev75/R75e150a.dat" right [ 33, 43 ] = ascii (fp) : "./hrtfs/elev75/L75e145a.dat" left + ascii (fp) : "./hrtfs/elev75/R75e145a.dat" right [ 33, 44 ] = ascii (fp) : "./hrtfs/elev75/L75e140a.dat" left + ascii (fp) : "./hrtfs/elev75/R75e140a.dat" right [ 33, 45 ] = ascii (fp) : "./hrtfs/elev75/L75e135a.dat" left + ascii (fp) : "./hrtfs/elev75/R75e135a.dat" right [ 33, 46 ] = ascii (fp) : "./hrtfs/elev75/L75e130a.dat" left + ascii (fp) : "./hrtfs/elev75/R75e130a.dat" right [ 33, 47 ] = ascii (fp) : "./hrtfs/elev75/L75e125a.dat" left + ascii (fp) : "./hrtfs/elev75/R75e125a.dat" right [ 33, 48 ] = ascii (fp) : "./hrtfs/elev75/L75e120a.dat" left + ascii (fp) : "./hrtfs/elev75/R75e120a.dat" right [ 33, 49 ] = ascii (fp) : "./hrtfs/elev75/L75e115a.dat" left + ascii (fp) : "./hrtfs/elev75/R75e115a.dat" right [ 33, 50 ] = ascii (fp) : "./hrtfs/elev75/L75e110a.dat" left + ascii (fp) : "./hrtfs/elev75/R75e110a.dat" right [ 33, 51 ] = ascii (fp) : "./hrtfs/elev75/L75e105a.dat" left + ascii (fp) : "./hrtfs/elev75/R75e105a.dat" right [ 33, 52 ] = ascii (fp) : "./hrtfs/elev75/L75e100a.dat" left + ascii (fp) : "./hrtfs/elev75/R75e100a.dat" right [ 33, 53 ] = ascii (fp) : "./hrtfs/elev75/L75e095a.dat" left + ascii (fp) : "./hrtfs/elev75/R75e095a.dat" right [ 33, 54 ] = ascii (fp) : "./hrtfs/elev75/L75e090a.dat" left + ascii (fp) : "./hrtfs/elev75/R75e090a.dat" right [ 33, 55 ] = ascii (fp) : "./hrtfs/elev75/L75e085a.dat" left + ascii (fp) : "./hrtfs/elev75/R75e085a.dat" right [ 33, 56 ] = ascii (fp) : "./hrtfs/elev75/L75e080a.dat" left + ascii (fp) : "./hrtfs/elev75/R75e080a.dat" right [ 33, 57 ] = ascii (fp) : "./hrtfs/elev75/L75e075a.dat" left + ascii (fp) : "./hrtfs/elev75/R75e075a.dat" right [ 33, 58 ] = ascii (fp) : "./hrtfs/elev75/L75e070a.dat" left + ascii (fp) : "./hrtfs/elev75/R75e070a.dat" right [ 33, 59 ] = ascii (fp) : "./hrtfs/elev75/L75e065a.dat" left + ascii (fp) : "./hrtfs/elev75/R75e065a.dat" right [ 33, 60 ] = ascii (fp) : "./hrtfs/elev75/L75e060a.dat" left + ascii (fp) : "./hrtfs/elev75/R75e060a.dat" right [ 33, 61 ] = ascii (fp) : "./hrtfs/elev75/L75e055a.dat" left + ascii (fp) : "./hrtfs/elev75/R75e055a.dat" right [ 33, 62 ] = ascii (fp) : "./hrtfs/elev75/L75e050a.dat" left + ascii (fp) : "./hrtfs/elev75/R75e050a.dat" right [ 33, 63 ] = ascii (fp) : "./hrtfs/elev75/L75e045a.dat" left + ascii (fp) : "./hrtfs/elev75/R75e045a.dat" right [ 33, 64 ] = ascii (fp) : "./hrtfs/elev75/L75e040a.dat" left + ascii (fp) : "./hrtfs/elev75/R75e040a.dat" right [ 33, 65 ] = ascii (fp) : "./hrtfs/elev75/L75e035a.dat" left + ascii (fp) : "./hrtfs/elev75/R75e035a.dat" right [ 33, 66 ] = ascii (fp) : "./hrtfs/elev75/L75e030a.dat" left + ascii (fp) : "./hrtfs/elev75/R75e030a.dat" right [ 33, 67 ] = ascii (fp) : "./hrtfs/elev75/L75e025a.dat" left + ascii (fp) : "./hrtfs/elev75/R75e025a.dat" right [ 33, 68 ] = ascii (fp) : "./hrtfs/elev75/L75e020a.dat" left + ascii (fp) : "./hrtfs/elev75/R75e020a.dat" right [ 33, 69 ] = ascii (fp) : "./hrtfs/elev75/L75e015a.dat" left + ascii (fp) : "./hrtfs/elev75/R75e015a.dat" right [ 33, 70 ] = ascii (fp) : "./hrtfs/elev75/L75e010a.dat" left + ascii (fp) : "./hrtfs/elev75/R75e010a.dat" right [ 33, 71 ] = ascii (fp) : "./hrtfs/elev75/L75e005a.dat" left + ascii (fp) : "./hrtfs/elev75/R75e005a.dat" right [ 34, 0 ] = ascii (fp) : "./hrtfs/elev80/L80e000a.dat" left + ascii (fp) : "./hrtfs/elev80/R80e000a.dat" right [ 34, 1 ] = ascii (fp) : "./hrtfs/elev80/L80e355a.dat" left + ascii (fp) : "./hrtfs/elev80/R80e355a.dat" right [ 34, 2 ] = ascii (fp) : "./hrtfs/elev80/L80e350a.dat" left + ascii (fp) : "./hrtfs/elev80/R80e350a.dat" right [ 34, 3 ] = ascii (fp) : "./hrtfs/elev80/L80e345a.dat" left + ascii (fp) : "./hrtfs/elev80/R80e345a.dat" right [ 34, 4 ] = ascii (fp) : "./hrtfs/elev80/L80e340a.dat" left + ascii (fp) : "./hrtfs/elev80/R80e340a.dat" right [ 34, 5 ] = ascii (fp) : "./hrtfs/elev80/L80e335a.dat" left + ascii (fp) : "./hrtfs/elev80/R80e335a.dat" right [ 34, 6 ] = ascii (fp) : "./hrtfs/elev80/L80e330a.dat" left + ascii (fp) : "./hrtfs/elev80/R80e330a.dat" right [ 34, 7 ] = ascii (fp) : "./hrtfs/elev80/L80e325a.dat" left + ascii (fp) : "./hrtfs/elev80/R80e325a.dat" right [ 34, 8 ] = ascii (fp) : "./hrtfs/elev80/L80e320a.dat" left + ascii (fp) : "./hrtfs/elev80/R80e320a.dat" right [ 34, 9 ] = ascii (fp) : "./hrtfs/elev80/L80e315a.dat" left + ascii (fp) : "./hrtfs/elev80/R80e315a.dat" right [ 34, 10 ] = ascii (fp) : "./hrtfs/elev80/L80e310a.dat" left + ascii (fp) : "./hrtfs/elev80/R80e310a.dat" right [ 34, 11 ] = ascii (fp) : "./hrtfs/elev80/L80e305a.dat" left + ascii (fp) : "./hrtfs/elev80/R80e305a.dat" right [ 34, 12 ] = ascii (fp) : "./hrtfs/elev80/L80e300a.dat" left + ascii (fp) : "./hrtfs/elev80/R80e300a.dat" right [ 34, 13 ] = ascii (fp) : "./hrtfs/elev80/L80e295a.dat" left + ascii (fp) : "./hrtfs/elev80/R80e295a.dat" right [ 34, 14 ] = ascii (fp) : "./hrtfs/elev80/L80e290a.dat" left + ascii (fp) : "./hrtfs/elev80/R80e290a.dat" right [ 34, 15 ] = ascii (fp) : "./hrtfs/elev80/L80e285a.dat" left + ascii (fp) : "./hrtfs/elev80/R80e285a.dat" right [ 34, 16 ] = ascii (fp) : "./hrtfs/elev80/L80e280a.dat" left + ascii (fp) : "./hrtfs/elev80/R80e280a.dat" right [ 34, 17 ] = ascii (fp) : "./hrtfs/elev80/L80e275a.dat" left + ascii (fp) : "./hrtfs/elev80/R80e275a.dat" right [ 34, 18 ] = ascii (fp) : "./hrtfs/elev80/L80e270a.dat" left + ascii (fp) : "./hrtfs/elev80/R80e270a.dat" right [ 34, 19 ] = ascii (fp) : "./hrtfs/elev80/L80e265a.dat" left + ascii (fp) : "./hrtfs/elev80/R80e265a.dat" right [ 34, 20 ] = ascii (fp) : "./hrtfs/elev80/L80e260a.dat" left + ascii (fp) : "./hrtfs/elev80/R80e260a.dat" right [ 34, 21 ] = ascii (fp) : "./hrtfs/elev80/L80e255a.dat" left + ascii (fp) : "./hrtfs/elev80/R80e255a.dat" right [ 34, 22 ] = ascii (fp) : "./hrtfs/elev80/L80e250a.dat" left + ascii (fp) : "./hrtfs/elev80/R80e250a.dat" right [ 34, 23 ] = ascii (fp) : "./hrtfs/elev80/L80e245a.dat" left + ascii (fp) : "./hrtfs/elev80/R80e245a.dat" right [ 34, 24 ] = ascii (fp) : "./hrtfs/elev80/L80e240a.dat" left + ascii (fp) : "./hrtfs/elev80/R80e240a.dat" right [ 34, 25 ] = ascii (fp) : "./hrtfs/elev80/L80e235a.dat" left + ascii (fp) : "./hrtfs/elev80/R80e235a.dat" right [ 34, 26 ] = ascii (fp) : "./hrtfs/elev80/L80e230a.dat" left + ascii (fp) : "./hrtfs/elev80/R80e230a.dat" right [ 34, 27 ] = ascii (fp) : "./hrtfs/elev80/L80e225a.dat" left + ascii (fp) : "./hrtfs/elev80/R80e225a.dat" right [ 34, 28 ] = ascii (fp) : "./hrtfs/elev80/L80e220a.dat" left + ascii (fp) : "./hrtfs/elev80/R80e220a.dat" right [ 34, 29 ] = ascii (fp) : "./hrtfs/elev80/L80e215a.dat" left + ascii (fp) : "./hrtfs/elev80/R80e215a.dat" right [ 34, 30 ] = ascii (fp) : "./hrtfs/elev80/L80e210a.dat" left + ascii (fp) : "./hrtfs/elev80/R80e210a.dat" right [ 34, 31 ] = ascii (fp) : "./hrtfs/elev80/L80e205a.dat" left + ascii (fp) : "./hrtfs/elev80/R80e205a.dat" right [ 34, 32 ] = ascii (fp) : "./hrtfs/elev80/L80e200a.dat" left + ascii (fp) : "./hrtfs/elev80/R80e200a.dat" right [ 34, 33 ] = ascii (fp) : "./hrtfs/elev80/L80e195a.dat" left + ascii (fp) : "./hrtfs/elev80/R80e195a.dat" right [ 34, 34 ] = ascii (fp) : "./hrtfs/elev80/L80e190a.dat" left + ascii (fp) : "./hrtfs/elev80/R80e190a.dat" right [ 34, 35 ] = ascii (fp) : "./hrtfs/elev80/L80e185a.dat" left + ascii (fp) : "./hrtfs/elev80/R80e185a.dat" right [ 34, 36 ] = ascii (fp) : "./hrtfs/elev80/L80e180a.dat" left + ascii (fp) : "./hrtfs/elev80/R80e180a.dat" right [ 34, 37 ] = ascii (fp) : "./hrtfs/elev80/L80e175a.dat" left + ascii (fp) : "./hrtfs/elev80/R80e175a.dat" right [ 34, 38 ] = ascii (fp) : "./hrtfs/elev80/L80e170a.dat" left + ascii (fp) : "./hrtfs/elev80/R80e170a.dat" right [ 34, 39 ] = ascii (fp) : "./hrtfs/elev80/L80e165a.dat" left + ascii (fp) : "./hrtfs/elev80/R80e165a.dat" right [ 34, 40 ] = ascii (fp) : "./hrtfs/elev80/L80e160a.dat" left + ascii (fp) : "./hrtfs/elev80/R80e160a.dat" right [ 34, 41 ] = ascii (fp) : "./hrtfs/elev80/L80e155a.dat" left + ascii (fp) : "./hrtfs/elev80/R80e155a.dat" right [ 34, 42 ] = ascii (fp) : "./hrtfs/elev80/L80e150a.dat" left + ascii (fp) : "./hrtfs/elev80/R80e150a.dat" right [ 34, 43 ] = ascii (fp) : "./hrtfs/elev80/L80e145a.dat" left + ascii (fp) : "./hrtfs/elev80/R80e145a.dat" right [ 34, 44 ] = ascii (fp) : "./hrtfs/elev80/L80e140a.dat" left + ascii (fp) : "./hrtfs/elev80/R80e140a.dat" right [ 34, 45 ] = ascii (fp) : "./hrtfs/elev80/L80e135a.dat" left + ascii (fp) : "./hrtfs/elev80/R80e135a.dat" right [ 34, 46 ] = ascii (fp) : "./hrtfs/elev80/L80e130a.dat" left + ascii (fp) : "./hrtfs/elev80/R80e130a.dat" right [ 34, 47 ] = ascii (fp) : "./hrtfs/elev80/L80e125a.dat" left + ascii (fp) : "./hrtfs/elev80/R80e125a.dat" right [ 34, 48 ] = ascii (fp) : "./hrtfs/elev80/L80e120a.dat" left + ascii (fp) : "./hrtfs/elev80/R80e120a.dat" right [ 34, 49 ] = ascii (fp) : "./hrtfs/elev80/L80e115a.dat" left + ascii (fp) : "./hrtfs/elev80/R80e115a.dat" right [ 34, 50 ] = ascii (fp) : "./hrtfs/elev80/L80e110a.dat" left + ascii (fp) : "./hrtfs/elev80/R80e110a.dat" right [ 34, 51 ] = ascii (fp) : "./hrtfs/elev80/L80e105a.dat" left + ascii (fp) : "./hrtfs/elev80/R80e105a.dat" right [ 34, 52 ] = ascii (fp) : "./hrtfs/elev80/L80e100a.dat" left + ascii (fp) : "./hrtfs/elev80/R80e100a.dat" right [ 34, 53 ] = ascii (fp) : "./hrtfs/elev80/L80e095a.dat" left + ascii (fp) : "./hrtfs/elev80/R80e095a.dat" right [ 34, 54 ] = ascii (fp) : "./hrtfs/elev80/L80e090a.dat" left + ascii (fp) : "./hrtfs/elev80/R80e090a.dat" right [ 34, 55 ] = ascii (fp) : "./hrtfs/elev80/L80e085a.dat" left + ascii (fp) : "./hrtfs/elev80/R80e085a.dat" right [ 34, 56 ] = ascii (fp) : "./hrtfs/elev80/L80e080a.dat" left + ascii (fp) : "./hrtfs/elev80/R80e080a.dat" right [ 34, 57 ] = ascii (fp) : "./hrtfs/elev80/L80e075a.dat" left + ascii (fp) : "./hrtfs/elev80/R80e075a.dat" right [ 34, 58 ] = ascii (fp) : "./hrtfs/elev80/L80e070a.dat" left + ascii (fp) : "./hrtfs/elev80/R80e070a.dat" right [ 34, 59 ] = ascii (fp) : "./hrtfs/elev80/L80e065a.dat" left + ascii (fp) : "./hrtfs/elev80/R80e065a.dat" right [ 34, 60 ] = ascii (fp) : "./hrtfs/elev80/L80e060a.dat" left + ascii (fp) : "./hrtfs/elev80/R80e060a.dat" right [ 34, 61 ] = ascii (fp) : "./hrtfs/elev80/L80e055a.dat" left + ascii (fp) : "./hrtfs/elev80/R80e055a.dat" right [ 34, 62 ] = ascii (fp) : "./hrtfs/elev80/L80e050a.dat" left + ascii (fp) : "./hrtfs/elev80/R80e050a.dat" right [ 34, 63 ] = ascii (fp) : "./hrtfs/elev80/L80e045a.dat" left + ascii (fp) : "./hrtfs/elev80/R80e045a.dat" right [ 34, 64 ] = ascii (fp) : "./hrtfs/elev80/L80e040a.dat" left + ascii (fp) : "./hrtfs/elev80/R80e040a.dat" right [ 34, 65 ] = ascii (fp) : "./hrtfs/elev80/L80e035a.dat" left + ascii (fp) : "./hrtfs/elev80/R80e035a.dat" right [ 34, 66 ] = ascii (fp) : "./hrtfs/elev80/L80e030a.dat" left + ascii (fp) : "./hrtfs/elev80/R80e030a.dat" right [ 34, 67 ] = ascii (fp) : "./hrtfs/elev80/L80e025a.dat" left + ascii (fp) : "./hrtfs/elev80/R80e025a.dat" right [ 34, 68 ] = ascii (fp) : "./hrtfs/elev80/L80e020a.dat" left + ascii (fp) : "./hrtfs/elev80/R80e020a.dat" right [ 34, 69 ] = ascii (fp) : "./hrtfs/elev80/L80e015a.dat" left + ascii (fp) : "./hrtfs/elev80/R80e015a.dat" right [ 34, 70 ] = ascii (fp) : "./hrtfs/elev80/L80e010a.dat" left + ascii (fp) : "./hrtfs/elev80/R80e010a.dat" right [ 34, 71 ] = ascii (fp) : "./hrtfs/elev80/L80e005a.dat" left + ascii (fp) : "./hrtfs/elev80/R80e005a.dat" right [ 35, 0 ] = ascii (fp) : "./hrtfs/elev85/L85e000a.dat" left + ascii (fp) : "./hrtfs/elev85/R85e000a.dat" right [ 35, 1 ] = ascii (fp) : "./hrtfs/elev85/L85e355a.dat" left + ascii (fp) : "./hrtfs/elev85/R85e355a.dat" right [ 35, 2 ] = ascii (fp) : "./hrtfs/elev85/L85e350a.dat" left + ascii (fp) : "./hrtfs/elev85/R85e350a.dat" right [ 35, 3 ] = ascii (fp) : "./hrtfs/elev85/L85e345a.dat" left + ascii (fp) : "./hrtfs/elev85/R85e345a.dat" right [ 35, 4 ] = ascii (fp) : "./hrtfs/elev85/L85e340a.dat" left + ascii (fp) : "./hrtfs/elev85/R85e340a.dat" right [ 35, 5 ] = ascii (fp) : "./hrtfs/elev85/L85e335a.dat" left + ascii (fp) : "./hrtfs/elev85/R85e335a.dat" right [ 35, 6 ] = ascii (fp) : "./hrtfs/elev85/L85e330a.dat" left + ascii (fp) : "./hrtfs/elev85/R85e330a.dat" right [ 35, 7 ] = ascii (fp) : "./hrtfs/elev85/L85e325a.dat" left + ascii (fp) : "./hrtfs/elev85/R85e325a.dat" right [ 35, 8 ] = ascii (fp) : "./hrtfs/elev85/L85e320a.dat" left + ascii (fp) : "./hrtfs/elev85/R85e320a.dat" right [ 35, 9 ] = ascii (fp) : "./hrtfs/elev85/L85e315a.dat" left + ascii (fp) : "./hrtfs/elev85/R85e315a.dat" right [ 35, 10 ] = ascii (fp) : "./hrtfs/elev85/L85e310a.dat" left + ascii (fp) : "./hrtfs/elev85/R85e310a.dat" right [ 35, 11 ] = ascii (fp) : "./hrtfs/elev85/L85e305a.dat" left + ascii (fp) : "./hrtfs/elev85/R85e305a.dat" right [ 35, 12 ] = ascii (fp) : "./hrtfs/elev85/L85e300a.dat" left + ascii (fp) : "./hrtfs/elev85/R85e300a.dat" right [ 35, 13 ] = ascii (fp) : "./hrtfs/elev85/L85e295a.dat" left + ascii (fp) : "./hrtfs/elev85/R85e295a.dat" right [ 35, 14 ] = ascii (fp) : "./hrtfs/elev85/L85e290a.dat" left + ascii (fp) : "./hrtfs/elev85/R85e290a.dat" right [ 35, 15 ] = ascii (fp) : "./hrtfs/elev85/L85e285a.dat" left + ascii (fp) : "./hrtfs/elev85/R85e285a.dat" right [ 35, 16 ] = ascii (fp) : "./hrtfs/elev85/L85e280a.dat" left + ascii (fp) : "./hrtfs/elev85/R85e280a.dat" right [ 35, 17 ] = ascii (fp) : "./hrtfs/elev85/L85e275a.dat" left + ascii (fp) : "./hrtfs/elev85/R85e275a.dat" right [ 35, 18 ] = ascii (fp) : "./hrtfs/elev85/L85e270a.dat" left + ascii (fp) : "./hrtfs/elev85/R85e270a.dat" right [ 35, 19 ] = ascii (fp) : "./hrtfs/elev85/L85e265a.dat" left + ascii (fp) : "./hrtfs/elev85/R85e265a.dat" right [ 35, 20 ] = ascii (fp) : "./hrtfs/elev85/L85e260a.dat" left + ascii (fp) : "./hrtfs/elev85/R85e260a.dat" right [ 35, 21 ] = ascii (fp) : "./hrtfs/elev85/L85e255a.dat" left + ascii (fp) : "./hrtfs/elev85/R85e255a.dat" right [ 35, 22 ] = ascii (fp) : "./hrtfs/elev85/L85e250a.dat" left + ascii (fp) : "./hrtfs/elev85/R85e250a.dat" right [ 35, 23 ] = ascii (fp) : "./hrtfs/elev85/L85e245a.dat" left + ascii (fp) : "./hrtfs/elev85/R85e245a.dat" right [ 35, 24 ] = ascii (fp) : "./hrtfs/elev85/L85e240a.dat" left + ascii (fp) : "./hrtfs/elev85/R85e240a.dat" right [ 35, 25 ] = ascii (fp) : "./hrtfs/elev85/L85e235a.dat" left + ascii (fp) : "./hrtfs/elev85/R85e235a.dat" right [ 35, 26 ] = ascii (fp) : "./hrtfs/elev85/L85e230a.dat" left + ascii (fp) : "./hrtfs/elev85/R85e230a.dat" right [ 35, 27 ] = ascii (fp) : "./hrtfs/elev85/L85e225a.dat" left + ascii (fp) : "./hrtfs/elev85/R85e225a.dat" right [ 35, 28 ] = ascii (fp) : "./hrtfs/elev85/L85e220a.dat" left + ascii (fp) : "./hrtfs/elev85/R85e220a.dat" right [ 35, 29 ] = ascii (fp) : "./hrtfs/elev85/L85e215a.dat" left + ascii (fp) : "./hrtfs/elev85/R85e215a.dat" right [ 35, 30 ] = ascii (fp) : "./hrtfs/elev85/L85e210a.dat" left + ascii (fp) : "./hrtfs/elev85/R85e210a.dat" right [ 35, 31 ] = ascii (fp) : "./hrtfs/elev85/L85e205a.dat" left + ascii (fp) : "./hrtfs/elev85/R85e205a.dat" right [ 35, 32 ] = ascii (fp) : "./hrtfs/elev85/L85e200a.dat" left + ascii (fp) : "./hrtfs/elev85/R85e200a.dat" right [ 35, 33 ] = ascii (fp) : "./hrtfs/elev85/L85e195a.dat" left + ascii (fp) : "./hrtfs/elev85/R85e195a.dat" right [ 35, 34 ] = ascii (fp) : "./hrtfs/elev85/L85e190a.dat" left + ascii (fp) : "./hrtfs/elev85/R85e190a.dat" right [ 35, 35 ] = ascii (fp) : "./hrtfs/elev85/L85e185a.dat" left + ascii (fp) : "./hrtfs/elev85/R85e185a.dat" right [ 35, 36 ] = ascii (fp) : "./hrtfs/elev85/L85e180a.dat" left + ascii (fp) : "./hrtfs/elev85/R85e180a.dat" right [ 35, 37 ] = ascii (fp) : "./hrtfs/elev85/L85e175a.dat" left + ascii (fp) : "./hrtfs/elev85/R85e175a.dat" right [ 35, 38 ] = ascii (fp) : "./hrtfs/elev85/L85e170a.dat" left + ascii (fp) : "./hrtfs/elev85/R85e170a.dat" right [ 35, 39 ] = ascii (fp) : "./hrtfs/elev85/L85e165a.dat" left + ascii (fp) : "./hrtfs/elev85/R85e165a.dat" right [ 35, 40 ] = ascii (fp) : "./hrtfs/elev85/L85e160a.dat" left + ascii (fp) : "./hrtfs/elev85/R85e160a.dat" right [ 35, 41 ] = ascii (fp) : "./hrtfs/elev85/L85e155a.dat" left + ascii (fp) : "./hrtfs/elev85/R85e155a.dat" right [ 35, 42 ] = ascii (fp) : "./hrtfs/elev85/L85e150a.dat" left + ascii (fp) : "./hrtfs/elev85/R85e150a.dat" right [ 35, 43 ] = ascii (fp) : "./hrtfs/elev85/L85e145a.dat" left + ascii (fp) : "./hrtfs/elev85/R85e145a.dat" right [ 35, 44 ] = ascii (fp) : "./hrtfs/elev85/L85e140a.dat" left + ascii (fp) : "./hrtfs/elev85/R85e140a.dat" right [ 35, 45 ] = ascii (fp) : "./hrtfs/elev85/L85e135a.dat" left + ascii (fp) : "./hrtfs/elev85/R85e135a.dat" right [ 35, 46 ] = ascii (fp) : "./hrtfs/elev85/L85e130a.dat" left + ascii (fp) : "./hrtfs/elev85/R85e130a.dat" right [ 35, 47 ] = ascii (fp) : "./hrtfs/elev85/L85e125a.dat" left + ascii (fp) : "./hrtfs/elev85/R85e125a.dat" right [ 35, 48 ] = ascii (fp) : "./hrtfs/elev85/L85e120a.dat" left + ascii (fp) : "./hrtfs/elev85/R85e120a.dat" right [ 35, 49 ] = ascii (fp) : "./hrtfs/elev85/L85e115a.dat" left + ascii (fp) : "./hrtfs/elev85/R85e115a.dat" right [ 35, 50 ] = ascii (fp) : "./hrtfs/elev85/L85e110a.dat" left + ascii (fp) : "./hrtfs/elev85/R85e110a.dat" right [ 35, 51 ] = ascii (fp) : "./hrtfs/elev85/L85e105a.dat" left + ascii (fp) : "./hrtfs/elev85/R85e105a.dat" right [ 35, 52 ] = ascii (fp) : "./hrtfs/elev85/L85e100a.dat" left + ascii (fp) : "./hrtfs/elev85/R85e100a.dat" right [ 35, 53 ] = ascii (fp) : "./hrtfs/elev85/L85e095a.dat" left + ascii (fp) : "./hrtfs/elev85/R85e095a.dat" right [ 35, 54 ] = ascii (fp) : "./hrtfs/elev85/L85e090a.dat" left + ascii (fp) : "./hrtfs/elev85/R85e090a.dat" right [ 35, 55 ] = ascii (fp) : "./hrtfs/elev85/L85e085a.dat" left + ascii (fp) : "./hrtfs/elev85/R85e085a.dat" right [ 35, 56 ] = ascii (fp) : "./hrtfs/elev85/L85e080a.dat" left + ascii (fp) : "./hrtfs/elev85/R85e080a.dat" right [ 35, 57 ] = ascii (fp) : "./hrtfs/elev85/L85e075a.dat" left + ascii (fp) : "./hrtfs/elev85/R85e075a.dat" right [ 35, 58 ] = ascii (fp) : "./hrtfs/elev85/L85e070a.dat" left + ascii (fp) : "./hrtfs/elev85/R85e070a.dat" right [ 35, 59 ] = ascii (fp) : "./hrtfs/elev85/L85e065a.dat" left + ascii (fp) : "./hrtfs/elev85/R85e065a.dat" right [ 35, 60 ] = ascii (fp) : "./hrtfs/elev85/L85e060a.dat" left + ascii (fp) : "./hrtfs/elev85/R85e060a.dat" right [ 35, 61 ] = ascii (fp) : "./hrtfs/elev85/L85e055a.dat" left + ascii (fp) : "./hrtfs/elev85/R85e055a.dat" right [ 35, 62 ] = ascii (fp) : "./hrtfs/elev85/L85e050a.dat" left + ascii (fp) : "./hrtfs/elev85/R85e050a.dat" right [ 35, 63 ] = ascii (fp) : "./hrtfs/elev85/L85e045a.dat" left + ascii (fp) : "./hrtfs/elev85/R85e045a.dat" right [ 35, 64 ] = ascii (fp) : "./hrtfs/elev85/L85e040a.dat" left + ascii (fp) : "./hrtfs/elev85/R85e040a.dat" right [ 35, 65 ] = ascii (fp) : "./hrtfs/elev85/L85e035a.dat" left + ascii (fp) : "./hrtfs/elev85/R85e035a.dat" right [ 35, 66 ] = ascii (fp) : "./hrtfs/elev85/L85e030a.dat" left + ascii (fp) : "./hrtfs/elev85/R85e030a.dat" right [ 35, 67 ] = ascii (fp) : "./hrtfs/elev85/L85e025a.dat" left + ascii (fp) : "./hrtfs/elev85/R85e025a.dat" right [ 35, 68 ] = ascii (fp) : "./hrtfs/elev85/L85e020a.dat" left + ascii (fp) : "./hrtfs/elev85/R85e020a.dat" right [ 35, 69 ] = ascii (fp) : "./hrtfs/elev85/L85e015a.dat" left + ascii (fp) : "./hrtfs/elev85/R85e015a.dat" right [ 35, 70 ] = ascii (fp) : "./hrtfs/elev85/L85e010a.dat" left + ascii (fp) : "./hrtfs/elev85/R85e010a.dat" right [ 35, 71 ] = ascii (fp) : "./hrtfs/elev85/L85e005a.dat" left + ascii (fp) : "./hrtfs/elev85/R85e005a.dat" right [ 36, 0 ] = ascii (fp) : "./hrtfs/elev90/L90e000a.dat" left + ascii (fp) : "./hrtfs/elev90/R90e000a.dat" right openal-soft-1.24.2/utils/IRC_1005.def000066400000000000000000001024641474041540300167420ustar00rootroot00000000000000# This is a makemhr HRIR definition file. It is used to define the layout and # source data to be processed into an OpenAL Soft compatible HRTF. # # This definition is used to transform the left and right ear HRIRs of any # raw data set from the IRCAM/AKG Listen HRTF database. # # The data sets are available free of charge from: # # http://recherche.ircam.fr/equipes/salles/listen/index.html # # Contact for the Listen HRTF Database: # # Olivier Warusfel , # Room Acoustics Team, IRCAM # 1, place Igor Stravinsky # 75004 PARIS, France rate = 44100 # The IRCAM sets are stereo because they provide both ear HRIRs. type = stereo # The raw sets have up to 8192 samples, but 2048 seems large enough. points = 2048 # No head radius was provided. Just use the average radius of 9 cm. radius = 0.09 # The IRCAM sets are single-field (like most others) with a distance between # the source and the listener of 1.95 meters. distance = 1.95 # This set isn't as dense as the MIT set. azimuths = 1, 6, 12, 24, 24, 24, 24, 24, 24, 24, 12, 6, 1 # The IRCAM source azimuth is counter-clockwise, so it needs to be flipped. # Left and right ear HRIRs (from the respective WAVE channels) are used to # create a stereo HRTF. # Replace all occurrences of IRC_#### for the desired subject (1005 was used # in this demonstration). [ 3, 0 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T000_P315.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T000_P315.wav" right [ 3, 1 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T345_P315.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T345_P315.wav" right [ 3, 2 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T330_P315.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T330_P315.wav" right [ 3, 3 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T315_P315.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T315_P315.wav" right [ 3, 4 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T300_P315.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T300_P315.wav" right [ 3, 5 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T285_P315.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T285_P315.wav" right [ 3, 6 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T270_P315.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T270_P315.wav" right [ 3, 7 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T255_P315.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T255_P315.wav" right [ 3, 8 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T240_P315.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T240_P315.wav" right [ 3, 9 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T225_P315.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T225_P315.wav" right [ 3, 10 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T210_P315.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T210_P315.wav" right [ 3, 11 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T195_P315.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T195_P315.wav" right [ 3, 12 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T180_P315.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T180_P315.wav" right [ 3, 13 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T165_P315.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T165_P315.wav" right [ 3, 14 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T150_P315.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T150_P315.wav" right [ 3, 15 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T135_P315.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T135_P315.wav" right [ 3, 16 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T120_P315.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T120_P315.wav" right [ 3, 17 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T105_P315.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T105_P315.wav" right [ 3, 18 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T090_P315.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T090_P315.wav" right [ 3, 19 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T075_P315.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T075_P315.wav" right [ 3, 20 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T060_P315.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T060_P315.wav" right [ 3, 21 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T045_P315.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T045_P315.wav" right [ 3, 22 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T030_P315.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T030_P315.wav" right [ 3, 23 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T015_P315.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T015_P315.wav" right [ 4, 0 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T000_P330.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T000_P330.wav" right [ 4, 1 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T345_P330.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T345_P330.wav" right [ 4, 2 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T330_P330.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T330_P330.wav" right [ 4, 3 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T315_P330.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T315_P330.wav" right [ 4, 4 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T300_P330.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T300_P330.wav" right [ 4, 5 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T285_P330.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T285_P330.wav" right [ 4, 6 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T270_P330.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T270_P330.wav" right [ 4, 7 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T255_P330.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T255_P330.wav" right [ 4, 8 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T240_P330.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T240_P330.wav" right [ 4, 9 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T225_P330.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T225_P330.wav" right [ 4, 10 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T210_P330.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T210_P330.wav" right [ 4, 11 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T195_P330.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T195_P330.wav" right [ 4, 12 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T180_P330.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T180_P330.wav" right [ 4, 13 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T165_P330.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T165_P330.wav" right [ 4, 14 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T150_P330.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T150_P330.wav" right [ 4, 15 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T135_P330.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T135_P330.wav" right [ 4, 16 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T120_P330.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T120_P330.wav" right [ 4, 17 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T105_P330.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T105_P330.wav" right [ 4, 18 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T090_P330.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T090_P330.wav" right [ 4, 19 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T075_P330.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T075_P330.wav" right [ 4, 20 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T060_P330.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T060_P330.wav" right [ 4, 21 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T045_P330.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T045_P330.wav" right [ 4, 22 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T030_P330.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T030_P330.wav" right [ 4, 23 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T015_P330.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T015_P330.wav" right [ 5, 0 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T000_P345.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T000_P345.wav" right [ 5, 1 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T345_P345.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T345_P345.wav" right [ 5, 2 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T330_P345.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T330_P345.wav" right [ 5, 3 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T315_P345.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T315_P345.wav" right [ 5, 4 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T300_P345.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T300_P345.wav" right [ 5, 5 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T285_P345.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T285_P345.wav" right [ 5, 6 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T270_P345.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T270_P345.wav" right [ 5, 7 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T255_P345.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T255_P345.wav" right [ 5, 8 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T240_P345.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T240_P345.wav" right [ 5, 9 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T225_P345.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T225_P345.wav" right [ 5, 10 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T210_P345.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T210_P345.wav" right [ 5, 11 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T195_P345.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T195_P345.wav" right [ 5, 12 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T180_P345.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T180_P345.wav" right [ 5, 13 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T165_P345.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T165_P345.wav" right [ 5, 14 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T150_P345.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T150_P345.wav" right [ 5, 15 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T135_P345.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T135_P345.wav" right [ 5, 16 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T120_P345.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T120_P345.wav" right [ 5, 17 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T105_P345.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T105_P345.wav" right [ 5, 18 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T090_P345.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T090_P345.wav" right [ 5, 19 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T075_P345.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T075_P345.wav" right [ 5, 20 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T060_P345.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T060_P345.wav" right [ 5, 21 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T045_P345.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T045_P345.wav" right [ 5, 22 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T030_P345.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T030_P345.wav" right [ 5, 23 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T015_P345.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T015_P345.wav" right [ 6, 0 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T000_P000.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T000_P000.wav" right [ 6, 1 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T345_P000.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T345_P000.wav" right [ 6, 2 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T330_P000.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T330_P000.wav" right [ 6, 3 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T315_P000.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T315_P000.wav" right [ 6, 4 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T300_P000.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T300_P000.wav" right [ 6, 5 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T285_P000.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T285_P000.wav" right [ 6, 6 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T270_P000.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T270_P000.wav" right [ 6, 7 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T255_P000.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T255_P000.wav" right [ 6, 8 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T240_P000.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T240_P000.wav" right [ 6, 9 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T225_P000.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T225_P000.wav" right [ 6, 10 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T210_P000.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T210_P000.wav" right [ 6, 11 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T195_P000.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T195_P000.wav" right [ 6, 12 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T180_P000.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T180_P000.wav" right [ 6, 13 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T165_P000.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T165_P000.wav" right [ 6, 14 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T150_P000.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T150_P000.wav" right [ 6, 15 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T135_P000.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T135_P000.wav" right [ 6, 16 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T120_P000.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T120_P000.wav" right [ 6, 17 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T105_P000.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T105_P000.wav" right [ 6, 18 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T090_P000.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T090_P000.wav" right [ 6, 19 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T075_P000.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T075_P000.wav" right [ 6, 20 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T060_P000.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T060_P000.wav" right [ 6, 21 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T045_P000.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T045_P000.wav" right [ 6, 22 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T030_P000.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T030_P000.wav" right [ 6, 23 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T015_P000.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T015_P000.wav" right [ 7, 0 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T000_P015.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T000_P015.wav" right [ 7, 1 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T345_P015.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T345_P015.wav" right [ 7, 2 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T330_P015.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T330_P015.wav" right [ 7, 3 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T315_P015.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T315_P015.wav" right [ 7, 4 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T300_P015.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T300_P015.wav" right [ 7, 5 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T285_P015.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T285_P015.wav" right [ 7, 6 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T270_P015.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T270_P015.wav" right [ 7, 7 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T255_P015.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T255_P015.wav" right [ 7, 8 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T240_P015.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T240_P015.wav" right [ 7, 9 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T225_P015.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T225_P015.wav" right [ 7, 10 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T210_P015.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T210_P015.wav" right [ 7, 11 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T195_P015.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T195_P015.wav" right [ 7, 12 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T180_P015.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T180_P015.wav" right [ 7, 13 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T165_P015.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T165_P015.wav" right [ 7, 14 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T150_P015.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T150_P015.wav" right [ 7, 15 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T135_P015.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T135_P015.wav" right [ 7, 16 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T120_P015.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T120_P015.wav" right [ 7, 17 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T105_P015.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T105_P015.wav" right [ 7, 18 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T090_P015.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T090_P015.wav" right [ 7, 19 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T075_P015.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T075_P015.wav" right [ 7, 20 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T060_P015.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T060_P015.wav" right [ 7, 21 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T045_P015.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T045_P015.wav" right [ 7, 22 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T030_P015.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T030_P015.wav" right [ 7, 23 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T015_P015.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T015_P015.wav" right [ 8, 0 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T000_P030.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T000_P030.wav" right [ 8, 1 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T345_P030.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T345_P030.wav" right [ 8, 2 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T330_P030.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T330_P030.wav" right [ 8, 3 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T315_P030.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T315_P030.wav" right [ 8, 4 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T300_P030.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T300_P030.wav" right [ 8, 5 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T285_P030.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T285_P030.wav" right [ 8, 6 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T270_P030.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T270_P030.wav" right [ 8, 7 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T255_P030.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T255_P030.wav" right [ 8, 8 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T240_P030.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T240_P030.wav" right [ 8, 9 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T225_P030.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T225_P030.wav" right [ 8, 10 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T210_P030.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T210_P030.wav" right [ 8, 11 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T195_P030.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T195_P030.wav" right [ 8, 12 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T180_P030.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T180_P030.wav" right [ 8, 13 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T165_P030.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T165_P030.wav" right [ 8, 14 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T150_P030.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T150_P030.wav" right [ 8, 15 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T135_P030.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T135_P030.wav" right [ 8, 16 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T120_P030.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T120_P030.wav" right [ 8, 17 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T105_P030.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T105_P030.wav" right [ 8, 18 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T090_P030.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T090_P030.wav" right [ 8, 19 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T075_P030.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T075_P030.wav" right [ 8, 20 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T060_P030.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T060_P030.wav" right [ 8, 21 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T045_P030.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T045_P030.wav" right [ 8, 22 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T030_P030.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T030_P030.wav" right [ 8, 23 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T015_P030.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T015_P030.wav" right [ 9, 0 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T000_P045.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T000_P045.wav" right [ 9, 1 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T345_P045.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T345_P045.wav" right [ 9, 2 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T330_P045.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T330_P045.wav" right [ 9, 3 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T315_P045.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T315_P045.wav" right [ 9, 4 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T300_P045.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T300_P045.wav" right [ 9, 5 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T285_P045.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T285_P045.wav" right [ 9, 6 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T270_P045.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T270_P045.wav" right [ 9, 7 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T255_P045.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T255_P045.wav" right [ 9, 8 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T240_P045.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T240_P045.wav" right [ 9, 9 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T225_P045.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T225_P045.wav" right [ 9, 10 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T210_P045.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T210_P045.wav" right [ 9, 11 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T195_P045.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T195_P045.wav" right [ 9, 12 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T180_P045.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T180_P045.wav" right [ 9, 13 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T165_P045.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T165_P045.wav" right [ 9, 14 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T150_P045.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T150_P045.wav" right [ 9, 15 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T135_P045.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T135_P045.wav" right [ 9, 16 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T120_P045.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T120_P045.wav" right [ 9, 17 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T105_P045.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T105_P045.wav" right [ 9, 18 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T090_P045.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T090_P045.wav" right [ 9, 19 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T075_P045.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T075_P045.wav" right [ 9, 20 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T060_P045.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T060_P045.wav" right [ 9, 21 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T045_P045.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T045_P045.wav" right [ 9, 22 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T030_P045.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T030_P045.wav" right [ 9, 23 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T015_P045.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T015_P045.wav" right [ 10, 0 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T000_P060.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T000_P060.wav" right [ 10, 1 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T330_P060.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T330_P060.wav" right [ 10, 2 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T300_P060.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T300_P060.wav" right [ 10, 3 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T270_P060.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T270_P060.wav" right [ 10, 4 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T240_P060.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T240_P060.wav" right [ 10, 5 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T210_P060.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T210_P060.wav" right [ 10, 6 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T180_P060.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T180_P060.wav" right [ 10, 7 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T150_P060.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T150_P060.wav" right [ 10, 8 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T120_P060.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T120_P060.wav" right [ 10, 9 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T090_P060.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T090_P060.wav" right [ 10, 10 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T060_P060.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T060_P060.wav" right [ 10, 11 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T030_P060.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T030_P060.wav" right [ 11, 0 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T000_P075.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T000_P075.wav" right [ 11, 1 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T300_P075.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T300_P075.wav" right [ 11, 2 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T240_P075.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T240_P075.wav" right [ 11, 3 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T180_P075.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T180_P075.wav" right [ 11, 4 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T120_P075.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T120_P075.wav" right [ 11, 5 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T060_P075.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T060_P075.wav" right [ 12, 0 ] = wave (0) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T000_P090.wav" left + wave (1) : "./IRC/RAW/WAV/IRC_1005_R/IRC_1005_R_R0195_T000_P090.wav" right openal-soft-1.24.2/utils/MIT_KEMAR.def000066400000000000000000001311021474041540300172170ustar00rootroot00000000000000# This is a makemhr HRIR definition file. It is used to define the layout and # source data to be processed into an OpenAL Soft compatible HRTF. # # This definition is used to transform the left ear HRIRs from the full set # of KEMAR HRIRs provided by Bill Gardner and Keith # Martin of MIT Media Laboratory. # # The data (full.zip) is available from: # # http://sound.media.mit.edu/resources/KEMAR.html # # It is copyrighted 1994 by MIT Media Laboratory, and provided free of charge # with no restrictions on use so long as the authors (above) are cited. # # This definition is used to generate the default HRTF table used by OpenAL # Soft. # The following are the data set metrics. They must always be specified at # the start of a definition file, but their order is not important. # Sampling rate of the HRIR data (in hertz). rate = 44100 # The channel type of incoming HRIR data (mono or stereo). Mono channel # inputs will result in mirroring to provide the right ear HRIRs. If not # specified, this defaults to mono. type = mono # The number of points to use from the HRIR data. This should be a # sufficiently large value (to encompass the entire impulse response). It # cannot be smaller than the truncation size (default is 32) specified on the # command line. points = 512 # The radius of the listener's head (measured ear-to-ear in meters). The # makemhr utility uses this value to rescale measured propagation delays when # a custom head radius is specified on the command line. It is also used as # the default radius when the spherical model is used to calculate an # approximate set of delays. This should match the data set as close as # possible for accurate rescaling when using the measured delays (the # default). At the moment, radius rescaling does not adjust HRIR coupling. radius = 0.09 # A list of the distances between the source and the listener (in meters) for # each field. These must start at or above the head radius and proceed in # ascending order. Since the MIT set is single-field, there is only one # distance. distance = 1.4 # A list of the number of azimuths measured for each elevation per field. # Elevations are separated by commas (,) while fields are separated by # semicolons (;). There must be at least 5 elevations covering 180 degrees # degrees of elevation for the data set to be viable. The poles (first and # last elevation) must be singular (an azimuth count of 1). azimuths = 1, 12, 24, 36, 45, 56, 60, 72, 72, 72, 72, 72, 60, 56, 45, 36, 24, 12, 1 # Following the metrics is the list of source HRIRs for each field, # elevation, and azimuth triplet. They don't have to be specified in order, # but the final composition must not be sparse. They can however begin above # a number of elevations (as typical for HRIR measurements). # # The field index is used to determine the distance coordinate (for mult- # field HRTFs) while the elevation and azimuth indices are used to determine # the resulting polar coordinates following OpenAL Soft's convention (-90 # degree elevation increasing counter-clockwise from the bottom; 0 degree # azimuth increasing clockwise from the front). # # More than one HRIR can be used per source. This allows the composition of # averaged magnitude responses or the specification of stereo HRTFs. Target # ears must (and can only be) specified for each source when the type metric # is set to 'stereo'. # # Source specification is of the form (~BNF): # # source = ( sf_index | mf_index ) source_ref [ '+' source_ref ]* # # sf_index = '[' ev_index ',' az_index ']' '=' # mf_index = '[' fd_index ',' ev_index ',' az_index ']' '=' # source_ref = mono_ref | stereo_ref # # fd_index = unsigned_integer # ev_index = unsigned_integer # az_index = unsigned_integer # mono_ref = ref_spec ':' filename # stereo_ref = ref_spec ':' filename ear # # ref_spec = ( wave_fmt '(' wave_parms ')' [ '@' start_sample ] ) | # ( bin_fmt '(' bini_parms ')' [ '@' start_byte ] ) | # ( bin_fmt '(' binf_parms ')' [ '@' start_byte ] ) | # ( ascii_fmt '(' asci_parms ')' [ '@' start_element ] ) | # ( ascii_fmt '(' ascf_parms ')' [ '@' start_element ] ) # filename = double_quoted_string # ear = 'left' | 'right' # # wave_fmt = 'wave' # wave_parms = channel # bin_fmt = 'bin_le' | 'bin_be' # bini_parms = 'int' ',' byte_size [ ',' bin_sig_bits ] [ ';' skip_bytes ] # binf_parms = 'fp' ',' byte_size [ ';' skip_bytes ] # ascii_fmt = 'ascii' # asci_parms = 'int' ',' sig_bits [ ';' skip_elements ] # ascf_parms = 'fp' [ ';' skip_elements ] # start_sample = unsigned_integer # start_byte = unsigned_integer # start_element = unsigned_integer # # channel = unsigned_integer # byte_size = unsigned_integer # bin_sig_bits = signed_integer # skip_bytes = unsigned_integer # sig_bits = unsigned_integer # skip_elements = unsigned_integer # # For bin_sig_bits, positive values mean the significant bits start at the # MSB (padding toward the LSB) while negative values mean they start at the # LSB. # Even though the MIT set is provided as stereo .wav files, each channel is # for a different sized KEMAR ear. Since it is not a stereo data set, no ear # is specified. The smaller KEMAR ear (in the left channel: 0) is used. [ 5, 0 ] = wave (0) : "./MITfull/elev-40/L-40e000a.wav" [ 5, 1 ] = wave (0) : "./MITfull/elev-40/L-40e006a.wav" [ 5, 2 ] = wave (0) : "./MITfull/elev-40/L-40e013a.wav" [ 5, 3 ] = wave (0) : "./MITfull/elev-40/L-40e019a.wav" [ 5, 4 ] = wave (0) : "./MITfull/elev-40/L-40e026a.wav" [ 5, 5 ] = wave (0) : "./MITfull/elev-40/L-40e032a.wav" [ 5, 6 ] = wave (0) : "./MITfull/elev-40/L-40e039a.wav" [ 5, 7 ] = wave (0) : "./MITfull/elev-40/L-40e045a.wav" [ 5, 8 ] = wave (0) : "./MITfull/elev-40/L-40e051a.wav" [ 5, 9 ] = wave (0) : "./MITfull/elev-40/L-40e058a.wav" [ 5, 10 ] = wave (0) : "./MITfull/elev-40/L-40e064a.wav" [ 5, 11 ] = wave (0) : "./MITfull/elev-40/L-40e071a.wav" [ 5, 12 ] = wave (0) : "./MITfull/elev-40/L-40e077a.wav" [ 5, 13 ] = wave (0) : "./MITfull/elev-40/L-40e084a.wav" [ 5, 14 ] = wave (0) : "./MITfull/elev-40/L-40e090a.wav" [ 5, 15 ] = wave (0) : "./MITfull/elev-40/L-40e096a.wav" [ 5, 16 ] = wave (0) : "./MITfull/elev-40/L-40e103a.wav" [ 5, 17 ] = wave (0) : "./MITfull/elev-40/L-40e109a.wav" [ 5, 18 ] = wave (0) : "./MITfull/elev-40/L-40e116a.wav" [ 5, 19 ] = wave (0) : "./MITfull/elev-40/L-40e122a.wav" [ 5, 20 ] = wave (0) : "./MITfull/elev-40/L-40e129a.wav" [ 5, 21 ] = wave (0) : "./MITfull/elev-40/L-40e135a.wav" [ 5, 22 ] = wave (0) : "./MITfull/elev-40/L-40e141a.wav" [ 5, 23 ] = wave (0) : "./MITfull/elev-40/L-40e148a.wav" [ 5, 24 ] = wave (0) : "./MITfull/elev-40/L-40e154a.wav" [ 5, 25 ] = wave (0) : "./MITfull/elev-40/L-40e161a.wav" [ 5, 26 ] = wave (0) : "./MITfull/elev-40/L-40e167a.wav" [ 5, 27 ] = wave (0) : "./MITfull/elev-40/L-40e174a.wav" [ 5, 28 ] = wave (0) : "./MITfull/elev-40/L-40e180a.wav" [ 5, 29 ] = wave (0) : "./MITfull/elev-40/L-40e186a.wav" [ 5, 30 ] = wave (0) : "./MITfull/elev-40/L-40e193a.wav" [ 5, 31 ] = wave (0) : "./MITfull/elev-40/L-40e199a.wav" [ 5, 32 ] = wave (0) : "./MITfull/elev-40/L-40e206a.wav" [ 5, 33 ] = wave (0) : "./MITfull/elev-40/L-40e212a.wav" [ 5, 34 ] = wave (0) : "./MITfull/elev-40/L-40e219a.wav" [ 5, 35 ] = wave (0) : "./MITfull/elev-40/L-40e225a.wav" [ 5, 36 ] = wave (0) : "./MITfull/elev-40/L-40e231a.wav" [ 5, 37 ] = wave (0) : "./MITfull/elev-40/L-40e238a.wav" [ 5, 38 ] = wave (0) : "./MITfull/elev-40/L-40e244a.wav" [ 5, 39 ] = wave (0) : "./MITfull/elev-40/L-40e251a.wav" [ 5, 40 ] = wave (0) : "./MITfull/elev-40/L-40e257a.wav" [ 5, 41 ] = wave (0) : "./MITfull/elev-40/L-40e264a.wav" [ 5, 42 ] = wave (0) : "./MITfull/elev-40/L-40e270a.wav" [ 5, 43 ] = wave (0) : "./MITfull/elev-40/L-40e276a.wav" [ 5, 44 ] = wave (0) : "./MITfull/elev-40/L-40e283a.wav" [ 5, 45 ] = wave (0) : "./MITfull/elev-40/L-40e289a.wav" [ 5, 46 ] = wave (0) : "./MITfull/elev-40/L-40e296a.wav" [ 5, 47 ] = wave (0) : "./MITfull/elev-40/L-40e302a.wav" [ 5, 48 ] = wave (0) : "./MITfull/elev-40/L-40e309a.wav" [ 5, 49 ] = wave (0) : "./MITfull/elev-40/L-40e315a.wav" [ 5, 50 ] = wave (0) : "./MITfull/elev-40/L-40e321a.wav" [ 5, 51 ] = wave (0) : "./MITfull/elev-40/L-40e328a.wav" [ 5, 52 ] = wave (0) : "./MITfull/elev-40/L-40e334a.wav" [ 5, 53 ] = wave (0) : "./MITfull/elev-40/L-40e341a.wav" [ 5, 54 ] = wave (0) : "./MITfull/elev-40/L-40e347a.wav" [ 5, 55 ] = wave (0) : "./MITfull/elev-40/L-40e354a.wav" [ 6, 0 ] = wave (0) : "./MITfull/elev-30/L-30e000a.wav" [ 6, 1 ] = wave (0) : "./MITfull/elev-30/L-30e006a.wav" [ 6, 2 ] = wave (0) : "./MITfull/elev-30/L-30e012a.wav" [ 6, 3 ] = wave (0) : "./MITfull/elev-30/L-30e018a.wav" [ 6, 4 ] = wave (0) : "./MITfull/elev-30/L-30e024a.wav" [ 6, 5 ] = wave (0) : "./MITfull/elev-30/L-30e030a.wav" [ 6, 6 ] = wave (0) : "./MITfull/elev-30/L-30e036a.wav" [ 6, 7 ] = wave (0) : "./MITfull/elev-30/L-30e042a.wav" [ 6, 8 ] = wave (0) : "./MITfull/elev-30/L-30e048a.wav" [ 6, 9 ] = wave (0) : "./MITfull/elev-30/L-30e054a.wav" [ 6, 10 ] = wave (0) : "./MITfull/elev-30/L-30e060a.wav" [ 6, 11 ] = wave (0) : "./MITfull/elev-30/L-30e066a.wav" [ 6, 12 ] = wave (0) : "./MITfull/elev-30/L-30e072a.wav" [ 6, 13 ] = wave (0) : "./MITfull/elev-30/L-30e078a.wav" [ 6, 14 ] = wave (0) : "./MITfull/elev-30/L-30e084a.wav" [ 6, 15 ] = wave (0) : "./MITfull/elev-30/L-30e090a.wav" [ 6, 16 ] = wave (0) : "./MITfull/elev-30/L-30e096a.wav" [ 6, 17 ] = wave (0) : "./MITfull/elev-30/L-30e102a.wav" [ 6, 18 ] = wave (0) : "./MITfull/elev-30/L-30e108a.wav" [ 6, 19 ] = wave (0) : "./MITfull/elev-30/L-30e114a.wav" [ 6, 20 ] = wave (0) : "./MITfull/elev-30/L-30e120a.wav" [ 6, 21 ] = wave (0) : "./MITfull/elev-30/L-30e126a.wav" [ 6, 22 ] = wave (0) : "./MITfull/elev-30/L-30e132a.wav" [ 6, 23 ] = wave (0) : "./MITfull/elev-30/L-30e138a.wav" [ 6, 24 ] = wave (0) : "./MITfull/elev-30/L-30e144a.wav" [ 6, 25 ] = wave (0) : "./MITfull/elev-30/L-30e150a.wav" [ 6, 26 ] = wave (0) : "./MITfull/elev-30/L-30e156a.wav" [ 6, 27 ] = wave (0) : "./MITfull/elev-30/L-30e162a.wav" [ 6, 28 ] = wave (0) : "./MITfull/elev-30/L-30e168a.wav" [ 6, 29 ] = wave (0) : "./MITfull/elev-30/L-30e174a.wav" [ 6, 30 ] = wave (0) : "./MITfull/elev-30/L-30e180a.wav" [ 6, 31 ] = wave (0) : "./MITfull/elev-30/L-30e186a.wav" [ 6, 32 ] = wave (0) : "./MITfull/elev-30/L-30e192a.wav" [ 6, 33 ] = wave (0) : "./MITfull/elev-30/L-30e198a.wav" [ 6, 34 ] = wave (0) : "./MITfull/elev-30/L-30e204a.wav" [ 6, 35 ] = wave (0) : "./MITfull/elev-30/L-30e210a.wav" [ 6, 36 ] = wave (0) : "./MITfull/elev-30/L-30e216a.wav" [ 6, 37 ] = wave (0) : "./MITfull/elev-30/L-30e222a.wav" [ 6, 38 ] = wave (0) : "./MITfull/elev-30/L-30e228a.wav" [ 6, 39 ] = wave (0) : "./MITfull/elev-30/L-30e234a.wav" [ 6, 40 ] = wave (0) : "./MITfull/elev-30/L-30e240a.wav" [ 6, 41 ] = wave (0) : "./MITfull/elev-30/L-30e246a.wav" [ 6, 42 ] = wave (0) : "./MITfull/elev-30/L-30e252a.wav" [ 6, 43 ] = wave (0) : "./MITfull/elev-30/L-30e258a.wav" [ 6, 44 ] = wave (0) : "./MITfull/elev-30/L-30e264a.wav" [ 6, 45 ] = wave (0) : "./MITfull/elev-30/L-30e270a.wav" [ 6, 46 ] = wave (0) : "./MITfull/elev-30/L-30e276a.wav" [ 6, 47 ] = wave (0) : "./MITfull/elev-30/L-30e282a.wav" [ 6, 48 ] = wave (0) : "./MITfull/elev-30/L-30e288a.wav" [ 6, 49 ] = wave (0) : "./MITfull/elev-30/L-30e294a.wav" [ 6, 50 ] = wave (0) : "./MITfull/elev-30/L-30e300a.wav" [ 6, 51 ] = wave (0) : "./MITfull/elev-30/L-30e306a.wav" [ 6, 52 ] = wave (0) : "./MITfull/elev-30/L-30e312a.wav" [ 6, 53 ] = wave (0) : "./MITfull/elev-30/L-30e318a.wav" [ 6, 54 ] = wave (0) : "./MITfull/elev-30/L-30e324a.wav" [ 6, 55 ] = wave (0) : "./MITfull/elev-30/L-30e330a.wav" [ 6, 56 ] = wave (0) : "./MITfull/elev-30/L-30e336a.wav" [ 6, 57 ] = wave (0) : "./MITfull/elev-30/L-30e342a.wav" [ 6, 58 ] = wave (0) : "./MITfull/elev-30/L-30e348a.wav" [ 6, 59 ] = wave (0) : "./MITfull/elev-30/L-30e354a.wav" [ 7, 0 ] = wave (0) : "./MITfull/elev-20/L-20e000a.wav" [ 7, 1 ] = wave (0) : "./MITfull/elev-20/L-20e005a.wav" [ 7, 2 ] = wave (0) : "./MITfull/elev-20/L-20e010a.wav" [ 7, 3 ] = wave (0) : "./MITfull/elev-20/L-20e015a.wav" [ 7, 4 ] = wave (0) : "./MITfull/elev-20/L-20e020a.wav" [ 7, 5 ] = wave (0) : "./MITfull/elev-20/L-20e025a.wav" [ 7, 6 ] = wave (0) : "./MITfull/elev-20/L-20e030a.wav" [ 7, 7 ] = wave (0) : "./MITfull/elev-20/L-20e035a.wav" [ 7, 8 ] = wave (0) : "./MITfull/elev-20/L-20e040a.wav" [ 7, 9 ] = wave (0) : "./MITfull/elev-20/L-20e045a.wav" [ 7, 10 ] = wave (0) : "./MITfull/elev-20/L-20e050a.wav" [ 7, 11 ] = wave (0) : "./MITfull/elev-20/L-20e055a.wav" [ 7, 12 ] = wave (0) : "./MITfull/elev-20/L-20e060a.wav" [ 7, 13 ] = wave (0) : "./MITfull/elev-20/L-20e065a.wav" [ 7, 14 ] = wave (0) : "./MITfull/elev-20/L-20e070a.wav" [ 7, 15 ] = wave (0) : "./MITfull/elev-20/L-20e075a.wav" [ 7, 16 ] = wave (0) : "./MITfull/elev-20/L-20e080a.wav" [ 7, 17 ] = wave (0) : "./MITfull/elev-20/L-20e085a.wav" [ 7, 18 ] = wave (0) : "./MITfull/elev-20/L-20e090a.wav" [ 7, 19 ] = wave (0) : "./MITfull/elev-20/L-20e095a.wav" [ 7, 20 ] = wave (0) : "./MITfull/elev-20/L-20e100a.wav" [ 7, 21 ] = wave (0) : "./MITfull/elev-20/L-20e105a.wav" [ 7, 22 ] = wave (0) : "./MITfull/elev-20/L-20e110a.wav" [ 7, 23 ] = wave (0) : "./MITfull/elev-20/L-20e115a.wav" [ 7, 24 ] = wave (0) : "./MITfull/elev-20/L-20e120a.wav" [ 7, 25 ] = wave (0) : "./MITfull/elev-20/L-20e125a.wav" [ 7, 26 ] = wave (0) : "./MITfull/elev-20/L-20e130a.wav" [ 7, 27 ] = wave (0) : "./MITfull/elev-20/L-20e135a.wav" [ 7, 28 ] = wave (0) : "./MITfull/elev-20/L-20e140a.wav" [ 7, 29 ] = wave (0) : "./MITfull/elev-20/L-20e145a.wav" [ 7, 30 ] = wave (0) : "./MITfull/elev-20/L-20e150a.wav" [ 7, 31 ] = wave (0) : "./MITfull/elev-20/L-20e155a.wav" [ 7, 32 ] = wave (0) : "./MITfull/elev-20/L-20e160a.wav" [ 7, 33 ] = wave (0) : "./MITfull/elev-20/L-20e165a.wav" [ 7, 34 ] = wave (0) : "./MITfull/elev-20/L-20e170a.wav" [ 7, 35 ] = wave (0) : "./MITfull/elev-20/L-20e175a.wav" [ 7, 36 ] = wave (0) : "./MITfull/elev-20/L-20e180a.wav" [ 7, 37 ] = wave (0) : "./MITfull/elev-20/L-20e185a.wav" [ 7, 38 ] = wave (0) : "./MITfull/elev-20/L-20e190a.wav" [ 7, 39 ] = wave (0) : "./MITfull/elev-20/L-20e195a.wav" [ 7, 40 ] = wave (0) : "./MITfull/elev-20/L-20e200a.wav" [ 7, 41 ] = wave (0) : "./MITfull/elev-20/L-20e205a.wav" [ 7, 42 ] = wave (0) : "./MITfull/elev-20/L-20e210a.wav" [ 7, 43 ] = wave (0) : "./MITfull/elev-20/L-20e215a.wav" [ 7, 44 ] = wave (0) : "./MITfull/elev-20/L-20e220a.wav" [ 7, 45 ] = wave (0) : "./MITfull/elev-20/L-20e225a.wav" [ 7, 46 ] = wave (0) : "./MITfull/elev-20/L-20e230a.wav" [ 7, 47 ] = wave (0) : "./MITfull/elev-20/L-20e235a.wav" [ 7, 48 ] = wave (0) : "./MITfull/elev-20/L-20e240a.wav" [ 7, 49 ] = wave (0) : "./MITfull/elev-20/L-20e245a.wav" [ 7, 50 ] = wave (0) : "./MITfull/elev-20/L-20e250a.wav" [ 7, 51 ] = wave (0) : "./MITfull/elev-20/L-20e255a.wav" [ 7, 52 ] = wave (0) : "./MITfull/elev-20/L-20e260a.wav" [ 7, 53 ] = wave (0) : "./MITfull/elev-20/L-20e265a.wav" [ 7, 54 ] = wave (0) : "./MITfull/elev-20/L-20e270a.wav" [ 7, 55 ] = wave (0) : "./MITfull/elev-20/L-20e275a.wav" [ 7, 56 ] = wave (0) : "./MITfull/elev-20/L-20e280a.wav" [ 7, 57 ] = wave (0) : "./MITfull/elev-20/L-20e285a.wav" [ 7, 58 ] = wave (0) : "./MITfull/elev-20/L-20e290a.wav" [ 7, 59 ] = wave (0) : "./MITfull/elev-20/L-20e295a.wav" [ 7, 60 ] = wave (0) : "./MITfull/elev-20/L-20e300a.wav" [ 7, 61 ] = wave (0) : "./MITfull/elev-20/L-20e305a.wav" [ 7, 62 ] = wave (0) : "./MITfull/elev-20/L-20e310a.wav" [ 7, 63 ] = wave (0) : "./MITfull/elev-20/L-20e315a.wav" [ 7, 64 ] = wave (0) : "./MITfull/elev-20/L-20e320a.wav" [ 7, 65 ] = wave (0) : "./MITfull/elev-20/L-20e325a.wav" [ 7, 66 ] = wave (0) : "./MITfull/elev-20/L-20e330a.wav" [ 7, 67 ] = wave (0) : "./MITfull/elev-20/L-20e335a.wav" [ 7, 68 ] = wave (0) : "./MITfull/elev-20/L-20e340a.wav" [ 7, 69 ] = wave (0) : "./MITfull/elev-20/L-20e345a.wav" [ 7, 70 ] = wave (0) : "./MITfull/elev-20/L-20e350a.wav" [ 7, 71 ] = wave (0) : "./MITfull/elev-20/L-20e355a.wav" [ 8, 0 ] = wave (0) : "./MITfull/elev-10/L-10e000a.wav" [ 8, 1 ] = wave (0) : "./MITfull/elev-10/L-10e005a.wav" [ 8, 2 ] = wave (0) : "./MITfull/elev-10/L-10e010a.wav" [ 8, 3 ] = wave (0) : "./MITfull/elev-10/L-10e015a.wav" [ 8, 4 ] = wave (0) : "./MITfull/elev-10/L-10e020a.wav" [ 8, 5 ] = wave (0) : "./MITfull/elev-10/L-10e025a.wav" [ 8, 6 ] = wave (0) : "./MITfull/elev-10/L-10e030a.wav" [ 8, 7 ] = wave (0) : "./MITfull/elev-10/L-10e035a.wav" [ 8, 8 ] = wave (0) : "./MITfull/elev-10/L-10e040a.wav" [ 8, 9 ] = wave (0) : "./MITfull/elev-10/L-10e045a.wav" [ 8, 10 ] = wave (0) : "./MITfull/elev-10/L-10e050a.wav" [ 8, 11 ] = wave (0) : "./MITfull/elev-10/L-10e055a.wav" [ 8, 12 ] = wave (0) : "./MITfull/elev-10/L-10e060a.wav" [ 8, 13 ] = wave (0) : "./MITfull/elev-10/L-10e065a.wav" [ 8, 14 ] = wave (0) : "./MITfull/elev-10/L-10e070a.wav" [ 8, 15 ] = wave (0) : "./MITfull/elev-10/L-10e075a.wav" [ 8, 16 ] = wave (0) : "./MITfull/elev-10/L-10e080a.wav" [ 8, 17 ] = wave (0) : "./MITfull/elev-10/L-10e085a.wav" [ 8, 18 ] = wave (0) : "./MITfull/elev-10/L-10e090a.wav" [ 8, 19 ] = wave (0) : "./MITfull/elev-10/L-10e095a.wav" [ 8, 20 ] = wave (0) : "./MITfull/elev-10/L-10e100a.wav" [ 8, 21 ] = wave (0) : "./MITfull/elev-10/L-10e105a.wav" [ 8, 22 ] = wave (0) : "./MITfull/elev-10/L-10e110a.wav" [ 8, 23 ] = wave (0) : "./MITfull/elev-10/L-10e115a.wav" [ 8, 24 ] = wave (0) : "./MITfull/elev-10/L-10e120a.wav" [ 8, 25 ] = wave (0) : "./MITfull/elev-10/L-10e125a.wav" [ 8, 26 ] = wave (0) : "./MITfull/elev-10/L-10e130a.wav" [ 8, 27 ] = wave (0) : "./MITfull/elev-10/L-10e135a.wav" [ 8, 28 ] = wave (0) : "./MITfull/elev-10/L-10e140a.wav" [ 8, 29 ] = wave (0) : "./MITfull/elev-10/L-10e145a.wav" [ 8, 30 ] = wave (0) : "./MITfull/elev-10/L-10e150a.wav" [ 8, 31 ] = wave (0) : "./MITfull/elev-10/L-10e155a.wav" [ 8, 32 ] = wave (0) : "./MITfull/elev-10/L-10e160a.wav" [ 8, 33 ] = wave (0) : "./MITfull/elev-10/L-10e165a.wav" [ 8, 34 ] = wave (0) : "./MITfull/elev-10/L-10e170a.wav" [ 8, 35 ] = wave (0) : "./MITfull/elev-10/L-10e175a.wav" [ 8, 36 ] = wave (0) : "./MITfull/elev-10/L-10e180a.wav" [ 8, 37 ] = wave (0) : "./MITfull/elev-10/L-10e185a.wav" [ 8, 38 ] = wave (0) : "./MITfull/elev-10/L-10e190a.wav" [ 8, 39 ] = wave (0) : "./MITfull/elev-10/L-10e195a.wav" [ 8, 40 ] = wave (0) : "./MITfull/elev-10/L-10e200a.wav" [ 8, 41 ] = wave (0) : "./MITfull/elev-10/L-10e205a.wav" [ 8, 42 ] = wave (0) : "./MITfull/elev-10/L-10e210a.wav" [ 8, 43 ] = wave (0) : "./MITfull/elev-10/L-10e215a.wav" [ 8, 44 ] = wave (0) : "./MITfull/elev-10/L-10e220a.wav" [ 8, 45 ] = wave (0) : "./MITfull/elev-10/L-10e225a.wav" [ 8, 46 ] = wave (0) : "./MITfull/elev-10/L-10e230a.wav" [ 8, 47 ] = wave (0) : "./MITfull/elev-10/L-10e235a.wav" [ 8, 48 ] = wave (0) : "./MITfull/elev-10/L-10e240a.wav" [ 8, 49 ] = wave (0) : "./MITfull/elev-10/L-10e245a.wav" [ 8, 50 ] = wave (0) : "./MITfull/elev-10/L-10e250a.wav" [ 8, 51 ] = wave (0) : "./MITfull/elev-10/L-10e255a.wav" [ 8, 52 ] = wave (0) : "./MITfull/elev-10/L-10e260a.wav" [ 8, 53 ] = wave (0) : "./MITfull/elev-10/L-10e265a.wav" [ 8, 54 ] = wave (0) : "./MITfull/elev-10/L-10e270a.wav" [ 8, 55 ] = wave (0) : "./MITfull/elev-10/L-10e275a.wav" [ 8, 56 ] = wave (0) : "./MITfull/elev-10/L-10e280a.wav" [ 8, 57 ] = wave (0) : "./MITfull/elev-10/L-10e285a.wav" [ 8, 58 ] = wave (0) : "./MITfull/elev-10/L-10e290a.wav" [ 8, 59 ] = wave (0) : "./MITfull/elev-10/L-10e295a.wav" [ 8, 60 ] = wave (0) : "./MITfull/elev-10/L-10e300a.wav" [ 8, 61 ] = wave (0) : "./MITfull/elev-10/L-10e305a.wav" [ 8, 62 ] = wave (0) : "./MITfull/elev-10/L-10e310a.wav" [ 8, 63 ] = wave (0) : "./MITfull/elev-10/L-10e315a.wav" [ 8, 64 ] = wave (0) : "./MITfull/elev-10/L-10e320a.wav" [ 8, 65 ] = wave (0) : "./MITfull/elev-10/L-10e325a.wav" [ 8, 66 ] = wave (0) : "./MITfull/elev-10/L-10e330a.wav" [ 8, 67 ] = wave (0) : "./MITfull/elev-10/L-10e335a.wav" [ 8, 68 ] = wave (0) : "./MITfull/elev-10/L-10e340a.wav" [ 8, 69 ] = wave (0) : "./MITfull/elev-10/L-10e345a.wav" [ 8, 70 ] = wave (0) : "./MITfull/elev-10/L-10e350a.wav" [ 8, 71 ] = wave (0) : "./MITfull/elev-10/L-10e355a.wav" [ 9, 0 ] = wave (0) : "./MITfull/elev0/L0e000a.wav" [ 9, 1 ] = wave (0) : "./MITfull/elev0/L0e005a.wav" [ 9, 2 ] = wave (0) : "./MITfull/elev0/L0e010a.wav" [ 9, 3 ] = wave (0) : "./MITfull/elev0/L0e015a.wav" [ 9, 4 ] = wave (0) : "./MITfull/elev0/L0e020a.wav" [ 9, 5 ] = wave (0) : "./MITfull/elev0/L0e025a.wav" [ 9, 6 ] = wave (0) : "./MITfull/elev0/L0e030a.wav" [ 9, 7 ] = wave (0) : "./MITfull/elev0/L0e035a.wav" [ 9, 8 ] = wave (0) : "./MITfull/elev0/L0e040a.wav" [ 9, 9 ] = wave (0) : "./MITfull/elev0/L0e045a.wav" [ 9, 10 ] = wave (0) : "./MITfull/elev0/L0e050a.wav" [ 9, 11 ] = wave (0) : "./MITfull/elev0/L0e055a.wav" [ 9, 12 ] = wave (0) : "./MITfull/elev0/L0e060a.wav" [ 9, 13 ] = wave (0) : "./MITfull/elev0/L0e065a.wav" [ 9, 14 ] = wave (0) : "./MITfull/elev0/L0e070a.wav" [ 9, 15 ] = wave (0) : "./MITfull/elev0/L0e075a.wav" [ 9, 16 ] = wave (0) : "./MITfull/elev0/L0e080a.wav" [ 9, 17 ] = wave (0) : "./MITfull/elev0/L0e085a.wav" [ 9, 18 ] = wave (0) : "./MITfull/elev0/L0e090a.wav" [ 9, 19 ] = wave (0) : "./MITfull/elev0/L0e095a.wav" [ 9, 20 ] = wave (0) : "./MITfull/elev0/L0e100a.wav" [ 9, 21 ] = wave (0) : "./MITfull/elev0/L0e105a.wav" [ 9, 22 ] = wave (0) : "./MITfull/elev0/L0e110a.wav" [ 9, 23 ] = wave (0) : "./MITfull/elev0/L0e115a.wav" [ 9, 24 ] = wave (0) : "./MITfull/elev0/L0e120a.wav" [ 9, 25 ] = wave (0) : "./MITfull/elev0/L0e125a.wav" [ 9, 26 ] = wave (0) : "./MITfull/elev0/L0e130a.wav" [ 9, 27 ] = wave (0) : "./MITfull/elev0/L0e135a.wav" [ 9, 28 ] = wave (0) : "./MITfull/elev0/L0e140a.wav" [ 9, 29 ] = wave (0) : "./MITfull/elev0/L0e145a.wav" [ 9, 30 ] = wave (0) : "./MITfull/elev0/L0e150a.wav" [ 9, 31 ] = wave (0) : "./MITfull/elev0/L0e155a.wav" [ 9, 32 ] = wave (0) : "./MITfull/elev0/L0e160a.wav" [ 9, 33 ] = wave (0) : "./MITfull/elev0/L0e165a.wav" [ 9, 34 ] = wave (0) : "./MITfull/elev0/L0e170a.wav" [ 9, 35 ] = wave (0) : "./MITfull/elev0/L0e175a.wav" [ 9, 36 ] = wave (0) : "./MITfull/elev0/L0e180a.wav" [ 9, 37 ] = wave (0) : "./MITfull/elev0/L0e185a.wav" [ 9, 38 ] = wave (0) : "./MITfull/elev0/L0e190a.wav" [ 9, 39 ] = wave (0) : "./MITfull/elev0/L0e195a.wav" [ 9, 40 ] = wave (0) : "./MITfull/elev0/L0e200a.wav" [ 9, 41 ] = wave (0) : "./MITfull/elev0/L0e205a.wav" [ 9, 42 ] = wave (0) : "./MITfull/elev0/L0e210a.wav" [ 9, 43 ] = wave (0) : "./MITfull/elev0/L0e215a.wav" [ 9, 44 ] = wave (0) : "./MITfull/elev0/L0e220a.wav" [ 9, 45 ] = wave (0) : "./MITfull/elev0/L0e225a.wav" [ 9, 46 ] = wave (0) : "./MITfull/elev0/L0e230a.wav" [ 9, 47 ] = wave (0) : "./MITfull/elev0/L0e235a.wav" [ 9, 48 ] = wave (0) : "./MITfull/elev0/L0e240a.wav" [ 9, 49 ] = wave (0) : "./MITfull/elev0/L0e245a.wav" [ 9, 50 ] = wave (0) : "./MITfull/elev0/L0e250a.wav" [ 9, 51 ] = wave (0) : "./MITfull/elev0/L0e255a.wav" [ 9, 52 ] = wave (0) : "./MITfull/elev0/L0e260a.wav" [ 9, 53 ] = wave (0) : "./MITfull/elev0/L0e265a.wav" [ 9, 54 ] = wave (0) : "./MITfull/elev0/L0e270a.wav" [ 9, 55 ] = wave (0) : "./MITfull/elev0/L0e275a.wav" [ 9, 56 ] = wave (0) : "./MITfull/elev0/L0e280a.wav" [ 9, 57 ] = wave (0) : "./MITfull/elev0/L0e285a.wav" [ 9, 58 ] = wave (0) : "./MITfull/elev0/L0e290a.wav" [ 9, 59 ] = wave (0) : "./MITfull/elev0/L0e295a.wav" [ 9, 60 ] = wave (0) : "./MITfull/elev0/L0e300a.wav" [ 9, 61 ] = wave (0) : "./MITfull/elev0/L0e305a.wav" [ 9, 62 ] = wave (0) : "./MITfull/elev0/L0e310a.wav" [ 9, 63 ] = wave (0) : "./MITfull/elev0/L0e315a.wav" [ 9, 64 ] = wave (0) : "./MITfull/elev0/L0e320a.wav" [ 9, 65 ] = wave (0) : "./MITfull/elev0/L0e325a.wav" [ 9, 66 ] = wave (0) : "./MITfull/elev0/L0e330a.wav" [ 9, 67 ] = wave (0) : "./MITfull/elev0/L0e335a.wav" [ 9, 68 ] = wave (0) : "./MITfull/elev0/L0e340a.wav" [ 9, 69 ] = wave (0) : "./MITfull/elev0/L0e345a.wav" [ 9, 70 ] = wave (0) : "./MITfull/elev0/L0e350a.wav" [ 9, 71 ] = wave (0) : "./MITfull/elev0/L0e355a.wav" [ 10, 0 ] = wave (0) : "./MITfull/elev10/L10e000a.wav" [ 10, 1 ] = wave (0) : "./MITfull/elev10/L10e005a.wav" [ 10, 2 ] = wave (0) : "./MITfull/elev10/L10e010a.wav" [ 10, 3 ] = wave (0) : "./MITfull/elev10/L10e015a.wav" [ 10, 4 ] = wave (0) : "./MITfull/elev10/L10e020a.wav" [ 10, 5 ] = wave (0) : "./MITfull/elev10/L10e025a.wav" [ 10, 6 ] = wave (0) : "./MITfull/elev10/L10e030a.wav" [ 10, 7 ] = wave (0) : "./MITfull/elev10/L10e035a.wav" [ 10, 8 ] = wave (0) : "./MITfull/elev10/L10e040a.wav" [ 10, 9 ] = wave (0) : "./MITfull/elev10/L10e045a.wav" [ 10, 10 ] = wave (0) : "./MITfull/elev10/L10e050a.wav" [ 10, 11 ] = wave (0) : "./MITfull/elev10/L10e055a.wav" [ 10, 12 ] = wave (0) : "./MITfull/elev10/L10e060a.wav" [ 10, 13 ] = wave (0) : "./MITfull/elev10/L10e065a.wav" [ 10, 14 ] = wave (0) : "./MITfull/elev10/L10e070a.wav" [ 10, 15 ] = wave (0) : "./MITfull/elev10/L10e075a.wav" [ 10, 16 ] = wave (0) : "./MITfull/elev10/L10e080a.wav" [ 10, 17 ] = wave (0) : "./MITfull/elev10/L10e085a.wav" [ 10, 18 ] = wave (0) : "./MITfull/elev10/L10e090a.wav" [ 10, 19 ] = wave (0) : "./MITfull/elev10/L10e095a.wav" [ 10, 20 ] = wave (0) : "./MITfull/elev10/L10e100a.wav" [ 10, 21 ] = wave (0) : "./MITfull/elev10/L10e105a.wav" [ 10, 22 ] = wave (0) : "./MITfull/elev10/L10e110a.wav" [ 10, 23 ] = wave (0) : "./MITfull/elev10/L10e115a.wav" [ 10, 24 ] = wave (0) : "./MITfull/elev10/L10e120a.wav" [ 10, 25 ] = wave (0) : "./MITfull/elev10/L10e125a.wav" [ 10, 26 ] = wave (0) : "./MITfull/elev10/L10e130a.wav" [ 10, 27 ] = wave (0) : "./MITfull/elev10/L10e135a.wav" [ 10, 28 ] = wave (0) : "./MITfull/elev10/L10e140a.wav" [ 10, 29 ] = wave (0) : "./MITfull/elev10/L10e145a.wav" [ 10, 30 ] = wave (0) : "./MITfull/elev10/L10e150a.wav" [ 10, 31 ] = wave (0) : "./MITfull/elev10/L10e155a.wav" [ 10, 32 ] = wave (0) : "./MITfull/elev10/L10e160a.wav" [ 10, 33 ] = wave (0) : "./MITfull/elev10/L10e165a.wav" [ 10, 34 ] = wave (0) : "./MITfull/elev10/L10e170a.wav" [ 10, 35 ] = wave (0) : "./MITfull/elev10/L10e175a.wav" [ 10, 36 ] = wave (0) : "./MITfull/elev10/L10e180a.wav" [ 10, 37 ] = wave (0) : "./MITfull/elev10/L10e185a.wav" [ 10, 38 ] = wave (0) : "./MITfull/elev10/L10e190a.wav" [ 10, 39 ] = wave (0) : "./MITfull/elev10/L10e195a.wav" [ 10, 40 ] = wave (0) : "./MITfull/elev10/L10e200a.wav" [ 10, 41 ] = wave (0) : "./MITfull/elev10/L10e205a.wav" [ 10, 42 ] = wave (0) : "./MITfull/elev10/L10e210a.wav" [ 10, 43 ] = wave (0) : "./MITfull/elev10/L10e215a.wav" [ 10, 44 ] = wave (0) : "./MITfull/elev10/L10e220a.wav" [ 10, 45 ] = wave (0) : "./MITfull/elev10/L10e225a.wav" [ 10, 46 ] = wave (0) : "./MITfull/elev10/L10e230a.wav" [ 10, 47 ] = wave (0) : "./MITfull/elev10/L10e235a.wav" [ 10, 48 ] = wave (0) : "./MITfull/elev10/L10e240a.wav" [ 10, 49 ] = wave (0) : "./MITfull/elev10/L10e245a.wav" [ 10, 50 ] = wave (0) : "./MITfull/elev10/L10e250a.wav" [ 10, 51 ] = wave (0) : "./MITfull/elev10/L10e255a.wav" [ 10, 52 ] = wave (0) : "./MITfull/elev10/L10e260a.wav" [ 10, 53 ] = wave (0) : "./MITfull/elev10/L10e265a.wav" [ 10, 54 ] = wave (0) : "./MITfull/elev10/L10e270a.wav" [ 10, 55 ] = wave (0) : "./MITfull/elev10/L10e275a.wav" [ 10, 56 ] = wave (0) : "./MITfull/elev10/L10e280a.wav" [ 10, 57 ] = wave (0) : "./MITfull/elev10/L10e285a.wav" [ 10, 58 ] = wave (0) : "./MITfull/elev10/L10e290a.wav" [ 10, 59 ] = wave (0) : "./MITfull/elev10/L10e295a.wav" [ 10, 60 ] = wave (0) : "./MITfull/elev10/L10e300a.wav" [ 10, 61 ] = wave (0) : "./MITfull/elev10/L10e305a.wav" [ 10, 62 ] = wave (0) : "./MITfull/elev10/L10e310a.wav" [ 10, 63 ] = wave (0) : "./MITfull/elev10/L10e315a.wav" [ 10, 64 ] = wave (0) : "./MITfull/elev10/L10e320a.wav" [ 10, 65 ] = wave (0) : "./MITfull/elev10/L10e325a.wav" [ 10, 66 ] = wave (0) : "./MITfull/elev10/L10e330a.wav" [ 10, 67 ] = wave (0) : "./MITfull/elev10/L10e335a.wav" [ 10, 68 ] = wave (0) : "./MITfull/elev10/L10e340a.wav" [ 10, 69 ] = wave (0) : "./MITfull/elev10/L10e345a.wav" [ 10, 70 ] = wave (0) : "./MITfull/elev10/L10e350a.wav" [ 10, 71 ] = wave (0) : "./MITfull/elev10/L10e355a.wav" [ 11, 0 ] = wave (0) : "./MITfull/elev20/L20e000a.wav" [ 11, 1 ] = wave (0) : "./MITfull/elev20/L20e005a.wav" [ 11, 2 ] = wave (0) : "./MITfull/elev20/L20e010a.wav" [ 11, 3 ] = wave (0) : "./MITfull/elev20/L20e015a.wav" [ 11, 4 ] = wave (0) : "./MITfull/elev20/L20e020a.wav" [ 11, 5 ] = wave (0) : "./MITfull/elev20/L20e025a.wav" [ 11, 6 ] = wave (0) : "./MITfull/elev20/L20e030a.wav" [ 11, 7 ] = wave (0) : "./MITfull/elev20/L20e035a.wav" [ 11, 8 ] = wave (0) : "./MITfull/elev20/L20e040a.wav" [ 11, 9 ] = wave (0) : "./MITfull/elev20/L20e045a.wav" [ 11, 10 ] = wave (0) : "./MITfull/elev20/L20e050a.wav" [ 11, 11 ] = wave (0) : "./MITfull/elev20/L20e055a.wav" [ 11, 12 ] = wave (0) : "./MITfull/elev20/L20e060a.wav" [ 11, 13 ] = wave (0) : "./MITfull/elev20/L20e065a.wav" [ 11, 14 ] = wave (0) : "./MITfull/elev20/L20e070a.wav" [ 11, 15 ] = wave (0) : "./MITfull/elev20/L20e075a.wav" [ 11, 16 ] = wave (0) : "./MITfull/elev20/L20e080a.wav" [ 11, 17 ] = wave (0) : "./MITfull/elev20/L20e085a.wav" [ 11, 18 ] = wave (0) : "./MITfull/elev20/L20e090a.wav" [ 11, 19 ] = wave (0) : "./MITfull/elev20/L20e095a.wav" [ 11, 20 ] = wave (0) : "./MITfull/elev20/L20e100a.wav" [ 11, 21 ] = wave (0) : "./MITfull/elev20/L20e105a.wav" [ 11, 22 ] = wave (0) : "./MITfull/elev20/L20e110a.wav" [ 11, 23 ] = wave (0) : "./MITfull/elev20/L20e115a.wav" [ 11, 24 ] = wave (0) : "./MITfull/elev20/L20e120a.wav" [ 11, 25 ] = wave (0) : "./MITfull/elev20/L20e125a.wav" [ 11, 26 ] = wave (0) : "./MITfull/elev20/L20e130a.wav" [ 11, 27 ] = wave (0) : "./MITfull/elev20/L20e135a.wav" [ 11, 28 ] = wave (0) : "./MITfull/elev20/L20e140a.wav" [ 11, 29 ] = wave (0) : "./MITfull/elev20/L20e145a.wav" [ 11, 30 ] = wave (0) : "./MITfull/elev20/L20e150a.wav" [ 11, 31 ] = wave (0) : "./MITfull/elev20/L20e155a.wav" [ 11, 32 ] = wave (0) : "./MITfull/elev20/L20e160a.wav" [ 11, 33 ] = wave (0) : "./MITfull/elev20/L20e165a.wav" [ 11, 34 ] = wave (0) : "./MITfull/elev20/L20e170a.wav" [ 11, 35 ] = wave (0) : "./MITfull/elev20/L20e175a.wav" [ 11, 36 ] = wave (0) : "./MITfull/elev20/L20e180a.wav" [ 11, 37 ] = wave (0) : "./MITfull/elev20/L20e185a.wav" [ 11, 38 ] = wave (0) : "./MITfull/elev20/L20e190a.wav" [ 11, 39 ] = wave (0) : "./MITfull/elev20/L20e195a.wav" [ 11, 40 ] = wave (0) : "./MITfull/elev20/L20e200a.wav" [ 11, 41 ] = wave (0) : "./MITfull/elev20/L20e205a.wav" [ 11, 42 ] = wave (0) : "./MITfull/elev20/L20e210a.wav" [ 11, 43 ] = wave (0) : "./MITfull/elev20/L20e215a.wav" [ 11, 44 ] = wave (0) : "./MITfull/elev20/L20e220a.wav" [ 11, 45 ] = wave (0) : "./MITfull/elev20/L20e225a.wav" [ 11, 46 ] = wave (0) : "./MITfull/elev20/L20e230a.wav" [ 11, 47 ] = wave (0) : "./MITfull/elev20/L20e235a.wav" [ 11, 48 ] = wave (0) : "./MITfull/elev20/L20e240a.wav" [ 11, 49 ] = wave (0) : "./MITfull/elev20/L20e245a.wav" [ 11, 50 ] = wave (0) : "./MITfull/elev20/L20e250a.wav" [ 11, 51 ] = wave (0) : "./MITfull/elev20/L20e255a.wav" [ 11, 52 ] = wave (0) : "./MITfull/elev20/L20e260a.wav" [ 11, 53 ] = wave (0) : "./MITfull/elev20/L20e265a.wav" [ 11, 54 ] = wave (0) : "./MITfull/elev20/L20e270a.wav" [ 11, 55 ] = wave (0) : "./MITfull/elev20/L20e275a.wav" [ 11, 56 ] = wave (0) : "./MITfull/elev20/L20e280a.wav" [ 11, 57 ] = wave (0) : "./MITfull/elev20/L20e285a.wav" [ 11, 58 ] = wave (0) : "./MITfull/elev20/L20e290a.wav" [ 11, 59 ] = wave (0) : "./MITfull/elev20/L20e295a.wav" [ 11, 60 ] = wave (0) : "./MITfull/elev20/L20e300a.wav" [ 11, 61 ] = wave (0) : "./MITfull/elev20/L20e305a.wav" [ 11, 62 ] = wave (0) : "./MITfull/elev20/L20e310a.wav" [ 11, 63 ] = wave (0) : "./MITfull/elev20/L20e315a.wav" [ 11, 64 ] = wave (0) : "./MITfull/elev20/L20e320a.wav" [ 11, 65 ] = wave (0) : "./MITfull/elev20/L20e325a.wav" [ 11, 66 ] = wave (0) : "./MITfull/elev20/L20e330a.wav" [ 11, 67 ] = wave (0) : "./MITfull/elev20/L20e335a.wav" [ 11, 68 ] = wave (0) : "./MITfull/elev20/L20e340a.wav" [ 11, 69 ] = wave (0) : "./MITfull/elev20/L20e345a.wav" [ 11, 70 ] = wave (0) : "./MITfull/elev20/L20e350a.wav" [ 11, 71 ] = wave (0) : "./MITfull/elev20/L20e355a.wav" [ 12, 0 ] = wave (0) : "./MITfull/elev30/L30e000a.wav" [ 12, 1 ] = wave (0) : "./MITfull/elev30/L30e006a.wav" [ 12, 2 ] = wave (0) : "./MITfull/elev30/L30e012a.wav" [ 12, 3 ] = wave (0) : "./MITfull/elev30/L30e018a.wav" [ 12, 4 ] = wave (0) : "./MITfull/elev30/L30e024a.wav" [ 12, 5 ] = wave (0) : "./MITfull/elev30/L30e030a.wav" [ 12, 6 ] = wave (0) : "./MITfull/elev30/L30e036a.wav" [ 12, 7 ] = wave (0) : "./MITfull/elev30/L30e042a.wav" [ 12, 8 ] = wave (0) : "./MITfull/elev30/L30e048a.wav" [ 12, 9 ] = wave (0) : "./MITfull/elev30/L30e054a.wav" [ 12, 10 ] = wave (0) : "./MITfull/elev30/L30e060a.wav" [ 12, 11 ] = wave (0) : "./MITfull/elev30/L30e066a.wav" [ 12, 12 ] = wave (0) : "./MITfull/elev30/L30e072a.wav" [ 12, 13 ] = wave (0) : "./MITfull/elev30/L30e078a.wav" [ 12, 14 ] = wave (0) : "./MITfull/elev30/L30e084a.wav" [ 12, 15 ] = wave (0) : "./MITfull/elev30/L30e090a.wav" [ 12, 16 ] = wave (0) : "./MITfull/elev30/L30e096a.wav" [ 12, 17 ] = wave (0) : "./MITfull/elev30/L30e102a.wav" [ 12, 18 ] = wave (0) : "./MITfull/elev30/L30e108a.wav" [ 12, 19 ] = wave (0) : "./MITfull/elev30/L30e114a.wav" [ 12, 20 ] = wave (0) : "./MITfull/elev30/L30e120a.wav" [ 12, 21 ] = wave (0) : "./MITfull/elev30/L30e126a.wav" [ 12, 22 ] = wave (0) : "./MITfull/elev30/L30e132a.wav" [ 12, 23 ] = wave (0) : "./MITfull/elev30/L30e138a.wav" [ 12, 24 ] = wave (0) : "./MITfull/elev30/L30e144a.wav" [ 12, 25 ] = wave (0) : "./MITfull/elev30/L30e150a.wav" [ 12, 26 ] = wave (0) : "./MITfull/elev30/L30e156a.wav" [ 12, 27 ] = wave (0) : "./MITfull/elev30/L30e162a.wav" [ 12, 28 ] = wave (0) : "./MITfull/elev30/L30e168a.wav" [ 12, 29 ] = wave (0) : "./MITfull/elev30/L30e174a.wav" [ 12, 30 ] = wave (0) : "./MITfull/elev30/L30e180a.wav" [ 12, 31 ] = wave (0) : "./MITfull/elev30/L30e186a.wav" [ 12, 32 ] = wave (0) : "./MITfull/elev30/L30e192a.wav" [ 12, 33 ] = wave (0) : "./MITfull/elev30/L30e198a.wav" [ 12, 34 ] = wave (0) : "./MITfull/elev30/L30e204a.wav" [ 12, 35 ] = wave (0) : "./MITfull/elev30/L30e210a.wav" [ 12, 36 ] = wave (0) : "./MITfull/elev30/L30e216a.wav" [ 12, 37 ] = wave (0) : "./MITfull/elev30/L30e222a.wav" [ 12, 38 ] = wave (0) : "./MITfull/elev30/L30e228a.wav" [ 12, 39 ] = wave (0) : "./MITfull/elev30/L30e234a.wav" [ 12, 40 ] = wave (0) : "./MITfull/elev30/L30e240a.wav" [ 12, 41 ] = wave (0) : "./MITfull/elev30/L30e246a.wav" [ 12, 42 ] = wave (0) : "./MITfull/elev30/L30e252a.wav" [ 12, 43 ] = wave (0) : "./MITfull/elev30/L30e258a.wav" [ 12, 44 ] = wave (0) : "./MITfull/elev30/L30e264a.wav" [ 12, 45 ] = wave (0) : "./MITfull/elev30/L30e270a.wav" [ 12, 46 ] = wave (0) : "./MITfull/elev30/L30e276a.wav" [ 12, 47 ] = wave (0) : "./MITfull/elev30/L30e282a.wav" [ 12, 48 ] = wave (0) : "./MITfull/elev30/L30e288a.wav" [ 12, 49 ] = wave (0) : "./MITfull/elev30/L30e294a.wav" [ 12, 50 ] = wave (0) : "./MITfull/elev30/L30e300a.wav" [ 12, 51 ] = wave (0) : "./MITfull/elev30/L30e306a.wav" [ 12, 52 ] = wave (0) : "./MITfull/elev30/L30e312a.wav" [ 12, 53 ] = wave (0) : "./MITfull/elev30/L30e318a.wav" [ 12, 54 ] = wave (0) : "./MITfull/elev30/L30e324a.wav" [ 12, 55 ] = wave (0) : "./MITfull/elev30/L30e330a.wav" [ 12, 56 ] = wave (0) : "./MITfull/elev30/L30e336a.wav" [ 12, 57 ] = wave (0) : "./MITfull/elev30/L30e342a.wav" [ 12, 58 ] = wave (0) : "./MITfull/elev30/L30e348a.wav" [ 12, 59 ] = wave (0) : "./MITfull/elev30/L30e354a.wav" [ 13, 0 ] = wave (0) : "./MITfull/elev40/L40e000a.wav" [ 13, 1 ] = wave (0) : "./MITfull/elev40/L40e006a.wav" [ 13, 2 ] = wave (0) : "./MITfull/elev40/L40e013a.wav" [ 13, 3 ] = wave (0) : "./MITfull/elev40/L40e019a.wav" [ 13, 4 ] = wave (0) : "./MITfull/elev40/L40e026a.wav" [ 13, 5 ] = wave (0) : "./MITfull/elev40/L40e032a.wav" [ 13, 6 ] = wave (0) : "./MITfull/elev40/L40e039a.wav" [ 13, 7 ] = wave (0) : "./MITfull/elev40/L40e045a.wav" [ 13, 8 ] = wave (0) : "./MITfull/elev40/L40e051a.wav" [ 13, 9 ] = wave (0) : "./MITfull/elev40/L40e058a.wav" [ 13, 10 ] = wave (0) : "./MITfull/elev40/L40e064a.wav" [ 13, 11 ] = wave (0) : "./MITfull/elev40/L40e071a.wav" [ 13, 12 ] = wave (0) : "./MITfull/elev40/L40e077a.wav" [ 13, 13 ] = wave (0) : "./MITfull/elev40/L40e084a.wav" [ 13, 14 ] = wave (0) : "./MITfull/elev40/L40e090a.wav" [ 13, 15 ] = wave (0) : "./MITfull/elev40/L40e096a.wav" [ 13, 16 ] = wave (0) : "./MITfull/elev40/L40e103a.wav" [ 13, 17 ] = wave (0) : "./MITfull/elev40/L40e109a.wav" [ 13, 18 ] = wave (0) : "./MITfull/elev40/L40e116a.wav" [ 13, 19 ] = wave (0) : "./MITfull/elev40/L40e122a.wav" [ 13, 20 ] = wave (0) : "./MITfull/elev40/L40e129a.wav" [ 13, 21 ] = wave (0) : "./MITfull/elev40/L40e135a.wav" [ 13, 22 ] = wave (0) : "./MITfull/elev40/L40e141a.wav" [ 13, 23 ] = wave (0) : "./MITfull/elev40/L40e148a.wav" [ 13, 24 ] = wave (0) : "./MITfull/elev40/L40e154a.wav" [ 13, 25 ] = wave (0) : "./MITfull/elev40/L40e161a.wav" [ 13, 26 ] = wave (0) : "./MITfull/elev40/L40e167a.wav" [ 13, 27 ] = wave (0) : "./MITfull/elev40/L40e174a.wav" [ 13, 28 ] = wave (0) : "./MITfull/elev40/L40e180a.wav" [ 13, 29 ] = wave (0) : "./MITfull/elev40/L40e186a.wav" [ 13, 30 ] = wave (0) : "./MITfull/elev40/L40e193a.wav" [ 13, 31 ] = wave (0) : "./MITfull/elev40/L40e199a.wav" [ 13, 32 ] = wave (0) : "./MITfull/elev40/L40e206a.wav" [ 13, 33 ] = wave (0) : "./MITfull/elev40/L40e212a.wav" [ 13, 34 ] = wave (0) : "./MITfull/elev40/L40e219a.wav" [ 13, 35 ] = wave (0) : "./MITfull/elev40/L40e225a.wav" [ 13, 36 ] = wave (0) : "./MITfull/elev40/L40e231a.wav" [ 13, 37 ] = wave (0) : "./MITfull/elev40/L40e238a.wav" [ 13, 38 ] = wave (0) : "./MITfull/elev40/L40e244a.wav" [ 13, 39 ] = wave (0) : "./MITfull/elev40/L40e251a.wav" [ 13, 40 ] = wave (0) : "./MITfull/elev40/L40e257a.wav" [ 13, 41 ] = wave (0) : "./MITfull/elev40/L40e264a.wav" [ 13, 42 ] = wave (0) : "./MITfull/elev40/L40e270a.wav" [ 13, 43 ] = wave (0) : "./MITfull/elev40/L40e276a.wav" [ 13, 44 ] = wave (0) : "./MITfull/elev40/L40e283a.wav" [ 13, 45 ] = wave (0) : "./MITfull/elev40/L40e289a.wav" [ 13, 46 ] = wave (0) : "./MITfull/elev40/L40e296a.wav" [ 13, 47 ] = wave (0) : "./MITfull/elev40/L40e302a.wav" [ 13, 48 ] = wave (0) : "./MITfull/elev40/L40e309a.wav" [ 13, 49 ] = wave (0) : "./MITfull/elev40/L40e315a.wav" [ 13, 50 ] = wave (0) : "./MITfull/elev40/L40e321a.wav" [ 13, 51 ] = wave (0) : "./MITfull/elev40/L40e328a.wav" [ 13, 52 ] = wave (0) : "./MITfull/elev40/L40e334a.wav" [ 13, 53 ] = wave (0) : "./MITfull/elev40/L40e341a.wav" [ 13, 54 ] = wave (0) : "./MITfull/elev40/L40e347a.wav" [ 13, 55 ] = wave (0) : "./MITfull/elev40/L40e354a.wav" [ 14, 0 ] = wave (0) : "./MITfull/elev50/L50e000a.wav" [ 14, 1 ] = wave (0) : "./MITfull/elev50/L50e008a.wav" [ 14, 2 ] = wave (0) : "./MITfull/elev50/L50e016a.wav" [ 14, 3 ] = wave (0) : "./MITfull/elev50/L50e024a.wav" [ 14, 4 ] = wave (0) : "./MITfull/elev50/L50e032a.wav" [ 14, 5 ] = wave (0) : "./MITfull/elev50/L50e040a.wav" [ 14, 6 ] = wave (0) : "./MITfull/elev50/L50e048a.wav" [ 14, 7 ] = wave (0) : "./MITfull/elev50/L50e056a.wav" [ 14, 8 ] = wave (0) : "./MITfull/elev50/L50e064a.wav" [ 14, 9 ] = wave (0) : "./MITfull/elev50/L50e072a.wav" [ 14, 10 ] = wave (0) : "./MITfull/elev50/L50e080a.wav" [ 14, 11 ] = wave (0) : "./MITfull/elev50/L50e088a.wav" [ 14, 12 ] = wave (0) : "./MITfull/elev50/L50e096a.wav" [ 14, 13 ] = wave (0) : "./MITfull/elev50/L50e104a.wav" [ 14, 14 ] = wave (0) : "./MITfull/elev50/L50e112a.wav" [ 14, 15 ] = wave (0) : "./MITfull/elev50/L50e120a.wav" [ 14, 16 ] = wave (0) : "./MITfull/elev50/L50e128a.wav" [ 14, 17 ] = wave (0) : "./MITfull/elev50/L50e136a.wav" [ 14, 18 ] = wave (0) : "./MITfull/elev50/L50e144a.wav" [ 14, 19 ] = wave (0) : "./MITfull/elev50/L50e152a.wav" [ 14, 20 ] = wave (0) : "./MITfull/elev50/L50e160a.wav" [ 14, 21 ] = wave (0) : "./MITfull/elev50/L50e168a.wav" [ 14, 22 ] = wave (0) : "./MITfull/elev50/L50e176a.wav" [ 14, 23 ] = wave (0) : "./MITfull/elev50/L50e184a.wav" [ 14, 24 ] = wave (0) : "./MITfull/elev50/L50e192a.wav" [ 14, 25 ] = wave (0) : "./MITfull/elev50/L50e200a.wav" [ 14, 26 ] = wave (0) : "./MITfull/elev50/L50e208a.wav" [ 14, 27 ] = wave (0) : "./MITfull/elev50/L50e216a.wav" [ 14, 28 ] = wave (0) : "./MITfull/elev50/L50e224a.wav" [ 14, 29 ] = wave (0) : "./MITfull/elev50/L50e232a.wav" [ 14, 30 ] = wave (0) : "./MITfull/elev50/L50e240a.wav" [ 14, 31 ] = wave (0) : "./MITfull/elev50/L50e248a.wav" [ 14, 32 ] = wave (0) : "./MITfull/elev50/L50e256a.wav" [ 14, 33 ] = wave (0) : "./MITfull/elev50/L50e264a.wav" [ 14, 34 ] = wave (0) : "./MITfull/elev50/L50e272a.wav" [ 14, 35 ] = wave (0) : "./MITfull/elev50/L50e280a.wav" [ 14, 36 ] = wave (0) : "./MITfull/elev50/L50e288a.wav" [ 14, 37 ] = wave (0) : "./MITfull/elev50/L50e296a.wav" [ 14, 38 ] = wave (0) : "./MITfull/elev50/L50e304a.wav" [ 14, 39 ] = wave (0) : "./MITfull/elev50/L50e312a.wav" [ 14, 40 ] = wave (0) : "./MITfull/elev50/L50e320a.wav" [ 14, 41 ] = wave (0) : "./MITfull/elev50/L50e328a.wav" [ 14, 42 ] = wave (0) : "./MITfull/elev50/L50e336a.wav" [ 14, 43 ] = wave (0) : "./MITfull/elev50/L50e344a.wav" [ 14, 44 ] = wave (0) : "./MITfull/elev50/L50e352a.wav" [ 15, 0 ] = wave (0) : "./MITfull/elev60/L60e000a.wav" [ 15, 1 ] = wave (0) : "./MITfull/elev60/L60e010a.wav" [ 15, 2 ] = wave (0) : "./MITfull/elev60/L60e020a.wav" [ 15, 3 ] = wave (0) : "./MITfull/elev60/L60e030a.wav" [ 15, 4 ] = wave (0) : "./MITfull/elev60/L60e040a.wav" [ 15, 5 ] = wave (0) : "./MITfull/elev60/L60e050a.wav" [ 15, 6 ] = wave (0) : "./MITfull/elev60/L60e060a.wav" [ 15, 7 ] = wave (0) : "./MITfull/elev60/L60e070a.wav" [ 15, 8 ] = wave (0) : "./MITfull/elev60/L60e080a.wav" [ 15, 9 ] = wave (0) : "./MITfull/elev60/L60e090a.wav" [ 15, 10 ] = wave (0) : "./MITfull/elev60/L60e100a.wav" [ 15, 11 ] = wave (0) : "./MITfull/elev60/L60e110a.wav" [ 15, 12 ] = wave (0) : "./MITfull/elev60/L60e120a.wav" [ 15, 13 ] = wave (0) : "./MITfull/elev60/L60e130a.wav" [ 15, 14 ] = wave (0) : "./MITfull/elev60/L60e140a.wav" [ 15, 15 ] = wave (0) : "./MITfull/elev60/L60e150a.wav" [ 15, 16 ] = wave (0) : "./MITfull/elev60/L60e160a.wav" [ 15, 17 ] = wave (0) : "./MITfull/elev60/L60e170a.wav" [ 15, 18 ] = wave (0) : "./MITfull/elev60/L60e180a.wav" [ 15, 19 ] = wave (0) : "./MITfull/elev60/L60e190a.wav" [ 15, 20 ] = wave (0) : "./MITfull/elev60/L60e200a.wav" [ 15, 21 ] = wave (0) : "./MITfull/elev60/L60e210a.wav" [ 15, 22 ] = wave (0) : "./MITfull/elev60/L60e220a.wav" [ 15, 23 ] = wave (0) : "./MITfull/elev60/L60e230a.wav" [ 15, 24 ] = wave (0) : "./MITfull/elev60/L60e240a.wav" [ 15, 25 ] = wave (0) : "./MITfull/elev60/L60e250a.wav" [ 15, 26 ] = wave (0) : "./MITfull/elev60/L60e260a.wav" [ 15, 27 ] = wave (0) : "./MITfull/elev60/L60e270a.wav" [ 15, 28 ] = wave (0) : "./MITfull/elev60/L60e280a.wav" [ 15, 29 ] = wave (0) : "./MITfull/elev60/L60e290a.wav" [ 15, 30 ] = wave (0) : "./MITfull/elev60/L60e300a.wav" [ 15, 31 ] = wave (0) : "./MITfull/elev60/L60e310a.wav" [ 15, 32 ] = wave (0) : "./MITfull/elev60/L60e320a.wav" [ 15, 33 ] = wave (0) : "./MITfull/elev60/L60e330a.wav" [ 15, 34 ] = wave (0) : "./MITfull/elev60/L60e340a.wav" [ 15, 35 ] = wave (0) : "./MITfull/elev60/L60e350a.wav" [ 16, 0 ] = wave (0) : "./MITfull/elev70/L70e000a.wav" [ 16, 1 ] = wave (0) : "./MITfull/elev70/L70e015a.wav" [ 16, 2 ] = wave (0) : "./MITfull/elev70/L70e030a.wav" [ 16, 3 ] = wave (0) : "./MITfull/elev70/L70e045a.wav" [ 16, 4 ] = wave (0) : "./MITfull/elev70/L70e060a.wav" [ 16, 5 ] = wave (0) : "./MITfull/elev70/L70e075a.wav" [ 16, 6 ] = wave (0) : "./MITfull/elev70/L70e090a.wav" [ 16, 7 ] = wave (0) : "./MITfull/elev70/L70e105a.wav" [ 16, 8 ] = wave (0) : "./MITfull/elev70/L70e120a.wav" [ 16, 9 ] = wave (0) : "./MITfull/elev70/L70e135a.wav" [ 16, 10 ] = wave (0) : "./MITfull/elev70/L70e150a.wav" [ 16, 11 ] = wave (0) : "./MITfull/elev70/L70e165a.wav" [ 16, 12 ] = wave (0) : "./MITfull/elev70/L70e180a.wav" [ 16, 13 ] = wave (0) : "./MITfull/elev70/L70e195a.wav" [ 16, 14 ] = wave (0) : "./MITfull/elev70/L70e210a.wav" [ 16, 15 ] = wave (0) : "./MITfull/elev70/L70e225a.wav" [ 16, 16 ] = wave (0) : "./MITfull/elev70/L70e240a.wav" [ 16, 17 ] = wave (0) : "./MITfull/elev70/L70e255a.wav" [ 16, 18 ] = wave (0) : "./MITfull/elev70/L70e270a.wav" [ 16, 19 ] = wave (0) : "./MITfull/elev70/L70e285a.wav" [ 16, 20 ] = wave (0) : "./MITfull/elev70/L70e300a.wav" [ 16, 21 ] = wave (0) : "./MITfull/elev70/L70e315a.wav" [ 16, 22 ] = wave (0) : "./MITfull/elev70/L70e330a.wav" [ 16, 23 ] = wave (0) : "./MITfull/elev70/L70e345a.wav" [ 17, 0 ] = wave (0) : "./MITfull/elev80/L80e000a.wav" [ 17, 1 ] = wave (0) : "./MITfull/elev80/L80e030a.wav" [ 17, 2 ] = wave (0) : "./MITfull/elev80/L80e060a.wav" [ 17, 3 ] = wave (0) : "./MITfull/elev80/L80e090a.wav" [ 17, 4 ] = wave (0) : "./MITfull/elev80/L80e120a.wav" [ 17, 5 ] = wave (0) : "./MITfull/elev80/L80e150a.wav" [ 17, 6 ] = wave (0) : "./MITfull/elev80/L80e180a.wav" [ 17, 7 ] = wave (0) : "./MITfull/elev80/L80e210a.wav" [ 17, 8 ] = wave (0) : "./MITfull/elev80/L80e240a.wav" [ 17, 9 ] = wave (0) : "./MITfull/elev80/L80e270a.wav" [ 17, 10 ] = wave (0) : "./MITfull/elev80/L80e300a.wav" [ 17, 11 ] = wave (0) : "./MITfull/elev80/L80e330a.wav" [ 18, 0 ] = wave (0) : "./MITfull/elev90/L90e000a.wav" openal-soft-1.24.2/utils/MIT_KEMAR_sofa.def000066400000000000000000000035571474041540300202430ustar00rootroot00000000000000# This is a makemhr HRIR definition file. It is used to define the layout and # source data to be processed into an OpenAL Soft compatible HRTF. # # This definition is used to transform the SOFA packaged KEMAR HRIRs # originally provided by Bill Gardner and Keith Martin # of MIT Media Laboratory. # # The SOFA conversion is available from: # # http://sofacoustics.org/data/database/mit/ # # The original data is available from: # # http://sound.media.mit.edu/resources/KEMAR.html # # It is copyrighted 1994 by MIT Media Laboratory, and provided free of charge # with no restrictions on use so long as the authors (above) are cited. # Sampling rate of the HRIR data (in hertz). rate = 44100 # The SOFA file is stereo, but the original data was mono. Channels are just # mirrored by azimuth; so save some memory by allowing OpenAL Soft to mirror # them at run time. type = mono points = 512 radius = 0.09 # The MIT set has only one field with a distance of 1.4m. distance = 1.4 # The MIT set varies the number of azimuths for each elevation to maintain # an average distance between them. azimuths = 1, 12, 24, 36, 45, 56, 60, 72, 72, 72, 72, 72, 60, 56, 45, 36, 24, 12, 1 # Normally the dataset would be composed manually by listing all necessary # 'sofa' sources with the appropriate radius, elevation, azimuth (counter- # clockwise for SOFA files) and receiver arguments: # # [ 5, 0 ] = sofa (1.4, -40.0, 0.0 : 0) : "./mit_kemar_normal_pinna.sofa" # [ 5, 1 ] = sofa (1.4, -40.0, 353.6 : 0) : "./mit_kemar_normal_pinna.sofa" # [ 5, 2 ] = sofa (1.4, -40.0, 347.1 : 0) : "./mit_kemar_normal_pinna.sofa" # [ 5, 3 ] = sofa (1.4, -40.0, 340.7 : 0) : "./mit_kemar_normal_pinna.sofa" # ... # # If HRIR composition isn't necessary, it's easier to just use the following: [ * ] = sofa : "./mit_kemar_normal_pinna.sofa" mono openal-soft-1.24.2/utils/SCUT_KEMAR.def000066400000000000000000000037571474041540300173620ustar00rootroot00000000000000# This is a makemhr HRIR definition file. It is used to define the layout and # source data to be processed into an OpenAL Soft compatible HRTF. # # This definition is used to transform the near-field KEMAR HRIRs provided by # Bosun Xie of the South China University of # Technology, Guangzhou, China; and converted from SCUT to SOFA format by # Piotr Majdak of the Acoustics Research Institute, # Austrian Academy of Sciences. # # A copy of the data (SCUT_KEMAR_radius_all.sofa) is available from: # # http://sofacoustics.org/data/database/scut/SCUT_KEMAR_radius_all.sofa # # It is provided under the Creative Commons CC 3.0 BY-SA-NC license: # # https://creativecommons.org/licenses/by-nc-sa/3.0/ rate = 44100 # While the SOFA file is stereo, doubling the size of the data set will cause # the utility to exhaust its address space if compiled 32-bit. Since the # dummy head is symmetric, the same results (ignoring variations caused by # measurement error) can be obtained using mono channel processing. type = mono points = 512 radius = 0.09 # This data set has 10 fields ranging from 0.2m to 1m. The layout was # obtained using the sofa-info utility. distance = 0.2, 0.25, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0 azimuths = 1, 24, 36, 72, 72, 72, 72, 72, 72, 72, 36, 24, 1; 1, 24, 36, 72, 72, 72, 72, 72, 72, 72, 36, 24, 1; 1, 24, 36, 72, 72, 72, 72, 72, 72, 72, 36, 24, 1; 1, 24, 36, 72, 72, 72, 72, 72, 72, 72, 36, 24, 1; 1, 24, 36, 72, 72, 72, 72, 72, 72, 72, 36, 24, 1; 1, 24, 36, 72, 72, 72, 72, 72, 72, 72, 36, 24, 1; 1, 24, 36, 72, 72, 72, 72, 72, 72, 72, 36, 24, 1; 1, 24, 36, 72, 72, 72, 72, 72, 72, 72, 36, 24, 1; 1, 24, 36, 72, 72, 72, 72, 72, 72, 72, 36, 24, 1; 1, 24, 36, 72, 72, 72, 72, 72, 72, 72, 36, 24, 1 # Given the above compatible layout, we can automatically process the entire # data set. [ * ] = sofa : "./SCUT_KEMAR_radius_all.sofa" mono openal-soft-1.24.2/utils/alsoft-config/000077500000000000000000000000001474041540300177245ustar00rootroot00000000000000openal-soft-1.24.2/utils/alsoft-config/CMakeLists.txt000066400000000000000000000021221474041540300224610ustar00rootroot00000000000000project(alsoft-config) if(Qt5Widgets_FOUND) qt5_wrap_ui(UIS mainwindow.ui) qt5_wrap_cpp(MOCS mainwindow.h) add_executable(alsoft-config main.cpp mainwindow.cpp mainwindow.h verstr.cpp verstr.h ${UIS} ${RSCS} ${TRS} ${MOCS}) target_link_libraries(alsoft-config PUBLIC Qt5::Widgets PRIVATE alsoft.common) target_include_directories(alsoft-config PRIVATE "${alsoft-config_BINARY_DIR}" "${OpenAL_BINARY_DIR}") target_compile_definitions(alsoft-config PRIVATE QT_NO_KEYWORDS) set_target_properties(alsoft-config PROPERTIES ${DEFAULT_TARGET_PROPS} RUNTIME_OUTPUT_DIRECTORY ${OpenAL_BINARY_DIR}) if(TARGET alsoft.build_version) add_dependencies(alsoft-config alsoft.build_version) endif() message(STATUS "Building configuration program") if(ALSOFT_INSTALL_UTILS) install(TARGETS alsoft-config RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) endif() endif() openal-soft-1.24.2/utils/alsoft-config/main.cpp000066400000000000000000000002541474041540300213550ustar00rootroot00000000000000#include "mainwindow.h" #include int main(int argc, char *argv[]) { QApplication a(argc, argv); MainWindow w; w.show(); return a.exec(); } openal-soft-1.24.2/utils/alsoft-config/mainwindow.cpp000066400000000000000000001622061474041540300226130ustar00rootroot00000000000000 #include "config.h" #include "config_backends.h" #include "config_simd.h" #include "mainwindow.h" #include #include #include #include #include #include #include #include #include "ui_mainwindow.h" #include "verstr.h" #ifdef _WIN32 #include #include #endif #include "almalloc.h" #include "alspan.h" namespace { struct BackendNamePair { /* NOLINTBEGIN(*-avoid-c-arrays) */ char backend_name[16]; char full_string[32]; /* NOLINTEND(*-avoid-c-arrays) */ }; constexpr std::array backendList{ #if HAVE_PIPEWIRE BackendNamePair{ "pipewire", "PipeWire" }, #endif #if HAVE_PULSEAUDIO BackendNamePair{ "pulse", "PulseAudio" }, #endif #if HAVE_WASAPI BackendNamePair{ "wasapi", "WASAPI" }, #endif #if HAVE_COREAUDIO BackendNamePair{ "core", "CoreAudio" }, #endif #if HAVE_OPENSL BackendNamePair{ "opensl", "OpenSL" }, #endif #if HAVE_ALSA BackendNamePair{ "alsa", "ALSA" }, #endif #if HAVE_SOLARIS BackendNamePair{ "solaris", "Solaris" }, #endif #if HAVE_SNDIO BackendNamePair{ "sndio", "SndIO" }, #endif #if HAVE_OSS BackendNamePair{ "oss", "OSS" }, #endif #if HAVE_DSOUND BackendNamePair{ "dsound", "DirectSound" }, #endif #if HAVE_WINMM BackendNamePair{ "winmm", "Windows Multimedia" }, #endif #if HAVE_PORTAUDIO BackendNamePair{ "port", "PortAudio" }, #endif #if HAVE_JACK BackendNamePair{ "jack", "JACK" }, #endif BackendNamePair{ "null", "Null Output" }, #if HAVE_WAVE BackendNamePair{ "wave", "Wave Writer" }, #endif }; struct NameValuePair { /* NOLINTBEGIN(*-avoid-c-arrays) */ const char name[64]; const char value[16]; /* NOLINTEND(*-avoid-c-arrays) */ }; constexpr std::array speakerModeList{ NameValuePair{ "Autodetect", "" }, NameValuePair{ "Mono", "mono" }, NameValuePair{ "Stereo", "stereo" }, NameValuePair{ "Quadraphonic", "quad" }, NameValuePair{ "5.1 Surround", "surround51" }, NameValuePair{ "6.1 Surround", "surround61" }, NameValuePair{ "7.1 Surround", "surround71" }, NameValuePair{ "3D7.1 Surround", "surround3d71" }, NameValuePair{ "Ambisonic, 1st Order", "ambi1" }, NameValuePair{ "Ambisonic, 2nd Order", "ambi2" }, NameValuePair{ "Ambisonic, 3rd Order", "ambi3" }, }; constexpr std::array sampleTypeList{ NameValuePair{ "Autodetect", "" }, NameValuePair{ "8-bit int", "int8" }, NameValuePair{ "8-bit uint", "uint8" }, NameValuePair{ "16-bit int", "int16" }, NameValuePair{ "16-bit uint", "uint16" }, NameValuePair{ "32-bit int", "int32" }, NameValuePair{ "32-bit uint", "uint32" }, NameValuePair{ "32-bit float", "float32" }, }; constexpr std::array resamplerList{ NameValuePair{ "Point", "point" }, NameValuePair{ "Linear", "linear" }, NameValuePair{ "Cubic Spline", "spline" }, NameValuePair{ "Default (Cubic Spline)", "" }, NameValuePair{ "4-point Gaussian", "gaussian" }, NameValuePair{ "11th order Sinc (fast)", "fast_bsinc12" }, NameValuePair{ "11th order Sinc", "bsinc12" }, NameValuePair{ "23rd order Sinc (fast)", "fast_bsinc24" }, NameValuePair{ "23rd order Sinc", "bsinc24" }, }; constexpr std::array stereoModeList{ NameValuePair{ "Autodetect", "" }, NameValuePair{ "Speakers", "speakers" }, NameValuePair{ "Headphones", "headphones" }, }; constexpr std::array stereoEncList{ NameValuePair{ "Default", "" }, NameValuePair{ "Basic", "panpot" }, NameValuePair{ "UHJ", "uhj" }, NameValuePair{ "Binaural", "hrtf" }, }; constexpr std::array ambiFormatList{ NameValuePair{ "Default", "" }, NameValuePair{ "AmbiX (ACN, SN3D)", "ambix" }, NameValuePair{ "Furse-Malham", "fuma" }, NameValuePair{ "ACN, N3D", "acn+n3d" }, NameValuePair{ "ACN, FuMa", "acn+fuma" }, }; constexpr std::array hrtfModeList{ NameValuePair{ "1st Order Ambisonic", "ambi1" }, NameValuePair{ "2nd Order Ambisonic", "ambi2" }, NameValuePair{ "3rd Order Ambisonic", "ambi3" }, NameValuePair{ "Default (Full)", "" }, NameValuePair{ "Full", "full" }, }; constexpr auto GetDefaultIndex(const al::span list) -> size_t { for(size_t i{0};i < list.size();++i) { if(!list[i].value[0]) return i; } throw std::runtime_error{"Failed to find default entry"}; } #ifdef Q_OS_WIN32 struct CoTaskMemDeleter { void operator()(void *buffer) { CoTaskMemFree(buffer); } }; /* NOLINTNEXTLINE(*-avoid-c-arrays) */ using WCharBufferPtr = std::unique_ptr; #endif QString getDefaultConfigName() { #ifdef Q_OS_WIN32 const char *fname{"alsoft.ini"}; static constexpr auto get_appdata_path = []() -> QString { auto buffer = WCharBufferPtr{}; if(const HRESULT hr{SHGetKnownFolderPath(FOLDERID_RoamingAppData, KF_FLAG_DONT_UNEXPAND, nullptr, al::out_ptr(buffer))}; SUCCEEDED(hr)) return QString::fromWCharArray(buffer.get()); return QString{}; }; QString base = get_appdata_path(); #else const char *fname{"alsoft.conf"}; QString base = qgetenv("XDG_CONFIG_HOME"); if(base.isEmpty()) { base = qgetenv("HOME"); if(base.isEmpty() == false) base += "/.config"; } #endif if(base.isEmpty() == false) return base +'/'+ fname; return fname; } QString getBaseDataPath() { #ifdef Q_OS_WIN32 static constexpr auto get_appdata_path = []() -> QString { auto buffer = WCharBufferPtr{}; if(const HRESULT hr{SHGetKnownFolderPath(FOLDERID_RoamingAppData, KF_FLAG_DONT_UNEXPAND, nullptr, al::out_ptr(buffer))}; SUCCEEDED(hr)) return QString::fromWCharArray(buffer.get()); return QString{}; }; QString base = get_appdata_path(); #else QString base = qgetenv("XDG_DATA_HOME"); if(base.isEmpty()) { base = qgetenv("HOME"); if(!base.isEmpty()) base += "/.local/share"; } #endif return base; } QStringList getAllDataPaths(const QString &append) { QStringList list; list.append(getBaseDataPath()); #ifdef Q_OS_WIN32 // TODO: Common AppData path #else QString paths = qgetenv("XDG_DATA_DIRS"); if(paths.isEmpty()) paths = "/usr/local/share/:/usr/share/"; #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) list += paths.split(QChar(':'), Qt::SkipEmptyParts); #else list += paths.split(QChar(':'), QString::SkipEmptyParts); #endif #endif QStringList::iterator iter = list.begin(); while(iter != list.end()) { if(iter->isEmpty()) iter = list.erase(iter); else { iter->append(append); iter++; } } return list; } QString getValueFromName(const al::span list, const QString &str) { for(size_t i{0};i < list.size();++i) { if(str == std::data(list[i].name)) return std::data(list[i].value); } return QString{}; } QString getNameFromValue(const al::span list, const QString &str) { for(size_t i{0};i < list.size();++i) { if(str == std::data(list[i].value)) return std::data(list[i].name); } return QString{}; } Qt::CheckState getCheckState(const QVariant &var) { if(var.isNull()) return Qt::PartiallyChecked; if(var.toBool()) return Qt::Checked; return Qt::Unchecked; } QString getCheckValue(const QCheckBox *checkbox) { const Qt::CheckState state{checkbox->checkState()}; if(state == Qt::Checked) return QStringLiteral("true"); if(state == Qt::Unchecked) return QStringLiteral("false"); return QString{}; } } MainWindow::MainWindow(QWidget *parent) : QMainWindow{parent} , ui{std::make_unique()} { ui->setupUi(this); for(auto &item : speakerModeList) ui->channelConfigCombo->addItem(std::data(item.name)); ui->channelConfigCombo->adjustSize(); for(auto &item : sampleTypeList) ui->sampleFormatCombo->addItem(std::data(item.name)); ui->sampleFormatCombo->adjustSize(); for(auto &item : stereoModeList) ui->stereoModeCombo->addItem(std::data(item.name)); ui->stereoModeCombo->adjustSize(); for(auto &item : stereoEncList) ui->stereoEncodingComboBox->addItem(std::data(item.name)); ui->stereoEncodingComboBox->adjustSize(); for(auto &item : ambiFormatList) ui->ambiFormatComboBox->addItem(std::data(item.name)); ui->ambiFormatComboBox->adjustSize(); ui->resamplerSlider->setRange(0, resamplerList.size()-1); ui->hrtfmodeSlider->setRange(0, hrtfModeList.size()-1); #if !HAVE_NEON && !HAVE_SSE ui->cpuExtDisabledLabel->move(ui->cpuExtDisabledLabel->x(), ui->cpuExtDisabledLabel->y() - 60); #else ui->cpuExtDisabledLabel->setVisible(false); #endif #if !HAVE_NEON #if !HAVE_SSE4_1 #if !HAVE_SSE3 #if !HAVE_SSE2 #if !HAVE_SSE ui->enableSSECheckBox->setVisible(false); #endif /* !SSE */ ui->enableSSE2CheckBox->setVisible(false); #endif /* !SSE2 */ ui->enableSSE3CheckBox->setVisible(false); #endif /* !SSE3 */ ui->enableSSE41CheckBox->setVisible(false); #endif /* !SSE4.1 */ ui->enableNeonCheckBox->setVisible(false); #else /* !Neon */ #if !HAVE_SSE4_1 #if !HAVE_SSE3 #if !HAVE_SSE2 #if !HAVE_SSE ui->enableNeonCheckBox->move(ui->enableNeonCheckBox->x(), ui->enableNeonCheckBox->y() - 30); ui->enableSSECheckBox->setVisible(false); #endif /* !SSE */ ui->enableSSE2CheckBox->setVisible(false); #endif /* !SSE2 */ ui->enableSSE3CheckBox->setVisible(false); #endif /* !SSE3 */ ui->enableSSE41CheckBox->setVisible(false); #endif /* !SSE4.1 */ #endif #if !ALSOFT_EAX ui->enableEaxCheck->setChecked(Qt::Unchecked); ui->enableEaxCheck->setEnabled(false); ui->enableEaxCheck->setVisible(false); #endif mPeriodSizeValidator = std::make_unique(64, 8192, this); ui->periodSizeEdit->setValidator(mPeriodSizeValidator.get()); mPeriodCountValidator = std::make_unique(2, 16, this); ui->periodCountEdit->setValidator(mPeriodCountValidator.get()); mSourceCountValidator = std::make_unique(0, 4096, this); ui->srcCountLineEdit->setValidator(mSourceCountValidator.get()); mEffectSlotValidator = std::make_unique(0, 64, this); ui->effectSlotLineEdit->setValidator(mEffectSlotValidator.get()); mSourceSendValidator = std::make_unique(0, 16, this); ui->srcSendLineEdit->setValidator(mSourceSendValidator.get()); mSampleRateValidator = std::make_unique(8000, 192000, this); ui->sampleRateCombo->lineEdit()->setValidator(mSampleRateValidator.get()); mJackBufferValidator = std::make_unique(0, 8192, this); ui->jackBufferSizeLine->setValidator(mJackBufferValidator.get()); connect(ui->actionLoad, &QAction::triggered, this, &MainWindow::loadConfigFromFile); connect(ui->actionSave_As, &QAction::triggered, this, &MainWindow::saveConfigAsFile); connect(ui->actionAbout, &QAction::triggered, this, &MainWindow::showAboutPage); connect(ui->closeCancelButton, &QPushButton::clicked, this, &MainWindow::cancelCloseAction); connect(ui->applyButton, &QPushButton::clicked, this, &MainWindow::saveCurrentConfig); auto qcb_cicint = static_cast(&QComboBox::currentIndexChanged); connect(ui->channelConfigCombo, qcb_cicint, this, &MainWindow::enableApplyButton); connect(ui->sampleFormatCombo, qcb_cicint, this, &MainWindow::enableApplyButton); connect(ui->stereoModeCombo, qcb_cicint, this, &MainWindow::enableApplyButton); connect(ui->sampleRateCombo, qcb_cicint, this, &MainWindow::enableApplyButton); connect(ui->sampleRateCombo, &QComboBox::editTextChanged, this, &MainWindow::enableApplyButton); connect(ui->resamplerSlider, &QSlider::valueChanged, this, &MainWindow::updateResamplerLabel); connect(ui->periodSizeSlider, &QSlider::valueChanged, this, &MainWindow::updatePeriodSizeEdit); connect(ui->periodSizeEdit, &QLineEdit::editingFinished, this, &MainWindow::updatePeriodSizeSlider); connect(ui->periodCountSlider, &QSlider::valueChanged, this, &MainWindow::updatePeriodCountEdit); connect(ui->periodCountEdit, &QLineEdit::editingFinished, this, &MainWindow::updatePeriodCountSlider); connect(ui->stereoEncodingComboBox, qcb_cicint, this, &MainWindow::enableApplyButton); connect(ui->ambiFormatComboBox, qcb_cicint, this, &MainWindow::enableApplyButton); connect(ui->outputLimiterCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); connect(ui->outputDitherCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); connect(ui->decoderHQModeCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); connect(ui->decoderDistCompCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); connect(ui->decoderNFEffectsCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); auto qdsb_vcd = static_cast(&QDoubleSpinBox::valueChanged); connect(ui->decoderSpeakerDistSpinBox, qdsb_vcd, this, &MainWindow::enableApplyButton); connect(ui->decoderQuadLineEdit, &QLineEdit::textChanged, this, &MainWindow::enableApplyButton); connect(ui->decoderQuadButton, &QPushButton::clicked, this, &MainWindow::selectQuadDecoderFile); connect(ui->decoder51LineEdit, &QLineEdit::textChanged, this, &MainWindow::enableApplyButton); connect(ui->decoder51Button, &QPushButton::clicked, this, &MainWindow::select51DecoderFile); connect(ui->decoder61LineEdit, &QLineEdit::textChanged, this, &MainWindow::enableApplyButton); connect(ui->decoder61Button, &QPushButton::clicked, this, &MainWindow::select61DecoderFile); connect(ui->decoder71LineEdit, &QLineEdit::textChanged, this, &MainWindow::enableApplyButton); connect(ui->decoder71Button, &QPushButton::clicked, this, &MainWindow::select71DecoderFile); connect(ui->decoder3D71LineEdit, &QLineEdit::textChanged, this, &MainWindow::enableApplyButton); connect(ui->decoder3D71Button, &QPushButton::clicked, this, &MainWindow::select3D71DecoderFile); connect(ui->preferredHrtfComboBox, qcb_cicint, this, &MainWindow::enableApplyButton); connect(ui->hrtfmodeSlider, &QSlider::valueChanged, this, &MainWindow::updateHrtfModeLabel); connect(ui->hrtfAddButton, &QPushButton::clicked, this, &MainWindow::addHrtfFile); connect(ui->hrtfRemoveButton, &QPushButton::clicked, this, &MainWindow::removeHrtfFile); connect(ui->hrtfFileList, &QListWidget::itemSelectionChanged, this, &MainWindow::updateHrtfRemoveButton); connect(ui->defaultHrtfPathsCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); connect(ui->srcCountLineEdit, &QLineEdit::editingFinished, this, &MainWindow::enableApplyButton); connect(ui->srcSendLineEdit, &QLineEdit::editingFinished, this, &MainWindow::enableApplyButton); connect(ui->effectSlotLineEdit, &QLineEdit::editingFinished, this, &MainWindow::enableApplyButton); connect(ui->enableSSECheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); connect(ui->enableSSE2CheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); connect(ui->enableSSE3CheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); connect(ui->enableSSE41CheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); connect(ui->enableNeonCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); ui->enabledBackendList->setContextMenuPolicy(Qt::CustomContextMenu); connect(ui->enabledBackendList, &QListWidget::customContextMenuRequested, this, &MainWindow::showEnabledBackendMenu); ui->disabledBackendList->setContextMenuPolicy(Qt::CustomContextMenu); connect(ui->disabledBackendList, &QListWidget::customContextMenuRequested, this, &MainWindow::showDisabledBackendMenu); connect(ui->backendCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); connect(ui->defaultReverbComboBox, qcb_cicint, this, &MainWindow::enableApplyButton); connect(ui->enableEaxReverbCheck, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); connect(ui->enableStdReverbCheck, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); connect(ui->enableAutowahCheck, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); connect(ui->enableChorusCheck, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); connect(ui->enableCompressorCheck, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); connect(ui->enableDistortionCheck, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); connect(ui->enableEchoCheck, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); connect(ui->enableEqualizerCheck, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); connect(ui->enableFlangerCheck, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); connect(ui->enableFrequencyShifterCheck, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); connect(ui->enableModulatorCheck, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); connect(ui->enableDedicatedCheck, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); connect(ui->enablePitchShifterCheck, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); connect(ui->enableVocalMorpherCheck, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); connect(ui->enableEaxCheck, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); connect(ui->pulseAutospawnCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); connect(ui->pulseAllowMovesCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); connect(ui->pulseFixRateCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); connect(ui->pulseAdjLatencyCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); connect(ui->pwireAssumeAudioCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); connect(ui->pwireRtMixCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); connect(ui->wasapiResamplerCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); connect(ui->jackAutospawnCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); connect(ui->jackConnectPortsCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); connect(ui->jackRtMixCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); connect(ui->jackBufferSizeSlider, &QSlider::valueChanged, this, &MainWindow::updateJackBufferSizeEdit); connect(ui->jackBufferSizeLine, &QLineEdit::editingFinished, this, &MainWindow::updateJackBufferSizeSlider); connect(ui->alsaDefaultDeviceLine, &QLineEdit::textChanged, this, &MainWindow::enableApplyButton); connect(ui->alsaDefaultCaptureLine, &QLineEdit::textChanged, this, &MainWindow::enableApplyButton); connect(ui->alsaResamplerCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); connect(ui->alsaMmapCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); connect(ui->ossDefaultDeviceLine, &QLineEdit::textChanged, this, &MainWindow::enableApplyButton); connect(ui->ossPlaybackPushButton, &QPushButton::clicked, this, &MainWindow::selectOSSPlayback); connect(ui->ossDefaultCaptureLine, &QLineEdit::textChanged, this, &MainWindow::enableApplyButton); connect(ui->ossCapturePushButton, &QPushButton::clicked, this, &MainWindow::selectOSSCapture); connect(ui->solarisDefaultDeviceLine, &QLineEdit::textChanged, this, &MainWindow::enableApplyButton); connect(ui->solarisPlaybackPushButton, &QPushButton::clicked, this, &MainWindow::selectSolarisPlayback); connect(ui->waveOutputLine, &QLineEdit::textChanged, this, &MainWindow::enableApplyButton); connect(ui->waveOutputButton, &QPushButton::clicked, this, &MainWindow::selectWaveOutput); connect(ui->waveBFormatCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton); ui->backendListWidget->setCurrentRow(0); ui->tabWidget->setCurrentIndex(0); for(int i = 1;i < ui->backendListWidget->count();i++) ui->backendListWidget->setRowHidden(i, true); for(size_t i{0};i < backendList.size();++i) { QList items = ui->backendListWidget->findItems( std::data(backendList[i].full_string), Qt::MatchFixedString); Q_FOREACH(QListWidgetItem *item, items) item->setHidden(false); } loadConfig(getDefaultConfigName()); } MainWindow::~MainWindow() = default; void MainWindow::closeEvent(QCloseEvent *event) { if(!mNeedsSave) event->accept(); else { QMessageBox::StandardButton btn = QMessageBox::warning(this, tr("Apply changes?"), tr("Save changes before quitting?"), QMessageBox::Save | QMessageBox::No | QMessageBox::Cancel); if(btn == QMessageBox::Save) saveCurrentConfig(); if(btn == QMessageBox::Cancel) event->ignore(); else event->accept(); } } void MainWindow::cancelCloseAction() { mNeedsSave = false; close(); } void MainWindow::showAboutPage() { QMessageBox::information(this, tr("About"), tr("OpenAL Soft Configuration Utility.\nBuilt for OpenAL Soft library version ") + GetVersionString()); } QStringList MainWindow::collectHrtfs() { QStringList ret; QStringList processed; for(int i = 0;i < ui->hrtfFileList->count();i++) { QDir dir(ui->hrtfFileList->item(i)->text()); QStringList fnames = dir.entryList(QDir::Files | QDir::Readable, QDir::Name); Q_FOREACH(const QString &fname, fnames) { if(!fname.endsWith(QStringLiteral(".mhr"), Qt::CaseInsensitive)) continue; QString fullname{dir.absoluteFilePath(fname)}; if(processed.contains(fullname)) continue; processed.push_back(fullname); QString name{fname.left(fname.length()-4)}; if(!ret.contains(name)) ret.push_back(name); else { size_t i{2}; do { QString s = name+" #"+QString::number(i); if(!ret.contains(s)) { ret.push_back(s); break; } ++i; } while(true); } } } if(ui->defaultHrtfPathsCheckBox->isChecked()) { QStringList paths = getAllDataPaths(QStringLiteral("/openal/hrtf")); Q_FOREACH(const QString &name, paths) { QDir dir{name}; QStringList fnames{dir.entryList(QDir::Files | QDir::Readable, QDir::Name)}; Q_FOREACH(const QString &fname, fnames) { if(!fname.endsWith(QStringLiteral(".mhr"), Qt::CaseInsensitive)) continue; QString fullname{dir.absoluteFilePath(fname)}; if(processed.contains(fullname)) continue; processed.push_back(fullname); QString name{fname.left(fname.length()-4)}; if(!ret.contains(name)) ret.push_back(name); else { size_t i{2}; do { QString s{name+" #"+QString::number(i)}; if(!ret.contains(s)) { ret.push_back(s); break; } ++i; } while(true); } } } #ifdef ALSOFT_EMBED_HRTF_DATA ret.push_back(QStringLiteral("Built-In HRTF")); #endif } return ret; } void MainWindow::loadConfigFromFile() { QString fname = QFileDialog::getOpenFileName(this, tr("Select Files")); if(fname.isEmpty() == false) loadConfig(fname); } void MainWindow::loadConfig(const QString &fname) { QSettings settings{fname, QSettings::IniFormat}; QString sampletype{settings.value(QStringLiteral("sample-type")).toString()}; ui->sampleFormatCombo->setCurrentIndex(0); if(sampletype.isEmpty() == false) { QString str{getNameFromValue(sampleTypeList, sampletype)}; if(!str.isEmpty()) { const int j{ui->sampleFormatCombo->findText(str)}; if(j > 0) ui->sampleFormatCombo->setCurrentIndex(j); } } QString channelconfig{settings.value(QStringLiteral("channels")).toString()}; ui->channelConfigCombo->setCurrentIndex(0); if(channelconfig.isEmpty() == false) { if(channelconfig == QStringLiteral("surround51rear")) channelconfig = QStringLiteral("surround51"); QString str{getNameFromValue(speakerModeList, channelconfig)}; if(!str.isEmpty()) { const int j{ui->channelConfigCombo->findText(str)}; if(j > 0) ui->channelConfigCombo->setCurrentIndex(j); } } QString srate{settings.value(QStringLiteral("frequency")).toString()}; if(srate.isEmpty()) ui->sampleRateCombo->setCurrentIndex(0); else { ui->sampleRateCombo->lineEdit()->clear(); ui->sampleRateCombo->lineEdit()->insert(srate); } ui->srcCountLineEdit->clear(); ui->srcCountLineEdit->insert(settings.value(QStringLiteral("sources")).toString()); ui->effectSlotLineEdit->clear(); ui->effectSlotLineEdit->insert(settings.value(QStringLiteral("slots")).toString()); ui->srcSendLineEdit->clear(); ui->srcSendLineEdit->insert(settings.value(QStringLiteral("sends")).toString()); auto resampler = settings.value(QStringLiteral("resampler")).toString().trimmed(); static constexpr auto defaultResamplerIndex = GetDefaultIndex(resamplerList); ui->resamplerSlider->setValue(defaultResamplerIndex); ui->resamplerLabel->setText(std::data(resamplerList[defaultResamplerIndex].name)); /* "Cubic" is an alias for the 4-point spline resampler. The "sinc4" and * "sinc8" resamplers are unsupported, use "gaussian" as a fallback. */ if(resampler == QLatin1String{"cubic"}) resampler = QStringLiteral("spline"); else if(resampler == QLatin1String{"sinc4"} || resampler == QLatin1String{"sinc8"}) resampler = QStringLiteral("gaussian"); /* The "bsinc" resampler name is an alias for "bsinc12". */ else if(resampler == QLatin1String{"bsinc"}) resampler = QStringLiteral("bsinc12"); for(int i = 0;resamplerList[i].name[0];i++) { if(resampler == std::data(resamplerList[i].value)) { ui->resamplerSlider->setValue(i); ui->resamplerLabel->setText(std::data(resamplerList[i].name)); break; } } QString stereomode{settings.value(QStringLiteral("stereo-mode")).toString().trimmed()}; ui->stereoModeCombo->setCurrentIndex(0); if(stereomode.isEmpty() == false) { QString str{getNameFromValue(stereoModeList, stereomode)}; if(!str.isEmpty()) { const int j{ui->stereoModeCombo->findText(str)}; if(j > 0) ui->stereoModeCombo->setCurrentIndex(j); } } int periodsize{settings.value("period_size").toInt()}; ui->periodSizeEdit->clear(); if(periodsize >= 64) { ui->periodSizeEdit->insert(QString::number(periodsize)); updatePeriodSizeSlider(); } int periodcount{settings.value("periods").toInt()}; ui->periodCountEdit->clear(); if(periodcount >= 2) { ui->periodCountEdit->insert(QString::number(periodcount)); updatePeriodCountSlider(); } ui->outputLimiterCheckBox->setCheckState(getCheckState(settings.value(QStringLiteral("output-limiter")))); ui->outputDitherCheckBox->setCheckState(getCheckState(settings.value(QStringLiteral("dither")))); QString stereopan{settings.value(QStringLiteral("stereo-encoding")).toString()}; ui->stereoEncodingComboBox->setCurrentIndex(0); if(stereopan.isEmpty() == false) { QString str{getNameFromValue(stereoEncList, stereopan)}; if(!str.isEmpty()) { const int j{ui->stereoEncodingComboBox->findText(str)}; if(j > 0) ui->stereoEncodingComboBox->setCurrentIndex(j); } } QString ambiformat{settings.value(QStringLiteral("ambi-format")).toString()}; ui->ambiFormatComboBox->setCurrentIndex(0); if(ambiformat.isEmpty() == false) { QString str{getNameFromValue(ambiFormatList, ambiformat)}; if(!str.isEmpty()) { const int j{ui->ambiFormatComboBox->findText(str)}; if(j > 0) ui->ambiFormatComboBox->setCurrentIndex(j); } } ui->decoderHQModeCheckBox->setChecked(getCheckState(settings.value(QStringLiteral("decoder/hq-mode")))); ui->decoderDistCompCheckBox->setCheckState(getCheckState(settings.value(QStringLiteral("decoder/distance-comp")))); ui->decoderNFEffectsCheckBox->setCheckState(getCheckState(settings.value(QStringLiteral("decoder/nfc")))); double speakerdist{settings.value(QStringLiteral("decoder/speaker-dist"), 1.0).toDouble()}; ui->decoderSpeakerDistSpinBox->setValue(speakerdist); ui->decoderQuadLineEdit->setText(settings.value(QStringLiteral("decoder/quad")).toString()); ui->decoder51LineEdit->setText(settings.value(QStringLiteral("decoder/surround51")).toString()); ui->decoder61LineEdit->setText(settings.value(QStringLiteral("decoder/surround61")).toString()); ui->decoder71LineEdit->setText(settings.value(QStringLiteral("decoder/surround71")).toString()); ui->decoder3D71LineEdit->setText(settings.value(QStringLiteral("decoder/surround3d71")).toString()); QStringList disabledCpuExts{settings.value(QStringLiteral("disable-cpu-exts")).toStringList()}; if(disabledCpuExts.size() == 1) disabledCpuExts = disabledCpuExts[0].split(QChar(',')); for(QString &name : disabledCpuExts) name = name.trimmed(); ui->enableSSECheckBox->setChecked(!disabledCpuExts.contains(QStringLiteral("sse"), Qt::CaseInsensitive)); ui->enableSSE2CheckBox->setChecked(!disabledCpuExts.contains(QStringLiteral("sse2"), Qt::CaseInsensitive)); ui->enableSSE3CheckBox->setChecked(!disabledCpuExts.contains(QStringLiteral("sse3"), Qt::CaseInsensitive)); ui->enableSSE41CheckBox->setChecked(!disabledCpuExts.contains(QStringLiteral("sse4.1"), Qt::CaseInsensitive)); ui->enableNeonCheckBox->setChecked(!disabledCpuExts.contains(QStringLiteral("neon"), Qt::CaseInsensitive)); auto hrtfmode = settings.value(QStringLiteral("hrtf-mode")).toString().trimmed(); static constexpr auto defaultHrtfModeIndex = GetDefaultIndex(hrtfModeList); ui->hrtfmodeSlider->setValue(defaultHrtfModeIndex); ui->hrtfmodeLabel->setText(std::data(hrtfModeList[defaultHrtfModeIndex].name)); /* The "basic" mode name is no longer supported. Use "ambi2" instead. */ if(hrtfmode == QLatin1String{"basic"}) hrtfmode = QStringLiteral("ambi2"); for(size_t i{0};i < hrtfModeList.size();++i) { if(hrtfmode == std::data(hrtfModeList[i].value)) { ui->hrtfmodeSlider->setValue(static_cast(i)); ui->hrtfmodeLabel->setText(std::data(hrtfModeList[i].name)); break; } } QStringList hrtf_paths{settings.value(QStringLiteral("hrtf-paths")).toStringList()}; if(hrtf_paths.size() == 1) hrtf_paths = hrtf_paths[0].split(QChar(',')); for(QString &name : hrtf_paths) name = name.trimmed(); if(!hrtf_paths.empty() && !hrtf_paths.back().isEmpty()) ui->defaultHrtfPathsCheckBox->setCheckState(Qt::Unchecked); else { hrtf_paths.removeAll(QString()); ui->defaultHrtfPathsCheckBox->setCheckState(Qt::Checked); } hrtf_paths.removeDuplicates(); ui->hrtfFileList->clear(); ui->hrtfFileList->addItems(hrtf_paths); updateHrtfRemoveButton(); ui->preferredHrtfComboBox->clear(); ui->preferredHrtfComboBox->addItem(QStringLiteral("- Any -")); if(ui->defaultHrtfPathsCheckBox->isChecked()) { QStringList hrtfs{collectHrtfs()}; Q_FOREACH(const QString &name, hrtfs) ui->preferredHrtfComboBox->addItem(name); } QString defaulthrtf{settings.value(QStringLiteral("default-hrtf")).toString()}; ui->preferredHrtfComboBox->setCurrentIndex(0); if(defaulthrtf.isEmpty() == false) { int i{ui->preferredHrtfComboBox->findText(defaulthrtf)}; if(i > 0) ui->preferredHrtfComboBox->setCurrentIndex(i); else { i = ui->preferredHrtfComboBox->count(); ui->preferredHrtfComboBox->addItem(defaulthrtf); ui->preferredHrtfComboBox->setCurrentIndex(i); } } ui->preferredHrtfComboBox->adjustSize(); ui->enabledBackendList->clear(); ui->disabledBackendList->clear(); QStringList drivers{settings.value(QStringLiteral("drivers")).toStringList()}; if(drivers.empty()) ui->backendCheckBox->setChecked(true); else { if(drivers.size() == 1) drivers = drivers[0].split(QChar(',')); for(QString &name : drivers) { name = name.trimmed(); /* Convert "mmdevapi" references to "wasapi" for backwards * compatibility. */ if(name == QLatin1String{"-mmdevapi"}) name = QStringLiteral("-wasapi"); else if(name == QLatin1String{"mmdevapi"}) name = QStringLiteral("wasapi"); } bool lastWasEmpty{false}; Q_FOREACH(const QString &backend, drivers) { lastWasEmpty = backend.isEmpty(); if(lastWasEmpty) continue; if(!backend.startsWith(QChar('-'))) { for(size_t j{0};j < backendList.size();++j) { if(backend == std::data(backendList[j].backend_name)) { ui->enabledBackendList->addItem(std::data(backendList[j].full_string)); break; } } } else if(backend.size() > 1) { QStringRef backendref{backend.rightRef(backend.size()-1)}; for(size_t j{0};j < backendList.size();++j) { if(backendref == std::data(backendList[j].backend_name)) { ui->disabledBackendList->addItem(std::data(backendList[j].full_string)); break; } } } } ui->backendCheckBox->setChecked(lastWasEmpty); } QString defaultreverb{settings.value(QStringLiteral("default-reverb")).toString().toLower()}; ui->defaultReverbComboBox->setCurrentIndex(0); if(defaultreverb.isEmpty() == false) { for(int i = 0;i < ui->defaultReverbComboBox->count();i++) { if(defaultreverb.compare(ui->defaultReverbComboBox->itemText(i).toLower()) == 0) { ui->defaultReverbComboBox->setCurrentIndex(i); break; } } } QStringList excludefx{settings.value(QStringLiteral("excludefx")).toStringList()}; if(excludefx.size() == 1) excludefx = excludefx[0].split(QChar(',')); for(QString &name : excludefx) name = name.trimmed(); ui->enableEaxReverbCheck->setChecked(!excludefx.contains(QStringLiteral("eaxreverb"), Qt::CaseInsensitive)); ui->enableStdReverbCheck->setChecked(!excludefx.contains(QStringLiteral("reverb"), Qt::CaseInsensitive)); ui->enableAutowahCheck->setChecked(!excludefx.contains(QStringLiteral("autowah"), Qt::CaseInsensitive)); ui->enableChorusCheck->setChecked(!excludefx.contains(QStringLiteral("chorus"), Qt::CaseInsensitive)); ui->enableCompressorCheck->setChecked(!excludefx.contains(QStringLiteral("compressor"), Qt::CaseInsensitive)); ui->enableDistortionCheck->setChecked(!excludefx.contains(QStringLiteral("distortion"), Qt::CaseInsensitive)); ui->enableEchoCheck->setChecked(!excludefx.contains(QStringLiteral("echo"), Qt::CaseInsensitive)); ui->enableEqualizerCheck->setChecked(!excludefx.contains(QStringLiteral("equalizer"), Qt::CaseInsensitive)); ui->enableFlangerCheck->setChecked(!excludefx.contains(QStringLiteral("flanger"), Qt::CaseInsensitive)); ui->enableFrequencyShifterCheck->setChecked(!excludefx.contains(QStringLiteral("fshifter"), Qt::CaseInsensitive)); ui->enableModulatorCheck->setChecked(!excludefx.contains(QStringLiteral("modulator"), Qt::CaseInsensitive)); ui->enableDedicatedCheck->setChecked(!excludefx.contains(QStringLiteral("dedicated"), Qt::CaseInsensitive)); ui->enablePitchShifterCheck->setChecked(!excludefx.contains(QStringLiteral("pshifter"), Qt::CaseInsensitive)); ui->enableVocalMorpherCheck->setChecked(!excludefx.contains(QStringLiteral("vmorpher"), Qt::CaseInsensitive)); if(ui->enableEaxCheck->isEnabled()) ui->enableEaxCheck->setChecked(getCheckState(settings.value(QStringLiteral("eax/enable"))) != Qt::Unchecked); ui->pulseAutospawnCheckBox->setCheckState(getCheckState(settings.value(QStringLiteral("pulse/spawn-server")))); ui->pulseAllowMovesCheckBox->setCheckState(getCheckState(settings.value(QStringLiteral("pulse/allow-moves")))); ui->pulseFixRateCheckBox->setCheckState(getCheckState(settings.value(QStringLiteral("pulse/fix-rate")))); ui->pulseAdjLatencyCheckBox->setCheckState(getCheckState(settings.value(QStringLiteral("pulse/adjust-latency")))); ui->pwireAssumeAudioCheckBox->setCheckState(getCheckState(settings.value(QStringLiteral("pipewire/assume-audio")))); ui->pwireRtMixCheckBox->setCheckState(getCheckState(settings.value(QStringLiteral("pipewire/rt-mix")))); ui->wasapiResamplerCheckBox->setCheckState(getCheckState(settings.value(QStringLiteral("wasapi/allow-resampler")))); ui->jackAutospawnCheckBox->setCheckState(getCheckState(settings.value(QStringLiteral("jack/spawn-server")))); ui->jackConnectPortsCheckBox->setCheckState(getCheckState(settings.value(QStringLiteral("jack/connect-ports")))); ui->jackRtMixCheckBox->setCheckState(getCheckState(settings.value(QStringLiteral("jack/rt-mix")))); ui->jackBufferSizeLine->setText(settings.value(QStringLiteral("jack/buffer-size"), QString()).toString()); updateJackBufferSizeSlider(); ui->alsaDefaultDeviceLine->setText(settings.value(QStringLiteral("alsa/device"), QString()).toString()); ui->alsaDefaultCaptureLine->setText(settings.value(QStringLiteral("alsa/capture"), QString()).toString()); ui->alsaResamplerCheckBox->setCheckState(getCheckState(settings.value(QStringLiteral("alsa/allow-resampler")))); ui->alsaMmapCheckBox->setCheckState(getCheckState(settings.value(QStringLiteral("alsa/mmap")))); ui->ossDefaultDeviceLine->setText(settings.value(QStringLiteral("oss/device"), QString()).toString()); ui->ossDefaultCaptureLine->setText(settings.value(QStringLiteral("oss/capture"), QString()).toString()); ui->solarisDefaultDeviceLine->setText(settings.value(QStringLiteral("solaris/device"), QString()).toString()); ui->waveOutputLine->setText(settings.value(QStringLiteral("wave/file"), QString()).toString()); ui->waveBFormatCheckBox->setChecked(settings.value(QStringLiteral("wave/bformat"), false).toBool()); ui->applyButton->setEnabled(false); ui->closeCancelButton->setText(tr("Close")); mNeedsSave = false; } void MainWindow::saveCurrentConfig() { saveConfig(getDefaultConfigName()); ui->applyButton->setEnabled(false); ui->closeCancelButton->setText(tr("Close")); mNeedsSave = false; QMessageBox::information(this, tr("Information"), tr("Applications using OpenAL need to be restarted for changes to take effect.")); } void MainWindow::saveConfigAsFile() { QString fname{QFileDialog::getOpenFileName(this, tr("Select Files"))}; if(fname.isEmpty() == false) { saveConfig(fname); ui->applyButton->setEnabled(false); mNeedsSave = false; } } void MainWindow::saveConfig(const QString &fname) const { QSettings settings{fname, QSettings::IniFormat}; /* HACK: Compound any stringlist values into a comma-separated string. */ QStringList allkeys{settings.allKeys()}; Q_FOREACH(const QString &key, allkeys) { QStringList vals{settings.value(key).toStringList()}; if(vals.size() > 1) settings.setValue(key, vals.join(QChar(','))); } settings.setValue(QStringLiteral("sample-type"), getValueFromName(sampleTypeList, ui->sampleFormatCombo->currentText())); settings.setValue(QStringLiteral("channels"), getValueFromName(speakerModeList, ui->channelConfigCombo->currentText())); uint rate{ui->sampleRateCombo->currentText().toUInt()}; if(rate <= 0) settings.setValue(QStringLiteral("frequency"), QString{}); else settings.setValue(QStringLiteral("frequency"), rate); settings.setValue(QStringLiteral("period_size"), ui->periodSizeEdit->text()); settings.setValue(QStringLiteral("periods"), ui->periodCountEdit->text()); settings.setValue(QStringLiteral("sources"), ui->srcCountLineEdit->text()); settings.setValue(QStringLiteral("slots"), ui->effectSlotLineEdit->text()); settings.setValue(QStringLiteral("resampler"), std::data(resamplerList[ui->resamplerSlider->value()].value)); settings.setValue(QStringLiteral("stereo-mode"), getValueFromName(stereoModeList, ui->stereoModeCombo->currentText())); settings.setValue(QStringLiteral("stereo-encoding"), getValueFromName(stereoEncList, ui->stereoEncodingComboBox->currentText())); settings.setValue(QStringLiteral("ambi-format"), getValueFromName(ambiFormatList, ui->ambiFormatComboBox->currentText())); settings.setValue(QStringLiteral("output-limiter"), getCheckValue(ui->outputLimiterCheckBox)); settings.setValue(QStringLiteral("dither"), getCheckValue(ui->outputDitherCheckBox)); settings.setValue(QStringLiteral("decoder/hq-mode"), getCheckValue(ui->decoderHQModeCheckBox)); settings.setValue(QStringLiteral("decoder/distance-comp"), getCheckValue(ui->decoderDistCompCheckBox)); settings.setValue(QStringLiteral("decoder/nfc"), getCheckValue(ui->decoderNFEffectsCheckBox)); double speakerdist{ui->decoderSpeakerDistSpinBox->value()}; settings.setValue(QStringLiteral("decoder/speaker-dist"), (speakerdist != 1.0) ? QString::number(speakerdist) : QString{} ); settings.setValue(QStringLiteral("decoder/quad"), ui->decoderQuadLineEdit->text()); settings.setValue(QStringLiteral("decoder/surround51"), ui->decoder51LineEdit->text()); settings.setValue(QStringLiteral("decoder/surround61"), ui->decoder61LineEdit->text()); settings.setValue(QStringLiteral("decoder/surround71"), ui->decoder71LineEdit->text()); settings.setValue(QStringLiteral("decoder/surround3d71"), ui->decoder3D71LineEdit->text()); QStringList strlist; if(!ui->enableSSECheckBox->isChecked()) strlist.append(QStringLiteral("sse")); if(!ui->enableSSE2CheckBox->isChecked()) strlist.append(QStringLiteral("sse2")); if(!ui->enableSSE3CheckBox->isChecked()) strlist.append(QStringLiteral("sse3")); if(!ui->enableSSE41CheckBox->isChecked()) strlist.append(QStringLiteral("sse4.1")); if(!ui->enableNeonCheckBox->isChecked()) strlist.append(QStringLiteral("neon")); settings.setValue(QStringLiteral("disable-cpu-exts"), strlist.join(QChar(','))); settings.setValue(QStringLiteral("hrtf-mode"), std::data(hrtfModeList[ui->hrtfmodeSlider->value()].value)); if(ui->preferredHrtfComboBox->currentIndex() == 0) settings.setValue(QStringLiteral("default-hrtf"), QString{}); else { QString str{ui->preferredHrtfComboBox->currentText()}; settings.setValue(QStringLiteral("default-hrtf"), str); } strlist.clear(); strlist.reserve(ui->hrtfFileList->count()); for(int i = 0;i < ui->hrtfFileList->count();i++) strlist.append(ui->hrtfFileList->item(i)->text()); if(!strlist.empty() && ui->defaultHrtfPathsCheckBox->isChecked()) strlist.append(QString{}); settings.setValue(QStringLiteral("hrtf-paths"), strlist.join(QChar{','})); strlist.clear(); for(int i = 0;i < ui->enabledBackendList->count();i++) { QString label{ui->enabledBackendList->item(i)->text()}; for(size_t j{0};j < backendList.size();++j) { if(label == std::data(backendList[j].full_string)) { strlist.append(std::data(backendList[j].backend_name)); break; } } } for(int i = 0;i < ui->disabledBackendList->count();i++) { QString label{ui->disabledBackendList->item(i)->text()}; for(size_t j{0};j < backendList.size();++j) { if(label == std::data(backendList[j].full_string)) { strlist.append(QChar{'-'}+QString{std::data(backendList[j].backend_name)}); break; } } } if(strlist.empty() && !ui->backendCheckBox->isChecked()) strlist.append(QStringLiteral("-all")); else if(ui->backendCheckBox->isChecked()) strlist.append(QString{}); settings.setValue(QStringLiteral("drivers"), strlist.join(QChar(','))); // TODO: Remove check when we can properly match global values. if(ui->defaultReverbComboBox->currentIndex() == 0) settings.setValue(QStringLiteral("default-reverb"), QString{}); else { QString str{ui->defaultReverbComboBox->currentText().toLower()}; settings.setValue(QStringLiteral("default-reverb"), str); } strlist.clear(); if(!ui->enableEaxReverbCheck->isChecked()) strlist.append(QStringLiteral("eaxreverb")); if(!ui->enableStdReverbCheck->isChecked()) strlist.append(QStringLiteral("reverb")); if(!ui->enableAutowahCheck->isChecked()) strlist.append(QStringLiteral("autowah")); if(!ui->enableChorusCheck->isChecked()) strlist.append(QStringLiteral("chorus")); if(!ui->enableDistortionCheck->isChecked()) strlist.append(QStringLiteral("distortion")); if(!ui->enableCompressorCheck->isChecked()) strlist.append(QStringLiteral("compressor")); if(!ui->enableEchoCheck->isChecked()) strlist.append(QStringLiteral("echo")); if(!ui->enableEqualizerCheck->isChecked()) strlist.append(QStringLiteral("equalizer")); if(!ui->enableFlangerCheck->isChecked()) strlist.append(QStringLiteral("flanger")); if(!ui->enableFrequencyShifterCheck->isChecked()) strlist.append(QStringLiteral("fshifter")); if(!ui->enableModulatorCheck->isChecked()) strlist.append(QStringLiteral("modulator")); if(!ui->enableDedicatedCheck->isChecked()) strlist.append(QStringLiteral("dedicated")); if(!ui->enablePitchShifterCheck->isChecked()) strlist.append(QStringLiteral("pshifter")); if(!ui->enableVocalMorpherCheck->isChecked()) strlist.append(QStringLiteral("vmorpher")); settings.setValue(QStringLiteral("excludefx"), strlist.join(QChar{','})); settings.setValue(QStringLiteral("eax/enable"), (!ui->enableEaxCheck->isEnabled() || ui->enableEaxCheck->isChecked()) ? QString{/*"true"*/} : QStringLiteral("false")); settings.setValue(QStringLiteral("pipewire/assume-audio"), getCheckValue(ui->pwireAssumeAudioCheckBox)); settings.setValue(QStringLiteral("pipewire/rt-mix"), getCheckValue(ui->pwireRtMixCheckBox)); settings.setValue(QStringLiteral("wasapi/allow-resampler"), getCheckValue(ui->wasapiResamplerCheckBox)); settings.setValue(QStringLiteral("pulse/spawn-server"), getCheckValue(ui->pulseAutospawnCheckBox)); settings.setValue(QStringLiteral("pulse/allow-moves"), getCheckValue(ui->pulseAllowMovesCheckBox)); settings.setValue(QStringLiteral("pulse/fix-rate"), getCheckValue(ui->pulseFixRateCheckBox)); settings.setValue(QStringLiteral("pulse/adjust-latency"), getCheckValue(ui->pulseAdjLatencyCheckBox)); settings.setValue(QStringLiteral("jack/spawn-server"), getCheckValue(ui->jackAutospawnCheckBox)); settings.setValue(QStringLiteral("jack/connect-ports"), getCheckValue(ui->jackConnectPortsCheckBox)); settings.setValue(QStringLiteral("jack/rt-mix"), getCheckValue(ui->jackRtMixCheckBox)); settings.setValue(QStringLiteral("jack/buffer-size"), ui->jackBufferSizeLine->text()); settings.setValue(QStringLiteral("alsa/device"), ui->alsaDefaultDeviceLine->text()); settings.setValue(QStringLiteral("alsa/capture"), ui->alsaDefaultCaptureLine->text()); settings.setValue(QStringLiteral("alsa/allow-resampler"), getCheckValue(ui->alsaResamplerCheckBox)); settings.setValue(QStringLiteral("alsa/mmap"), getCheckValue(ui->alsaMmapCheckBox)); settings.setValue(QStringLiteral("oss/device"), ui->ossDefaultDeviceLine->text()); settings.setValue(QStringLiteral("oss/capture"), ui->ossDefaultCaptureLine->text()); settings.setValue(QStringLiteral("solaris/device"), ui->solarisDefaultDeviceLine->text()); settings.setValue(QStringLiteral("wave/file"), ui->waveOutputLine->text()); settings.setValue(QStringLiteral("wave/bformat"), ui->waveBFormatCheckBox->isChecked() ? QStringLiteral("true") : QString{/*"false"*/} ); /* Remove empty keys * FIXME: Should only remove keys whose value matches the globally-specified value. */ allkeys = settings.allKeys(); Q_FOREACH(const QString &key, allkeys) { QString str{settings.value(key).toString()}; if(str.isEmpty()) settings.remove(key); } } void MainWindow::enableApplyButton() { if(!mNeedsSave) ui->applyButton->setEnabled(true); mNeedsSave = true; ui->closeCancelButton->setText(tr("Cancel")); } void MainWindow::updateResamplerLabel(int num) { ui->resamplerLabel->setText(std::data(resamplerList[num].name)); enableApplyButton(); } void MainWindow::updatePeriodSizeEdit(int size) { ui->periodSizeEdit->clear(); if(size >= 64) ui->periodSizeEdit->insert(QString::number(size)); enableApplyButton(); } void MainWindow::updatePeriodSizeSlider() { int pos = ui->periodSizeEdit->text().toInt(); if(pos >= 64) ui->periodSizeSlider->setSliderPosition(std::min(pos, 8192)); enableApplyButton(); } void MainWindow::updatePeriodCountEdit(int count) { ui->periodCountEdit->clear(); if(count >= 2) ui->periodCountEdit->insert(QString::number(count)); enableApplyButton(); } void MainWindow::updatePeriodCountSlider() { int pos = ui->periodCountEdit->text().toInt(); if(pos < 2) pos = 0; else if(pos > 16) pos = 16; ui->periodCountSlider->setSliderPosition(pos); enableApplyButton(); } void MainWindow::selectQuadDecoderFile() { selectDecoderFile(ui->decoderQuadLineEdit, "Select Quadraphonic Decoder");} void MainWindow::select51DecoderFile() { selectDecoderFile(ui->decoder51LineEdit, "Select 5.1 Surround Decoder");} void MainWindow::select61DecoderFile() { selectDecoderFile(ui->decoder61LineEdit, "Select 6.1 Surround Decoder");} void MainWindow::select71DecoderFile() { selectDecoderFile(ui->decoder71LineEdit, "Select 7.1 Surround Decoder");} void MainWindow::select3D71DecoderFile() { selectDecoderFile(ui->decoder3D71LineEdit, "Select 3D7.1 Surround Decoder");} void MainWindow::selectDecoderFile(QLineEdit *line, const char *caption) { QString dir{line->text()}; if(dir.isEmpty() || QDir::isRelativePath(dir)) { QStringList paths{getAllDataPaths("/openal/presets")}; while(!paths.isEmpty()) { if(QDir{paths.last()}.exists()) { dir = paths.last(); break; } paths.removeLast(); } } QString fname{QFileDialog::getOpenFileName(this, tr(caption), dir, tr("AmbDec Files (*.ambdec);;All Files (*.*)"))}; if(!fname.isEmpty()) { line->setText(fname); enableApplyButton(); } } void MainWindow::updateJackBufferSizeEdit(int size) { ui->jackBufferSizeLine->clear(); if(size > 0) ui->jackBufferSizeLine->insert(QString::number(1<jackBufferSizeLine->text().toInt()}; auto pos = static_cast(floor(log2(value) + 0.5)); ui->jackBufferSizeSlider->setSliderPosition(pos); enableApplyButton(); } void MainWindow::updateHrtfModeLabel(int num) { ui->hrtfmodeLabel->setText(std::data(hrtfModeList[static_cast(num)].name)); enableApplyButton(); } void MainWindow::addHrtfFile() { QString path{QFileDialog::getExistingDirectory(this, tr("Select HRTF Path"))}; if(path.isEmpty() == false && !getAllDataPaths(QStringLiteral("/openal/hrtf")).contains(path)) { ui->hrtfFileList->addItem(path); enableApplyButton(); } } void MainWindow::removeHrtfFile() { QList> selected{ui->hrtfFileList->selectedItems()}; if(!selected.isEmpty()) { std::for_each(selected.begin(), selected.end(), std::default_delete{}); enableApplyButton(); } } void MainWindow::updateHrtfRemoveButton() { ui->hrtfRemoveButton->setEnabled(!ui->hrtfFileList->selectedItems().empty()); } void MainWindow::showEnabledBackendMenu(QPoint pt) { QHash actionMap; pt = ui->enabledBackendList->mapToGlobal(pt); QMenu ctxmenu; QAction *removeAction{ctxmenu.addAction(QIcon::fromTheme("list-remove"), "Remove")}; if(ui->enabledBackendList->selectedItems().empty()) removeAction->setEnabled(false); ctxmenu.addSeparator(); for(size_t i{0};i < backendList.size();++i) { QString backend{std::data(backendList[i].full_string)}; QAction *action{ctxmenu.addAction(QString("Add ")+backend)}; actionMap[action] = backend; if(!ui->enabledBackendList->findItems(backend, Qt::MatchFixedString).empty() || !ui->disabledBackendList->findItems(backend, Qt::MatchFixedString).empty()) action->setEnabled(false); } QAction *gotAction{ctxmenu.exec(pt)}; if(gotAction == removeAction) { QList> selected{ui->enabledBackendList->selectedItems()}; std::for_each(selected.begin(), selected.end(), std::default_delete{}); enableApplyButton(); } else if(gotAction != nullptr) { auto iter = actionMap.constFind(gotAction); if(iter != actionMap.cend()) ui->enabledBackendList->addItem(iter.value()); enableApplyButton(); } } void MainWindow::showDisabledBackendMenu(QPoint pt) { QHash actionMap; pt = ui->disabledBackendList->mapToGlobal(pt); QMenu ctxmenu; QAction *removeAction{ctxmenu.addAction(QIcon::fromTheme("list-remove"), "Remove")}; if(ui->disabledBackendList->selectedItems().empty()) removeAction->setEnabled(false); ctxmenu.addSeparator(); for(size_t i{0};i < backendList.size();++i) { QString backend{std::data(backendList[i].full_string)}; QAction *action{ctxmenu.addAction(QString("Add ")+backend)}; actionMap[action] = backend; if(!ui->disabledBackendList->findItems(backend, Qt::MatchFixedString).empty() || !ui->enabledBackendList->findItems(backend, Qt::MatchFixedString).empty()) action->setEnabled(false); } QAction *gotAction{ctxmenu.exec(pt)}; if(gotAction == removeAction) { QList> selected{ui->disabledBackendList->selectedItems()}; std::for_each(selected.begin(), selected.end(), std::default_delete{}); enableApplyButton(); } else if(gotAction != nullptr) { auto iter = actionMap.constFind(gotAction); if(iter != actionMap.cend()) ui->disabledBackendList->addItem(iter.value()); enableApplyButton(); } } void MainWindow::selectOSSPlayback() { QString current{ui->ossDefaultDeviceLine->text()}; if(current.isEmpty()) current = ui->ossDefaultDeviceLine->placeholderText(); QString fname{QFileDialog::getOpenFileName(this, tr("Select Playback Device"), current)}; if(!fname.isEmpty()) { ui->ossDefaultDeviceLine->setText(fname); enableApplyButton(); } } void MainWindow::selectOSSCapture() { QString current{ui->ossDefaultCaptureLine->text()}; if(current.isEmpty()) current = ui->ossDefaultCaptureLine->placeholderText(); QString fname{QFileDialog::getOpenFileName(this, tr("Select Capture Device"), current)}; if(!fname.isEmpty()) { ui->ossDefaultCaptureLine->setText(fname); enableApplyButton(); } } void MainWindow::selectSolarisPlayback() { QString current{ui->solarisDefaultDeviceLine->text()}; if(current.isEmpty()) current = ui->solarisDefaultDeviceLine->placeholderText(); QString fname{QFileDialog::getOpenFileName(this, tr("Select Playback Device"), current)}; if(!fname.isEmpty()) { ui->solarisDefaultDeviceLine->setText(fname); enableApplyButton(); } } void MainWindow::selectWaveOutput() { QString fname{QFileDialog::getSaveFileName(this, tr("Select Wave File Output"), ui->waveOutputLine->text(), tr("Wave Files (*.wav *.amb);;All Files (*.*)"))}; if(!fname.isEmpty()) { ui->waveOutputLine->setText(fname); enableApplyButton(); } } openal-soft-1.24.2/utils/alsoft-config/mainwindow.h000066400000000000000000000037161474041540300222600ustar00rootroot00000000000000#ifndef MAINWINDOW_H #define MAINWINDOW_H #include #include #include namespace Ui { class MainWindow; } class MainWindow : public QMainWindow { Q_OBJECT private Q_SLOTS: void cancelCloseAction(); void saveCurrentConfig(); void saveConfigAsFile(); void loadConfigFromFile(); void showAboutPage(); void enableApplyButton(); void updateResamplerLabel(int num); void updatePeriodSizeEdit(int size); void updatePeriodSizeSlider(); void updatePeriodCountEdit(int count); void updatePeriodCountSlider(); void selectQuadDecoderFile(); void select51DecoderFile(); void select61DecoderFile(); void select71DecoderFile(); void select3D71DecoderFile(); void updateJackBufferSizeEdit(int size); void updateJackBufferSizeSlider(); void updateHrtfModeLabel(int num); void addHrtfFile(); void removeHrtfFile(); void updateHrtfRemoveButton(); void showEnabledBackendMenu(QPoint pt); void showDisabledBackendMenu(QPoint pt); void selectOSSPlayback(); void selectOSSCapture(); void selectSolarisPlayback(); void selectWaveOutput(); public: explicit MainWindow(QWidget *parent=nullptr); ~MainWindow() override; private: std::unique_ptr mPeriodSizeValidator; std::unique_ptr mPeriodCountValidator; std::unique_ptr mSourceCountValidator; std::unique_ptr mEffectSlotValidator; std::unique_ptr mSourceSendValidator; std::unique_ptr mSampleRateValidator; std::unique_ptr mJackBufferValidator; std::unique_ptr ui; bool mNeedsSave{}; void closeEvent(QCloseEvent *event) override; void selectDecoderFile(QLineEdit *line, const char *caption); QStringList collectHrtfs(); void loadConfig(const QString &fname); void saveConfig(const QString &fname) const; }; #endif // MAINWINDOW_H openal-soft-1.24.2/utils/alsoft-config/mainwindow.ui000066400000000000000000002235621474041540300224510ustar00rootroot00000000000000 MainWindow 0 0 564 469 564 460 OpenAL Soft Configuration .. 470 405 81 31 Apply .. 10 0 541 401 0 Playback 110 50 80 31 The output sample type. Currently, all mixing is done with 32-bit float and converted to the output sample type as needed. QComboBox::AdjustToContents 0 50 101 31 Sample Format: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 20 101 31 Channels: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 110 20 80 31 The default output channel configuration. Note that not all backends can properly detect the channel configuration and may default to stereo output. QComboBox::AdjustToContents 380 20 100 31 The playback/mixing sample rate. true QComboBox::NoInsert QComboBox::AdjustToContents Autodetect 8000 11025 16000 22050 32000 44100 48000 290 20 81 31 Sample Rate: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 290 50 81 31 Stereo Mode: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 380 50 101 31 How to treat stereo output. As headphones, HRTF or crossfeed filters may be used to improve binaural quality, which may not otherwise be suitable for speakers. -11 180 551 201 Advanced Settings Qt::AlignCenter 20 30 511 81 Buffer Metrics Qt::AlignCenter 260 20 241 51 The number of update periods. Higher values create a larger mix ahead, which helps protect against skips when the CPU is under load, but increases the delay between a sound getting mixed and being heard. 60 0 161 21 Period Count Qt::AlignCenter 99 20 141 21 1 16 1 2 1 true Qt::Horizontal QSlider::TicksBelow 1 40 20 51 21 3 10 20 241 51 The update period size, in sample frames. This is the number of frames needed for each mixing update. 60 20 191 21 63 8192 1 1024 63 true Qt::Horizontal QSlider::TicksBelow 512 10 0 201 21 Period Samples Qt::AlignCenter 0 20 51 21 20ms 130 120 111 31 Basic uses standard amplitude panning (aka pair-wise, stereo pair, etc). UHJ creates a stereo-compatible two-channel UHJ mix, which encodes some surround sound information into stereo output that can be decoded with a surround sound receiver. Binaural applies HRTF filters to create an illusion of 3D placement with headphones. 20 120 101 31 Stereo Encoding: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 260 120 121 31 Ambisonic Format: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 390 120 131 31 30 160 231 20 Applies a gain limiter on the final mixed output. This reduces the volume when the output samples would otherwise be clamped, avoiding excessive clipping noise. Enable Gain Limiter true 270 160 261 21 Applies dithering on the final mix for 8- and 16-bit output. This replaces the distortion created by nearest-value quantization with low-level whitenoise. Enable Dithering true 60 90 421 81 Resampler Quality Qt::AlignCenter 50 50 321 21 Default Qt::AlignCenter 80 30 251 23 Qt::Horizontal 20 30 51 21 Speed Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 340 30 51 21 Quality Renderer 30 20 181 21 Enables high-quality ambisonic rendering. This mode is capable of frequency-dependent processing, creating a better reproduction of 3D sound rendering over surround sound speakers. Qt::RightToLeft High Quality Mode: true 30 50 181 21 This applies the necessary delays and attenuation to make the speakers behave as though they are all equidistant, which is important for proper playback of 3D sound rendering. Requires the proper distances to be specified in the decoder configuration file. Qt::RightToLeft Distance Compensation: true -10 140 551 231 Decoder Configurations Qt::AlignCenter 130 30 301 25 20 30 101 25 Quadraphonic: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 440 30 91 25 Browse... 130 70 301 25 440 70 91 25 Browse... 20 70 101 25 5.1 Surround: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 20 110 101 25 6.1 Surround: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 130 110 301 25 440 110 91 25 Browse... 440 150 91 25 Browse... 130 150 301 25 20 150 101 25 7.1 Surround: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 20 190 101 25 3D7.1 Surround: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 130 190 301 25 440 190 91 25 Browse... 30 80 181 21 Simulates and compensates for low-frequency effects caused by the curvature of nearby sound-waves, which creates a more realistic perception of sound distance. Note that the effect may be stronger or weaker than intended if the application doesn't use or specify an appropriate unit scale, or if incorrect speaker distances are set in the decoder configuration file. Qt::RightToLeft Near-Field Effects: true 30 110 471 21 Specifies the speaker distance in meters, used by the near-field control filters with surround sound output. For ambisonic output modes, this value is the basis for the NFC-HOA Reference Delay parameter (calculated as delay_seconds = speaker_dist/343.3). This value is not used when a decoder configuration is set for the output mode (since they specify the per-speaker distances, overriding this setting), or when the NFC filters are off. 45 0 111 21 Speaker Distance: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 165 0 101 21 meters 2 0.100000000000000 10.000000000000000 0.010000000000000 1.000000000000000 HRTF -10 200 551 181 Advanced Settings Qt::AlignCenter false false 20 30 511 141 HRTF Profile Paths Qt::AlignCenter 20 20 391 81 A list of additional paths containing HRTF data sets. QAbstractItemView::InternalMove true QAbstractItemView::ExtendedSelection Qt::ElideNone 420 20 81 21 Add... .. false 180 110 151 21 Include the default system paths in addition to any listed above. Include Default Paths true 420 50 81 21 Remove .. 30 20 91 31 Preferred HRTF: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 130 20 161 31 The default HRTF to use if the application doesn't request one. 50 100 441 81 HRTF Render Method 20 30 51 21 Speed Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 340 30 51 21 Quality 80 30 251 21 Qt::Horizontal 50 50 321 21 Default Qt::AlignCenter Backends 0 11 111 361 true General PipeWire WASAPI PulseAudio JACK ALSA OSS Solaris Wave Writer 110 10 421 361 0 20 190 391 21 When checked, allows all other available backends not listed in the priority or disabled lists. Allow Other Backends true 220 30 191 151 Disabled backend driver list. 20 30 191 151 The backend driver list order. Unknown backends and duplicated names are ignored. QAbstractItemView::InternalMove 230 10 171 20 Disabled Backends: 30 10 171 20 Priority Backends: 20 10 161 21 Assumes PipeWire has support for audio, allowing the backend to initialize even when no audio devices are reported. Assume audio support true 20 40 161 21 Renders samples directly in the real-time processing callback. This allows for lower latency and less overall CPU utilization, but can increase the risk of underruns when increasing the amount of processing the mixer needs to do. Real-time Mixing true 20 10 191 21 Specifies whether to allow an extra resampler pass on the output. Enabling this will allow the playback device to be set to a different sample rate than the actual output can accept, causing the backend to apply its own resampling pass after OpenAL Soft mixes the sources and processes effects for output. Allow Resampler true 20 10 141 21 Automatically spawn a PulseAudio server if one is not already running. AutoSpawn Server true 20 40 161 21 Allows moving PulseAudio streams to different devices during playback or capture. Note that the device specifier and device format will not change to match the new device. Allow Moving Streams true 20 70 121 21 When checked, fix the OpenAL device's sample rate to match the PulseAudio device. Fix Sample Rate true 20 100 111 21 Attempts to adjust the overall latency of device playback. Note that this may have adverse effects on the resulting internal buffer sizes and mixing updates, leading to performance problems and drop-outs. Adjust Latency true 20 10 141 21 AutoSpawn Server true 10 110 401 80 The update buffer size, in samples, that the backend will keep buffered to handle the server's real-time processing requests. Must be a power of 2. Ignored when Real-time Mixing is used. Buffer Size Qt::AlignCenter 320 30 71 21 0 10 30 301 21 13 1 4 Qt::Horizontal QSlider::TicksBelow 1 20 40 141 21 AutoConnect Ports true 20 70 141 21 Renders samples directly in the real-time processing callback. This allows for lower latency and less overall CPU utilization, but can increase the risk of underruns when increasing the amount of processing the mixer needs to do. Real-time Mixing true 10 30 141 21 Default Playback Device: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 160 30 231 21 default 10 60 141 21 Default Capture Device: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 160 60 231 21 default 20 100 191 21 Allow use of ALSA's software resampler. This lets the OpenAL device to be set to a different sample rate than the backend device, but incurs another resample pass on top of OpenAL's resampler. Allow Resampler true 210 100 191 21 Accesses the audio device buffer through an mmap, potentially avoiding an extra sample buffer copy during updates. MMap Buffer true 10 30 141 21 Default Playback Device: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 160 30 151 21 /dev/dsp 10 60 141 21 Default Capture Device: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 160 60 151 21 /dev/dsp 320 30 91 21 Browse... 320 60 91 21 Browse... 160 30 151 21 /dev/audio 10 30 141 21 Default Playback Device: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 320 30 91 21 Browse... 10 30 71 21 Output File: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 90 30 221 21 0 90 421 71 <html><head/><body><p align="center"><span style=" font-style:italic;">Warning: The specified output file will be OVERWRITTEN WITHOUT</span></p><p align="center"><span style=" font-style:italic;">QUESTION when the Wave Writer device is opened.</span></p></body></html> 320 30 91 21 Browse... 120 60 191 21 Create .amb (B-Format) files Resources 190 20 51 21 The maximum number of allocatable sources. Lower values may help for systems with apps that try to play more sounds than the CPU can handle. 4 256 10 20 171 21 Number of Sound Sources: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 10 50 171 21 Number of Effect Slots: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 190 50 51 21 The maximum number of Auxiliary Effect Slots an app can create. A slot can use a non-negligible amount of CPU time if an effect is set on it even if no sources are feeding it, so this may help when apps use more than the system can handle. 3 64 10 80 171 21 Number of Source Sends: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 190 80 51 21 Limits the number of auxiliary sends allowed per source. Setting this higher than the default has no effect. 2 16 10 120 511 121 Enables use of specific CPU extensions. Certain methods may utilize CPU extensions when detected, and disabling these can be useful for preventing those extensions from being used. CPU Extensions 100 20 71 31 SSE true 180 20 71 31 SSE2 true 100 50 71 31 Neon true 340 20 71 31 SSE4.1 true 260 20 71 31 SSE3 true 101 80 311 31 <html><head/><body><p align="center"><span style=" font-style:italic;">No support enabled for CPU Extensions</span></p></body></html> Effects 10 60 511 241 Specifies which effects apps can recognize. Disabling effects can help for apps that try to use ones that are too intensive for the system to handle. Enabled Effects 70 30 131 21 EAX Reverb true 70 60 131 21 Standard Reverb true 70 90 131 21 Chorus true 70 150 131 21 Distortion true 70 180 131 21 Echo true 320 30 131 21 Equalizer true 320 90 131 21 Flanger true 320 150 131 21 Ring Modulator true 320 180 131 21 Enables both the Dedicated Dialog and Dedicated LFE effects added by the ALC_EXT_DEDICATED extension. Dedicated ... true 70 120 111 21 Compressor true 320 120 131 21 Pitch Shifter true 320 60 131 21 Frequency Shifter true 70 210 131 21 Autowah true 320 210 131 21 Vocal morpher true 10 20 141 31 Default Reverb Effect: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 160 20 135 31 QComboBox::AdjustToContents None Generic PaddedCell Room Bathroom Livingroom Stoneroom Auditorium ConcertHall Cave Arena Hangar CarpetedHallway Hallway StoneCorridor Alley Forest City Mountains Quarry Plain ParkingLot SewerPipe Underwater Drugged Dizzy Psychotic 30 320 231 21 Enables legacy EAX API support. Enable EAX API support 370 405 91 31 Cancel .. 0 0 564 29 &File &Help .. &Quit .. Save &As... Save Configuration As .. &Load... Load Configuration File &About... backendListWidget currentRowChanged(int) backendStackedWidget setCurrentIndex(int) 69 233 329 232 ShowHRTFContextMenu(QPoint) openal-soft-1.24.2/utils/alsoft-config/verstr.cpp000066400000000000000000000002651474041540300217600ustar00rootroot00000000000000 #include "verstr.h" #include "version.h" QString GetVersionString() { return QStringLiteral(ALSOFT_VERSION "-" ALSOFT_GIT_COMMIT_HASH " (" ALSOFT_GIT_BRANCH " branch)."); } openal-soft-1.24.2/utils/alsoft-config/verstr.h000066400000000000000000000001521474041540300214200ustar00rootroot00000000000000#ifndef VERSTR_H #define VERSTR_H #include QString GetVersionString(); #endif /* VERSTR_H */ openal-soft-1.24.2/utils/makemhr/000077500000000000000000000000001474041540300166155ustar00rootroot00000000000000openal-soft-1.24.2/utils/makemhr/loaddef.cpp000066400000000000000000001755021474041540300207310ustar00rootroot00000000000000/* * HRTF utility for producing and demonstrating the process of creating an * OpenAL Soft compatible HRIR data set. * * Copyright (C) 2011-2019 Christopher Fitzgerald * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * Or visit: http://www.gnu.org/licenses/old-licenses/gpl-2.0.html */ #include "loaddef.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "albit.h" #include "almalloc.h" #include "alnumeric.h" #include "alspan.h" #include "alstring.h" #include "filesystem.h" #include "fmt/core.h" #include "makemhr.h" #include "polyphase_resampler.h" #include "sofa-support.h" #include "mysofa.h" namespace { using namespace std::string_view_literals; // Constants for accessing the token reader's ring buffer. constexpr uint TRRingBits{16}; constexpr uint TRRingSize{1 << TRRingBits}; constexpr uint TRRingMask{TRRingSize - 1}; // The token reader's load interval in bytes. constexpr uint TRLoadSize{TRRingSize >> 2}; // Token reader state for parsing the data set definition. struct TokenReaderT { std::istream &mIStream; std::string mName; uint mLine{}; uint mColumn{}; std::array mRing{}; std::streamsize mIn{}; std::streamsize mOut{}; explicit TokenReaderT(std::istream &istream) noexcept : mIStream{istream} { } TokenReaderT(const TokenReaderT&) = default; }; // The limits for the listener's head 'radius' in the data set definition. constexpr double MinRadius{0.05}; constexpr double MaxRadius{0.15}; // The maximum number of channels that can be addressed for a WAVE file // source listed in the data set definition. constexpr uint MaxWaveChannels{65535}; // The limits to the byte size for a binary source listed in the definition // file. enum : uint { MinBinSize = 2, MaxBinSize = 4 }; // The limits to the number of significant bits for an ASCII source listed in // the data set definition. enum : uint { MinASCIIBits = 16, MaxASCIIBits = 32 }; // The four-character-codes for RIFF/RIFX WAVE file chunks. enum : uint { FOURCC_RIFF = 0x46464952, // 'RIFF' FOURCC_RIFX = 0x58464952, // 'RIFX' FOURCC_WAVE = 0x45564157, // 'WAVE' FOURCC_FMT = 0x20746D66, // 'fmt ' FOURCC_DATA = 0x61746164, // 'data' FOURCC_LIST = 0x5453494C, // 'LIST' FOURCC_WAVL = 0x6C766177, // 'wavl' FOURCC_SLNT = 0x746E6C73, // 'slnt' }; // The supported wave formats. enum : uint { WAVE_FORMAT_PCM = 0x0001, WAVE_FORMAT_IEEE_FLOAT = 0x0003, WAVE_FORMAT_EXTENSIBLE = 0xFFFE, }; enum ByteOrderT { BO_NONE, BO_LITTLE, BO_BIG }; // Source format for the references listed in the data set definition. enum SourceFormatT { SF_NONE, SF_ASCII, // ASCII text file. SF_BIN_LE, // Little-endian binary file. SF_BIN_BE, // Big-endian binary file. SF_WAVE, // RIFF/RIFX WAVE file. SF_SOFA // Spatially Oriented Format for Accoustics (SOFA) file. }; // Element types for the references listed in the data set definition. enum ElementTypeT { ET_NONE, ET_INT, // Integer elements. ET_FP // Floating-point elements. }; // Source reference state used when loading sources. struct SourceRefT { SourceFormatT mFormat; ElementTypeT mType; uint mSize; int mBits; uint mChannel; double mAzimuth; double mElevation; double mRadius; uint mSkip; uint mOffset; std::string mPath; }; /* Whitespace is not significant. It can process tokens as identifiers, numbers * (integer and floating-point), strings, and operators. Strings must be * encapsulated by double-quotes and cannot span multiple lines. */ // Setup the reader on the given file. The filename can be NULL if no error // output is desired. void TrSetup(const al::span startbytes, const std::string_view filename, TokenReaderT *tr) { std::string_view namepart; if(!filename.empty()) { const auto fslashpos = filename.rfind('/'); const auto bslashpos = filename.rfind('\\'); const auto slashpos = (bslashpos >= filename.size()) ? fslashpos : (fslashpos >= filename.size()) ? bslashpos : std::max(fslashpos, bslashpos); if(slashpos < filename.size()) namepart = filename.substr(slashpos+1); } tr->mName = namepart; tr->mLine = 1; tr->mColumn = 1; tr->mIn = 0; tr->mOut = 0; if(!startbytes.empty()) { assert(startbytes.size() <= tr->mRing.size()); std::copy(startbytes.cbegin(), startbytes.cend(), tr->mRing.begin()); tr->mIn += std::streamsize(startbytes.size()); } } // Prime the reader's ring buffer, and return a result indicating that there // is text to process. auto TrLoad(TokenReaderT *tr) -> int { std::istream &istream = tr->mIStream; auto toLoad = std::streamsize{TRRingSize} - (tr->mIn - tr->mOut); if(toLoad >= TRLoadSize && istream.good()) { // Load TRLoadSize (or less if at the end of the file) per read. toLoad = TRLoadSize; const auto in = tr->mIn&TRRingMask; std::streamsize count{TRRingSize - in}; if(count < toLoad) { istream.read(al::to_address(tr->mRing.begin() + in), count); tr->mIn += istream.gcount(); istream.read(tr->mRing.data(), toLoad-count); tr->mIn += istream.gcount(); } else { istream.read(al::to_address(tr->mRing.begin() + in), toLoad); tr->mIn += istream.gcount(); } if(tr->mOut >= TRRingSize) { tr->mOut -= TRRingSize; tr->mIn -= TRRingSize; } } if(tr->mIn > tr->mOut) return 1; return 0; } // Error display routine. Only displays when the base name is not NULL. // Used to display an error at a saved line/column. template void TrErrorAt(const TokenReaderT *tr, uint line, uint column, fmt::format_string fmt, Args&& ...args) { if(tr->mName.empty()) return; fmt::print(stderr, "\nError ({}:{}:{}): ", tr->mName, line, column); fmt::println(stderr, fmt, std::forward(args)...); } // Used to display an error at the current line/column. template void TrError(const TokenReaderT *tr, fmt::format_string fmt, Args&& ...args) { TrErrorAt(tr, tr->mLine, tr->mColumn, fmt, std::forward(args)...); } // Skips to the next line. void TrSkipLine(TokenReaderT *tr) { char ch; while(TrLoad(tr)) { ch = tr->mRing[tr->mOut&TRRingMask]; tr->mOut++; if(ch == '\n') { tr->mLine++; tr->mColumn = 1; break; } tr->mColumn ++; } } // Skips to the next token. auto TrSkipWhitespace(TokenReaderT *tr) -> int { while(TrLoad(tr)) { char ch{tr->mRing[tr->mOut&TRRingMask]}; if(isspace(ch)) { tr->mOut++; if(ch == '\n') { tr->mLine++; tr->mColumn = 1; } else tr->mColumn++; } else if(ch == '#') TrSkipLine(tr); else return 1; } return 0; } // Get the line and/or column of the next token (or the end of input). void TrIndication(TokenReaderT *tr, uint *line, uint *column) { TrSkipWhitespace(tr); if(line) *line = tr->mLine; if(column) *column = tr->mColumn; } // Checks to see if a token is (likely to be) an identifier. It does not // display any errors and will not proceed to the next token. auto TrIsIdent(TokenReaderT *tr) -> int { if(!TrSkipWhitespace(tr)) return 0; char ch{tr->mRing[tr->mOut&TRRingMask]}; return ch == '_' || isalpha(ch); } // Checks to see if a token is the given operator. It does not display any // errors and will not proceed to the next token. auto TrIsOperator(TokenReaderT *tr, const std::string_view op) -> int { if(!TrSkipWhitespace(tr)) return 0; auto out = tr->mOut; size_t len{0}; while(len < op.size() && out < tr->mIn) { if(tr->mRing[out&TRRingMask] != op[len]) break; ++len; ++out; } if(len == op.size()) return 1; return 0; } /* The TrRead*() routines obtain the value of a matching token type. They * display type, form, and boundary errors and will proceed to the next * token. */ // Reads and validates an identifier token. auto TrReadIdent(TokenReaderT *tr) -> std::string { auto ret = std::string{}; auto col = tr->mColumn; if(TrSkipWhitespace(tr)) { col = tr->mColumn; auto ch = char{tr->mRing[tr->mOut&TRRingMask]}; if(ch == '_' || isalpha(ch)) { do { ret += ch; tr->mColumn += 1; tr->mOut += 1; if(!TrLoad(tr)) break; ch = tr->mRing[tr->mOut&TRRingMask]; } while(ch == '_' || std::isdigit(ch) || std::isalpha(ch)); return ret; } } TrErrorAt(tr, tr->mLine, col, "Expected an identifier."); ret.clear(); return ret; } // Reads and validates (including bounds) an integer token. auto TrReadInt(TokenReaderT *tr, const int loBound, const int hiBound, int *value) -> int { uint col{tr->mColumn}; if(TrSkipWhitespace(tr)) { col = tr->mColumn; uint len{0}; std::array temp{}; char ch{tr->mRing[tr->mOut&TRRingMask]}; if(ch == '+' || ch == '-') { temp[len] = ch; len++; tr->mOut++; } uint digis{0}; while(TrLoad(tr)) { ch = tr->mRing[tr->mOut&TRRingMask]; if(!isdigit(ch)) break; if(len < 64) temp[len] = ch; len++; digis++; tr->mOut++; } tr->mColumn += len; if(digis > 0 && ch != '.' && !isalpha(ch)) { if(len > 64) { TrErrorAt(tr, tr->mLine, col, "Integer is too long."); return 0; } temp[len] = '\0'; *value = static_cast(strtol(temp.data(), nullptr, 10)); if(*value < loBound || *value > hiBound) { TrErrorAt(tr, tr->mLine, col, "Expected a value from {} to {}.", loBound, hiBound); return 0; } return 1; } } TrErrorAt(tr, tr->mLine, col, "Expected an integer."); return 0; } // Reads and validates (including bounds) a float token. auto TrReadFloat(TokenReaderT *tr, const double loBound, const double hiBound, double *value) -> int { uint col{tr->mColumn}; if(TrSkipWhitespace(tr)) { col = tr->mColumn; std::array temp{}; uint len{0}; char ch{tr->mRing[tr->mOut&TRRingMask]}; if(ch == '+' || ch == '-') { temp[len] = ch; len++; tr->mOut++; } uint digis{0}; while(TrLoad(tr)) { ch = tr->mRing[tr->mOut&TRRingMask]; if(!isdigit(ch)) break; if(len < 64) temp[len] = ch; len++; digis++; tr->mOut++; } if(ch == '.') { if(len < 64) temp[len] = ch; len++; tr->mOut++; } while(TrLoad(tr)) { ch = tr->mRing[tr->mOut&TRRingMask]; if(!isdigit(ch)) break; if(len < 64) temp[len] = ch; len++; digis++; tr->mOut++; } if(digis > 0) { if(ch == 'E' || ch == 'e') { if(len < 64) temp[len] = ch; len++; digis = 0; tr->mOut++; if(ch == '+' || ch == '-') { if(len < 64) temp[len] = ch; len++; tr->mOut++; } while(TrLoad(tr)) { ch = tr->mRing[tr->mOut&TRRingMask]; if(!isdigit(ch)) break; if(len < 64) temp[len] = ch; len++; digis++; tr->mOut++; } } tr->mColumn += len; if(digis > 0 && ch != '.' && !isalpha(ch)) { if(len > 64) { TrErrorAt(tr, tr->mLine, col, "Float is too long."); return 0; } temp[len] = '\0'; *value = strtod(temp.data(), nullptr); if(*value < loBound || *value > hiBound) { TrErrorAt(tr, tr->mLine, col, "Expected a value from {:f} to {:f}.", loBound, hiBound); return 0; } return 1; } } else tr->mColumn += len; } TrErrorAt(tr, tr->mLine, col, "Expected a float."); return 0; } // Reads and validates a string token. auto TrReadString(TokenReaderT *tr) -> std::optional { auto ret = std::string{}; auto col = tr->mColumn; if(TrSkipWhitespace(tr)) { col = tr->mColumn; if(char ch{tr->mRing[tr->mOut&TRRingMask]}; ch == '\"') { tr->mOut++; size_t len{0}; while(TrLoad(tr)) { ch = tr->mRing[tr->mOut&TRRingMask]; tr->mOut++; if(ch == '\"') break; if(ch == '\n') { TrErrorAt(tr, tr->mLine, col, "Unterminated string at end of line."); return std::nullopt; } ret += ch; len++; } if(ch != '\"') { tr->mColumn += static_cast(1 + len); TrErrorAt(tr, tr->mLine, col, "Unterminated string at end of input."); return std::nullopt; } tr->mColumn += static_cast(2 + len); return std::optional{std::move(ret)}; } } TrErrorAt(tr, tr->mLine, col, "Expected a string."); return std::nullopt; } // Reads and validates the given operator. auto TrReadOperator(TokenReaderT *tr, const std::string_view op) -> int { uint col{tr->mColumn}; if(TrSkipWhitespace(tr)) { col = tr->mColumn; size_t len{0}; while(len < op.size() && TrLoad(tr)) { if(tr->mRing[tr->mOut&TRRingMask] != op[len]) break; ++len; tr->mOut += 1; } tr->mColumn += static_cast(len); if(len == op.size()) return 1; } TrErrorAt(tr, tr->mLine, col, "Expected '{}' operator.", op); return 0; } /************************* *** File source input *** *************************/ // Read a binary value of the specified byte order and byte size from a file, // storing it as a 32-bit unsigned integer. auto ReadBin4(std::istream &istream, const std::string_view filename, const ByteOrderT order, const uint bytes, uint32_t *out) -> int { std::array in{}; istream.read(reinterpret_cast(in.data()), static_cast(bytes)); if(istream.gcount() != bytes) { fmt::println(stderr, "\nError: Bad read from file '{}'.", filename); return 0; } uint32_t accum{0}; switch(order) { case BO_LITTLE: for(uint i = 0;i < bytes;i++) accum = (accum<<8) | in[bytes - i - 1]; break; case BO_BIG: for(uint i = 0;i < bytes;i++) accum = (accum<<8) | in[i]; break; default: break; } *out = accum; return 1; } // Read a binary value of the specified byte order from a file, storing it as // a 64-bit unsigned integer. auto ReadBin8(std::istream &istream, const std::string_view filename, const ByteOrderT order, uint64_t *out) -> int { std::array in{}; istream.read(reinterpret_cast(in.data()), 8); if(istream.gcount() != 8) { fmt::println(stderr, "\nError: Bad read from file '{}'.", filename); return 0; } uint64_t accum{}; switch(order) { case BO_LITTLE: for(uint i{0};i < 8;++i) accum = (accum<<8) | in[8 - i - 1]; break; case BO_BIG: for(uint i{0};i < 8;++i) accum = (accum<<8) | in[i]; break; default: break; } *out = accum; return 1; } /* Read a binary value of the specified type, byte order, and byte size from * a file, converting it to a double. For integer types, the significant * bits are used to normalize the result. The sign of bits determines * whether they are padded toward the MSB (negative) or LSB (positive). * Floating-point types are not normalized. */ auto ReadBinAsDouble(std::istream &istream, const std::string_view filename, const ByteOrderT order, const ElementTypeT type, const uint bytes, const int bits, double *out) -> int { *out = 0.0; if(bytes > 4) { uint64_t val{}; if(!ReadBin8(istream, filename, order, &val)) return 0; if(type == ET_FP) *out = al::bit_cast(val); } else { uint32_t val{}; if(!ReadBin4(istream, filename, order, bytes, &val)) return 0; if(type == ET_FP) *out = al::bit_cast(val); else { if(bits > 0) val >>= (8*bytes) - (static_cast(bits)); else val &= (0xFFFFFFFF >> (32+bits)); if(val&static_cast(1<<(std::abs(bits)-1))) val |= (0xFFFFFFFF << std::abs(bits)); *out = static_cast(val) / static_cast(1<<(std::abs(bits)-1)); } } return 1; } /* Read an ascii value of the specified type from a file, converting it to a * double. For integer types, the significant bits are used to normalize the * result. The sign of the bits should always be positive. This also skips * up to one separator character before the element itself. */ auto ReadAsciiAsDouble(TokenReaderT *tr, const std::string_view filename, const ElementTypeT type, const uint bits, double *out) -> int { if(TrIsOperator(tr, ",")) TrReadOperator(tr, ","); else if(TrIsOperator(tr, ":")) TrReadOperator(tr, ":"); else if(TrIsOperator(tr, ";")) TrReadOperator(tr, ";"); else if(TrIsOperator(tr, "|")) TrReadOperator(tr, "|"); if(type == ET_FP) { if(!TrReadFloat(tr, -std::numeric_limits::infinity(), std::numeric_limits::infinity(), out)) { fmt::println(stderr, "\nError: Bad read from file '{}'.", filename); return 0; } } else { int v; if(!TrReadInt(tr, -(1<<(bits-1)), (1<<(bits-1))-1, &v)) { fmt::println(stderr, "\nError: Bad read from file '{}'.", filename); return 0; } *out = v / static_cast((1<<(bits-1))-1); } return 1; } // Read the RIFF/RIFX WAVE format chunk from a file, validating it against // the source parameters and data set metrics. auto ReadWaveFormat(std::istream &istream, const ByteOrderT order, const uint hrirRate, SourceRefT *src) -> int { uint32_t fourCC, chunkSize; uint32_t format, channels, rate, dummy, block, size, bits; chunkSize = 0; do { if(chunkSize > 0) istream.seekg(static_cast(chunkSize), std::ios::cur); if(!ReadBin4(istream, src->mPath, BO_LITTLE, 4, &fourCC) || !ReadBin4(istream, src->mPath, order, 4, &chunkSize)) return 0; } while(fourCC != FOURCC_FMT); if(!ReadBin4(istream, src->mPath, order, 2, &format) || !ReadBin4(istream, src->mPath, order, 2, &channels) || !ReadBin4(istream, src->mPath, order, 4, &rate) || !ReadBin4(istream, src->mPath, order, 4, &dummy) || !ReadBin4(istream, src->mPath, order, 2, &block)) return 0; block /= channels; if(chunkSize > 14) { if(!ReadBin4(istream, src->mPath, order, 2, &size)) return 0; size = std::max(size/8, block); } else size = block; if(format == WAVE_FORMAT_EXTENSIBLE) { istream.seekg(2, std::ios::cur); if(!ReadBin4(istream, src->mPath, order, 2, &bits)) return 0; if(bits == 0) bits = 8 * size; istream.seekg(4, std::ios::cur); if(!ReadBin4(istream, src->mPath, order, 2, &format)) return 0; istream.seekg(static_cast(chunkSize - 26), std::ios::cur); } else { bits = 8 * size; if(chunkSize > 14) istream.seekg(static_cast(chunkSize - 16), std::ios::cur); else istream.seekg(static_cast(chunkSize - 14), std::ios::cur); } if(format != WAVE_FORMAT_PCM && format != WAVE_FORMAT_IEEE_FLOAT) { fmt::println(stderr, "\nError: Unsupported WAVE format in file '{}'.", src->mPath); return 0; } if(src->mChannel >= channels) { fmt::println(stderr, "\nError: Missing source channel in WAVE file '{}'.", src->mPath); return 0; } if(rate != hrirRate) { fmt::println(stderr, "\nError: Mismatched source sample rate in WAVE file '{}'.", src->mPath); return 0; } if(format == WAVE_FORMAT_PCM) { if(size < 2 || size > 4) { fmt::println(stderr, "\nError: Unsupported sample size in WAVE file '{}'.", src->mPath); return 0; } if(bits < 16 || bits > (8*size)) { fmt::println(stderr, "\nError: Bad significant bits in WAVE file '{}'.", src->mPath); return 0; } src->mType = ET_INT; } else { if(size != 4 && size != 8) { fmt::println(stderr, "\nError: Unsupported sample size in WAVE file '{}'.", src->mPath); return 0; } src->mType = ET_FP; } src->mSize = size; src->mBits = static_cast(bits); src->mSkip = channels; return 1; } // Read a RIFF/RIFX WAVE data chunk, converting all elements to doubles. auto ReadWaveData(std::istream &istream, const SourceRefT *src, const ByteOrderT order, const al::span hrir) -> int { auto pre = static_cast(src->mSize * src->mChannel); auto post = static_cast(src->mSize * (src->mSkip - src->mChannel - 1)); auto skip = 0; for(size_t i{0};i < hrir.size();++i) { skip += pre; if(skip > 0) istream.seekg(skip, std::ios::cur); if(!ReadBinAsDouble(istream, src->mPath, order, src->mType, src->mSize, src->mBits, &hrir[i])) return 0; skip = post; } if(skip > 0) istream.seekg(skip, std::ios::cur); return 1; } // Read the RIFF/RIFX WAVE list or data chunk, converting all elements to // doubles. auto ReadWaveList(std::istream &istream, const SourceRefT *src, const ByteOrderT order, const al::span hrir) -> int { uint32_t fourCC, chunkSize, listSize, count; uint block, skip, offset, i; double lastSample; for(;;) { if(!ReadBin4(istream, src->mPath, BO_LITTLE, 4, &fourCC) || !ReadBin4(istream, src->mPath, order, 4, &chunkSize)) return 0; if(fourCC == FOURCC_DATA) { block = src->mSize * src->mSkip; count = chunkSize / block; if(count < (src->mOffset + hrir.size())) { fmt::println(stderr, "\nError: Bad read from file '{}'.", src->mPath); return 0; } using off_type = std::istream::off_type; istream.seekg(off_type(src->mOffset) * off_type(block), std::ios::cur); if(!ReadWaveData(istream, src, order, hrir)) return 0; return 1; } if(fourCC == FOURCC_LIST) { if(!ReadBin4(istream, src->mPath, BO_LITTLE, 4, &fourCC)) return 0; chunkSize -= 4; if(fourCC == FOURCC_WAVL) break; } if(chunkSize > 0) istream.seekg(static_cast(chunkSize), std::ios::cur); } listSize = chunkSize; block = src->mSize * src->mSkip; skip = src->mOffset; offset = 0; lastSample = 0.0; while(offset < hrir.size() && listSize > 8) { if(!ReadBin4(istream, src->mPath, BO_LITTLE, 4, &fourCC) || !ReadBin4(istream, src->mPath, order, 4, &chunkSize)) return 0; listSize -= 8 + chunkSize; if(fourCC == FOURCC_DATA) { count = chunkSize / block; if(count > skip) { using off_type = std::istream::off_type; istream.seekg(off_type(skip) * off_type(block), std::ios::cur); chunkSize -= skip * block; count -= skip; skip = 0; if(count > (hrir.size() - offset)) count = static_cast(hrir.size() - offset); if(!ReadWaveData(istream, src, order, hrir.subspan(offset, count))) return 0; chunkSize -= count * block; offset += count; lastSample = hrir[offset - 1]; } else { skip -= count; count = 0; } } else if(fourCC == FOURCC_SLNT) { if(!ReadBin4(istream, src->mPath, order, 4, &count)) return 0; chunkSize -= 4; if(count > skip) { count -= skip; skip = 0; if(count > (hrir.size() - offset)) count = static_cast(hrir.size() - offset); for(i = 0; i < count; i ++) hrir[offset + i] = lastSample; offset += count; } else { skip -= count; count = 0; } } if(chunkSize > 0) istream.seekg(static_cast(chunkSize), std::ios::cur); } if(offset < hrir.size()) { fmt::println(stderr, "\nError: Bad read from file '{}'.", src->mPath); return 0; } return 1; } // Load a source HRIR from an ASCII text file containing a list of elements // separated by whitespace or common list operators (',', ';', ':', '|'). auto LoadAsciiSource(std::istream &istream, const SourceRefT *src, const al::span hrir) -> int { TokenReaderT tr{istream}; TrSetup({}, {}, &tr); for(uint i{0};i < src->mOffset;++i) { double dummy{}; if(!ReadAsciiAsDouble(&tr, src->mPath, src->mType, static_cast(src->mBits), &dummy)) return 0; } for(size_t i{0};i < hrir.size();++i) { if(!ReadAsciiAsDouble(&tr, src->mPath, src->mType, static_cast(src->mBits), &hrir[i])) return 0; for(uint j{0};j < src->mSkip;++j) { double dummy{}; if(!ReadAsciiAsDouble(&tr, src->mPath, src->mType, static_cast(src->mBits), &dummy)) return 0; } } return 1; } // Load a source HRIR from a binary file. auto LoadBinarySource(std::istream &istream, const SourceRefT *src, const ByteOrderT order, const al::span hrir) -> int { istream.seekg(static_cast(src->mOffset), std::ios::beg); for(size_t i{0};i < hrir.size();++i) { if(!ReadBinAsDouble(istream, src->mPath, order, src->mType, src->mSize, src->mBits, &hrir[i])) return 0; if(src->mSkip > 0) istream.seekg(static_cast(src->mSkip), std::ios::cur); } return 1; } // Load a source HRIR from a RIFF/RIFX WAVE file. auto LoadWaveSource(std::istream &istream, SourceRefT *src, const uint hrirRate, const al::span hrir) -> int { uint32_t fourCC, dummy; ByteOrderT order; if(!ReadBin4(istream, src->mPath, BO_LITTLE, 4, &fourCC) || !ReadBin4(istream, src->mPath, BO_LITTLE, 4, &dummy)) return 0; if(fourCC == FOURCC_RIFF) order = BO_LITTLE; else if(fourCC == FOURCC_RIFX) order = BO_BIG; else { fmt::println(stderr, "\nError: No RIFF/RIFX chunk in file '{}'.", src->mPath); return 0; } if(!ReadBin4(istream, src->mPath, BO_LITTLE, 4, &fourCC)) return 0; if(fourCC != FOURCC_WAVE) { fmt::println(stderr, "\nError: Not a RIFF/RIFX WAVE file '{}'.", src->mPath); return 0; } if(!ReadWaveFormat(istream, order, hrirRate, src)) return 0; if(!ReadWaveList(istream, src, order, hrir)) return 0; return 1; } struct SofaEasyDeleter { void operator()(gsl::owner sofa) { if(sofa->neighborhood) mysofa_neighborhood_free(sofa->neighborhood); if(sofa->lookup) mysofa_lookup_free(sofa->lookup); if(sofa->hrtf) mysofa_free(sofa->hrtf); delete sofa; } }; using SofaEasyPtr = std::unique_ptr; struct SofaCacheEntry { std::string mName; uint mSampleRate{}; SofaEasyPtr mSofa; }; std::vector gSofaCache; // Load a Spatially Oriented Format for Accoustics (SOFA) file. auto LoadSofaFile(SourceRefT *src, const uint hrirRate, const uint n) -> MYSOFA_EASY* { const std::string_view srcname{src->mPath}; auto iter = std::find_if(gSofaCache.begin(), gSofaCache.end(), [srcname,hrirRate](SofaCacheEntry &entry) -> bool { return entry.mName == srcname && entry.mSampleRate == hrirRate; }); if(iter != gSofaCache.end()) return iter->mSofa.get(); SofaEasyPtr sofa{new(std::nothrow) MYSOFA_EASY{}}; if(!sofa) { fmt::println(stderr, "\nError: Out of memory."); return nullptr; } sofa->lookup = nullptr; sofa->neighborhood = nullptr; int err; sofa->hrtf = mysofa_load(src->mPath.c_str(), &err); if(!sofa->hrtf) { fmt::println(stderr, "\nError: Could not load source file '{}': {} ({}).", src->mPath, SofaErrorStr(err), err); return nullptr; } /* NOTE: Some valid SOFA files are failing this check. */ err = mysofa_check(sofa->hrtf); if(err != MYSOFA_OK) fmt::println(stderr, "\nWarning: Supposedly malformed source file '{}': {} ({}).", src->mPath, SofaErrorStr(err), err); if((src->mOffset + n) > sofa->hrtf->N) { fmt::println(stderr, "\nError: Not enough samples in SOFA file '{}'.", src->mPath); return nullptr; } if(src->mChannel >= sofa->hrtf->R) { fmt::println(stderr, "\nError: Missing source receiver in SOFA file '{}'.", src->mPath); return nullptr; } mysofa_tocartesian(sofa->hrtf); sofa->lookup = mysofa_lookup_init(sofa->hrtf); if(sofa->lookup == nullptr) { fmt::println(stderr, "\nError: Out of memory."); return nullptr; } gSofaCache.emplace_back(SofaCacheEntry{std::string{srcname}, hrirRate, std::move(sofa)}); return gSofaCache.back().mSofa.get(); } // Copies the HRIR data from a particular SOFA measurement. void ExtractSofaHrir(const MYSOFA_HRTF *hrtf, const size_t index, const size_t channel, const size_t offset, const al::span hrir) { const auto irValues = al::span{hrtf->DataIR.values, hrtf->DataIR.elements} .subspan((index*hrtf->R + channel)*hrtf->N + offset); std::copy_n(irValues.cbegin(), hrir.size(), hrir.begin()); } // Load a source HRIR from a Spatially Oriented Format for Accoustics (SOFA) // file. auto LoadSofaSource(SourceRefT *src, const uint hrirRate, const al::span hrir) -> int { MYSOFA_EASY *sofa{LoadSofaFile(src, hrirRate, static_cast(hrir.size()))}; if(sofa == nullptr) return 0; /* NOTE: At some point it may be beneficial or necessary to consider the various coordinate systems, listener/source orientations, and directional vectors defined in the SOFA file. */ std::array target{ static_cast(src->mAzimuth), static_cast(src->mElevation), static_cast(src->mRadius) }; mysofa_s2c(target.data()); int nearest{mysofa_lookup(sofa->lookup, target.data())}; if(nearest < 0) { fmt::println(stderr, "\nError: Lookup failed in source file '{}'.", src->mPath); return 0; } al::span coords = al::span{sofa->hrtf->SourcePosition.values, sofa->hrtf->M*3_uz} .subspan(static_cast(nearest)*3_uz).first<3>(); if(std::abs(coords[0] - target[0]) > 0.001 || std::abs(coords[1] - target[1]) > 0.001 || std::abs(coords[2] - target[2]) > 0.001) { fmt::println(stderr, "\nError: No impulse response at coordinates ({:.3f}r, {:.1f}ev, {:.1f}az) in file '{}'.", src->mRadius, src->mElevation, src->mAzimuth, src->mPath); target[0] = coords[0]; target[1] = coords[1]; target[2] = coords[2]; mysofa_c2s(target.data()); fmt::println(stderr, " Nearest candidate at ({:.3f}r, {:.1f}ev, {:.1f}az).", target[2], target[1], target[0]); return 0; } ExtractSofaHrir(sofa->hrtf, static_cast(nearest), src->mChannel, src->mOffset, hrir); return 1; } // Load a source HRIR from a supported file type. auto LoadSource(SourceRefT *src, const uint hrirRate, const al::span hrir) -> int { auto istream = fs::ifstream{}; if(src->mFormat != SF_SOFA) { if(src->mFormat == SF_ASCII) istream.open(fs::u8path(src->mPath)); else istream.open(fs::u8path(src->mPath), std::ios::binary); if(!istream.good()) { fmt::println(stderr, "\nError: Could not open source file '{}'.", src->mPath); return 0; } } switch(src->mFormat) { case SF_ASCII: return LoadAsciiSource(istream, src, hrir); case SF_BIN_LE: return LoadBinarySource(istream, src, BO_LITTLE, hrir); case SF_BIN_BE: return LoadBinarySource(istream, src, BO_BIG, hrir); case SF_WAVE: return LoadWaveSource(istream, src, hrirRate, hrir); case SF_SOFA: return LoadSofaSource(src, hrirRate, hrir); case SF_NONE: break; } return 0; } // Match the channel type from a given identifier. auto MatchChannelType(const std::string_view ident) -> ChannelTypeT { if(al::case_compare(ident, "mono"sv) == 0) return CT_MONO; if(al::case_compare(ident, "stereo"sv) == 0) return CT_STEREO; return CT_NONE; } // Process the data set definition to read and validate the data set metrics. auto ProcessMetrics(TokenReaderT *tr, const uint fftSize, const uint truncSize, const ChannelModeT chanMode, HrirDataT *hData) -> int { int hasRate = 0, hasType = 0, hasPoints = 0, hasRadius = 0; int hasDistance = 0, hasAzimuths = 0; uint line, col; double fpVal; uint points; int intVal; std::array distances{}; uint fdCount = 0; std::array evCounts{}; auto azCounts = std::vector>(MAX_FD_COUNT); for(auto &azs : azCounts) azs.fill(0u); TrIndication(tr, &line, &col); while(TrIsIdent(tr)) { TrIndication(tr, &line, &col); const auto ident = TrReadIdent(tr); if(ident.empty()) return 0; if(al::case_compare(ident, "rate"sv) == 0) { if(hasRate) { TrErrorAt(tr, line, col, "Redefinition of 'rate'."); return 0; } if(!TrReadOperator(tr, "=")) return 0; if(!TrReadInt(tr, MIN_RATE, MAX_RATE, &intVal)) return 0; hData->mIrRate = static_cast(intVal); hasRate = 1; } else if(al::case_compare(ident, "type"sv) == 0) { if(hasType) { TrErrorAt(tr, line, col, "Redefinition of 'type'."); return 0; } if(!TrReadOperator(tr, "=")) return 0; const auto type = TrReadIdent(tr); if(type.empty()) return 0; hData->mChannelType = MatchChannelType(type); if(hData->mChannelType == CT_NONE) { TrErrorAt(tr, line, col, "Expected a channel type."); return 0; } if(hData->mChannelType == CT_STEREO) { if(chanMode == CM_ForceMono) hData->mChannelType = CT_MONO; } hasType = 1; } else if(al::case_compare(ident, "points"sv) == 0) { if(hasPoints) { TrErrorAt(tr, line, col, "Redefinition of 'points'."); return 0; } if(!TrReadOperator(tr, "=")) return 0; TrIndication(tr, &line, &col); if(!TrReadInt(tr, MIN_POINTS, MAX_POINTS, &intVal)) return 0; points = static_cast(intVal); if(fftSize > 0 && points > fftSize) { TrErrorAt(tr, line, col, "Value exceeds the overridden FFT size."); return 0; } if(points < truncSize) { TrErrorAt(tr, line, col, "Value is below the truncation size."); return 0; } hData->mIrPoints = points; hData->mFftSize = fftSize; hData->mIrSize = std::max(points, 1u + (fftSize/2u)); hasPoints = 1; } else if(al::case_compare(ident, "radius"sv) == 0) { if(hasRadius) { TrErrorAt(tr, line, col, "Redefinition of 'radius'."); return 0; } if(!TrReadOperator(tr, "=")) return 0; if(!TrReadFloat(tr, MinRadius, MaxRadius, &fpVal)) return 0; hData->mRadius = fpVal; hasRadius = 1; } else if(al::case_compare(ident, "distance"sv) == 0) { auto count = uint{0}; if(hasDistance) { TrErrorAt(tr, line, col, "Redefinition of 'distance'."); return 0; } if(!TrReadOperator(tr, "=")) return 0; for(;;) { if(!TrReadFloat(tr, MIN_DISTANCE, MAX_DISTANCE, &fpVal)) return 0; if(count > 0 && fpVal <= distances[count - 1]) { TrError(tr, "Distances are not ascending."); return 0; } distances[count++] = fpVal; if(!TrIsOperator(tr, ",")) break; if(count >= MAX_FD_COUNT) { TrError(tr, "Exceeded the maximum of {} fields.", MAX_FD_COUNT); return 0; } TrReadOperator(tr, ","); } if(fdCount != 0 && count != fdCount) { TrError(tr, "Did not match the specified number of {} fields.", fdCount); return 0; } fdCount = count; hasDistance = 1; } else if(al::case_compare(ident, "azimuths"sv) == 0) { auto count = uint{0}; if(hasAzimuths) { TrErrorAt(tr, line, col, "Redefinition of 'azimuths'."); return 0; } if(!TrReadOperator(tr, "=")) return 0; evCounts[0] = 0; for(;;) { if(!TrReadInt(tr, MIN_AZ_COUNT, MAX_AZ_COUNT, &intVal)) return 0; azCounts[count][evCounts[count]++] = static_cast(intVal); if(TrIsOperator(tr, ",")) { if(evCounts[count] >= MAX_EV_COUNT) { TrError(tr, "Exceeded the maximum of {} elevations.", MAX_EV_COUNT); return 0; } TrReadOperator(tr, ","); } else { if(evCounts[count] < MIN_EV_COUNT) { TrErrorAt(tr, line, col, "Did not reach the minimum of {} azimuth counts.", MIN_EV_COUNT); return 0; } if(azCounts[count][0] != 1 || azCounts[count][evCounts[count] - 1] != 1) { TrError(tr, "Poles are not singular for field {}.", count - 1); return 0; } count++; if(!TrIsOperator(tr, ";")) break; if(count >= MAX_FD_COUNT) { TrError(tr, "Exceeded the maximum number of %d fields.", MAX_FD_COUNT); return 0; } evCounts[count] = 0; TrReadOperator(tr, ";"); } } if(fdCount != 0 && count != fdCount) { TrError(tr, "Did not match the specified number of %d fields.", fdCount); return 0; } fdCount = count; hasAzimuths = 1; } else { TrErrorAt(tr, line, col, "Expected a metric name."); return 0; } TrSkipWhitespace(tr); } if(!(hasRate && hasPoints && hasRadius && hasDistance && hasAzimuths)) { TrErrorAt(tr, line, col, "Expected a metric name."); return 0; } if(distances[0] < hData->mRadius) { TrError(tr, "Distance cannot start below head radius."); return 0; } if(hData->mChannelType == CT_NONE) hData->mChannelType = CT_MONO; const auto azs = al::span{azCounts}.first(); if(!PrepareHrirData(al::span{distances}.first(fdCount), evCounts, azs, hData)) { fmt::println(stderr, "Error: Out of memory."); exit(-1); } return 1; } // Parse an index triplet from the data set definition. auto ReadIndexTriplet(TokenReaderT *tr, const HrirDataT *hData, uint *fi, uint *ei, uint *ai)->int { int intVal; if(hData->mFds.size() > 1) { if(!TrReadInt(tr, 0, static_cast(hData->mFds.size()-1), &intVal)) return 0; *fi = static_cast(intVal); if(!TrReadOperator(tr, ",")) return 0; } else { *fi = 0; } if(!TrReadInt(tr, 0, static_cast(hData->mFds[*fi].mEvs.size()-1), &intVal)) return 0; *ei = static_cast(intVal); if(!TrReadOperator(tr, ",")) return 0; if(!TrReadInt(tr, 0, static_cast(hData->mFds[*fi].mEvs[*ei].mAzs.size()-1), &intVal)) return 0; *ai = static_cast(intVal); return 1; } // Match the source format from a given identifier. auto MatchSourceFormat(const std::string_view ident) -> SourceFormatT { if(al::case_compare(ident, "ascii"sv) == 0) return SF_ASCII; if(al::case_compare(ident, "bin_le"sv) == 0) return SF_BIN_LE; if(al::case_compare(ident, "bin_be"sv) == 0) return SF_BIN_BE; if(al::case_compare(ident, "wave"sv) == 0) return SF_WAVE; if(al::case_compare(ident, "sofa"sv) == 0) return SF_SOFA; return SF_NONE; } // Match the source element type from a given identifier. auto MatchElementType(const std::string_view ident) -> ElementTypeT { if(al::case_compare(ident, "int"sv) == 0) return ET_INT; if(al::case_compare(ident, "fp"sv) == 0) return ET_FP; return ET_NONE; } // Parse and validate a source reference from the data set definition. auto ReadSourceRef(TokenReaderT *tr, SourceRefT *src) -> int { uint line, col; double fpVal; int intVal; TrIndication(tr, &line, &col); auto ident = TrReadIdent(tr); if(ident.empty()) return 0; src->mFormat = MatchSourceFormat(ident); if(src->mFormat == SF_NONE) { TrErrorAt(tr, line, col, "Expected a source format."); return 0; } if(!TrReadOperator(tr, "(")) return 0; if(src->mFormat == SF_SOFA) { if(!TrReadFloat(tr, MIN_DISTANCE, MAX_DISTANCE, &fpVal)) return 0; src->mRadius = fpVal; if(!TrReadOperator(tr, ",")) return 0; if(!TrReadFloat(tr, -90.0, 90.0, &fpVal)) return 0; src->mElevation = fpVal; if(!TrReadOperator(tr, ",")) return 0; if(!TrReadFloat(tr, -360.0, 360.0, &fpVal)) return 0; src->mAzimuth = fpVal; if(!TrReadOperator(tr, ":")) return 0; if(!TrReadInt(tr, 0, MaxWaveChannels, &intVal)) return 0; src->mType = ET_NONE; src->mSize = 0; src->mBits = 0; src->mChannel = static_cast(intVal); src->mSkip = 0; } else if(src->mFormat == SF_WAVE) { if(!TrReadInt(tr, 0, MaxWaveChannels, &intVal)) return 0; src->mType = ET_NONE; src->mSize = 0; src->mBits = 0; src->mChannel = static_cast(intVal); src->mSkip = 0; } else { TrIndication(tr, &line, &col); ident = TrReadIdent(tr); if(ident.empty()) return 0; src->mType = MatchElementType(ident); if(src->mType == ET_NONE) { TrErrorAt(tr, line, col, "Expected a source element type."); return 0; } if(src->mFormat == SF_BIN_LE || src->mFormat == SF_BIN_BE) { if(!TrReadOperator(tr, ",")) return 0; if(src->mType == ET_INT) { if(!TrReadInt(tr, MinBinSize, MaxBinSize, &intVal)) return 0; src->mSize = static_cast(intVal); if(!TrIsOperator(tr, ",")) src->mBits = static_cast(8*src->mSize); else { TrReadOperator(tr, ","); TrIndication(tr, &line, &col); if(!TrReadInt(tr, -2147483647-1, 2147483647, &intVal)) return 0; if(std::abs(intVal) < int{MinBinSize}*8 || static_cast(std::abs(intVal)) > (8*src->mSize)) { TrErrorAt(tr, line, col, "Expected a value of (+/-) {} to {}.", MinBinSize*8, 8*src->mSize); return 0; } src->mBits = intVal; } } else { TrIndication(tr, &line, &col); if(!TrReadInt(tr, -2147483647-1, 2147483647, &intVal)) return 0; if(intVal != 4 && intVal != 8) { TrErrorAt(tr, line, col, "Expected a value of 4 or 8."); return 0; } src->mSize = static_cast(intVal); src->mBits = 0; } } else if(src->mFormat == SF_ASCII && src->mType == ET_INT) { if(!TrReadOperator(tr, ",")) return 0; if(!TrReadInt(tr, MinASCIIBits, MaxASCIIBits, &intVal)) return 0; src->mSize = 0; src->mBits = intVal; } else { src->mSize = 0; src->mBits = 0; } if(!TrIsOperator(tr, ";")) src->mSkip = 0; else { TrReadOperator(tr, ";"); if(!TrReadInt(tr, 0, 0x7FFFFFFF, &intVal)) return 0; src->mSkip = static_cast(intVal); } } if(!TrReadOperator(tr, ")")) return 0; if(TrIsOperator(tr, "@")) { TrReadOperator(tr, "@"); if(!TrReadInt(tr, 0, 0x7FFFFFFF, &intVal)) return 0; src->mOffset = static_cast(intVal); } else src->mOffset = 0; if(!TrReadOperator(tr, ":")) return 0; auto srcpath = TrReadString(tr); if(!srcpath) return 0; src->mPath = std::move(*srcpath); return 1; } // Parse and validate a SOFA source reference from the data set definition. auto ReadSofaRef(TokenReaderT *tr, SourceRefT *src) -> int { uint line, col; int intVal; TrIndication(tr, &line, &col); const auto ident = TrReadIdent(tr); if(ident.empty()) return 0; src->mFormat = MatchSourceFormat(ident); if(src->mFormat != SF_SOFA) { TrErrorAt(tr, line, col, "Expected the SOFA source format."); return 0; } src->mType = ET_NONE; src->mSize = 0; src->mBits = 0; src->mChannel = 0; src->mSkip = 0; if(TrIsOperator(tr, "@")) { TrReadOperator(tr, "@"); if(!TrReadInt(tr, 0, 0x7FFFFFFF, &intVal)) return 0; src->mOffset = static_cast(intVal); } else src->mOffset = 0; if(!TrReadOperator(tr, ":")) return 0; auto srcpath = TrReadString(tr); if(!srcpath) return 0; src->mPath = std::move(*srcpath); return 1; } // Match the target ear (index) from a given identifier. auto MatchTargetEar(const std::string_view ident) -> std::optional { if(al::case_compare(ident, "left"sv) == 0) return 0u; if(al::case_compare(ident, "right"sv) == 0) return 1u; return std::nullopt; } // Calculate the onset time of an HRIR and average it with any existing // timing for its field, elevation, azimuth, and ear. constexpr int OnsetRateMultiple{10}; auto AverageHrirOnset(PPhaseResampler &rs, al::span upsampled, const uint rate, const al::span hrir, const double f, const double onset) -> double { rs.process(hrir, upsampled); auto abs_lt = [](const double lhs, const double rhs) -> bool { return std::abs(lhs) < std::abs(rhs); }; auto iter = std::max_element(upsampled.cbegin(), upsampled.cend(), abs_lt); return Lerp(onset, static_cast(std::distance(upsampled.cbegin(), iter))/(10*rate), f); } // Calculate the magnitude response of an HRIR and average it with any // existing responses for its field, elevation, azimuth, and ear. void AverageHrirMagnitude(const uint fftSize, const al::span hrir, const double f, const al::span mag) { const uint m{1 + (fftSize/2)}; std::vector h(fftSize); std::vector r(m); auto hiter = std::copy(hrir.cbegin(), hrir.cend(), h.begin()); std::fill(hiter, h.end(), 0.0); forward_fft(h); MagnitudeResponse(h, r); for(uint i{0};i < m;++i) mag[i] = Lerp(mag[i], r[i], f); } // Process the list of sources in the data set definition. auto ProcessSources(TokenReaderT *tr, HrirDataT *hData, const uint outRate) -> int { const uint channels{(hData->mChannelType == CT_STEREO) ? 2u : 1u}; hData->mHrirsBase.resize(size_t{channels} * hData->mIrCount * hData->mIrSize); const auto hrirs = al::span{hData->mHrirsBase}; auto hrir = std::vector(hData->mIrSize); uint line, col, fi, ei, ai; std::vector onsetSamples(size_t{OnsetRateMultiple} * hData->mIrPoints); PPhaseResampler onsetResampler; onsetResampler.init(hData->mIrRate, OnsetRateMultiple*hData->mIrRate); std::optional resampler; if(outRate && outRate != hData->mIrRate) resampler.emplace().init(hData->mIrRate, outRate); const double rateScale{outRate ? static_cast(outRate) / hData->mIrRate : 1.0}; const uint irPoints{outRate ? std::min(static_cast(std::ceil(hData->mIrPoints*rateScale)), hData->mIrPoints) : hData->mIrPoints}; fmt::print("Loading sources..."); fflush(stdout); int count{0}; while(TrIsOperator(tr, "[")) { std::array factor{1.0, 1.0}; TrIndication(tr, &line, &col); TrReadOperator(tr, "["); if(TrIsOperator(tr, "*")) { TrReadOperator(tr, "*"); if(!TrReadOperator(tr, "]") || !TrReadOperator(tr, "=")) return 0; TrIndication(tr, &line, &col); SourceRefT src{}; if(!ReadSofaRef(tr, &src)) return 0; if(hData->mChannelType == CT_STEREO) { const auto type = TrReadIdent(tr); if(type.empty()) return 0; switch(MatchChannelType(type)) { case CT_NONE: TrErrorAt(tr, line, col, "Expected a channel type."); return 0; case CT_MONO: src.mChannel = 0; break; case CT_STEREO: src.mChannel = 1; break; } } else { const auto type = TrReadIdent(tr); if(type.empty()) return 0; if(MatchChannelType(type) != CT_MONO) { TrErrorAt(tr, line, col, "Expected a mono channel type."); return 0; } src.mChannel = 0; } MYSOFA_EASY *sofa{LoadSofaFile(&src, hData->mIrRate, hData->mIrPoints)}; if(!sofa) return 0; const auto srcPosValues = al::span{sofa->hrtf->SourcePosition.values, sofa->hrtf->M*3_uz}; for(uint si{0};si < sofa->hrtf->M;++si) { fmt::print("\rLoading sources... {} of {}", si+1, sofa->hrtf->M); fflush(stdout); std::array aer{srcPosValues[3_uz*si], srcPosValues[3_uz*si + 1], srcPosValues[3_uz*si + 2]}; mysofa_c2s(aer.data()); if(std::fabs(aer[1]) >= 89.999f) aer[0] = 0.0f; else aer[0] = std::fmod(360.0f - aer[0], 360.0f); auto field = std::find_if(hData->mFds.cbegin(), hData->mFds.cend(), [&aer](const HrirFdT &fld) -> bool { return (std::abs(aer[2] - fld.mDistance) < 0.001); }); if(field == hData->mFds.cend()) continue; fi = static_cast(std::distance(hData->mFds.cbegin(), field)); const double evscale{180.0 / static_cast(field->mEvs.size()-1)}; double ef{(90.0 + aer[1]) / evscale}; ei = static_cast(std::round(ef)); ef = (ef - ei) * evscale; if(std::abs(ef) >= 0.1) continue; const double azscale{360.0 / static_cast(field->mEvs[ei].mAzs.size())}; double af{aer[0] / azscale}; ai = static_cast(std::round(af)); af = (af - ai) * azscale; ai %= static_cast(field->mEvs[ei].mAzs.size()); if(std::abs(af) >= 0.1) continue; HrirAzT *azd = &field->mEvs[ei].mAzs[ai]; if(!azd->mIrs[0].empty()) { TrErrorAt(tr, line, col, "Redefinition of source [ {}, {}, {} ].", fi, ei, ai); return 0; } const auto hrirPoints = al::span{hrir}.first(hData->mIrPoints); ExtractSofaHrir(sofa->hrtf, si, 0, src.mOffset, hrirPoints); azd->mIrs[0] = hrirs.subspan(size_t{hData->mIrSize}*azd->mIndex, hData->mIrSize); azd->mDelays[0] = AverageHrirOnset(onsetResampler, onsetSamples, hData->mIrRate, hrirPoints, 1.0, azd->mDelays[0]); if(resampler) resampler->process(hrirPoints, hrir); AverageHrirMagnitude(hData->mFftSize, al::span{hrir}.first(irPoints), 1.0, azd->mIrs[0]); if(src.mChannel == 1) { ExtractSofaHrir(sofa->hrtf, si, 1, src.mOffset, hrirPoints); azd->mIrs[1] = hrirs.subspan( (size_t{hData->mIrCount}+azd->mIndex) * hData->mIrSize, hData->mIrSize); azd->mDelays[1] = AverageHrirOnset(onsetResampler, onsetSamples, hData->mIrRate, hrirPoints, 1.0, azd->mDelays[1]); if(resampler) resampler->process(hrirPoints, hrir); AverageHrirMagnitude(hData->mFftSize, al::span{hrir}.first(irPoints), 1.0, azd->mIrs[1]); } // TODO: Since some SOFA files contain minimum phase HRIRs, // it would be beneficial to check for per-measurement delays // (when available) to reconstruct the HRTDs. } continue; } if(!ReadIndexTriplet(tr, hData, &fi, &ei, &ai)) return 0; if(!TrReadOperator(tr, "]")) return 0; HrirAzT *azd = &hData->mFds[fi].mEvs[ei].mAzs[ai]; if(!azd->mIrs[0].empty()) { TrErrorAt(tr, line, col, "Redefinition of source."); return 0; } if(!TrReadOperator(tr, "=")) return 0; while(true) { SourceRefT src{}; if(!ReadSourceRef(tr, &src)) return 0; // TODO: Would be nice to display 'x of y files', but that would // require preparing the source refs first to get a total count // before loading them. ++count; fmt::print("\rLoading sources... {} file{}", count, (count==1)?"":"s"); fflush(stdout); if(!LoadSource(&src, hData->mIrRate, al::span{hrir}.first(hData->mIrPoints))) return 0; auto ti = uint{0}; if(hData->mChannelType == CT_STEREO) { const auto ident = TrReadIdent(tr); if(ident.empty()) return 0; if(auto earopt = MatchTargetEar(ident)) ti = *earopt; else { TrErrorAt(tr, line, col, "Expected a target ear."); return 0; } } const auto hrirPoints = al::span{hrir}.first(hData->mIrPoints); azd->mIrs[ti] = hrirs.subspan((ti*size_t{hData->mIrCount}+azd->mIndex)*hData->mIrSize, hData->mIrSize); azd->mDelays[ti] = AverageHrirOnset(onsetResampler, onsetSamples, hData->mIrRate, hrirPoints, 1.0/factor[ti], azd->mDelays[ti]); if(resampler) resampler->process(hrirPoints, hrir); AverageHrirMagnitude(hData->mFftSize, al::span{hrir}.first(irPoints), 1.0/factor[ti], azd->mIrs[ti]); factor[ti] += 1.0; if(!TrIsOperator(tr, "+")) break; TrReadOperator(tr, "+"); } if(hData->mChannelType == CT_STEREO) { if(azd->mIrs[0].empty()) { TrErrorAt(tr, line, col, "Missing left ear source reference(s)."); return 0; } if(azd->mIrs[1].empty()) { TrErrorAt(tr, line, col, "Missing right ear source reference(s)."); return 0; } } } fmt::println(""); hrir.clear(); if(resampler) { hData->mIrRate = outRate; hData->mIrPoints = irPoints; resampler.reset(); } for(fi = 0;fi < hData->mFds.size();fi++) { for(ei = 0;ei < hData->mFds[fi].mEvs.size();ei++) { for(ai = 0;ai < hData->mFds[fi].mEvs[ei].mAzs.size();ai++) { HrirAzT *azd = &hData->mFds[fi].mEvs[ei].mAzs[ai]; if(!azd->mIrs[0].empty()) break; } if(ai < hData->mFds[fi].mEvs[ei].mAzs.size()) break; } if(ei >= hData->mFds[fi].mEvs.size()) { TrError(tr, "Missing source references [ {}, *, * ].", fi); return 0; } hData->mFds[fi].mEvStart = ei; for(;ei < hData->mFds[fi].mEvs.size();ei++) { for(ai = 0;ai < hData->mFds[fi].mEvs[ei].mAzs.size();ai++) { HrirAzT *azd = &hData->mFds[fi].mEvs[ei].mAzs[ai]; if(azd->mIrs[0].empty()) { TrError(tr, "Missing source reference [ {}, {}, {} ].", fi, ei, ai); return 0; } } } } for(uint ti{0};ti < channels;ti++) { for(fi = 0;fi < hData->mFds.size();fi++) { for(ei = 0;ei < hData->mFds[fi].mEvs.size();ei++) { for(ai = 0;ai < hData->mFds[fi].mEvs[ei].mAzs.size();ai++) { HrirAzT *azd = &hData->mFds[fi].mEvs[ei].mAzs[ai]; azd->mIrs[ti] = hrirs.subspan( (ti*size_t{hData->mIrCount} + azd->mIndex) * hData->mIrSize, hData->mIrSize); } } } } if(!TrLoad(tr)) { gSofaCache.clear(); return 1; } TrError(tr, "Errant data at end of source list."); gSofaCache.clear(); return 0; } } /* namespace */ bool LoadDefInput(std::istream &istream, const al::span startbytes, const std::string_view filename, const uint fftSize, const uint truncSize, const uint outRate, const ChannelModeT chanMode, HrirDataT *hData) { TokenReaderT tr{istream}; TrSetup(startbytes, filename, &tr); if(!ProcessMetrics(&tr, fftSize, truncSize, chanMode, hData) || !ProcessSources(&tr, hData, outRate)) return false; return true; } openal-soft-1.24.2/utils/makemhr/loaddef.h000066400000000000000000000005731474041540300203710ustar00rootroot00000000000000#ifndef LOADDEF_H #define LOADDEF_H #include #include #include "alspan.h" #include "makemhr.h" bool LoadDefInput(std::istream &istream, const al::span startbytes, const std::string_view filename, const uint fftSize, const uint truncSize, const uint outRate, const ChannelModeT chanMode, HrirDataT *hData); #endif /* LOADDEF_H */ openal-soft-1.24.2/utils/makemhr/loadsofa.cpp000066400000000000000000000507531474041540300211230ustar00rootroot00000000000000/* * HRTF utility for producing and demonstrating the process of creating an * OpenAL Soft compatible HRIR data set. * * Copyright (C) 2018-2019 Christopher Fitzgerald * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * Or visit: http://www.gnu.org/licenses/old-licenses/gpl-2.0.html */ #include "loadsofa.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "alspan.h" #include "alnumeric.h" #include "fmt/core.h" #include "makemhr.h" #include "polyphase_resampler.h" #include "sofa-support.h" #include "mysofa.h" namespace { using namespace std::string_view_literals; using uint = unsigned int; /* Attempts to produce a compatible layout. Most data sets tend to be * uniform and have the same major axis as used by OpenAL Soft's HRTF model. * This will remove outliers and produce a maximally dense layout when * possible. Those sets that contain purely random measurements or use * different major axes will fail. */ auto PrepareLayout(const al::span xyzs, HrirDataT *hData) -> bool { fmt::println("Detecting compatible layout..."); auto fds = GetCompatibleLayout(xyzs); if(fds.size() > MAX_FD_COUNT) { fmt::println("Incompatible layout (inumerable radii)."); return false; } std::array distances{}; std::array evCounts{}; auto azCounts = std::vector>(MAX_FD_COUNT); for(auto &azs : azCounts) azs.fill(0u); uint fi{0u}, ir_total{0u}; for(const auto &field : fds) { distances[fi] = field.mDistance; evCounts[fi] = field.mEvCount; for(uint ei{0u};ei < field.mEvStart;ei++) azCounts[fi][ei] = field.mAzCounts[field.mEvCount-ei-1]; for(uint ei{field.mEvStart};ei < field.mEvCount;ei++) { azCounts[fi][ei] = field.mAzCounts[ei]; ir_total += field.mAzCounts[ei]; } ++fi; } fmt::println("Using {} of {} IRs.", ir_total, xyzs.size()/3); const auto azs = al::span{azCounts}.first(); return PrepareHrirData(al::span{distances}.first(fi), evCounts, azs, hData); } float GetSampleRate(MYSOFA_HRTF *sofaHrtf) { const char *srate_dim{nullptr}; const char *srate_units{nullptr}; MYSOFA_ARRAY *srate_array{&sofaHrtf->DataSamplingRate}; MYSOFA_ATTRIBUTE *srate_attrs{srate_array->attributes}; while(srate_attrs) { if("DIMENSION_LIST"sv == srate_attrs->name) { if(srate_dim) { fmt::println(stderr, "Duplicate SampleRate.DIMENSION_LIST"); return 0.0f; } srate_dim = srate_attrs->value; } else if("Units"sv == srate_attrs->name) { if(srate_units) { fmt::println(stderr, "Duplicate SampleRate.Units"); return 0.0f; } srate_units = srate_attrs->value; } else fmt::println(stderr, "Unexpected sample rate attribute: {} = {}", srate_attrs->name, srate_attrs->value); srate_attrs = srate_attrs->next; } if(!srate_dim) { fmt::println(stderr, "Missing sample rate dimensions"); return 0.0f; } if(srate_dim != "I"sv) { fmt::println(stderr, "Unsupported sample rate dimensions: {}", srate_dim); return 0.0f; } if(!srate_units) { fmt::println(stderr, "Missing sample rate unit type"); return 0.0f; } if(srate_units != "hertz"sv) { fmt::println(stderr, "Unsupported sample rate unit type: {}", srate_units); return 0.0f; } /* I dimensions guarantees 1 element, so just extract it. */ const auto values = al::span{srate_array->values, sofaHrtf->I}; if(values[0] < float{MIN_RATE} || values[0] > float{MAX_RATE}) { fmt::println(stderr, "Sample rate out of range: {:f} (expected {} to {})", values[0], MIN_RATE, MAX_RATE); return 0.0f; } return values[0]; } enum class DelayType : uint8_t { None, I_R, /* [1][Channels] */ M_R, /* [HRIRs][Channels] */ }; auto PrepareDelay(MYSOFA_HRTF *sofaHrtf) -> std::optional { const char *delay_dim{nullptr}; MYSOFA_ARRAY *delay_array{&sofaHrtf->DataDelay}; MYSOFA_ATTRIBUTE *delay_attrs{delay_array->attributes}; while(delay_attrs) { if("DIMENSION_LIST"sv == delay_attrs->name) { if(delay_dim) { fmt::println(stderr, "Duplicate Delay.DIMENSION_LIST"); return std::nullopt; } delay_dim = delay_attrs->value; } else fmt::println(stderr, "Unexpected delay attribute: {} = {}", delay_attrs->name, delay_attrs->value ? delay_attrs->value : ""); delay_attrs = delay_attrs->next; } if(!delay_dim) { fmt::println(stderr, "Missing delay dimensions"); return DelayType::None; } if(delay_dim == "I,R"sv) return DelayType::I_R; if(delay_dim == "M,R"sv) return DelayType::M_R; fmt::println(stderr, "Unsupported delay dimensions: {}", delay_dim); return std::nullopt; } bool CheckIrData(MYSOFA_HRTF *sofaHrtf) { const char *ir_dim{nullptr}; MYSOFA_ARRAY *ir_array{&sofaHrtf->DataIR}; MYSOFA_ATTRIBUTE *ir_attrs{ir_array->attributes}; while(ir_attrs) { if("DIMENSION_LIST"sv == ir_attrs->name) { if(ir_dim) { fmt::println(stderr, "Duplicate IR.DIMENSION_LIST"); return false; } ir_dim = ir_attrs->value; } else fmt::println(stderr, "Unexpected IR attribute: {} = {}", ir_attrs->name, ir_attrs->value ? ir_attrs->value : ""); ir_attrs = ir_attrs->next; } if(!ir_dim) { fmt::println(stderr, "Missing IR dimensions"); return false; } if(ir_dim != "M,R,N"sv) { fmt::println(stderr, "Unsupported IR dimensions: {}", ir_dim); return false; } return true; } /* Calculate the onset time of a HRIR. */ constexpr int OnsetRateMultiple{10}; auto CalcHrirOnset(PPhaseResampler &rs, const uint rate, al::span upsampled, const al::span hrir) -> double { rs.process(hrir, upsampled); auto abs_lt = [](const double lhs, const double rhs) -> bool { return std::abs(lhs) < std::abs(rhs); }; auto iter = std::max_element(upsampled.cbegin(), upsampled.cend(), abs_lt); return static_cast(std::distance(upsampled.cbegin(), iter)) / (double{OnsetRateMultiple}*rate); } /* Calculate the magnitude response of a HRIR. */ void CalcHrirMagnitude(const uint points, al::span h, const al::span hrir) { auto iter = std::copy_n(hrir.cbegin(), points, h.begin()); std::fill(iter, h.end(), complex_d{0.0, 0.0}); forward_fft(h); MagnitudeResponse(h, hrir.first((h.size()/2) + 1)); } bool LoadResponses(MYSOFA_HRTF *sofaHrtf, HrirDataT *hData, const DelayType delayType, const uint outRate) { std::atomic loaded_count{0u}; auto load_proc = [sofaHrtf,hData,delayType,outRate,&loaded_count]() -> bool { const uint channels{(hData->mChannelType == CT_STEREO) ? 2u : 1u}; hData->mHrirsBase.resize(channels * size_t{hData->mIrCount} * hData->mIrSize, 0.0); const auto hrirs = al::span{hData->mHrirsBase}; std::vector restmp; std::optional resampler; if(outRate && outRate != hData->mIrRate) { resampler.emplace().init(hData->mIrRate, outRate); restmp.resize(sofaHrtf->N); } const auto srcPosValues = al::span{sofaHrtf->SourcePosition.values, sofaHrtf->M*3_uz}; const auto irValues = al::span{sofaHrtf->DataIR.values, size_t{sofaHrtf->M}*sofaHrtf->R*sofaHrtf->N}; for(uint si{0u};si < sofaHrtf->M;++si) { loaded_count.fetch_add(1u); std::array aer{srcPosValues[3_uz*si], srcPosValues[3_uz*si + 1], srcPosValues[3_uz*si + 2]}; mysofa_c2s(aer.data()); if(std::abs(aer[1]) >= 89.999f) aer[0] = 0.0f; else aer[0] = std::fmod(360.0f - aer[0], 360.0f); auto field = std::find_if(hData->mFds.cbegin(), hData->mFds.cend(), [&aer](const HrirFdT &fld) -> bool { return (std::abs(aer[2] - fld.mDistance) < 0.001); }); if(field == hData->mFds.cend()) continue; const double evscale{180.0 / static_cast(field->mEvs.size()-1)}; double ef{(90.0 + aer[1]) / evscale}; auto ei = static_cast(std::round(ef)); ef = (ef - ei) * evscale; if(std::abs(ef) >= 0.1) continue; const double azscale{360.0 / static_cast(field->mEvs[ei].mAzs.size())}; double af{aer[0] / azscale}; auto ai = static_cast(std::round(af)); af = (af-ai) * azscale; ai %= static_cast(field->mEvs[ei].mAzs.size()); if(std::abs(af) >= 0.1) continue; HrirAzT &azd = field->mEvs[ei].mAzs[ai]; if(!azd.mIrs[0].empty()) { fmt::println(stderr, "\nMultiple measurements near [ a={:f}, e={:f}, r={:f} ].", aer[0], aer[1], aer[2]); return false; } for(uint ti{0u};ti < channels;++ti) { azd.mIrs[ti] = hrirs.subspan( (size_t{hData->mIrCount}*ti + azd.mIndex) * hData->mIrSize, hData->mIrSize); const auto ir = irValues.subspan((size_t{si}*sofaHrtf->R + ti)*sofaHrtf->N, sofaHrtf->N); if(!resampler) std::copy_n(ir.cbegin(), ir.size(), azd.mIrs[ti].begin()); else { std::copy_n(ir.cbegin(), ir.size(), restmp.begin()); resampler->process(restmp, azd.mIrs[ti]); } } /* Include any per-channel or per-HRIR delays. */ if(delayType == DelayType::I_R) { const auto delayValues = al::span{sofaHrtf->DataDelay.values, size_t{sofaHrtf->I}*sofaHrtf->R}; for(uint ti{0u};ti < channels;++ti) azd.mDelays[ti] = delayValues[ti] / static_cast(hData->mIrRate); } else if(delayType == DelayType::M_R) { const auto delayValues = al::span{sofaHrtf->DataDelay.values, size_t{sofaHrtf->M}*sofaHrtf->R}; for(uint ti{0u};ti < channels;++ti) azd.mDelays[ti] = delayValues[si*sofaHrtf->R + ti] / static_cast(hData->mIrRate); } } if(outRate && outRate != hData->mIrRate) { const double scale{static_cast(outRate) / hData->mIrRate}; hData->mIrRate = outRate; hData->mIrPoints = std::min(static_cast(std::ceil(hData->mIrPoints*scale)), hData->mIrSize); } return true; }; std::future_status load_status{}; auto load_future = std::async(std::launch::async, load_proc); do { load_status = load_future.wait_for(std::chrono::milliseconds{50}); fmt::print("\rLoading HRIRs... {} of {}", loaded_count.load(), sofaHrtf->M); fflush(stdout); } while(load_status != std::future_status::ready); fmt::println(""); return load_future.get(); } /* Calculates the frequency magnitudes of the HRIR set. Work is delegated to * this struct, which runs asynchronously on one or more threads (sharing the * same calculator object). */ struct MagCalculator { const uint mFftSize{}; const uint mIrPoints{}; std::vector> mIrs; std::atomic mCurrent{}; std::atomic mDone{}; MagCalculator(const uint fftsize, const uint irpoints) : mFftSize{fftsize}, mIrPoints{irpoints} { } void Worker() { auto htemp = std::vector(mFftSize); while(true) { /* Load the current index to process. */ size_t idx{mCurrent.load()}; do { /* If the index is at the end, we're done. */ if(idx >= mIrs.size()) return; /* Otherwise, increment the current index atomically so other * threads know to go to the next one. If this call fails, the * current index was just changed by another thread and the new * value is loaded into idx, which we'll recheck. */ } while(!mCurrent.compare_exchange_weak(idx, idx+1, std::memory_order_relaxed)); CalcHrirMagnitude(mIrPoints, htemp, mIrs[idx]); /* Increment the number of IRs done. */ mDone.fetch_add(1); } } }; } // namespace bool LoadSofaFile(const std::string_view filename, const uint numThreads, const uint fftSize, const uint truncSize, const uint outRate, const ChannelModeT chanMode, HrirDataT *hData) { int err; MySofaHrtfPtr sofaHrtf{mysofa_load(std::string{filename}.c_str(), &err)}; if(!sofaHrtf) { fmt::println("Error: Could not load {}: {} ({})", filename, SofaErrorStr(err), err); return false; } /* NOTE: Some valid SOFA files are failing this check. */ err = mysofa_check(sofaHrtf.get()); if(err != MYSOFA_OK) fmt::println(stderr, "Warning: Supposedly malformed source file '{}': {} ({})", filename, SofaErrorStr(err), err); mysofa_tocartesian(sofaHrtf.get()); /* Make sure emitter and receiver counts are sane. */ if(sofaHrtf->E != 1) { fmt::println(stderr, "{} emitters not supported", sofaHrtf->E); return false; } if(sofaHrtf->R > 2 || sofaHrtf->R < 1) { fmt::println(stderr, "{} receivers not supported", sofaHrtf->R); return false; } /* Assume R=2 is a stereo measurement, and R=1 is mono left-ear-only. */ if(sofaHrtf->R == 2 && chanMode == CM_AllowStereo) hData->mChannelType = CT_STEREO; else hData->mChannelType = CT_MONO; /* Check and set the FFT and IR size. */ if(sofaHrtf->N > fftSize) { fmt::println(stderr, "Sample points exceeds the FFT size ({} > {}).", sofaHrtf->N, fftSize); return false; } if(sofaHrtf->N < truncSize) { fmt::println(stderr, "Sample points is below the truncation size ({} < {}).", sofaHrtf->N, truncSize); return false; } hData->mIrPoints = sofaHrtf->N; hData->mFftSize = fftSize; hData->mIrSize = std::max(1u + (fftSize/2u), sofaHrtf->N); /* Assume a default head radius of 9cm. */ hData->mRadius = 0.09; hData->mIrRate = static_cast(std::lround(GetSampleRate(sofaHrtf.get()))); if(!hData->mIrRate) return false; const auto delayType = PrepareDelay(sofaHrtf.get()); if(!delayType) return false; if(!CheckIrData(sofaHrtf.get())) return false; if(!PrepareLayout(al::span{sofaHrtf->SourcePosition.values, sofaHrtf->M*3_uz}, hData)) return false; if(!LoadResponses(sofaHrtf.get(), hData, *delayType, outRate)) return false; sofaHrtf = nullptr; for(uint fi{0u};fi < hData->mFds.size();fi++) { uint ei{0u}; for(;ei < hData->mFds[fi].mEvs.size();ei++) { uint ai{0u}; for(;ai < hData->mFds[fi].mEvs[ei].mAzs.size();ai++) { HrirAzT &azd = hData->mFds[fi].mEvs[ei].mAzs[ai]; if(!azd.mIrs[0].empty()) break; } if(ai < hData->mFds[fi].mEvs[ei].mAzs.size()) break; } if(ei >= hData->mFds[fi].mEvs.size()) { fmt::println(stderr, "Missing source references [ {}, *, * ].", fi); return false; } hData->mFds[fi].mEvStart = ei; for(;ei < hData->mFds[fi].mEvs.size();ei++) { for(uint ai{0u};ai < hData->mFds[fi].mEvs[ei].mAzs.size();ai++) { HrirAzT &azd = hData->mFds[fi].mEvs[ei].mAzs[ai]; if(azd.mIrs[0].empty()) { fmt::println(stderr, "Missing source reference [ {}, {}, {} ].", fi, ei, ai); return false; } } } } size_t hrir_total{0}; const uint channels{(hData->mChannelType == CT_STEREO) ? 2u : 1u}; const auto hrirs = al::span{hData->mHrirsBase}; for(uint fi{0u};fi < hData->mFds.size();fi++) { for(uint ei{0u};ei < hData->mFds[fi].mEvStart;ei++) { for(uint ai{0u};ai < hData->mFds[fi].mEvs[ei].mAzs.size();ai++) { HrirAzT &azd = hData->mFds[fi].mEvs[ei].mAzs[ai]; for(size_t ti{0u};ti < channels;ti++) azd.mIrs[ti] = hrirs.subspan((hData->mIrCount*ti + azd.mIndex)*hData->mIrSize, hData->mIrSize); } } for(uint ei{hData->mFds[fi].mEvStart};ei < hData->mFds[fi].mEvs.size();ei++) hrir_total += hData->mFds[fi].mEvs[ei].mAzs.size() * channels; } std::atomic hrir_done{0}; auto onset_proc = [hData,channels,&hrir_done]() -> bool { /* Temporary buffer used to calculate the IR's onset. */ auto upsampled = std::vector(size_t{OnsetRateMultiple} * hData->mIrPoints); /* This resampler is used to help detect the response onset. */ PPhaseResampler rs; rs.init(hData->mIrRate, OnsetRateMultiple*hData->mIrRate); for(auto &field : hData->mFds) { for(auto &elev : field.mEvs.subspan(field.mEvStart)) { for(auto &azd : elev.mAzs) { for(uint ti{0};ti < channels;ti++) { hrir_done.fetch_add(1u, std::memory_order_acq_rel); azd.mDelays[ti] += CalcHrirOnset(rs, hData->mIrRate, upsampled, azd.mIrs[ti].first(hData->mIrPoints)); } } } } return true; }; std::future_status load_status{}; auto load_future = std::async(std::launch::async, onset_proc); do { load_status = load_future.wait_for(std::chrono::milliseconds{50}); fmt::print("\rCalculating HRIR onsets... {} of {}", hrir_done.load(), hrir_total); fflush(stdout); } while(load_status != std::future_status::ready); fmt::println(""); if(!load_future.get()) return false; MagCalculator calculator{hData->mFftSize, hData->mIrPoints}; for(auto &field : hData->mFds) { for(auto &elev : field.mEvs.subspan(field.mEvStart)) { for(auto &azd : elev.mAzs) { for(uint ti{0};ti < channels;ti++) calculator.mIrs.push_back(azd.mIrs[ti]); } } } std::vector thrds; thrds.reserve(numThreads); for(size_t i{0};i < numThreads;++i) thrds.emplace_back(&MagCalculator::Worker, &calculator); size_t count; do { std::this_thread::sleep_for(std::chrono::milliseconds{50}); count = calculator.mDone.load(); fmt::print("\rCalculating HRIR magnitudes... {} of {}", count, calculator.mIrs.size()); fflush(stdout); } while(count != calculator.mIrs.size()); fmt::println(""); for(auto &thrd : thrds) { if(thrd.joinable()) thrd.join(); } return true; } openal-soft-1.24.2/utils/makemhr/loadsofa.h000066400000000000000000000004531474041540300205600ustar00rootroot00000000000000#ifndef LOADSOFA_H #define LOADSOFA_H #include #include "makemhr.h" bool LoadSofaFile(const std::string_view filename, const uint numThreads, const uint fftSize, const uint truncSize, const uint outRate, const ChannelModeT chanMode, HrirDataT *hData); #endif /* LOADSOFA_H */ openal-soft-1.24.2/utils/makemhr/makemhr.cpp000066400000000000000000001546601474041540300207610ustar00rootroot00000000000000/* * HRTF utility for producing and demonstrating the process of creating an * OpenAL Soft compatible HRIR data set. * * Copyright (C) 2011-2019 Christopher Fitzgerald * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * Or visit: http://www.gnu.org/licenses/old-licenses/gpl-2.0.html * * -------------------------------------------------------------------------- * * A big thanks goes out to all those whose work done in the field of * binaural sound synthesis using measured HRTFs makes this utility and the * OpenAL Soft implementation possible. * * The algorithm for diffuse-field equalization was adapted from the work * done by Rio Emmanuel and Larcher Veronique of IRCAM and Bill Gardner of * MIT Media Laboratory. It operates as follows: * * 1. Take the FFT of each HRIR and only keep the magnitude responses. * 2. Calculate the diffuse-field power-average of all HRIRs weighted by * their contribution to the total surface area covered by their * measurement. This has since been modified to use coverage volume for * multi-field HRIR data sets. * 3. Take the diffuse-field average and limit its magnitude range. * 4. Equalize the responses by using the inverse of the diffuse-field * average. * 5. Reconstruct the minimum-phase responses. * 5. Zero the DC component. * 6. IFFT the result and truncate to the desired-length minimum-phase FIR. * * The spherical head algorithm for calculating propagation delay was adapted * from the paper: * * Modeling Interaural Time Difference Assuming a Spherical Head * Joel David Miller * Music 150, Musical Acoustics, Stanford University * December 2, 2001 * * The formulae for calculating the Kaiser window metrics are from the * the textbook: * * Discrete-Time Signal Processing * Alan V. Oppenheim and Ronald W. Schafer * Prentice-Hall Signal Processing Series * 1999 */ #define _UNICODE /* NOLINT(bugprone-reserved-identifier) */ #include "config.h" #include "makemhr.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "alcomplex.h" #include "alnumbers.h" #include "alnumeric.h" #include "alspan.h" #include "alstring.h" #include "filesystem.h" #include "fmt/core.h" #include "loaddef.h" #include "loadsofa.h" #include "win_main_utf8.h" HrirDataT::~HrirDataT() = default; namespace { using namespace std::string_view_literals; struct FileDeleter { void operator()(gsl::owner f) { fclose(f); } }; using FilePtr = std::unique_ptr; // The epsilon used to maintain signal stability. constexpr double Epsilon{1e-9}; // The limits to the FFT window size override on the command line. constexpr uint MinFftSize{65536}; constexpr uint MaxFftSize{131072}; // The limits to the equalization range limit on the command line. constexpr double MinLimit{2.0}; constexpr double MaxLimit{120.0}; // The limits to the truncation window size on the command line. constexpr uint MinTruncSize{16}; constexpr uint MaxTruncSize{128}; // The limits to the custom head radius on the command line. constexpr double MinCustomRadius{0.05}; constexpr double MaxCustomRadius{0.15}; // The maximum propagation delay value supported by OpenAL Soft. constexpr double MaxHrtd{63.0}; // The OpenAL Soft HRTF format marker. It stands for minimum-phase head // response protocol 03. constexpr auto GetMHRMarker() noexcept { return "MinPHR03"sv; } // Head model used for calculating the impulse delays. enum HeadModelT { HM_None, HM_Dataset, // Measure the onset from the dataset. HM_Sphere, // Calculate the onset using a spherical head model. HM_Default = HM_Dataset }; // The defaults for the command line options. constexpr uint DefaultFftSize{65536}; constexpr bool DefaultEqualize{true}; constexpr bool DefaultSurface{true}; constexpr double DefaultLimit{24.0}; constexpr uint DefaultTruncSize{64}; constexpr double DefaultCustomRadius{0.0}; /* Channel index enums. Mono uses LeftChannel only. */ enum ChannelIndex : uint { LeftChannel = 0u, RightChannel = 1u }; /* Performs a string substitution. Any case-insensitive occurrences of the * pattern string are replaced with the replacement string. The result is * truncated if necessary. */ auto StrSubst(std::string_view in, const std::string_view pat, const std::string_view rep) -> std::string { std::string ret; ret.reserve(in.size() + pat.size()); while(in.size() >= pat.size()) { if(al::starts_with(in, pat)) { in = in.substr(pat.size()); ret += rep; } else { size_t endpos{1}; while(endpos < in.size() && std::toupper(in[endpos]) != std::toupper(pat.front())) ++endpos; ret += in.substr(0, endpos); in = in.substr(endpos); } } ret += in; return ret; } /********************* *** Math routines *** *********************/ // Simple clamp routine. double Clamp(const double val, const double lower, const double upper) { return std::min(std::max(val, lower), upper); } inline uint dither_rng(uint *seed) { *seed = *seed * 96314165 + 907633515; return *seed; } // Performs a triangular probability density function dither. The input samples // should be normalized (-1 to +1). void TpdfDither(const al::span out, const al::span in, const double scale, const size_t channel, const size_t step, uint *seed) { static constexpr double PRNG_SCALE = 1.0 / std::numeric_limits::max(); assert(channel < step); for(size_t i{0};i < in.size();++i) { uint prn0{dither_rng(seed)}; uint prn1{dither_rng(seed)}; out[i*step + channel] = std::round(in[i]*scale + (prn0*PRNG_SCALE - prn1*PRNG_SCALE)); } } /* Apply a range limit (in dB) to the given magnitude response. This is used * to adjust the effects of the diffuse-field average on the equalization * process. */ void LimitMagnitudeResponse(const uint n, const uint m, const double limit, const al::span inout) { const double halfLim{limit / 2.0}; // Convert the response to dB. for(uint i{0};i < m;++i) inout[i] = 20.0 * std::log10(inout[i]); // Use six octaves to calculate the average magnitude of the signal. const auto lower = (static_cast(std::ceil(n / std::pow(2.0, 8.0)))) - 1; const auto upper = (static_cast(std::floor(n / std::pow(2.0, 2.0)))) - 1; double ave{0.0}; for(uint i{lower};i <= upper;++i) ave += inout[i]; ave /= upper - lower + 1; // Keep the response within range of the average magnitude. for(uint i{0};i < m;++i) inout[i] = Clamp(inout[i], ave - halfLim, ave + halfLim); // Convert the response back to linear magnitude. for(uint i{0};i < m;++i) inout[i] = std::pow(10.0, inout[i] / 20.0); } /* Reconstructs the minimum-phase component for the given magnitude response * of a signal. This is equivalent to phase recomposition, sans the missing * residuals (which were discarded). The mirrored half of the response is * reconstructed. */ void MinimumPhase(const al::span mags, const al::span out) { assert(mags.size() == out.size()); const size_t m{(mags.size()/2) + 1}; size_t i; for(i = 0;i < m;i++) out[i] = std::log(mags[i]); for(;i < mags.size();++i) { mags[i] = mags[mags.size() - i]; out[i] = out[mags.size() - i]; } complex_hilbert(out); // Remove any DC offset the filter has. mags[0] = Epsilon; for(i = 0;i < mags.size();++i) out[i] = std::polar(mags[i], out[i].imag()); } /*************************** *** File storage output *** ***************************/ // Write an ASCII string to a file. auto WriteAscii(const std::string_view out, std::ostream &ostream, const std::string_view filename) -> int { if(!ostream.write(out.data(), std::streamsize(out.size())) || ostream.bad()) { fmt::println(stderr, "\nError: Bad write to file '{}'.", filename); return 0; } return 1; } // Write a binary value of the given byte order and byte size to a file, // loading it from a 32-bit unsigned integer. auto WriteBin4(const uint bytes, const uint32_t in, std::ostream &ostream, const std::string_view filename) -> int { std::array out{}; for(uint i{0};i < bytes;i++) out[i] = static_cast((in>>(i*8)) & 0x000000FF); if(!ostream.write(out.data(), std::streamsize(bytes)) || ostream.bad()) { fmt::println(stderr, "\nError: Bad write to file '{}'.", filename); return 0; } return 1; } // Store the OpenAL Soft HRTF data set. auto StoreMhr(const HrirDataT *hData, const std::string_view filename) -> bool { const uint channels{(hData->mChannelType == CT_STEREO) ? 2u : 1u}; const uint n{hData->mIrPoints}; uint dither_seed{22222}; auto ostream = fs::ofstream{fs::u8path(filename), std::ios::binary}; if(!ostream.is_open()) { fmt::println(stderr, "\nError: Could not open MHR file '{}'.", filename); return false; } if(!WriteAscii(GetMHRMarker(), ostream, filename)) return false; if(!WriteBin4(4, hData->mIrRate, ostream, filename)) return false; if(!WriteBin4(1, static_cast(hData->mChannelType), ostream, filename)) return false; if(!WriteBin4(1, hData->mIrPoints, ostream, filename)) return false; if(!WriteBin4(1, static_cast(hData->mFds.size()), ostream, filename)) return false; for(size_t fi{hData->mFds.size()-1};fi < hData->mFds.size();--fi) { auto fdist = static_cast(std::round(1000.0 * hData->mFds[fi].mDistance)); if(!WriteBin4(2, fdist, ostream, filename)) return false; if(!WriteBin4(1, static_cast(hData->mFds[fi].mEvs.size()), ostream, filename)) return false; for(size_t ei{0};ei < hData->mFds[fi].mEvs.size();++ei) { const auto &elev = hData->mFds[fi].mEvs[ei]; if(!WriteBin4(1, static_cast(elev.mAzs.size()), ostream, filename)) return false; } } for(size_t fi{hData->mFds.size()-1};fi < hData->mFds.size();--fi) { static constexpr double scale{8388607.0}; static constexpr uint bps{3u}; for(const auto &evd : hData->mFds[fi].mEvs) { for(const auto &azd : evd.mAzs) { std::array out{}; TpdfDither(out, azd.mIrs[0].first(n), scale, 0, channels, &dither_seed); if(hData->mChannelType == CT_STEREO) TpdfDither(out, azd.mIrs[1].first(n), scale, 1, channels, &dither_seed); const size_t numsamples{size_t{channels} * n}; for(size_t i{0};i < numsamples;i++) { const auto v = static_cast(Clamp(out[i], -scale-1.0, scale)); if(!WriteBin4(bps, static_cast(v), ostream, filename)) return false; } } } } for(size_t fi{hData->mFds.size()-1};fi < hData->mFds.size();--fi) { /* Delay storage has 2 bits of extra precision. */ static constexpr double DelayPrecScale{4.0}; for(const auto &evd : hData->mFds[fi].mEvs) { for(const auto &azd : evd.mAzs) { auto v = static_cast(std::round(azd.mDelays[0]*DelayPrecScale)); if(!WriteBin4(1, v, ostream, filename)) return false; if(hData->mChannelType == CT_STEREO) { v = static_cast(std::round(azd.mDelays[1]*DelayPrecScale)); if(!WriteBin4(1, v, ostream, filename)) return false; } } } } return true; } /*********************** *** HRTF processing *** ***********************/ /* Balances the maximum HRIR magnitudes of multi-field data sets by * independently normalizing each field in relation to the overall maximum. * This is done to ignore distance attenuation. */ void BalanceFieldMagnitudes(const HrirDataT *hData, const uint channels, const uint m) { std::array maxMags{}; double maxMag{0.0}; for(size_t fi{0};fi < hData->mFds.size();++fi) { for(size_t ei{hData->mFds[fi].mEvStart};ei < hData->mFds[fi].mEvs.size();++ei) { for(const auto &azd : hData->mFds[fi].mEvs[ei].mAzs) { for(size_t ti{0};ti < channels;++ti) { for(size_t i{0};i < m;++i) maxMags[fi] = std::max(azd.mIrs[ti][i], maxMags[fi]); } } } maxMag = std::max(maxMags[fi], maxMag); } for(size_t fi{0};fi < hData->mFds.size();++fi) { const double magFactor{maxMag / maxMags[fi]}; for(size_t ei{hData->mFds[fi].mEvStart};ei < hData->mFds[fi].mEvs.size();++ei) { for(const auto &azd : hData->mFds[fi].mEvs[ei].mAzs) { for(size_t ti{0};ti < channels;++ti) { for(size_t i{0};i < m;++i) azd.mIrs[ti][i] *= magFactor; } } } } } /* Calculate the contribution of each HRIR to the diffuse-field average based * on its coverage volume. All volumes are centered at the spherical HRIR * coordinates and measured by extruded solid angle. */ void CalculateDfWeights(const HrirDataT *hData, const al::span weights) { double sum, innerRa, outerRa, evs, ev, upperEv, lowerEv; double solidAngle, solidVolume; uint fi, ei; sum = 0.0; // The head radius acts as the limit for the inner radius. innerRa = hData->mRadius; for(fi = 0;fi < hData->mFds.size();fi++) { // Each volume ends half way between progressive field measurements. if((fi + 1) < hData->mFds.size()) outerRa = 0.5f * (hData->mFds[fi].mDistance + hData->mFds[fi + 1].mDistance); // The final volume has its limit extended to some practical value. // This is done to emphasize the far-field responses in the average. else outerRa = 10.0f; const double raPowDiff{std::pow(outerRa, 3.0) - std::pow(innerRa, 3.0)}; evs = al::numbers::pi / 2.0 / static_cast(hData->mFds[fi].mEvs.size() - 1); for(ei = hData->mFds[fi].mEvStart;ei < hData->mFds[fi].mEvs.size();ei++) { const auto &elev = hData->mFds[fi].mEvs[ei]; // For each elevation, calculate the upper and lower limits of // the patch band. ev = elev.mElevation; lowerEv = std::max(-al::numbers::pi / 2.0, ev - evs); upperEv = std::min(al::numbers::pi / 2.0, ev + evs); // Calculate the surface area of the patch band. solidAngle = 2.0 * al::numbers::pi * (std::sin(upperEv) - std::sin(lowerEv)); // Then the volume of the extruded patch band. solidVolume = solidAngle * raPowDiff / 3.0; // Each weight is the volume of one extruded patch. weights[(fi*MAX_EV_COUNT) + ei] = solidVolume / static_cast(elev.mAzs.size()); // Sum the total coverage volume of the HRIRs for all fields. sum += solidAngle; } innerRa = outerRa; } for(fi = 0;fi < hData->mFds.size();fi++) { // Normalize the weights given the total surface coverage for all // fields. for(ei = hData->mFds[fi].mEvStart;ei < hData->mFds[fi].mEvs.size();ei++) weights[(fi * MAX_EV_COUNT) + ei] /= sum; } } /* Calculate the diffuse-field average from the given magnitude responses of * the HRIR set. Weighting can be applied to compensate for the varying * coverage of each HRIR. The final average can then be limited by the * specified magnitude range (in positive dB; 0.0 to skip). */ void CalculateDiffuseFieldAverage(const HrirDataT *hData, const uint channels, const uint m, const bool weighted, const double limit, const al::span dfa) { std::vector weights(hData->mFds.size() * MAX_EV_COUNT); uint count; if(weighted) { // Use coverage weighting to calculate the average. CalculateDfWeights(hData, weights); } else { double weight; // If coverage weighting is not used, the weights still need to be // averaged by the number of existing HRIRs. count = hData->mIrCount; for(size_t fi{0};fi < hData->mFds.size();++fi) { for(size_t ei{0};ei < hData->mFds[fi].mEvStart;++ei) count -= static_cast(hData->mFds[fi].mEvs[ei].mAzs.size()); } weight = 1.0 / count; for(size_t fi{0};fi < hData->mFds.size();++fi) { for(size_t ei{hData->mFds[fi].mEvStart};ei < hData->mFds[fi].mEvs.size();++ei) weights[(fi * MAX_EV_COUNT) + ei] = weight; } } for(size_t ti{0};ti < channels;++ti) { for(size_t i{0};i < m;++i) dfa[(ti * m) + i] = 0.0; for(size_t fi{0};fi < hData->mFds.size();++fi) { for(size_t ei{hData->mFds[fi].mEvStart};ei < hData->mFds[fi].mEvs.size();++ei) { for(size_t ai{0};ai < hData->mFds[fi].mEvs[ei].mAzs.size();++ai) { HrirAzT *azd = &hData->mFds[fi].mEvs[ei].mAzs[ai]; // Get the weight for this HRIR's contribution. double weight = weights[(fi * MAX_EV_COUNT) + ei]; // Add this HRIR's weighted power average to the total. for(size_t i{0};i < m;++i) dfa[(ti * m) + i] += weight * azd->mIrs[ti][i] * azd->mIrs[ti][i]; } } } // Finish the average calculation and keep it from being too small. for(size_t i{0};i < m;++i) dfa[(ti * m) + i] = std::max(sqrt(dfa[(ti * m) + i]), Epsilon); // Apply a limit to the magnitude range of the diffuse-field average // if desired. if(limit > 0.0) LimitMagnitudeResponse(hData->mFftSize, m, limit, dfa.subspan(ti * m)); } } // Perform diffuse-field equalization on the magnitude responses of the HRIR // set using the given average response. void DiffuseFieldEqualize(const uint channels, const uint m, const al::span dfa, const HrirDataT *hData) { for(size_t fi{0};fi < hData->mFds.size();++fi) { for(size_t ei{hData->mFds[fi].mEvStart};ei < hData->mFds[fi].mEvs.size();++ei) { for(auto &azd : hData->mFds[fi].mEvs[ei].mAzs) { for(size_t ti{0};ti < channels;++ti) { for(size_t i{0};i < m;++i) azd.mIrs[ti][i] /= dfa[(ti * m) + i]; } } } } } /* Given field and elevation indices and an azimuth, calculate the indices of * the two HRIRs that bound the coordinate along with a factor for * calculating the continuous HRIR using interpolation. */ void CalcAzIndices(const HrirFdT &field, const uint ei, const double az, uint *a0, uint *a1, double *af) { double f{(2.0*al::numbers::pi + az) * static_cast(field.mEvs[ei].mAzs.size()) / (2.0*al::numbers::pi)}; const uint i{static_cast(f) % static_cast(field.mEvs[ei].mAzs.size())}; f -= std::floor(f); *a0 = i; *a1 = (i + 1) % static_cast(field.mEvs[ei].mAzs.size()); *af = f; } /* Synthesize any missing onset timings at the bottom elevations of each field. * This just mirrors some top elevations for the bottom, and blends the * remaining elevations (not an accurate model). */ void SynthesizeOnsets(HrirDataT *hData) { const uint channels{(hData->mChannelType == CT_STEREO) ? 2u : 1u}; auto proc_field = [channels](HrirFdT &field) -> void { /* Get the starting elevation from the measurements, and use it as the * upper elevation limit for what needs to be calculated. */ const uint upperElevReal{field.mEvStart}; if(upperElevReal <= 0) return; /* Get the lowest half of the missing elevations' delays by mirroring * the top elevation delays. The responses are on a spherical grid * centered between the ears, so these should align. */ uint ei{}; if(channels > 1) { /* Take the polar opposite position of the desired measurement and * swap the ears. */ field.mEvs[0].mAzs[0].mDelays[0] = field.mEvs[field.mEvs.size()-1].mAzs[0].mDelays[1]; field.mEvs[0].mAzs[0].mDelays[1] = field.mEvs[field.mEvs.size()-1].mAzs[0].mDelays[0]; for(ei = 1u;ei < (upperElevReal+1)/2;++ei) { const uint topElev{static_cast(field.mEvs.size()-ei-1)}; for(uint ai{0u};ai < field.mEvs[ei].mAzs.size();ai++) { uint a0, a1; double af; /* Rotate this current azimuth by a half-circle, and lookup * the mirrored elevation to find the indices for the polar * opposite position (may need blending). */ const double az{field.mEvs[ei].mAzs[ai].mAzimuth + al::numbers::pi}; CalcAzIndices(field, topElev, az, &a0, &a1, &af); /* Blend the delays, and again, swap the ears. */ field.mEvs[ei].mAzs[ai].mDelays[0] = Lerp( field.mEvs[topElev].mAzs[a0].mDelays[1], field.mEvs[topElev].mAzs[a1].mDelays[1], af); field.mEvs[ei].mAzs[ai].mDelays[1] = Lerp( field.mEvs[topElev].mAzs[a0].mDelays[0], field.mEvs[topElev].mAzs[a1].mDelays[0], af); } } } else { field.mEvs[0].mAzs[0].mDelays[0] = field.mEvs[field.mEvs.size()-1].mAzs[0].mDelays[0]; for(ei = 1u;ei < (upperElevReal+1)/2;++ei) { const uint topElev{static_cast(field.mEvs.size()-ei-1)}; for(uint ai{0u};ai < field.mEvs[ei].mAzs.size();ai++) { uint a0, a1; double af; /* For mono data sets, mirror the azimuth front<->back * since the other ear is a mirror of what we have (e.g. * the left ear's back-left is simulated with the right * ear's front-right, which uses the left ear's front-left * measurement). */ double az{field.mEvs[ei].mAzs[ai].mAzimuth}; if(az <= al::numbers::pi) az = al::numbers::pi - az; else az = (al::numbers::pi*2.0)-az + al::numbers::pi; CalcAzIndices(field, topElev, az, &a0, &a1, &af); field.mEvs[ei].mAzs[ai].mDelays[0] = Lerp( field.mEvs[topElev].mAzs[a0].mDelays[0], field.mEvs[topElev].mAzs[a1].mDelays[0], af); } } } /* Record the lowest elevation filled in with the mirrored top. */ const uint lowerElevFake{ei-1u}; /* Fill in the remaining delays using bilinear interpolation. This * helps smooth the transition back to the real delays. */ for(;ei < upperElevReal;++ei) { const double ef{(field.mEvs[upperElevReal].mElevation - field.mEvs[ei].mElevation) / (field.mEvs[upperElevReal].mElevation - field.mEvs[lowerElevFake].mElevation)}; for(uint ai{0u};ai < field.mEvs[ei].mAzs.size();ai++) { uint a0, a1, a2, a3; double af0, af1; double az{field.mEvs[ei].mAzs[ai].mAzimuth}; CalcAzIndices(field, upperElevReal, az, &a0, &a1, &af0); CalcAzIndices(field, lowerElevFake, az, &a2, &a3, &af1); std::array blend{{ (1.0-ef) * (1.0-af0), (1.0-ef) * ( af0), ( ef) * (1.0-af1), ( ef) * ( af1) }}; for(uint ti{0u};ti < channels;ti++) { field.mEvs[ei].mAzs[ai].mDelays[ti] = field.mEvs[upperElevReal].mAzs[a0].mDelays[ti]*blend[0] + field.mEvs[upperElevReal].mAzs[a1].mDelays[ti]*blend[1] + field.mEvs[lowerElevFake].mAzs[a2].mDelays[ti]*blend[2] + field.mEvs[lowerElevFake].mAzs[a3].mDelays[ti]*blend[3]; } } } }; std::for_each(hData->mFds.begin(), hData->mFds.end(), proc_field); } /* Attempt to synthesize any missing HRIRs at the bottom elevations of each * field. Right now this just blends the lowest elevation HRIRs together and * applies a low-pass filter to simulate body occlusion. It is a simple, if * inaccurate model. */ void SynthesizeHrirs(HrirDataT *hData) { const uint channels{(hData->mChannelType == CT_STEREO) ? 2u : 1u}; auto htemp = std::vector(hData->mFftSize); const uint m{hData->mFftSize/2u + 1u}; auto filter = std::vector(m); const double beta{3.5e-6 * hData->mIrRate}; auto proc_field = [channels,m,beta,&htemp,&filter](HrirFdT &field) -> void { const uint oi{field.mEvStart}; if(oi <= 0) return; for(uint ti{0u};ti < channels;ti++) { uint a0, a1; double af; /* Use the lowest immediate-left response for the left ear and * lowest immediate-right response for the right ear. Given no comb * effects as a result of the left response reaching the right ear * and vice-versa, this produces a decent phantom-center response * underneath the head. */ CalcAzIndices(field, oi, al::numbers::pi / ((ti==0) ? -2.0 : 2.0), &a0, &a1, &af); for(uint i{0u};i < m;i++) { field.mEvs[0].mAzs[0].mIrs[ti][i] = Lerp(field.mEvs[oi].mAzs[a0].mIrs[ti][i], field.mEvs[oi].mAzs[a1].mIrs[ti][i], af); } } for(uint ei{1u};ei < field.mEvStart;ei++) { const double of{static_cast(ei) / field.mEvStart}; const double b{(1.0 - of) * beta}; std::array lp{}; /* Calculate a low-pass filter to simulate body occlusion. */ lp[0] = Lerp(1.0, lp[0], b); lp[1] = Lerp(lp[0], lp[1], b); lp[2] = Lerp(lp[1], lp[2], b); lp[3] = Lerp(lp[2], lp[3], b); htemp[0] = lp[3]; for(size_t i{1u};i < htemp.size();i++) { lp[0] = Lerp(0.0, lp[0], b); lp[1] = Lerp(lp[0], lp[1], b); lp[2] = Lerp(lp[1], lp[2], b); lp[3] = Lerp(lp[2], lp[3], b); htemp[i] = lp[3]; } /* Get the filter's frequency-domain response and extract the * frequency magnitudes (phase will be reconstructed later)). */ FftForward(static_cast(htemp.size()), htemp.data()); std::transform(htemp.cbegin(), htemp.cbegin()+m, filter.begin(), [](const complex_d c) -> double { return std::abs(c); }); for(uint ai{0u};ai < field.mEvs[ei].mAzs.size();ai++) { uint a0, a1; double af; CalcAzIndices(field, oi, field.mEvs[ei].mAzs[ai].mAzimuth, &a0, &a1, &af); for(uint ti{0u};ti < channels;ti++) { for(uint i{0u};i < m;i++) { /* Blend the two defined HRIRs closest to this azimuth, * then blend that with the synthesized -90 elevation. */ const double s1{Lerp(field.mEvs[oi].mAzs[a0].mIrs[ti][i], field.mEvs[oi].mAzs[a1].mIrs[ti][i], af)}; const double s{Lerp(field.mEvs[0].mAzs[0].mIrs[ti][i], s1, of)}; field.mEvs[ei].mAzs[ai].mIrs[ti][i] = s * filter[i]; } } } } const double b{beta}; std::array lp{}; lp[0] = Lerp(1.0, lp[0], b); lp[1] = Lerp(lp[0], lp[1], b); lp[2] = Lerp(lp[1], lp[2], b); lp[3] = Lerp(lp[2], lp[3], b); htemp[0] = lp[3]; for(size_t i{1u};i < htemp.size();i++) { lp[0] = Lerp(0.0, lp[0], b); lp[1] = Lerp(lp[0], lp[1], b); lp[2] = Lerp(lp[1], lp[2], b); lp[3] = Lerp(lp[2], lp[3], b); htemp[i] = lp[3]; } FftForward(static_cast(htemp.size()), htemp.data()); std::transform(htemp.cbegin(), htemp.cbegin()+m, filter.begin(), [](const complex_d c) -> double { return std::abs(c); }); for(uint ti{0u};ti < channels;ti++) { for(uint i{0u};i < m;i++) field.mEvs[0].mAzs[0].mIrs[ti][i] *= filter[i]; } }; std::for_each(hData->mFds.begin(), hData->mFds.end(), proc_field); } // The following routines assume a full set of HRIRs for all elevations. /* Perform minimum-phase reconstruction using the magnitude responses of the * HRIR set. Work is delegated to this struct, which runs asynchronously on one * or more threads (sharing the same reconstructor object). */ struct HrirReconstructor { std::vector> mIrs; std::atomic mCurrent{}; std::atomic mDone{}; uint mFftSize{}; uint mIrPoints{}; void Worker() { auto h = std::vector(mFftSize); auto mags = std::vector(mFftSize); size_t m{(mFftSize/2) + 1}; while(true) { /* Load the current index to process. */ size_t idx{mCurrent.load()}; do { /* If the index is at the end, we're done. */ if(idx >= mIrs.size()) return; /* Otherwise, increment the current index atomically so other * threads know to go to the next one. If this call fails, the * current index was just changed by another thread and the new * value is loaded into idx, which we'll recheck. */ } while(!mCurrent.compare_exchange_weak(idx, idx+1, std::memory_order_relaxed)); /* Now do the reconstruction, and apply the inverse FFT to get the * time-domain response. */ for(size_t i{0};i < m;++i) mags[i] = std::max(mIrs[idx][i], Epsilon); MinimumPhase(mags, h); FftInverse(mFftSize, h.data()); for(uint i{0u};i < mIrPoints;++i) mIrs[idx][i] = h[i].real(); /* Increment the number of IRs done. */ mDone.fetch_add(1); } } }; void ReconstructHrirs(const HrirDataT *hData, const uint numThreads) { const uint channels{(hData->mChannelType == CT_STEREO) ? 2u : 1u}; /* Set up the reconstructor with the needed size info and pointers to the * IRs to process. */ HrirReconstructor reconstructor; reconstructor.mCurrent.store(0, std::memory_order_relaxed); reconstructor.mDone.store(0, std::memory_order_relaxed); reconstructor.mFftSize = hData->mFftSize; reconstructor.mIrPoints = hData->mIrPoints; for(const auto &field : hData->mFds) { for(auto &elev : field.mEvs) { for(const auto &azd : elev.mAzs) { for(uint ti{0u};ti < channels;ti++) reconstructor.mIrs.push_back(azd.mIrs[ti]); } } } /* Launch threads to work on reconstruction. */ std::vector thrds; thrds.reserve(numThreads); for(size_t i{0};i < numThreads;++i) thrds.emplace_back(&HrirReconstructor::Worker, &reconstructor); /* Keep track of the number of IRs done, periodically reporting it. */ size_t count; do { std::this_thread::sleep_for(std::chrono::milliseconds{50}); count = reconstructor.mDone.load(); size_t pcdone{count * 100 / reconstructor.mIrs.size()}; fmt::print("\r{:3}% done ({} of {})", pcdone, count, reconstructor.mIrs.size()); fflush(stdout); } while(count < reconstructor.mIrs.size()); fmt::println(""); for(auto &thrd : thrds) { if(thrd.joinable()) thrd.join(); } } // Normalize the HRIR set and slightly attenuate the result. void NormalizeHrirs(HrirDataT *hData) { const uint channels{(hData->mChannelType == CT_STEREO) ? 2u : 1u}; const uint irSize{hData->mIrPoints}; /* Find the maximum amplitude and RMS out of all the IRs. */ struct LevelPair { double amp, rms; }; auto mesasure_channel = [irSize](const LevelPair levels, al::span ir) { /* Calculate the peak amplitude and RMS of this IR. */ ir = ir.first(irSize); auto current = std::accumulate(ir.cbegin(), ir.cend(), LevelPair{0.0, 0.0}, [](const LevelPair cur, const double impulse) { return LevelPair{std::max(std::abs(impulse), cur.amp), cur.rms + impulse*impulse}; }); current.rms = std::sqrt(current.rms / irSize); /* Accumulate levels by taking the maximum amplitude and RMS. */ return LevelPair{std::max(current.amp, levels.amp), std::max(current.rms, levels.rms)}; }; auto measure_azi = [channels,mesasure_channel](const LevelPair levels, const HrirAzT &azi) { return std::accumulate(azi.mIrs.begin(), azi.mIrs.begin()+channels, levels, mesasure_channel); }; auto measure_elev = [measure_azi](const LevelPair levels, const HrirEvT &elev) { return std::accumulate(elev.mAzs.cbegin(), elev.mAzs.cend(), levels, measure_azi); }; auto measure_field = [measure_elev](const LevelPair levels, const HrirFdT &field) { return std::accumulate(field.mEvs.cbegin(), field.mEvs.cend(), levels, measure_elev); }; const auto maxlev = std::accumulate(hData->mFds.begin(), hData->mFds.end(), LevelPair{0.0, 0.0}, measure_field); /* Normalize using the maximum RMS of the HRIRs. The RMS measure for the * non-filtered signal is of an impulse with equal length (to the filter): * * rms_impulse = sqrt(sum([ 1^2, 0^2, 0^2, ... ]) / n) * = sqrt(1 / n) * * This helps keep a more consistent volume between the non-filtered signal * and various data sets. */ double factor{std::sqrt(1.0 / irSize) / maxlev.rms}; /* Also ensure the samples themselves won't clip. */ factor = std::min(factor, 0.99/maxlev.amp); /* Now scale all IRs by the given factor. */ auto proc_channel = [irSize,factor](al::span ir) { ir = ir.first(irSize); std::transform(ir.cbegin(), ir.cend(), ir.begin(), [factor](double s) { return s * factor; }); }; auto proc_azi = [channels,proc_channel](HrirAzT &azi) { std::for_each(azi.mIrs.begin(), azi.mIrs.begin()+channels, proc_channel); }; auto proc_elev = [proc_azi](HrirEvT &elev) { std::for_each(elev.mAzs.begin(), elev.mAzs.end(), proc_azi); }; auto proc1_field = [proc_elev](HrirFdT &field) { std::for_each(field.mEvs.begin(), field.mEvs.end(), proc_elev); }; std::for_each(hData->mFds.begin(), hData->mFds.end(), proc1_field); } // Calculate the left-ear time delay using a spherical head model. double CalcLTD(const double ev, const double az, const double rad, const double dist) { double azp, dlp, l, al; azp = std::asin(std::cos(ev) * std::sin(az)); dlp = std::sqrt((dist*dist) + (rad*rad) + (2.0*dist*rad*sin(azp))); l = std::sqrt((dist*dist) - (rad*rad)); al = (0.5 * al::numbers::pi) + azp; if(dlp > l) dlp = l + (rad * (al - std::acos(rad / dist))); return dlp / 343.3; } // Calculate the effective head-related time delays for each minimum-phase // HRIR. This is done per-field since distance delay is ignored. void CalculateHrtds(const HeadModelT model, const double radius, HrirDataT *hData) { uint channels = (hData->mChannelType == CT_STEREO) ? 2 : 1; double customRatio{radius / hData->mRadius}; uint ti; if(model == HM_Sphere) { for(auto &field : hData->mFds) { for(auto &elev : field.mEvs) { for(auto &azd : elev.mAzs) { for(ti = 0;ti < channels;ti++) azd.mDelays[ti] = CalcLTD(elev.mElevation, azd.mAzimuth, radius, field.mDistance); } } } } else if(customRatio != 1.0) { for(auto &field : hData->mFds) { for(auto &elev : field.mEvs) { for(auto &azd : elev.mAzs) { for(ti = 0;ti < channels;ti++) azd.mDelays[ti] *= customRatio; } } } } double maxHrtd{0.0}; for(auto &field : hData->mFds) { double minHrtd{std::numeric_limits::infinity()}; for(auto &elev : field.mEvs) { for(auto &azd : elev.mAzs) { for(ti = 0;ti < channels;ti++) minHrtd = std::min(azd.mDelays[ti], minHrtd); } } for(auto &elev : field.mEvs) { for(auto &azd : elev.mAzs) { for(ti = 0;ti < channels;ti++) { azd.mDelays[ti] = (azd.mDelays[ti]-minHrtd) * hData->mIrRate; maxHrtd = std::max(maxHrtd, azd.mDelays[ti]); } } } } if(maxHrtd > MaxHrtd) { fmt::println(" Scaling for max delay of {:f} samples to {:f}\n...", maxHrtd, MaxHrtd); const double scale{MaxHrtd / maxHrtd}; for(auto &field : hData->mFds) { for(auto &elev : field.mEvs) { for(auto &azd : elev.mAzs) { for(ti = 0;ti < channels;ti++) azd.mDelays[ti] *= scale; } } } } } } // namespace // Allocate and configure dynamic HRIR structures. bool PrepareHrirData(const al::span distances, const al::span evCounts, const al::span,MAX_FD_COUNT> azCounts, HrirDataT *hData) { uint evTotal{0}, azTotal{0}; for(size_t fi{0};fi < distances.size();++fi) { evTotal += evCounts[fi]; for(size_t ei{0};ei < evCounts[fi];++ei) azTotal += azCounts[fi][ei]; } if(!evTotal || !azTotal) return false; hData->mEvsBase.resize(evTotal); hData->mAzsBase.resize(azTotal); hData->mFds.resize(distances.size()); hData->mIrCount = azTotal; evTotal = 0; azTotal = 0; for(size_t fi{0};fi < distances.size();++fi) { hData->mFds[fi].mDistance = distances[fi]; hData->mFds[fi].mEvStart = 0; hData->mFds[fi].mEvs = al::span{hData->mEvsBase}.subspan(evTotal, evCounts[fi]); evTotal += evCounts[fi]; for(uint ei{0};ei < evCounts[fi];++ei) { uint azCount = azCounts[fi][ei]; hData->mFds[fi].mEvs[ei].mElevation = -al::numbers::pi / 2.0 + al::numbers::pi * ei / (evCounts[fi] - 1); hData->mFds[fi].mEvs[ei].mAzs = al::span{hData->mAzsBase}.subspan(azTotal, azCount); for(uint ai{0};ai < azCount;ai++) { hData->mFds[fi].mEvs[ei].mAzs[ai].mAzimuth = 2.0 * al::numbers::pi * ai / azCount; hData->mFds[fi].mEvs[ei].mAzs[ai].mIndex = azTotal + ai; hData->mFds[fi].mEvs[ei].mAzs[ai].mDelays[0] = 0.0; hData->mFds[fi].mEvs[ei].mAzs[ai].mDelays[1] = 0.0; hData->mFds[fi].mEvs[ei].mAzs[ai].mIrs[0] = {}; hData->mFds[fi].mEvs[ei].mAzs[ai].mIrs[1] = {}; } azTotal += azCount; } } return true; } namespace { /* Parse the data set definition and process the source data, storing the * resulting data set as desired. If the input name is NULL it will read * from standard input. */ bool ProcessDefinition(std::string_view inName, const uint outRate, const ChannelModeT chanMode, const bool farfield, const uint numThreads, const uint fftSize, const bool equalize, const bool surface, const double limit, const uint truncSize, const HeadModelT model, const double radius, const std::string_view outName) { HrirDataT hData; fmt::println("Using {} thread{}.", numThreads, (numThreads==1)?"":"s"); if(inName.empty() || inName == "-"sv) { inName = "stdin"sv; fmt::println("Reading HRIR definition from {}...", inName); if(!LoadDefInput(std::cin, {}, inName, fftSize, truncSize, outRate, chanMode, &hData)) return false; } else { auto input = std::make_unique(fs::u8path(inName)); if(!input->is_open()) { fmt::println(stderr, "Error: Could not open input file '{}'", inName); return false; } std::array startbytes{}; input->read(startbytes.data(), startbytes.size()); if(input->gcount() != startbytes.size() || !input->good()) { fmt::println(stderr, "Error: Could not read input file '{}'", inName); return false; } if(startbytes[0] == '\x89' && startbytes[1] == 'H' && startbytes[2] == 'D' && startbytes[3] == 'F') { input = nullptr; fmt::println("Reading HRTF data from {}...", inName); if(!LoadSofaFile(inName, numThreads, fftSize, truncSize, outRate, chanMode, &hData)) return false; } else { fmt::println("Reading HRIR definition from {}...", inName); if(!LoadDefInput(*input, startbytes, inName, fftSize, truncSize, outRate, chanMode, &hData)) return false; } } if(equalize) { uint c{(hData.mChannelType == CT_STEREO) ? 2u : 1u}; uint m{hData.mFftSize/2u + 1u}; auto dfa = std::vector(size_t{c} * m); if(hData.mFds.size() > 1) { fmt::println("Balancing field magnitudes..."); BalanceFieldMagnitudes(&hData, c, m); } fmt::println("Calculating diffuse-field average..."); CalculateDiffuseFieldAverage(&hData, c, m, surface, limit, dfa); fmt::println("Performing diffuse-field equalization..."); DiffuseFieldEqualize(c, m, dfa, &hData); } if(hData.mFds.size() > 1) { fmt::println("Sorting {} fields...", hData.mFds.size()); std::sort(hData.mFds.begin(), hData.mFds.end(), [](const HrirFdT &lhs, const HrirFdT &rhs) noexcept { return lhs.mDistance < rhs.mDistance; }); if(farfield) { fmt::println("Clearing {} near field{}...", hData.mFds.size()-1, (hData.mFds.size()-1 != 1) ? "s" : ""); hData.mFds.erase(hData.mFds.cbegin(), hData.mFds.cend()-1); } } fmt::println("Synthesizing missing elevations..."); if(model == HM_Dataset) SynthesizeOnsets(&hData); SynthesizeHrirs(&hData); fmt::println("Performing minimum phase reconstruction..."); ReconstructHrirs(&hData, numThreads); fmt::println("Truncating minimum-phase HRIRs..."); hData.mIrPoints = truncSize; fmt::println("Normalizing final HRIRs..."); NormalizeHrirs(&hData); fmt::println("Calculating impulse delays..."); CalculateHrtds(model, (radius > DefaultCustomRadius) ? radius : hData.mRadius, &hData); const auto rateStr = std::to_string(hData.mIrRate); const auto expName = StrSubst(outName, "%r"sv, rateStr); fmt::println("Creating MHR data set {}...", expName); return StoreMhr(&hData, expName); } void PrintHelp(const std::string_view argv0, FILE *ofile) { fmt::println(ofile, "Usage: {} [