pax_global_header00006660000000000000000000000064151222062710014510gustar00rootroot0000000000000052 comment=75c00596307bf05ba7bbc8c7022836bf52f17477 kcat-openal-soft-75c0059/000077500000000000000000000000001512220627100151355ustar00rootroot00000000000000kcat-openal-soft-75c0059/.clang-tidy000066400000000000000000000235031512220627100171740ustar00rootroot00000000000000--- Checks: '-*, bugprone-argument-comment, bugprone-assert-side-effect, bugprone-assignment-in-if-condition, bugprone-bad-signal-to-kill-thread, bugprone-bitwise-pointer-cast, 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-unchecked-optional-access, bugprone-undefined-memory-manipulation, bugprone-undelegated-constructor, bugprone-unhandled-*, bugprone-unique-ptr-array-mismatch, bugprone-unsafe-functions, bugprone-unused-*, bugprone-unintended-char-ostream-output, bugprone-use-after-move, bugprone-virtual-near-miss, cert-dcl50-cpp, cert-dcl58-cpp, cert-env33-c, cert-err34-c, cert-err52-cpp, cert-err58-cpp, cert-err60-cpp, cert-flp30-c, cert-mem57-cpp, cert-oop57-cpp, cert-oop58-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-reinterpret-cast, cppcoreguidelines-pro-type-static-cast-downcast, cppcoreguidelines-pro-type-union-access, cppcoreguidelines-pro-type-vararg, cppcoreguidelines-rvalue-reference-param-not-moved, cppcoreguidelines-slicing, cppcoreguidelines-virtual-class-destructor, google-build-explicit-make-pair, google-default-arguments, google-explicit-constructor, hicpp-exception-baseclass, misc-confusable-identifiers, misc-const-correctness, misc-coroutine-hostile-raii, misc-misleading-*, misc-non-copyable-objects, misc-static-assert, misc-throw-by-value-catch-by-reference, misc-uniqueptr-reset-release, misc-use-anonymous-namespace, 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-isolate-declaration, 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-*' kcat-openal-soft-75c0059/.github/000077500000000000000000000000001512220627100164755ustar00rootroot00000000000000kcat-openal-soft-75c0059/.github/workflows/000077500000000000000000000000001512220627100205325ustar00rootroot00000000000000kcat-openal-soft-75c0059/.github/workflows/ci.yml000066400000000000000000000256261512220627100216630ustar00rootroot00000000000000name: 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 \ -DFORCE_STATIC_VCRT=ON \ -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreadedDebug", 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 \ -DFORCE_STATIC_VCRT=ON \ -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreadedDebug", 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-15, cmake_opts: "-DALSOFT_REQUIRE_COREAUDIO=ON \ -DALSOFT_TESTS=ON", build_type: "Release" } - { name: "macOS-10.9-Release", os: macos-15, cmake_opts: "-DCMAKE_OSX_DEPLOYMENT_TARGET=10.9 \ -DALSOFT_REQUIRE_COREAUDIO=ON \ -DALSOFT_TESTS=ON", build_type: "Release" } - { name: "iOS-Release", os: macos-15, 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 \ qt6-base-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 \ -DALSOFT_REQUIRE_OBOE=ON \ -DOBOE_SOURCE=oboe", build_type: "Release" } - { name: "Android_arm64-v8a-Release", os: ubuntu-latest, cmake_opts: "-DANDROID_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 \ -DALSOFT_REQUIRE_OBOE=ON \ -DOBOE_SOURCE=oboe", build_type: "Release" } steps: - uses: actions/checkout@v4 with: fetch-depth: '0' - name: Setup Oboe for Android if: ${{ contains(matrix.config.name, 'Android') }} run: git clone https://github.com/google/oboe --depth=1 --branch 1.10.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}}/LICENSE-pffft -o "build/release/OpenALSoft/Documentation/License_PFFFT.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" curl https://raw.githubusercontent.com/${{github.repository}}/${{github.ref_name}}/logging/log.cmd -o "build/release/OpenALSoft/Win32/log.cmd" curl https://raw.githubusercontent.com/${{github.repository}}/${{github.ref_name}}/logging/log.cmd -o "build/release/OpenALSoft/Win64/log.cmd" 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/*" kcat-openal-soft-75c0059/.github/workflows/utils.yml000066400000000000000000000061421512220627100224200ustar00rootroot00000000000000name: 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" kcat-openal-soft-75c0059/.gitignore000066400000000000000000000001701512220627100171230ustar00rootroot00000000000000build*/ winbuild win64build .vs/ ## kdevelop *.kdev4 ## qt-creator CMakeLists.txt.user* ## CLion .idea __pycache__/ kcat-openal-soft-75c0059/.travis.yml000066400000000000000000000074721512220627100172600ustar00rootroot00000000000000language: 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 kcat-openal-soft-75c0059/BSD-3Clause000066400000000000000000000031641512220627100167710ustar00rootroot00000000000000Portions 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. kcat-openal-soft-75c0059/CMakeLists.txt000066400000000000000000002464501512220627100177100ustar00rootroot00000000000000# CMake build file list for OpenAL cmake_minimum_required(VERSION 3.13...3.31) 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() # Don't mistake macOS for unix if(UNIX AND NOT ANDROID AND NOT APPLE AND NOT RISCOS AND NOT HAIKU) set(UNIX_ELF ON) else() set(UNIX_ELF OFF) endif() project(OpenAL VERSION 1.25.0 LANGUAGES C CXX) 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++20. CXX_STANDARD 20 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(CheckCXXSymbolExists) include(CheckLinkerFlag) include(CheckStructHasMember) include(CMakePackageConfigHelpers) include(GNUInstallDirs) find_package(PkgConfig) find_package(SDL3 QUIET) option(ALSOFT_DLOPEN "Check for the dlopen API for loading optional libs" ON) option(ALSOFT_DLOPEN_NOTES "Record dlopen dependencies in .note.dlopen section" ${UNIX_ELF}) 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() if(MSVC) option(FORCE_STATIC_VCRT "Force /MT for static VC runtimes" OFF) if(FORCE_STATIC_VCRT) if(CMAKE_VERSION VERSION_GREATER_EQUAL 15.0) set(ALSOFT_STD_VERSION_PROPS ${ALSOFT_STD_VERSION_PROPS} MSVC_RUNTIME_LIBRARY MultiThreaded$<$:Debug>) else() 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() endif() endif() if(WIN32 AND MINGW AND CMAKE_SIZEOF_VOID_P MATCHES "4") # HACK: 32-bit MinGW defines __STDCPP_DEFAULT_NEW_ALIGNMENT__ to 16, while # its default operator new relies on the system/Windows malloc that only # guarantees 8-byte alignment, leading to structs that need 16-byte # alignment being misaligned. This switch overrides that to assume only # 8-byte alignment from the default operator new. add_compile_options("$<$:-faligned-new=8>") endif() # Setting ALSOFT_STL_HARDENING=None disables/minimizes STL hardening, doing # only checks required by the standard (or can't be disabled at all). Should # really only be used where maximum performance is critical and the extra # safety can be sacrificed. # # ALSOFT_STL_HARDENING=Performance enables some STL hardening, checking for more # significant issues that can also be done "fast", that's easier to optimize # away when unneeded and/or not become a bottleneck. This is generally # considered fine for production use, and is the default here. # # ALSOFT_STL_HARDENING=Debug enables even more STL hardening, which has a much # higher runtime cost for less benefit. It's not intended for production use. set(ALSOFT_STL_HARDENING "Performance" CACHE STRING "Specifies the level of C++ STL hardening. None, Performance, Debug") if(ALSOFT_STL_HARDENING STREQUAL "Default") message(NOTICE "STL hardening option \"${ALSOFT_STL_HARDENING}\" deprecated, please set \"Performance\"") set(ALSOFT_STL_HARDENING "Performance" CACHE STRING "Specifies the level of C++ STL hardening. None, Performance, Debug" FORCE) endif() check_cxx_symbol_exists(__GLIBCXX__ "version" HAVE_LIBSTDCXX) check_cxx_symbol_exists(_LIBCPP_VERSION "version" HAVE_LIBCXX) check_cxx_symbol_exists(_MSVC_STL_UPDATE "version" HAVE_MSSTL) set(HARDENING_LEVEL "Default") if(NOT (ALSOFT_STL_HARDENING STREQUAL "")) if(HAVE_LIBSTDCXX) if(ALSOFT_STL_HARDENING STREQUAL "Debug") add_compile_definitions("_GLIBCXX_DEBUG=1") set(HARDENING_LEVEL "Debug") elseif(ALSOFT_STL_HARDENING STREQUAL "None") # TODO: Not sure how to fully disable STL hardening with libstdc++, # aside from not defining anything and using the compiler's # default. _GLIBCXX_NO_ASSERTIONS can explicitly disable the # hardening assertions, but not all STL debugging functionality. add_compile_definitions("_GLIBCXX_NO_ASSERTIONS=1") set(HARDENING_LEVEL "None") else() if(NOT (ALSOFT_STL_HARDENING STREQUAL "Performance")) message(NOTICE "Unsupported STL hardening option \"${ALSOFT_STL_HARDENING}\", using Performance") endif() add_compile_definitions("_GLIBCXX_ASSERTIONS=1") set(HARDENING_LEVEL "Performance") endif() elseif(HAVE_LIBCXX) if(ALSOFT_STL_HARDENING STREQUAL "Debug") add_compile_definitions("_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_DEBUG") set(HARDENING_LEVEL "Debug") elseif(ALSOFT_STL_HARDENING STREQUAL "None") add_compile_definitions("_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_NONE") set(HARDENING_LEVEL "None") else() if(NOT (ALSOFT_STL_HARDENING STREQUAL "Performance")) message(NOTICE "Unsupported STL hardening option \"${ALSOFT_STL_HARDENING}\", using Performance") endif() add_compile_definitions("_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST") set(HARDENING_LEVEL "Performance") endif() elseif(HAVE_MSSTL) # MSVC doesn't seem to have anything more than an on/off switch for STL # hardening, so Debug and Performance are the same here. if(ALSOFT_STL_HARDENING STREQUAL "Debug") add_compile_definitions("_MSVC_STL_HARDENING=1") set(HARDENING_LEVEL "Enabled") elseif(ALSOFT_STL_HARDENING STREQUAL "None") add_compile_definitions("_MSVC_STL_HARDENING=0") set(HARDENING_LEVEL "None") else() if(NOT (ALSOFT_STL_HARDENING STREQUAL "Performance")) message(NOTICE "Unsupported STL hardening option \"${ALSOFT_STL_HARDENING}\", using Performance") endif() add_compile_definitions("_MSVC_STL_HARDENING=1") set(HARDENING_LEVEL "Enabled") endif() endif() endif() # Work out whether C++20 modules are supported. Currently only works with CMake # 3.28+ and the Ninja or VS 17 2022 generator, along with GCC 15+, Clang 17+, # or MSVC 14.34+. Unfortunately CMake doesn't provide any check to tell whether # it supports modules with the given generator and compiler, so we have to do a # check ourselves (which is far less inclusive and more pessimistic than what # it can actually do, especially as CMake continues to update and improve). option(ALSOFT_ENABLE_MODULES "Enable use of C++20 modules when supported" ON) set(HAVE_CXXMODULES FALSE) if(ALSOFT_ENABLE_MODULES) if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.28 AND (CMAKE_GENERATOR STREQUAL "Ninja" OR CMAKE_GENERATOR STREQUAL "Visual Studio 17 2022")) if((CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 17.0) OR (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 15.0) OR (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 14.34)) message(STATUS "C++20 modules enabled using ${CMAKE_CXX_COMPILER_ID} compiler v${CMAKE_CXX_COMPILER_VERSION} (CMake ${CMAKE_VERSION}, ${CMAKE_GENERATOR} generator)") set(HAVE_CXXMODULES TRUE) else() message(NOTICE "C++20 modules not supported with ${CMAKE_CXX_COMPILER_ID} compiler v${CMAKE_CXX_COMPILER_VERSION}") endif() else() message(NOTICE "C++20 modules not supported with ${CMAKE_GENERATOR} on CMake ${CMAKE_VERSION}") endif() endif() add_subdirectory(fmt-11.2.0 EXCLUDE_FROM_ALL) 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 WIN32_LEAN_AND_MEAN) 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() if(NOT ALSOFT_UWP) # Some systems may need NTDDI_VERSION defined to NTDDI_VISTA or later check_c_source_compiles("#define INITGUID #include #include int main() { SHGetKnownFolderPath(&FOLDERID_RoamingAppData, KF_FLAG_DONT_UNEXPAND, NULL, NULL); return 0; }" HAVE_SHGETKNOWNFOLDERPATH_NO_NTDDI) if(NOT HAVE_SHGETKNOWNFOLDERPATH_NO_NTDDI) set(CPP_DEFS ${CPP_DEFS} NTDDI_VERSION=NTDDI_VISTA) endif() 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(EXPORT_DECL "") # 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() # C4127 - conditional expression is constant # C4324 - structure was padded due to alignment specifier # C4373 - virtual function overrides 'base_function', previous versions of # the compiler did not override when parameters only differed by # const/volatile qualifiers set(C_FLAGS ${C_FLAGS} /W4 /wd4127 /wd4324 /wd4373 /utf-8 $<$:/EHsc>) 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() else() # FIXME: Should include -Winline, but GCC tries inlining too much with # fmtlib due to all the constexpr functions and template classes, spamming # a bunch of warnings that we can't do anything about. Until something like # feeble_inline gets implemented and used, or inline is ignored by default, # we'll have to ignore inline warning. # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=93008 set(C_FLAGS ${C_FLAGS} -Wunused -Wall -Wextra -Wshadow -Wconversion -Wcast-align -Wpedantic -Wformat -Wformat=2 $<$:-Wold-style-cast -Wnon-virtual-dtor -Woverloaded-virtual>) check_c_compiler_flag(-Wtrampolines HAVE_WTRAMPOLINES) if(HAVE_WTRAMPOLINES) set(C_FLAGS ${C_FLAGS} -Wtrampolines) endif() check_c_compiler_flag(-Wconversion HAVE_WCONVERSION) if(HAVE_WCONVERSION) set(C_FLAGS ${C_FLAGS} -Wconversion) endif() check_c_compiler_flag(-Warith-conversion HAVE_WARITH_CONVERSION) if(HAVE_WARITH_CONVERSION) set(C_FLAGS ${C_FLAGS} -Warith-conversion) endif() check_c_compiler_flag(-Wbidi-chars=any HAVE_WBIDI_CHARS_ANY) if(HAVE_WBIDI_CHARS_ANY) set(C_FLAGS ${C_FLAGS} -Wbidi-chars=any) 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 -Werror=format-security $<$:-Werror=implicit -Werror=incompatible-pointer-types -Werror=int-conversion>) 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() check_linker_flag(C "-Wl,-z,noexecstack" HAVE_WL_Z_NOEXECSTACK) if(HAVE_WL_Z_NOEXECSTACK) set(LINKER_FLAGS ${LINKER_FLAGS} "-Wl,-z,noexecstack") endif() check_linker_flag(C "-Wl,-z,relro" HAVE_WL_Z_RELRO) if(HAVE_WL_Z_RELRO) set(LINKER_FLAGS ${LINKER_FLAGS} "-Wl,-z,relro") endif() check_linker_flag(C "-Wl,-z,now" HAVE_WL_Z_NOW) if(HAVE_WL_Z_NOW) set(LINKER_FLAGS ${LINKER_FLAGS} "-Wl,-z,now") endif() check_linker_flag(C "-Wl,--as-needed,--no-copy-dt-needed-entries" HAVE_WL_AS_NEEDED) if(HAVE_WL_AS_NEEDED) set(LINKER_FLAGS ${LINKER_FLAGS} "-Wl,--as-needed,--no-copy-dt-needed-entries") 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/albit.h common/alcomplex.cpp common/alcomplex.h common/alformat.hpp common/almalloc.cpp common/almalloc.h common/alnumeric.h common/alstring.cpp common/alstring.h common/althrd_setname.cpp common/althrd_setname.h common/althreads.h common/altypes.hpp common/atomic.h common/comptr.h common/dynload.cpp common/dynload.h common/dlopennote.h common/expected.hpp 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.h common/strutils.cpp common/strutils.hpp common/vecmat.h common/vector.h gsl/include/gsl/algorithm gsl/include/gsl/assert gsl/include/gsl/byte gsl/include/gsl/gsl gsl/include/gsl/narrow gsl/include/gsl/pointers gsl/include/gsl/span gsl/include/gsl/span_ext gsl/include/gsl/util gsl/include/gsl/zstring ) # 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.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/hrtf_loader.cpp core/hrtf_loader.hpp core/hrtf_resource.cpp core/hrtf_resource.hpp 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/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.cpp 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_WASAPI 0) set(HAVE_DSOUND 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_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) # 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() if (NOT ALSOFT_UWP) # 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() # 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() endif() endif() if(ALSOFT_REQUIRE_WASAPI AND NOT HAVE_WASAPI) message(FATAL_ERROR "Failed to enable required WASAPI backend") endif() if(ALSOFT_REQUIRE_DSOUND AND NOT HAVE_DSOUND) message(FATAL_ERROR "Failed to enable required DSound backend") endif() if(ALSOFT_REQUIRE_WINMM AND NOT HAVE_WINMM) message(FATAL_ERROR "Failed to enable required WinMM 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") # Get the known version info to create version.h set(LIB_VERSION_NUM ${OpenAL_VERSION_MAJOR},${OpenAL_VERSION_MINOR},${OpenAL_VERSION_PATCH},0) 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=${OpenAL_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 "") set(GIT_COMMIT_HASH "") 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() # Set a 16KB page size for Android if(ANDROID) set(CPP_DEFS ${CPP_DEFS} __BIONIC_NO_PAGE_SIZE_MACRO) check_linker_flag(C "-Wl,-z,max-page-size=16384" HAS_MAX_PAGE_SIZE_16384) if(HAS_MAX_PAGE_SIZE_16384) set(LINKER_FLAGS ${LINKER_FLAGS} "-Wl,-z,max-page-size=16384") endif() endif() if(ALSOFT_UTILS) find_package(MySOFA) if(NOT ALSOFT_NO_CONFIG_UTIL) find_package(Qt6Widgets QUIET) if(NOT Qt6Widgets_FOUND) message(STATUS "Could NOT find Qt6Widgets") endif() endif() endif() if(ALSOFT_UTILS OR ALSOFT_EXAMPLES) find_package(SndFile) endif() if(ALSOFT_EXAMPLES AND SDL3_FOUND) find_package(FFmpeg COMPONENTS AVFORMAT AVCODEC AVUTIL SWSCALE SWRESAMPLE) 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 "${OpenAL_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() if(UNIX_ELF) if(ALSOFT_DLOPEN_NOTES) set(CHECK_ELF_DLNOTES_SRC [==[ #ifndef __ELF__ ELF DL notes is only supported on ELF platforms #endif __attribute__ ((used,aligned(4),section(".note.dlopen"))) static const struct { struct { int a; int b; int c; } hdr; char name[4]; __attribute__((aligned(4))) char json[24]; } dlnote = { { 4, 0x407c0c0aU, 16 }, "FDO", "[\\"a\\":{\\"a\\":\\"1\\",\\"b\\":\\"2\\"}]" }; int main(int argc, char *argv[]) { return argc + dlnote.hdr.a; } ]==]) check_c_source_compiles("${CHECK_ELF_DLNOTES_SRC}" COMPILER_SUPPORTS_ELFNOTES) if(COMPILER_SUPPORTS_ELFNOTES) set(HAVE_DLOPEN_NOTES TRUE) endif() endif() 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) set(MODULES_OBJS ) if(HAVE_CXXMODULES) set(MODULES_OBJS modules/alc.cppm modules/al.cppm modules/alstd.cppm modules/efx.cppm modules/alext.cppm modules/openal.cppm) # For importing while building the examples and utilities (functions are # set to be imported from the shared library, or as free functions with # static library builds). add_library(openal.modules OBJECT EXCLUDE_FROM_ALL) target_sources(openal.modules PUBLIC FILE_SET CXX_MODULES FILES ${MODULES_OBJS}) target_include_directories(openal.modules PRIVATE ${OpenAL_SOURCE_DIR}/include) target_compile_definitions(openal.modules PRIVATE ${CPP_DEFS}) target_compile_options(openal.modules PRIVATE ${C_FLAGS}) set_target_properties(openal.modules PROPERTIES ${ALSOFT_STD_VERSION_PROPS} POSITION_INDEPENDENT_CODE TRUE) set(MODULES_TARGET openal.modules) # For importing while building the library (functions are set to be # exported from the shared library). add_library(alsoft.modules OBJECT EXCLUDE_FROM_ALL) target_sources(alsoft.modules PUBLIC FILE_SET CXX_MODULES FILES ${MODULES_OBJS}) target_compile_definitions(alsoft.modules PRIVATE AL_ALEXT_PROTOTYPES "ALC_API=${EXPORT_DECL}" "AL_API=${EXPORT_DECL}" ${CPP_DEFS}) target_compile_options(alsoft.modules PRIVATE ${C_FLAGS}) set_target_properties(alsoft.modules PROPERTIES ${ALSOFT_STD_VERSION_PROPS} POSITION_INDEPENDENT_CODE TRUE) else() set(MODULES_TARGET ) endif() 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 ${OpenAL_SOURCE_DIR}/gsl/include) 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) if(HAVE_CXXMODULES) set(MODULES_OBJS ${MODULES_OBJS} alsoft-modules/gsl.cppm) target_sources(alsoft.common PUBLIC FILE_SET CXX_MODULES FILES alsoft-modules/gsl.cppm) endif() if(CMAKE_SYSTEM_NAME STREQUAL "Darwin" AND DEFINED CMAKE_OSX_DEPLOYMENT_TARGET AND CMAKE_OSX_DEPLOYMENT_TARGET VERSION_LESS "10.13") # Targeting versions prior to macOS 10.13 requires specifying custom # alignment-aware operators, which we do. This lets the compiler know they # exist and it can use them. target_compile_options(alsoft.common PUBLIC "-faligned-allocation") endif() 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} ${MODULES_OBJS}) # CMake 3.20+ can use cmake_path(COMPARE ...), or CMake 3.24+ can use # PATH_EQUAL. if(NOT obj STREQUAL "${CMAKE_BINARY_DIR}/default_hrtf.txt") list(APPEND NEED_ANALYZE_SOURCE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/${obj}") endif() endforeach() # Build main library if(LIBTYPE STREQUAL "STATIC") if(TARGET openal.modules) target_compile_definitions(openal.modules PUBLIC AL_LIBTYPE_STATIC) endif() add_library(${IMPL_TARGET} STATIC ${COMMON_OBJS} ${OPENAL_OBJS} ${ALC_OBJS} ${CORE_OBJS}) target_compile_definitions(${IMPL_TARGET} PUBLIC AL_LIBTYPE_STATIC) target_include_directories(${IMPL_TARGET} PRIVATE ${OpenAL_SOURCE_DIR}/gsl/include) target_link_libraries(${IMPL_TARGET} PRIVATE ${LINKER_FLAGS} ${EXTRA_LIBS} ${MATH_LIB} $) if(CMAKE_SYSTEM_NAME STREQUAL "Darwin" AND DEFINED CMAKE_OSX_DEPLOYMENT_TARGET AND CMAKE_OSX_DEPLOYMENT_TARGET VERSION_LESS "10.13") # The static library doesn't link with alsoft.common, so needs this # compile flag set for itself. target_compile_options(${IMPL_TARGET} PRIVATE "-faligned-allocation") endif() 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) if(HAVE_CXXMODULES) add_library(alsoft.router-modules OBJECT EXCLUDE_FROM_ALL) target_sources(alsoft.router-modules PUBLIC FILE_SET CXX_MODULES FILES alsoft-modules/router.cppm) target_compile_definitions(alsoft.router-modules PRIVATE AL_ALEXT_PROTOTYPES "ALC_API=${EXPORT_DECL}" "AL_API=${EXPORT_DECL}" ${CPP_DEFS}) target_compile_options(alsoft.router-modules PRIVATE ${C_FLAGS}) target_link_libraries(alsoft.router-modules PRIVATE alsoft::fmt alsoft.modules) set_target_properties(alsoft.router-modules PROPERTIES ${ALSOFT_STD_VERSION_PROPS} POSITION_INDEPENDENT_CODE TRUE) list(APPEND NEED_ANALYZE_SOURCE_FILES "${CMAKE_SOURCE_DIR}/alsoft-modules/router.cppm") endif() 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 $<$:alsoft.modules alsoft.router-modules>) 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() execute_process(COMMAND ${NUGET_EXE} 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 "${OpenAL_VERSION}" MACOSX_FRAMEWORK_BUNDLE_VERSION "${BUNDLE_VERSION}" XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "" XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "NO" XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "NO" XCODE_ATTRIBUTE_SKIP_INSTALL "YES" XCODE_ATTRIBUTE_DEBUG_INFORMATION_FORMAT "dwarf-with-dsym" XCODE_ATTRIBUTE_GCC_GENERATE_DEBUGGING_SYMBOLS "YES" XCODE_ATTRIBUTE_ENABLE_STDEBUG_INFORMATION_FORMAT "dwarf-with-dsym" XCODE_ATTRIBUTE_CONFIGURATION_BUILD_DIR "$(inherited)" 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 ${OpenAL_VERSION} SOVERSION ${OpenAL_VERSION_MAJOR} ) 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() message(STATUS "") message(STATUS "STL hardening level: ${HARDENING_LEVEL}") 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 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) 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_BINARY_DIR} ${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}) list(APPEND NEED_ANALYZE_SOURCE_FILES "${CMAKE_SOURCE_DIR}/utils/openal-info.c") if(ALSOFT_INSTALL_UTILS) 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}) list(APPEND NEED_ANALYZE_SOURCE_FILES "${CMAKE_SOURCE_DIR}/utils/uhjdecoder.cpp") 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}) list(APPEND NEED_ANALYZE_SOURCE_FILES "${CMAKE_SOURCE_DIR}/utils/uhjencoder.cpp") 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}) list(APPEND NEED_ANALYZE_SOURCE_FILES "${CMAKE_SOURCE_DIR}/utils/sofa-support.cpp" "${CMAKE_SOURCE_DIR}/utils/sofa-support.h") 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}) list(APPEND NEED_ANALYZE_SOURCE_FILES "${CMAKE_SOURCE_DIR}/utils/makemhr/loaddef.cpp" "${CMAKE_SOURCE_DIR}/utils/makemhr/loaddef.h" "${CMAKE_SOURCE_DIR}/utils/makemhr/loadsofa.cpp" "${CMAKE_SOURCE_DIR}/utils/makemhr/loadsofa.h" "${CMAKE_SOURCE_DIR}/utils/makemhr/makemhr.cpp" "${CMAKE_SOURCE_DIR}/utils/makemhr/makemhr.h") if(ALSOFT_INSTALL_UTILS) 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_BINARY_DIR} ${OpenAL_SOURCE_DIR}/utils) target_compile_options(sofa-info PRIVATE ${C_FLAGS}) target_link_libraries(sofa-info PRIVATE alsoft.common ${LINKER_FLAGS} alsoft.sofa-support ${UNICODE_FLAG} alsoft::fmt) set_target_properties(sofa-info PROPERTIES ${ALSOFT_STD_VERSION_PROPS}) list(APPEND NEED_ANALYZE_SOURCE_FILES "${CMAKE_SOURCE_DIR}/utils/sofa-info.cpp") endif() message(STATUS "Building utility programs") if(NOT ALSOFT_NO_CONFIG_UTIL) add_subdirectory(utils/alsoft-config) if(TARGET alsoft-config) target_compile_definitions(alsoft-config PRIVATE ${CPP_DEFS}) target_compile_options(alsoft-config PRIVATE ${C_FLAGS}) # Don't include LINKER_FLAGS, which can unnecessarily static link # libgcc, libwinpthreads, and libstdc++, since Qt will dynamic link # them regardless. set_target_properties(alsoft-config PROPERTIES ${ALSOFT_STD_VERSION_PROPS} RUNTIME_OUTPUT_DIRECTORY ${OpenAL_BINARY_DIR}) message(STATUS "Building configuration program") if(ALSOFT_INSTALL_UTILS) set(EXTRA_INSTALLS ${EXTRA_INSTALLS} alsoft-config) endif() endif() 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 examples/common/alhelpers.hpp) 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) list(APPEND NEED_ANALYZE_SOURCE_FILES "${CMAKE_SOURCE_DIR}/examples/common/alhelpers.c" "${CMAKE_SOURCE_DIR}/examples/common/alhelpers.h") 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}) list(APPEND NEED_ANALYZE_SOURCE_FILES "${CMAKE_SOURCE_DIR}/examples/altonegen.c") 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}) list(APPEND NEED_ANALYZE_SOURCE_FILES "${CMAKE_SOURCE_DIR}/examples/alrecord.c") add_executable(aldebug examples/aldebug.cpp) target_link_libraries(aldebug PRIVATE ${LINKER_FLAGS} alsoft.common alsoft.excommon ${UNICODE_FLAG} alsoft::fmt ${MODULES_TARGET}) set_target_properties(aldebug PROPERTIES ${ALSOFT_STD_VERSION_PROPS}) list(APPEND NEED_ANALYZE_SOURCE_FILES "${CMAKE_SOURCE_DIR}/examples/aldebug.cpp") add_executable(allafplay examples/allafplay.cpp) target_link_libraries(allafplay PRIVATE ${LINKER_FLAGS} alsoft.common alsoft.excommon ${UNICODE_FLAG} alsoft::fmt ${MODULES_TARGET}) set_target_properties(allafplay PROPERTIES ${ALSOFT_STD_VERSION_PROPS}) list(APPEND NEED_ANALYZE_SOURCE_FILES "${CMAKE_SOURCE_DIR}/examples/allafplay.cpp") 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}) list(APPEND NEED_ANALYZE_SOURCE_FILES "${CMAKE_SOURCE_DIR}/examples/alplay.c") 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}) list(APPEND NEED_ANALYZE_SOURCE_FILES "${CMAKE_SOURCE_DIR}/examples/alstream.c") 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}) list(APPEND NEED_ANALYZE_SOURCE_FILES "${CMAKE_SOURCE_DIR}/examples/alreverb.c") 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}) list(APPEND NEED_ANALYZE_SOURCE_FILES "${CMAKE_SOURCE_DIR}/examples/almultireverb.c") 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}) list(APPEND NEED_ANALYZE_SOURCE_FILES "${CMAKE_SOURCE_DIR}/examples/allatency.c") 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}) list(APPEND NEED_ANALYZE_SOURCE_FILES "${CMAKE_SOURCE_DIR}/examples/alhrtf.c") add_executable(alstreamcb examples/alstreamcb.cpp) target_link_libraries(alstreamcb PRIVATE ${LINKER_FLAGS} SndFile::SndFile alsoft.common alsoft.excommon ${UNICODE_FLAG} alsoft::fmt ${MODULES_TARGET}) set_target_properties(alstreamcb PROPERTIES ${ALSOFT_STD_VERSION_PROPS}) list(APPEND NEED_ANALYZE_SOURCE_FILES "${CMAKE_SOURCE_DIR}/examples/alstreamcb.cpp") add_executable(aldirect examples/aldirect.cpp) target_link_libraries(aldirect PRIVATE ${LINKER_FLAGS} SndFile::SndFile alsoft.common alsoft.excommon ${UNICODE_FLAG} alsoft::fmt ${MODULES_TARGET}) set_target_properties(aldirect PROPERTIES ${ALSOFT_STD_VERSION_PROPS}) list(APPEND NEED_ANALYZE_SOURCE_FILES "${CMAKE_SOURCE_DIR}/examples/aldirect.cpp") 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}) list(APPEND NEED_ANALYZE_SOURCE_FILES "${CMAKE_SOURCE_DIR}/examples/alconvolve.c") 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}) list(APPEND NEED_ANALYZE_SOURCE_FILES "${CMAKE_SOURCE_DIR}/examples/alloopback.c") 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.common alsoft.excommon alsoft::fmt ${MODULES_TARGET}) set_target_properties(alffplay PROPERTIES ${ALSOFT_STD_VERSION_PROPS}) list(APPEND NEED_ANALYZE_SOURCE_FILES "${CMAKE_SOURCE_DIR}/examples/alffplay.cpp") if(ALSOFT_INSTALL_EXAMPLES) set(EXTRA_INSTALLS ${EXTRA_INSTALLS} alffplay) endif() message(STATUS "Building SDL3+FFmpeg example programs") endif() endif() message(STATUS "") 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}) 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() kcat-openal-soft-75c0059/COPYING000066400000000000000000000554421512220627100162020ustar00rootroot00000000000000 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 kcat-openal-soft-75c0059/ChangeLog000066400000000000000000000717211512220627100167170ustar00rootroot00000000000000openal-soft-1.25.0: Updated library codebase to C++20. Fixed alcIsExtensionPresent to do a case-insensitive compare. Fixed potential noise when switching reverbs. Fixed reverb panning with certain output modes. Fixed retrieving the alGetProcAddressDirect extension function. Fixed negative source offsets with a callback buffer. Fixed a memory issue that could occur in rare situations with looping sources. Fixed compiling for and running on older macOS versions. Fixed using unicode in environment variable values on Windows. Fixed memory alignment issues with 32-bit MinGW builds. Fixed a crash in sofa-info with SOFA files that have null strings. Updated alsoft-config to Qt6. Added build options for STL hardening. Performant checks meant for production are enabled by default. Added support for fourth-order ambisonics. Added support for CAF files to the Wave Writer backend. Added optional support for C++20 modules. These are intended to be copied into projects wishing to use them, since modules depend on being built with compatible compile flags as the sources they're imported into. Added a .note.dlopen section to ELF shared library builds to record dynamic dependencies for build maintainers. Converted the headers to XML files, which are used to generate the headers (and modules) with a script. Other scripts can be made to generate bindings for various other languages. Changed AL_PANNING_ENABLED_SOFT to allow being toggled on playing sources. Changed the default period size to 512 sample frames. Changed the default Super Stereo width to 0.46. openal-soft-1.24.3: Fixed using as a static library when linked into another project that uses fmtlib. Fixed building with static VC runtimes. Fixed building with Windows headers that default to older targets. Fixed building on 32-bit targets that use 32-bit file offsets. Fixed handling WASAPI enumerated device changes. Fixed a crash with UWP builds when __wargv is null. Fixed using AL_FORMAT_BFORMAT3D_I32. Improved the bsinc resamplers' cutoff frequencies. Slightly reduced the aliasing noise in the cubic spline resampler. Added new bsinc48 and fast_bsinc48 resampler options. Added support for 16KB page sizes on Android. Added support for using NFC filters with UHJ output. openal-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. kcat-openal-soft-75c0059/LICENSE-pffft000066400000000000000000000032051512220627100172450ustar00rootroot00000000000000A 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. kcat-openal-soft-75c0059/OpenALConfig.cmake.in000066400000000000000000000010011512220627100210000ustar00rootroot00000000000000if((NOT DEFINED CMAKE_VERSION) OR (CMAKE_VERSION VERSION_LESS "3.1")) message(FATAL_ERROR "CMake >= 3.1 required") endif() include("${CMAKE_CURRENT_LIST_DIR}/OpenALTargets.cmake") set(OPENAL_FOUND ON) set(OPENAL_INCLUDE_DIR $) set(OPENAL_LIBRARY $) set(OPENAL_DEFINITIONS $) set(OpenAL_VERSION @PACKAGE_VERSION@) set(OPENAL_VERSION_STRING @PACKAGE_VERSION@) kcat-openal-soft-75c0059/README.md000066400000000000000000000123561512220627100164230ustar00rootroot00000000000000OpenAL 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. kcat-openal-soft-75c0059/XCompile-Android.txt000066400000000000000000000013051512220627100207730ustar00rootroot00000000000000# 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") kcat-openal-soft-75c0059/XCompile.txt000066400000000000000000000027541512220627100174260ustar00rootroot00000000000000# 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) kcat-openal-soft-75c0059/al/000077500000000000000000000000001512220627100155315ustar00rootroot00000000000000kcat-openal-soft-75c0059/al/auxeffectslot.cpp000066400000000000000000001400461512220627100211160ustar00rootroot00000000000000/** * 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 #include #include "AL/al.h" #include "AL/alext.h" #include "AL/efx.h" #include "alc/alu.h" #include "alc/context.h" #include "alc/device.h" #include "alc/effects/base.h" #include "alc/inprogext.h" #include "alformat.hpp" #include "almalloc.h" #include "alnumeric.h" #include "atomic.h" #include "buffer.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 "gsl/gsl" #include "opthelpers.h" #if ALSOFT_EAX #include "eax/api.h" #include "eax/call.h" #include "eax/effect.h" #include "eax/fx_slot_index.h" #endif namespace { using SubListAllocator = al::allocator>; [[nodiscard]] auto getFactoryByType(EffectSlotType const type) -> gsl::not_null { 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(); } throw std::runtime_error{al::format("Unexpected effect slot type: {:#x}", al::to_underlying(type))}; } [[nodiscard]] auto LookupEffectSlot(std::nothrow_t, gsl::not_null const context, u32 const id) noexcept -> al::EffectSlot* { const auto lidx = (id-1) >> 6; const auto slidx = (id-1) & 0x3f; if(lidx >= context->mEffectSlotList.size()) [[unlikely]] return nullptr; auto &sublist = context->mEffectSlotList[lidx]; if(sublist.mFreeMask & (1_u64 << slidx)) [[unlikely]] return nullptr; return std::to_address(std::next(sublist.mEffectSlots->begin(), slidx)); } [[nodiscard]] auto LookupEffectSlot(gsl::not_null const context, u32 const id) -> gsl::not_null { if(auto *const slot = LookupEffectSlot(std::nothrow, context, id)) [[likely]] return gsl::make_not_null(slot); context->throw_error(AL_INVALID_NAME, "Invalid effect slot ID {}", id); } [[nodiscard]] auto LookupEffect(std::nothrow_t, gsl::not_null const device, u32 const id) noexcept -> al::Effect* { const auto lidx = (id-1) >> 6; const auto slidx = (id-1) & 0x3f; if(lidx >= device->EffectList.size()) [[unlikely]] return nullptr; auto &sublist = device->EffectList[lidx]; if(sublist.mFreeMask & (1_u64 << slidx)) [[unlikely]] return nullptr; return std::to_address(std::next(sublist.mEffects->begin(), slidx)); } [[nodiscard]] auto LookupEffect(gsl::not_null const context, u32 const id) -> gsl::not_null { if(auto *const effect = LookupEffect(std::nothrow, al::get_not_null(context->mALDevice), id)) [[likely]] return gsl::make_not_null(effect); context->throw_error(AL_INVALID_NAME, "Invalid effect ID {}", id); } [[nodiscard]] auto LookupBuffer(std::nothrow_t, gsl::not_null const device, u32 const id) noexcept -> al::Buffer* { const auto lidx = (id-1) >> 6; const auto slidx = (id-1) & 0x3f; if(lidx >= device->BufferList.size()) [[unlikely]] return nullptr; auto &sublist = device->BufferList[lidx]; if(sublist.mFreeMask & (1_u64 << slidx)) [[unlikely]] return nullptr; return std::to_address(std::next(sublist.mBuffers->begin(), slidx)); } [[nodiscard]] auto LookupBuffer(gsl::not_null const context, u32 const id) -> gsl::not_null { if(auto *const buffer = LookupBuffer(std::nothrow, al::get_not_null(context->mALDevice), id)) [[likely]] return gsl::make_not_null(buffer); context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", id); } void AddActiveEffectSlots(std::span const> const auxslots, gsl::not_null const context) { if(auxslots.empty()) return; auto *curarray = context->mActiveAuxSlots.load(std::memory_order_acquire); if((curarray->size()>>1) > (std::numeric_limits::max()>>1)-auxslots.size()) throw std::runtime_error{"Too many active effect slots"}; auto newcount = (curarray->size()>>1) + auxslots.size(); /* Insert the new effect slots into the head of the new array, followed by * the existing ones. */ auto newarray = EffectSlotBase::CreatePtrArray(newcount<<1); { const auto new_end = std::ranges::transform(auxslots, newarray->begin(), &al::EffectSlot::mSlot).out; std::ranges::copy(*curarray | std::views::take(curarray->size()>>1), new_end); } /* Sort and remove duplicates. Reallocate newarray if any duplicates were * removed. */ std::ranges::sort(*newarray | std::views::take(newcount)); if(const auto removed = std::ranges::unique(*newarray | std::views::take(newcount)).size()) [[unlikely]] { newcount -= removed; auto const oldarray = std::move(newarray); newarray = EffectSlotBase::CreatePtrArray(newcount<<1); std::ranges::copy(*oldarray | std::views::take(newcount), newarray->begin()); } std::ranges::fill(*newarray | std::views::drop(newcount), nullptr); auto oldarray = context->mActiveAuxSlots.exchange(std::move(newarray), std::memory_order_acq_rel); std::ignore = context->mDevice->waitForMix(); } void RemoveActiveEffectSlots(std::span const> const auxslots, gsl::not_null const context) { if(auxslots.empty()) return; /* Copy the existing slots, excluding those specified in auxslots. */ auto *curarray = context->mActiveAuxSlots.load(std::memory_order_acquire); auto tmparray = std::vector{}; tmparray.reserve(curarray->size()>>1); std::ranges::copy_if(*curarray | std::views::take(curarray->size()>>1), std::back_inserter(tmparray), [auxslots](const EffectSlotBase *slot) -> bool { return std::ranges::find(auxslots, slot, &al::EffectSlot::mSlot) == auxslots.end(); }); /* Reallocate with the new size. */ auto newarray = EffectSlotBase::CreatePtrArray(tmparray.size()<<1); auto new_end = std::ranges::copy(tmparray, newarray->begin()).out; std::ranges::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 const 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(gsl::not_null const context, usize const needed) noexcept -> bool try { auto count = std::accumulate(context->mEffectSlotList.cbegin(), context->mEffectSlotList.cend(), 0_uz, [](usize const cur, const EffectSlotSubList &sublist) noexcept -> usize { return cur + gsl::narrow_cast(std::popcount(sublist.mFreeMask)); }); while(needed > count) { if(context->mEffectSlotList.size() >= 1<<25) [[unlikely]] return false; auto sublist = EffectSlotSubList{}; sublist.mFreeMask = ~0_u64; sublist.mEffectSlots = SubListAllocator{}.allocate(1); context->mEffectSlotList.emplace_back(std::move(sublist)); count += std::tuple_size_v; } return true; } catch(...) { return false; } [[nodiscard]] auto AllocEffectSlot(gsl::not_null const context) -> gsl::not_null { auto const sublist = std::ranges::find_if(context->mEffectSlotList, &EffectSlotSubList::mFreeMask); auto const lidx = gsl::narrow_cast(std::distance(context->mEffectSlotList.begin(), sublist)); auto const slidx = gsl::narrow_cast(std::countr_zero(sublist->mFreeMask)); ASSUME(slidx < 64); auto const slot = gsl::make_not_null(std::construct_at( std::to_address(std::next(sublist->mEffectSlots->begin(), slidx)), context)); aluInitEffectPanning(slot->mSlot, context); /* Add 1 to avoid ID 0. */ slot->mId = ((lidx<<6) | slidx) + 1; context->mNumEffectSlots += 1; sublist->mFreeMask &= ~(1_u64 << slidx); return slot; } void FreeEffectSlot(gsl::not_null const context, gsl::not_null const slot) { context->mEffectSlotNames.erase(slot->mId); const auto id = slot->mId - 1; const auto lidx = id >> 6; const auto slidx = id & 0x3f; std::destroy_at(std::to_address(slot)); context->mEffectSlotList[lidx].mFreeMask |= 1_u64 << slidx; context->mNumEffectSlots--; } void UpdateProps(gsl::not_null const slot, gsl::not_null const context) { if(!context->mDeferUpdates && slot->mState == SlotState::Playing) { slot->updateProps(context); return; } slot->mPropsDirty = true; } void alGenAuxiliaryEffectSlots(gsl::not_null const context, ALsizei const n, ALuint *const 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 const device = al::get_not_null(context->mALDevice); const auto eids = std::span{effectslots, gsl::narrow_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"); auto slots = std::vector>{}; try { if(eids.size() == 1) { /* Special handling for the easy and normal case. */ eids[0] = AllocEffectSlot(context)->mId; } else { slots.reserve(eids.size()); std::generate_n(std::back_inserter(slots), eids.size(), [context]{ return AllocEffectSlot(context); }); std::ranges::transform(slots, eids.begin(), &al::EffectSlot::mId); } } catch(std::exception& e) { ERR("Exception allocating effectslot {} of {}: {}", slots.size()+1, n, e.what()); std::ranges::for_each(slots, [context](gsl::not_null const slot) -> void { FreeEffectSlot(context, slot); }); 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()); } void alDeleteAuxiliaryEffectSlots(gsl::not_null const context, ALsizei const n, ALuint const *const effectslots) noexcept try { if(n < 0) [[unlikely]] context->throw_error(AL_INVALID_VALUE, "Deleting {} effect slots", n); if(n <= 0) [[unlikely]] return; auto slotlock = std::lock_guard{context->mEffectSlotLock}; if(n == 1) { auto slot = LookupEffectSlot(context, *effectslots); if(slot->mRef.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 auto eids = std::span{effectslots, gsl::narrow_cast(n)}; auto slots = std::vector>{}; slots.reserve(eids.size()); std::ranges::transform(eids, std::back_inserter(slots), [context](u32 const eid) -> gsl::not_null { auto const slot = LookupEffectSlot(context, eid); if(slot->mRef.load(std::memory_order_relaxed) != 0) context->throw_error(AL_INVALID_OPERATION, "Deleting in-use effect slot {}", eid); return slot; }); /* All effectslots are valid, remove and delete them */ RemoveActiveEffectSlots(slots, context); std::ranges::for_each(eids, [context](const ALuint eid) -> void { if(auto *slot = LookupEffectSlot(std::nothrow, context, eid)) FreeEffectSlot(context, gsl::make_not_null(slot)); }); } } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } auto alIsAuxiliaryEffectSlot(gsl::not_null const context, ALuint const effectslot) noexcept -> ALboolean { const auto slotlock = std::lock_guard{context->mEffectSlotLock}; if(LookupEffectSlot(std::nothrow, context, effectslot) != nullptr) return AL_TRUE; return AL_FALSE; } void alAuxiliaryEffectSloti(gsl::not_null const context, ALuint const effectslot, ALenum const param, ALint const value) noexcept try { const auto proplock = std::lock_guard{context->mPropLock}; const auto slotlock = std::lock_guard{context->mEffectSlotLock}; auto slot = LookupEffectSlot(context, effectslot); auto targetref = al::intrusive_ptr{}; switch(param) { case AL_EFFECTSLOT_EFFECT: { auto const device = al::get_not_null(context->mALDevice); const auto effectlock = std::lock_guard{device->EffectLock}; if(value == 0) slot->initEffect(0, AL_EFFECT_NULL, EffectProps{}, context); else { auto const effect = LookupEffect(context, as_unsigned(value)); slot->initEffect(effect->mId, effect->mType, effect->mProps, context); } } 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->mAuxSendAuto == !!value)) [[likely]] { slot->mAuxSendAuto = !!value; UpdateProps(slot, context); } return; case AL_EFFECTSLOT_TARGET_SOFT: if(value != 0) { auto const target = LookupEffectSlot(context, as_unsigned(value)); if(slot->mTarget.get() == target) return; auto const *checker = target.get(); while(checker && checker != slot) checker = checker->mTarget.get(); if(checker) context->throw_error(AL_INVALID_OPERATION, "Setting target of effect slot ID {} to {} creates circular chain", slot->mId, target->mId); targetref = target->newReference(); } else if(!slot->mTarget) return; if(slot->mTarget) { /* We must force an update if there was an existing effect slot * target, in case it's about to be deleted. */ slot->mTarget = std::move(targetref); slot->updateProps(context); } else { slot->mTarget = std::move(targetref); UpdateProps(slot, context); } return; case AL_BUFFER: if(auto const *const buffer = slot->mBuffer.get()) { if(buffer->mId == as_unsigned(value)) return; } else if(value == 0) return; if(slot->mState == SlotState::Playing) { auto state = getFactoryByType(slot->mEffect.Type)->create(); auto const device = al::get_not_null(context->mALDevice); auto bufferlock = std::unique_lock{device->BufferLock}; auto buffer = al::intrusive_ptr{}; if(value) { auto const buf = LookupBuffer(context, as_unsigned(value)); if(buf->mCallback) context->throw_error(AL_INVALID_OPERATION, "Callback buffer not valid for effects"); buffer = buf->newReference(); } /* Stop the effect slot from processing while we switch buffers. */ RemoveActiveEffectSlots({&slot, 1}, context); slot->mBuffer = std::move(buffer); bufferlock.unlock(); state->mOutTarget = device->Dry.Buffer; { const auto mixer_mode = FPUCtl{}; state->deviceUpdate(device, slot->mBuffer.get()); } slot->mEffect.State = std::move(state); slot->mPropsDirty = false; slot->updateProps(context); AddActiveEffectSlots({&slot, 1}, context); } else { auto const device = al::get_not_null(context->mALDevice); auto bufferlock = std::unique_lock{device->BufferLock}; if(value) { auto const buffer = LookupBuffer(context, as_unsigned(value)); if(buffer->mCallback) context->throw_error(AL_INVALID_OPERATION, "Callback buffer not valid for effects"); slot->mBuffer = buffer->newReference(); } else slot->mBuffer.reset(); bufferlock.unlock(); const auto mixer_mode = FPUCtl{}; auto *state = slot->mEffect.State.get(); state->deviceUpdate(device, slot->mBuffer.get()); slot->mPropsDirty = true; } 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()); } void alAuxiliaryEffectSlotiv(gsl::not_null 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_BUFFER: alAuxiliaryEffectSloti(context, effectslot, param, *values); return; } const auto slotlock [[maybe_unused]] = std::lock_guard{context->mEffectSlotLock}; std::ignore = LookupEffectSlot(context, 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()); } void alAuxiliaryEffectSlotf(gsl::not_null context, ALuint effectslot, ALenum param, ALfloat value) noexcept try { const auto proplock = std::lock_guard{context->mPropLock}; const auto slotlock = std::lock_guard{context->mEffectSlotLock}; auto slot = LookupEffectSlot(context, 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->mGain == value)) [[likely]] { slot->mGain = 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()); } void alAuxiliaryEffectSlotfv(gsl::not_null context, ALuint effectslot, ALenum param, const ALfloat *values) noexcept try { switch(param) { case AL_EFFECTSLOT_GAIN: alAuxiliaryEffectSlotf(context, effectslot, param, *values); return; } const auto slotlock [[maybe_unused]] = std::lock_guard{context->mEffectSlotLock}; std::ignore = LookupEffectSlot(context, 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()); } void alGetAuxiliaryEffectSloti(gsl::not_null context, ALuint effectslot, ALenum param, ALint *value) noexcept try { const auto slotlock = std::lock_guard{context->mEffectSlotLock}; auto slot = LookupEffectSlot(context, effectslot); switch(param) { case AL_EFFECTSLOT_EFFECT: *value = as_signed(slot->mEffectId); return; case AL_EFFECTSLOT_AUXILIARY_SEND_AUTO: *value = slot->mAuxSendAuto ? AL_TRUE : AL_FALSE; return; case AL_EFFECTSLOT_TARGET_SOFT: if(auto *target = slot->mTarget.get()) *value = as_signed(target->mId); else *value = 0; return; case AL_BUFFER: if(auto *buffer = slot->mBuffer.get()) *value = as_signed(buffer->mId); 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()); } void alGetAuxiliaryEffectSlotiv(gsl::not_null 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_BUFFER: alGetAuxiliaryEffectSloti(context, effectslot, param, values); return; } const auto slotlock [[maybe_unused]] = std::lock_guard{context->mEffectSlotLock}; std::ignore = LookupEffectSlot(context, 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()); } void alGetAuxiliaryEffectSlotf(gsl::not_null context, ALuint effectslot, ALenum param, ALfloat *value) noexcept try { const auto slotlock = std::lock_guard{context->mEffectSlotLock}; auto slot = LookupEffectSlot(context, effectslot); switch(param) { case AL_EFFECTSLOT_GAIN: *value = slot->mGain; 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()); } void alGetAuxiliaryEffectSlotfv(gsl::not_null context, ALuint effectslot, ALenum param, ALfloat *values) noexcept try { switch(param) { case AL_EFFECTSLOT_GAIN: alGetAuxiliaryEffectSlotf(context, effectslot, param, values); return; } const auto slotlock [[maybe_unused]] = std::lock_guard{context->mEffectSlotLock}; std::ignore = LookupEffectSlot(context, 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()); } } // namespace AL_API DECL_FUNC2(void, alGenAuxiliaryEffectSlots, ALsizei,n, ALuint*,effectslots) AL_API DECL_FUNC2(void, alDeleteAuxiliaryEffectSlots, ALsizei,n, const ALuint*,effectslots) AL_API DECL_FUNC1(ALboolean, alIsAuxiliaryEffectSlot, ALuint,effectslot) AL_API DECL_FUNC3(void, alAuxiliaryEffectSloti, ALuint,effectslot, ALenum,param, ALint,value) AL_API DECL_FUNC3(void, alAuxiliaryEffectSlotiv, ALuint,effectslot, ALenum,param, const ALint*,values) AL_API DECL_FUNC3(void, alAuxiliaryEffectSlotf, ALuint,effectslot, ALenum,param, ALfloat,value) AL_API DECL_FUNC3(void, alAuxiliaryEffectSlotfv, ALuint,effectslot, ALenum,param, const ALfloat*,values) AL_API DECL_FUNC3(void, alGetAuxiliaryEffectSloti, ALuint,effectslot, ALenum,param, ALint*,value) AL_API DECL_FUNC3(void, alGetAuxiliaryEffectSlotiv, ALuint,effectslot, ALenum,param, ALint*,values) AL_API DECL_FUNC3(void, alGetAuxiliaryEffectSlotf, ALuint,effectslot, ALenum,param, ALfloat*,value) AL_API DECL_FUNC3(void, alGetAuxiliaryEffectSlotfv, ALuint,effectslot, ALenum,param, ALfloat*,values) al::EffectSlot::EffectSlot(gsl::not_null context) : mSlot{context->getEffectSlot()} #if ALSOFT_EAX , mEaxALContext{context} #endif { mSlot->InUse = true; try { auto state = getFactoryByType(EffectSlotType::None)->create(); mEffect.State = state; mSlot->mEffectState = std::move(state); } catch(...) { mSlot->InUse = false; throw; } } al::EffectSlot::~EffectSlot() { if(auto *slot = mSlot->Update.exchange(nullptr, std::memory_order_relaxed)) slot->State = nullptr; mSlot->mEffectState = nullptr; mSlot->InUse = false; } auto al::EffectSlot::initEffect(u32 const effectId, ALenum const effectType, EffectProps const &effectProps, gsl::not_null const context) -> void { const auto newtype = EffectSlotTypeFromEnum(effectType); if(newtype != mEffect.Type) { auto state = getFactoryByType(newtype)->create(); auto const device = al::get_not_null(context->mALDevice); state->mOutTarget = device->Dry.Buffer; { const auto mixer_mode = FPUCtl{}; state->deviceUpdate(device, mBuffer.get()); } mEffect.Type = newtype; mEffect.Props = effectProps; mEffect.State = std::move(state); } else if(newtype != EffectSlotType::None) mEffect.Props = effectProps; mEffectId = effectId; /* Remove state references from old effect slot property updates. */ auto *props = context->mFreeEffectSlotProps.load(); while(props) { props->State = nullptr; props = props->next.load(std::memory_order_relaxed); } } void al::EffectSlot::updateProps(gsl::not_null const context) const { /* Get an unused property container, or allocate a new one as needed. */ auto *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 = mGain; props->AuxSendAuto = mAuxSendAuto; props->Target = mTarget ? mTarget->mSlot.get() : nullptr; props->Type = mEffect.Type; props->Props = mEffect.Props; props->State = mEffect.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 al::EffectSlot::SetName(gsl::not_null const context, u32 const id, std::string_view const name) { const auto slotlock = std::lock_guard{context->mEffectSlotLock}; std::ignore = LookupEffectSlot(context, id); context->mEffectSlotNames.insert_or_assign(id, name); } void UpdateAllEffectSlotProps(gsl::not_null context) { const auto slotlock = std::lock_guard{context->mEffectSlotLock}; for(auto &sublist : context->mEffectSlotList) { auto usemask = ~sublist.mFreeMask; while(usemask) { const auto idx = as_unsigned(std::countr_zero(usemask)); usemask ^= 1_u64 << idx; auto &slot = (*sublist.mEffectSlots)[idx]; if(std::exchange(slot.mPropsDirty, false)) slot.updateProps(context); } } } EffectSlotSubList::~EffectSlotSubList() { if(!mEffectSlots) return; uint64_t usemask{~mFreeMask}; while(usemask) { const int idx{std::countr_zero(usemask)}; std::destroy_at(std::to_address(mEffectSlots->begin() + idx)); usemask &= ~(1_u64 << idx); } mFreeMask = ~usemask; SubListAllocator{}.deallocate(mEffectSlots, 1); mEffectSlots = nullptr; } AL_API void AL_APIENTRY alAuxiliaryEffectSlotPlaySOFT(ALuint) noexcept { const auto context = GetContextRef(); if(!context) [[unlikely]] return; context->setError(AL_INVALID_OPERATION, "alAuxiliaryEffectSlotPlaySOFT not supported"); } AL_API void AL_APIENTRY alAuxiliaryEffectSlotPlayvSOFT(ALsizei, const ALuint*) noexcept { const auto context = GetContextRef(); if(!context) [[unlikely]] return; context->setError(AL_INVALID_OPERATION, "alAuxiliaryEffectSlotPlayvSOFT not supported"); } AL_API void AL_APIENTRY alAuxiliaryEffectSlotStopSOFT(ALuint) noexcept { const auto context = GetContextRef(); if(!context) [[unlikely]] return; context->setError(AL_INVALID_OPERATION, "alAuxiliaryEffectSlotStopSOFT not supported"); } AL_API void AL_APIENTRY alAuxiliaryEffectSlotStopvSOFT(ALsizei, const ALuint*) noexcept { const auto context = GetContextRef(); if(!context) [[unlikely]] return; context->setError(AL_INVALID_OPERATION, "alAuxiliaryEffectSlotStopvSOFT not supported"); } #if ALSOFT_EAX void al::EffectSlot::eax_initialize(EaxFxSlotIndexValue const index) { if(index >= EAX_MAX_FXSLOTS) eax_fail("Index out of range."); mEaxFXSlotIndex = index; eax_fx_slot_set_defaults(); mEaxEffect = std::make_unique(); if(index == 0) mEaxEffect->init(); else if(index == 1) mEaxEffect->init(); else mEaxEffect->init(); } void al::EffectSlot::eax_commit() { if(mEaxDf.any()) { auto df = std::bitset{}; switch(mEaxVersion) { case 1: case 2: case 3: eax5_fx_slot_commit(mEax123, df); break; case 4: eax4_fx_slot_commit(df); break; case 5: eax5_fx_slot_commit(mEax5, df); break; } mEaxDf.reset(); if(df.test(eax_volume_dirty_bit)) eax_fx_slot_set_volume(); if(df.test(eax_flags_dirty_bit)) eax_fx_slot_set_flags(); } if(mEaxEffect->commit(mEaxVersion)) eax_set_efx_slot_effect(*mEaxEffect); } [[noreturn]] void al::EffectSlot::eax_fail(std::string_view const message) { throw Exception{message}; } [[noreturn]] void al::EffectSlot::eax_fail_unknown_effect_id() { eax_fail("Unknown effect ID."); } [[noreturn]] void al::EffectSlot::eax_fail_unknown_property_id() { eax_fail("Unknown property ID."); } [[noreturn]] void al::EffectSlot::eax_fail_unknown_version() { eax_fail("Unknown version."); } void al::EffectSlot::eax4_fx_slot_ensure_unlocked() const { if(eax4_fx_slot_is_legacy()) eax_fail("Locked legacy slot."); } ALenum al::EffectSlot::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& al::EffectSlot::eax_get_eax_default_effect_guid() const noexcept { switch(mEaxFXSlotIndex) { case 0: return EAX_REVERB_EFFECT; case 1: return EAX_CHORUS_EFFECT; default: return EAX_NULL_GUID; } } auto al::EffectSlot::eax_get_eax_default_lock() const noexcept -> eax_long { return eax4_fx_slot_is_legacy() ? EAXFXSLOT_LOCKED : EAXFXSLOT_UNLOCKED; } void al::EffectSlot::eax4_fx_slot_set_defaults(EAX40FXSLOTPROPERTIES& props) const 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 al::EffectSlot::eax5_fx_slot_set_defaults(EAX50FXSLOTPROPERTIES& props) const 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 al::EffectSlot::eax_fx_slot_set_defaults() { eax5_fx_slot_set_defaults(mEax123.i); eax4_fx_slot_set_defaults(mEax4.i); eax5_fx_slot_set_defaults(mEax5.i); mEax = mEax5.i; mEaxDf.reset(); } void al::EffectSlot::eax4_fx_slot_get(const EaxCall& call, const EAX40FXSLOTPROPERTIES& props) { switch(call.get_property_id()) { case EAXFXSLOT_ALLPARAMETERS: call.store(props); break; case EAXFXSLOT_LOADEFFECT: call.store(props.guidLoadEffect); break; case EAXFXSLOT_VOLUME: call.store(props.lVolume); break; case EAXFXSLOT_LOCK: call.store(props.lLock); break; case EAXFXSLOT_FLAGS: call.store(props.ulFlags); break; default: eax_fail_unknown_property_id(); } } void al::EffectSlot::eax5_fx_slot_get(const EaxCall& call, const EAX50FXSLOTPROPERTIES& props) { switch(call.get_property_id()) { case EAXFXSLOT_ALLPARAMETERS: call.store(props); break; case EAXFXSLOT_LOADEFFECT: call.store(props.guidLoadEffect); break; case EAXFXSLOT_VOLUME: call.store(props.lVolume); break; case EAXFXSLOT_LOCK: call.store(props.lLock); break; case EAXFXSLOT_FLAGS: call.store(props.ulFlags); break; case EAXFXSLOT_OCCLUSION: call.store(props.lOcclusion); break; case EAXFXSLOT_OCCLUSIONLFRATIO: call.store(props.flOcclusionLFRatio); break; default: eax_fail_unknown_property_id(); } } void al::EffectSlot::eax_fx_slot_get(const EaxCall& call) const { switch(call.get_version()) { case 4: eax4_fx_slot_get(call, mEax4.i); break; case 5: eax5_fx_slot_get(call, mEax5.i); break; default: eax_fail_unknown_version(); } } auto al::EffectSlot::eax_get(const EaxCall& call) const -> bool { switch(call.get_property_set_id()) { case EaxCallPropertySetId::fx_slot: eax_fx_slot_get(call); break; case EaxCallPropertySetId::fx_slot_effect: mEaxEffect->get(call); break; default: eax_fail_unknown_property_id(); } return false; } void al::EffectSlot::eax_fx_slot_load_effect(int version, ALenum altype) const { if(!IsValidEffectType(altype)) altype = AL_EFFECT_NULL; mEaxEffect->set_defaults(version, altype); } void al::EffectSlot::eax_fx_slot_set_volume() { const auto volume = std::clamp(mEax.lVolume, EAXFXSLOT_MINVOLUME, EAXFXSLOT_MAXVOLUME); const auto gain = level_mb_to_gain(gsl::narrow_cast(volume)); eax_set_efx_slot_gain(gain); } void al::EffectSlot::eax_fx_slot_set_environment_flag() { eax_set_efx_slot_send_auto((mEax.ulFlags & EAXFXSLOTFLAGS_ENVIRONMENT) != 0u); } void al::EffectSlot::eax_fx_slot_set_flags() { eax_fx_slot_set_environment_flag(); } void al::EffectSlot::eax4_fx_slot_set_all(const EaxCall& call) { eax4_fx_slot_ensure_unlocked(); const auto &src = call.load(); Eax4AllValidator{}(src); auto &dst = mEax4.i; mEaxDf.set(eax_load_effect_dirty_bit); // Always reset the effect. if(dst.lVolume != src.lVolume) mEaxDf.set(eax_volume_dirty_bit); if(dst.lLock != src.lLock) mEaxDf.set(eax_lock_dirty_bit); if(dst.ulFlags != src.ulFlags) mEaxDf.set(eax_flags_dirty_bit); dst = src; } void al::EffectSlot::eax5_fx_slot_set_all(const EaxCall& call) { const auto &src = call.load(); Eax5AllValidator{}(src); auto &dst = mEax5.i; mEaxDf.set(eax_load_effect_dirty_bit); // Always reset the effect. if(dst.lVolume != src.lVolume) mEaxDf.set(eax_volume_dirty_bit); if(dst.lLock != src.lLock) mEaxDf.set(eax_lock_dirty_bit); if(dst.ulFlags != src.ulFlags) mEaxDf.set(eax_flags_dirty_bit); if(dst.lOcclusion != src.lOcclusion) mEaxDf.set(eax_flags_dirty_bit); if(dst.flOcclusionLFRatio != src.flOcclusionLFRatio) mEaxDf.set(eax_flags_dirty_bit); dst = src; } auto al::EffectSlot::eax_fx_slot_should_update_sources() const noexcept -> bool { static constexpr auto dirty_bits = std::bitset{ (1u << eax_occlusion_dirty_bit) | (1u << eax_occlusion_lf_ratio_dirty_bit) | (1u << eax_flags_dirty_bit) }; return (mEaxDf & dirty_bits).any(); } // Returns `true` if all sources should be updated, or `false` otherwise. auto al::EffectSlot::eax4_fx_slot_set(const EaxCall& call) -> bool { auto& dst = mEax4.i; switch(call.get_property_id()) { case EAXFXSLOT_NONE: break; case EAXFXSLOT_ALLPARAMETERS: eax4_fx_slot_set_all(call); if(mEaxDf.test(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_load_effect_dirty_bit); if(mEaxDf.test(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_volume_dirty_bit); break; case EAXFXSLOT_LOCK: eax4_fx_slot_ensure_unlocked(); eax_fx_slot_set(call, dst.lLock, eax_lock_dirty_bit); break; case EAXFXSLOT_FLAGS: eax_fx_slot_set(call, dst.ulFlags, eax_flags_dirty_bit); 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. auto al::EffectSlot::eax5_fx_slot_set(const EaxCall& call) -> bool { auto& dst = mEax5.i; switch(call.get_property_id()) { case EAXFXSLOT_NONE: break; case EAXFXSLOT_ALLPARAMETERS: eax5_fx_slot_set_all(call); if(mEaxDf.test(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_load_effect_dirty_bit); if(mEaxDf.test(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_volume_dirty_bit); break; case EAXFXSLOT_LOCK: eax_fx_slot_set(call, dst.lLock, eax_lock_dirty_bit); break; case EAXFXSLOT_FLAGS: eax_fx_slot_set(call, dst.ulFlags, eax_flags_dirty_bit); break; case EAXFXSLOT_OCCLUSION: eax_fx_slot_set(call, dst.lOcclusion, eax_occlusion_dirty_bit); break; case EAXFXSLOT_OCCLUSIONLFRATIO: eax_fx_slot_set(call, dst.flOcclusionLFRatio, eax_occlusion_lf_ratio_dirty_bit); 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. auto al::EffectSlot::eax_fx_slot_set(const EaxCall& call) -> bool { 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. auto al::EffectSlot::eax_set(const EaxCall& call) -> bool { auto ret = false; switch(call.get_property_set_id()) { case EaxCallPropertySetId::fx_slot: ret = eax_fx_slot_set(call); break; case EaxCallPropertySetId::fx_slot_effect: mEaxEffect->set(call); break; default: eax_fail_unknown_property_id(); } const auto version = call.get_version(); if(mEaxVersion != version) mEaxDf.set(); mEaxVersion = version; return ret; } void al::EffectSlot::eax4_fx_slot_commit(std::bitset& dst_df) { eax_fx_slot_commit_property(mEax4, dst_df, eax_load_effect_dirty_bit, &EAX40FXSLOTPROPERTIES::guidLoadEffect); eax_fx_slot_commit_property(mEax4, dst_df, eax_volume_dirty_bit, &EAX40FXSLOTPROPERTIES::lVolume); eax_fx_slot_commit_property(mEax4, dst_df, eax_lock_dirty_bit, &EAX40FXSLOTPROPERTIES::lLock); eax_fx_slot_commit_property(mEax4, dst_df, eax_flags_dirty_bit, &EAX40FXSLOTPROPERTIES::ulFlags); auto& dst_i = mEax; if(dst_i.lOcclusion != EAXFXSLOT_DEFAULTOCCLUSION) { dst_df.set(eax_occlusion_dirty_bit); dst_i.lOcclusion = EAXFXSLOT_DEFAULTOCCLUSION; } if(dst_i.flOcclusionLFRatio != EAXFXSLOT_DEFAULTOCCLUSIONLFRATIO) { dst_df.set(eax_occlusion_lf_ratio_dirty_bit); dst_i.flOcclusionLFRatio = EAXFXSLOT_DEFAULTOCCLUSIONLFRATIO; } } void al::EffectSlot::eax5_fx_slot_commit(Eax5State &state, std::bitset &dst_df) { eax_fx_slot_commit_property(state, dst_df, eax_load_effect_dirty_bit, &EAX50FXSLOTPROPERTIES::guidLoadEffect); eax_fx_slot_commit_property(state, dst_df, eax_volume_dirty_bit, &EAX50FXSLOTPROPERTIES::lVolume); eax_fx_slot_commit_property(state, dst_df, eax_lock_dirty_bit, &EAX50FXSLOTPROPERTIES::lLock); eax_fx_slot_commit_property(state, dst_df, eax_flags_dirty_bit, &EAX50FXSLOTPROPERTIES::ulFlags); eax_fx_slot_commit_property(state, dst_df, eax_occlusion_dirty_bit, &EAX50FXSLOTPROPERTIES::lOcclusion); eax_fx_slot_commit_property(state, dst_df, eax_occlusion_lf_ratio_dirty_bit, &EAX50FXSLOTPROPERTIES::flOcclusionLFRatio); } void al::EffectSlot::eax_set_efx_slot_effect(EaxEffect const &effect) { #define EAX_PREFIX "[EAX_SET_EFFECT_SLOT_EFFECT] " initEffect(0, effect.al_effect_type_, effect.al_effect_props_, mEaxALContext); if(mState == SlotState::Initial) { mPropsDirty = false; updateProps(mEaxALContext); auto effect_slot_ptr = gsl::make_not_null(this); AddActiveEffectSlots({&effect_slot_ptr, 1}, mEaxALContext); mState = SlotState::Playing; return; } mPropsDirty = true; #undef EAX_PREFIX } void al::EffectSlot::eax_set_efx_slot_send_auto(bool const is_send_auto) { if(mAuxSendAuto == is_send_auto) return; mAuxSendAuto = is_send_auto; mPropsDirty = true; } void al::EffectSlot::eax_set_efx_slot_gain(f32 const gain) { #define EAX_PREFIX "[EAX_SET_EFFECT_SLOT_GAIN] " if(gain == mGain) return; if(gain < 0.0f || gain > 1.0f) ERR(EAX_PREFIX "Slot gain out of range: {}", gain); mGain = std::clamp(gain, 0.0f, 1.0f); mPropsDirty = true; #undef EAX_PREFIX } void al::EffectSlot::EaxDeleter::operator()(gsl::not_null const effect_slot) const { #define EAX_PREFIX "[EAX_DELETE_EFFECT_SLOT] " auto const context = al::get_not_null(effect_slot->mEaxALContext); auto slotlock = std::lock_guard{context->mEffectSlotLock}; if(effect_slot->mRef.load(std::memory_order_relaxed) != 0) { ERR(EAX_PREFIX "Deleting in-use effect slot {}.", effect_slot->mId); return; } RemoveActiveEffectSlots({&effect_slot, 1}, context); FreeEffectSlot(context, effect_slot); #undef EAX_PREFIX } auto eax_create_al_effect_slot(gsl::not_null const context) -> EaxAlEffectSlotUPtr { #define EAX_PREFIX "[EAX_MAKE_EFFECT_SLOT] " auto slotlock = std::lock_guard{context->mEffectSlotLock}; if(auto const& device = *context->mALDevice; 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 } #endif // ALSOFT_EAX kcat-openal-soft-75c0059/al/auxeffectslot.h000066400000000000000000000304111512220627100205550ustar00rootroot00000000000000#ifndef AL_AUXEFFECTSLOT_H #define AL_AUXEFFECTSLOT_H #include "config.h" #include #include #include #include #include #include #include #include #include "AL/al.h" #include "alnumeric.h" #include "core/effects/base.h" #include "core/effectslot.h" #include "gsl/gsl" #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 #if ALSOFT_EAX /* NOLINTNEXTLINE(clazy-copyable-polymorphic) Exceptions must be copyable. */ class EaxFxSlotException final : public EaxException { public: explicit EaxFxSlotException(const std::string_view message) : EaxException{"EAX_FX_SLOT", message} { } }; #endif // ALSOFT_EAX enum class SlotState : bool { Initial, Playing, }; namespace al { struct Context; struct Buffer; struct EffectSlot { u32 mEffectId{}; f32 mGain{1.0f}; bool mAuxSendAuto{true}; al::intrusive_ptr mTarget; al::intrusive_ptr mBuffer; struct EffectData { EffectSlotType Type{EffectSlotType::None}; EffectProps Props; al::intrusive_ptr State; }; EffectData mEffect; bool mPropsDirty{true}; SlotState mState{SlotState::Initial}; std::atomic mRef{0u}; gsl::not_null mSlot; /* Self ID */ u32 mId{}; explicit EffectSlot(gsl::not_null context); EffectSlot(const EffectSlot&) = delete; auto operator=(const EffectSlot&) -> EffectSlot& = delete; ~EffectSlot(); auto inc_ref() noexcept { return mRef.fetch_add(1, std::memory_order_acq_rel)+1; } auto dec_ref() noexcept { return mRef.fetch_sub(1, std::memory_order_acq_rel)-1; } auto newReference() noexcept { mRef.fetch_add(1, std::memory_order_acq_rel); return al::intrusive_ptr{this}; } auto initEffect(u32 effectId, ALenum effectType, const EffectProps &effectProps, gsl::not_null context) -> void; void updateProps(gsl::not_null context) const; static void SetName(gsl::not_null context, u32 id, std::string_view name); #if ALSOFT_EAX void eax_initialize(EaxFxSlotIndexValue index); [[nodiscard]] auto eax_get_index() const noexcept -> EaxFxSlotIndexValue { return mEaxFXSlotIndex; } [[nodiscard]] auto eax_get_eax_fx_slot() const noexcept -> const EAX50FXSLOTPROPERTIES& { return mEax; } // 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: enum { eax_load_effect_dirty_bit, eax_volume_dirty_bit, eax_lock_dirty_bit, eax_flags_dirty_bit, eax_occlusion_dirty_bit, eax_occlusion_lf_ratio_dirty_bit, eax_dirty_bit_count }; using Exception = EaxFxSlotException; struct Eax4State { EAX40FXSLOTPROPERTIES i; // Immediate. }; struct Eax5State { EAX50FXSLOTPROPERTIES i; // Immediate. }; struct EaxRangeValidator { template void operator()(const std::string_view 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()(eax_long const lVolume) const { EaxRangeValidator{}( "Volume", lVolume, EAXFXSLOT_MINVOLUME, EAXFXSLOT_MAXVOLUME); } }; struct Eax4LockValidator { void operator()(eax_long const lLock) const { EaxRangeValidator{}( "Lock", lLock, EAXFXSLOT_MINLOCK, EAXFXSLOT_MAXLOCK); } }; struct Eax4FlagsValidator { void operator()(eax_ulong const ulFlags) const { EaxRangeValidator{}( "Flags", ulFlags, 0_eax_ulong, ~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()(eax_ulong const ulFlags) const { EaxRangeValidator{}( "Flags", ulFlags, 0_eax_ulong, ~EAX50FXSLOTFLAGS_RESERVED); } }; struct Eax5OcclusionValidator { void operator()(eax_long const lOcclusion) const { EaxRangeValidator{}( "Occlusion", lOcclusion, EAXFXSLOT_MINOCCLUSION, EAXFXSLOT_MAXOCCLUSION); } }; struct Eax5OcclusionLfRatioValidator { void operator()(float const 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); } }; gsl::not_null const mEaxALContext; EaxFxSlotIndexValue mEaxFXSlotIndex{}; int mEaxVersion{}; // Current EAX version. std::bitset mEaxDf; // Dirty flags for the current EAX version. EaxEffectUPtr mEaxEffect; Eax5State mEax123{}; // EAX1/EAX2/EAX3 state. Eax4State mEax4{}; // EAX4 state. Eax5State mEax5{}; // EAX5 state. EAX50FXSLOTPROPERTIES mEax{}; // Current EAX state. [[noreturn]] static void eax_fail(std::string_view 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 void eax_fx_slot_set(const EaxCall &call, auto &dst, size_t dirty_bit) { const auto &src = call.load>(); TValidator{}(src); if(dst != src) { mEaxDf.set(dirty_bit); 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 void eax_fx_slot_set_dirty(const EaxCall &call, auto &dst, size_t dirty_bit) { const auto &src = call.load>(); TValidator{}(src); mEaxDf.set(dirty_bit); dst = src; } [[nodiscard]] constexpr auto eax4_fx_slot_is_legacy() const noexcept -> bool { return mEaxFXSlotIndex < 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 -> eax_long; void eax4_fx_slot_set_defaults(EAX40FXSLOTPROPERTIES& props) const noexcept; void eax5_fx_slot_set_defaults(EAX50FXSLOTPROPERTIES& props) const noexcept; void eax_fx_slot_set_defaults(); static void eax4_fx_slot_get(const EaxCall& call, const EAX40FXSLOTPROPERTIES& props); static void eax5_fx_slot_get(const EaxCall& call, const EAX50FXSLOTPROPERTIES& props); void eax_fx_slot_get(const EaxCall& call) const; // Returns `true` if all sources should be updated, or `false` otherwise. [[nodiscard]] auto eax_get(const EaxCall& call) const -> bool; void eax_fx_slot_load_effect(int version, ALenum altype) const; 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); void eax_fx_slot_commit_property(auto &state, std::bitset &dst_df, size_t dirty_bit, std::invocable auto member) noexcept { if(mEaxDf.test(dirty_bit)) { dst_df.set(dirty_bit); std::invoke(member, mEax) = std::invoke(member, state.i); } } void eax4_fx_slot_commit(std::bitset& dst_df); void eax5_fx_slot_commit(Eax5State& state, std::bitset& dst_df); // `alAuxiliaryEffectSloti(effect_slot, AL_EFFECTSLOT_EFFECT, effect)` void eax_set_efx_slot_effect(EaxEffect const &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(f32 gain); public: class EaxDeleter { public: void operator()(gsl::not_null effect_slot) const; }; #endif // ALSOFT_EAX }; } /* namespace al */ void UpdateAllEffectSlotProps(gsl::not_null context); #if ALSOFT_EAX using EaxAlEffectSlotUPtr = std::unique_ptr; auto eax_create_al_effect_slot(gsl::not_null context) -> EaxAlEffectSlotUPtr; #endif // ALSOFT_EAX struct EffectSlotSubList { uint64_t mFreeMask{~0_u64}; gsl::owner*> mEffectSlots{nullptr}; EffectSlotSubList() noexcept = default; EffectSlotSubList(const EffectSlotSubList&) = delete; EffectSlotSubList(EffectSlotSubList&& rhs) noexcept : mFreeMask{rhs.mFreeMask}, mEffectSlots{rhs.mEffectSlots} { rhs.mFreeMask = ~0_u64; rhs.mEffectSlots = nullptr; } ~EffectSlotSubList(); EffectSlotSubList& operator=(const EffectSlotSubList&) = delete; EffectSlotSubList& operator=(EffectSlotSubList&& rhs) noexcept { std::swap(mFreeMask, rhs.mFreeMask); std::swap(mEffectSlots, rhs.mEffectSlots); return *this; } }; #endif kcat-openal-soft-75c0059/al/buffer.cpp000066400000000000000000002026521512220627100175150ustar00rootroot00000000000000/** * 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 #include #include #include #include "AL/al.h" #include "AL/alext.h" #include "alc/context.h" #include "alc/device.h" #include "alc/inprogext.h" #include "alformat.hpp" #include "almalloc.h" #include "alnumeric.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 "gsl/gsl" #include "intrusive_ptr.h" #include "opthelpers.h" #if ALSOFT_EAX #include #include "eax/globals.h" #include "eax/x_ram.h" #endif // ALSOFT_EAX using uint = unsigned int; namespace { using SubListAllocator = al::allocator>; constexpr auto AmbiLayoutFromEnum(ALenum const 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 const layout) -> ALenum { switch(layout) { case AmbiLayout::FuMa: return AL_FUMA_SOFT; case AmbiLayout::ACN: return AL_ACN_SOFT; } throw std::runtime_error{al::format("Invalid AmbiLayout: {}", int{al::to_underlying(layout)})}; } constexpr auto AmbiScalingFromEnum(ALenum const 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 const 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{al::format("Invalid AmbiScaling: {}", int{al::to_underlying(scale)})}; } #if ALSOFT_EAX constexpr auto EaxStorageFromEnum(ALenum const 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 const 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{al::format("Invalid EaxStorage: {}", int{al::to_underlying(storage)})}; } auto eax_x_ram_check_availability(const al::Device &device, al::Buffer const &buffer, u32 const newsize) noexcept -> bool { auto 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.mEaxXRamIsHardware) freemem += buffer.mOriginalSize; return freemem >= newsize; } void eax_x_ram_apply(al::Device &device, al::Buffer &buffer) noexcept { if(buffer.mEaxXRamIsHardware) return; if(device.eax_x_ram_free_size >= buffer.mOriginalSize) { device.eax_x_ram_free_size -= buffer.mOriginalSize; buffer.mEaxXRamIsHardware = true; } } void eax_x_ram_clear(al::Device &al_device, al::Buffer &al_buffer) noexcept { if(al_buffer.mEaxXRamIsHardware) al_device.eax_x_ram_free_size += al_buffer.mOriginalSize; al_buffer.mEaxXRamIsHardware = false; } #endif // ALSOFT_EAX constexpr auto INVALID_STORAGE_MASK = ~gsl::narrow(AL_MAP_READ_BIT_SOFT | AL_MAP_WRITE_BIT_SOFT | AL_MAP_PERSISTENT_BIT_SOFT | AL_PRESERVE_DATA_BIT_SOFT); constexpr auto MAP_READ_WRITE_FLAGS = ALbitfieldSOFT{AL_MAP_READ_BIT_SOFT | AL_MAP_WRITE_BIT_SOFT}; constexpr auto INVALID_MAP_FLAGS = ~gsl::narrow(AL_MAP_READ_BIT_SOFT | AL_MAP_WRITE_BIT_SOFT | AL_MAP_PERSISTENT_BIT_SOFT); [[nodiscard]] auto EnsureBuffers(gsl::not_null const device, usize const needed) noexcept -> bool try { auto count = std::accumulate(device->BufferList.cbegin(), device->BufferList.cend(), 0_uz, [](usize const cur, const BufferSubList &sublist) noexcept -> usize { return cur + gsl::narrow_cast(std::popcount(sublist.mFreeMask)); }); while(needed > count) { if(device->BufferList.size() >= 1_uz<<25) [[unlikely]] return false; auto sublist = BufferSubList{}; sublist.mFreeMask = ~0_u64; sublist.mBuffers = SubListAllocator{}.allocate(1); device->BufferList.emplace_back(std::move(sublist)); count += std::tuple_size_v; } return true; } catch(...) { return false; } [[nodiscard]] auto AllocBuffer(gsl::not_null const device) noexcept -> gsl::not_null { auto sublist = std::ranges::find_if(device->BufferList, &BufferSubList::mFreeMask); auto lidx = std::distance(device->BufferList.begin(), sublist); auto slidx = std::countr_zero(sublist->mFreeMask); ASSUME(slidx < 64); auto const buffer = gsl::make_not_null(std::construct_at( std::to_address(sublist->mBuffers->begin() + slidx))); /* Add 1 to avoid buffer ID 0. */ buffer->mId = gsl::narrow_cast((lidx<<6) | slidx) + 1; sublist->mFreeMask &= ~(1_u64 << slidx); return buffer; } void FreeBuffer(gsl::not_null const device, gsl::not_null const buffer) { #if ALSOFT_EAX eax_x_ram_clear(*device, *buffer); #endif // ALSOFT_EAX device->mBufferNames.erase(buffer->mId); const auto id = buffer->mId - 1; const auto lidx = id >> 6; const auto slidx = id & 0x3f; std::destroy_at(buffer.get()); device->BufferList[lidx].mFreeMask |= 1_u64 << slidx; } [[nodiscard]] inline auto LookupBuffer(std::nothrow_t, gsl::not_null const device, u32 const id) noexcept -> al::Buffer* { const auto lidx = (id-1) >> 6; const auto slidx = (id-1) & 0x3f; if(lidx >= device->BufferList.size()) [[unlikely]] return nullptr; auto &sublist = device->BufferList[lidx]; if(sublist.mFreeMask & (1_u64 << slidx)) [[unlikely]] return nullptr; return std::to_address(std::next(sublist.mBuffers->begin(), slidx)); } [[nodiscard]] auto LookupBuffer(gsl::not_null const context, u32 const id) -> gsl::not_null { if(auto *const buffer = LookupBuffer(std::nothrow, al::get_not_null(context->mALDevice), id)) [[likely]] return gsl::make_not_null(buffer); context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", id); } [[nodiscard]] constexpr auto SanitizeAlignment(FmtType const type, u32 const align) noexcept -> u32 { if(align == 0) { /* Default to 65/64 sample frames per block for compatibility. */ if(type == FmtIMA4) 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(gsl::not_null const context, gsl::not_null const ALBuf, i32 const freq, u32 const size, FmtChannels const DstChannels, FmtType const DstType, std::span const SrcData, ALbitfieldSOFT const access) { if(ALBuf->mRef.load(std::memory_order_relaxed) != 0 || ALBuf->mMappedAccess != 0) context->throw_error(AL_INVALID_OPERATION, "Modifying storage for in-use buffer {}", ALBuf->mId); auto const samplesPerBlock = SanitizeAlignment(DstType, ALBuf->mUnpackAlign); if(samplesPerBlock < 1) context->throw_error(AL_INVALID_VALUE, "Invalid unpack alignment {} for {} samples", ALBuf->mUnpackAlign, NameFromFormat(DstType)); auto const ambiorder = IsBFormat(DstChannels) ? ALBuf->mUnpackAmbiOrder : (IsUHJ(DstChannels) ? 1_u32 : 0_u32); 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 != samplesPerBlock) 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. */ auto const NumChannels = ChannelsFromFmt(DstChannels, ambiorder); auto const bytesPerBlock = NumChannels * ((DstType == FmtIMA4) ? (samplesPerBlock-1_u32)/2_u32 + 4_u32 : (DstType == FmtMSADPCM) ? (samplesPerBlock-2_u32)/2_u32 + 7_u32 : (samplesPerBlock * BytesFromFmt(DstType))); if((size%bytesPerBlock) != 0) context->throw_error(AL_INVALID_VALUE, "Data size {} is not a multiple of frame size {} ({} unpack alignment)", size, bytesPerBlock, samplesPerBlock); auto const blocks = size / bytesPerBlock; if(blocks > std::numeric_limits::max()/samplesPerBlock) context->throw_error(AL_OUT_OF_MEMORY, "Buffer size overflow, {} blocks x {} samples per block", blocks, samplesPerBlock); if(blocks > std::numeric_limits::max()/bytesPerBlock) context->throw_error(AL_OUT_OF_MEMORY, "Buffer size overflow, {} frames x {} bytes per frame", blocks, bytesPerBlock); #if ALSOFT_EAX if(ALBuf->mEaxXRamMode == 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 auto const newsize = usize{blocks} * bytesPerBlock; auto const needRealloc = std::visit([ALBuf,DstType,newsize,access](T &datavec) -> bool { using vector_t = std::remove_cvref_t; using sample_t = vector_t::value_type; /* A new sample type must reallocate. */ if(DstType != SampleInfo::format()) return true; if(datavec.size() != newsize/sizeof(sample_t)) { if(!(access&AL_PRESERVE_DATA_BIT_SOFT)) return true; /* Reallocate in situ, to preserve existing samples as needed. */ datavec.resize(newsize, SampleInfo::silence()); ALBuf->mData = datavec; } return false; }, ALBuf->mDataStorage); if(needRealloc) { auto do_realloc = [ALBuf,newsize](T value) { using vector_t = al::vector; ALBuf->mData = ALBuf->mDataStorage.emplace(newsize/sizeof(T), value); }; switch(DstType) { case FmtUByte: do_realloc(SampleInfo::silence()); break; case FmtShort: do_realloc(SampleInfo::silence()); break; case FmtInt: do_realloc(SampleInfo::silence()); break; case FmtFloat: do_realloc(SampleInfo::silence()); break; case FmtDouble: do_realloc(SampleInfo::silence()); break; case FmtMulaw: do_realloc(SampleInfo::silence()); break; case FmtAlaw: do_realloc(SampleInfo::silence()); break; case FmtIMA4: do_realloc(SampleInfo::silence()); break; case FmtMSADPCM: do_realloc(SampleInfo::silence()); break; } } auto const bufferbytes = std::visit([](auto&& dataspan) -> std::span { return std::as_writable_bytes(dataspan); }, ALBuf->mData); std::ranges::copy(SrcData | std::views::take(newsize), bufferbytes.begin()); #if ALSOFT_EAX eax_x_ram_clear(*context->mALDevice, *ALBuf); #endif ALBuf->mBlockAlign = (DstType == FmtIMA4 || DstType == FmtMSADPCM) ? samplesPerBlock : 1_u32; ALBuf->mOriginalSize = size; ALBuf->mAccess = access; ALBuf->mSampleRate = gsl::narrow_cast(freq); ALBuf->mChannels = DstChannels; ALBuf->mType = DstType; ALBuf->mAmbiOrder = ambiorder; ALBuf->mCallback = nullptr; ALBuf->mUserData = nullptr; ALBuf->mSampleLen = blocks * samplesPerBlock; ALBuf->mLoopStart = 0; ALBuf->mLoopEnd = ALBuf->mSampleLen; #if ALSOFT_EAX if(eax_g_is_enabled && ALBuf->mEaxXRamMode == EaxStorage::Hardware) eax_x_ram_apply(*context->mALDevice, *ALBuf); #endif } /** Prepares the buffer to use the specified callback, using the specified format. */ void PrepareCallback(gsl::not_null const context, gsl::not_null const ALBuf, i32 const freq, FmtChannels const DstChannels, FmtType const DstType, ALBUFFERCALLBACKTYPESOFT const callback, void *const userptr) { if(ALBuf->mRef.load(std::memory_order_relaxed) != 0 || ALBuf->mMappedAccess != 0) context->throw_error(AL_INVALID_OPERATION, "Modifying callback for in-use buffer {}", ALBuf->mId); const auto ambiorder = IsBFormat(DstChannels) ? ALBuf->mUnpackAmbiOrder : (IsUHJ(DstChannels) ? 1_u32 : 0_u32); const auto samplesPerBlock = SanitizeAlignment(DstType, ALBuf->mUnpackAlign); if(samplesPerBlock < 1) context->throw_error(AL_INVALID_VALUE, "Invalid unpack alignment {} for {} samples", ALBuf->mUnpackAlign, NameFromFormat(DstType)); const auto bytesPerBlock = ChannelsFromFmt(DstChannels, ambiorder) * ((DstType == FmtIMA4) ? (samplesPerBlock-1_u32)/2_u32 + 4_u32 : (DstType == FmtMSADPCM) ? (samplesPerBlock-2_u32)/2_u32 + 7_u32 : (samplesPerBlock * 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 auto line_size = DeviceBase::MixerLineSize*MaxPitch + MaxResamplerEdge; const auto line_blocks = (line_size + samplesPerBlock-1_u32) / samplesPerBlock; const auto newsize = line_blocks * bytesPerBlock; auto do_realloc = [ALBuf,newsize](T value) { using vector_t = al::vector; ALBuf->mData = ALBuf->mDataStorage.emplace(newsize/sizeof(T), value); }; switch(DstType) { case FmtUByte: do_realloc(SampleInfo::silence()); break; case FmtShort: do_realloc(SampleInfo::silence()); break; case FmtInt: do_realloc(SampleInfo::silence()); break; case FmtFloat: do_realloc(SampleInfo::silence()); break; case FmtDouble: do_realloc(SampleInfo::silence()); break; case FmtMulaw: do_realloc(SampleInfo::silence()); break; case FmtAlaw: do_realloc(SampleInfo::silence()); break; case FmtIMA4: do_realloc(SampleInfo::silence()); break; case FmtMSADPCM: do_realloc(SampleInfo::silence()); break; } #if ALSOFT_EAX eax_x_ram_clear(*context->mALDevice, *ALBuf); #endif ALBuf->mCallback = callback; ALBuf->mUserData = userptr; ALBuf->mOriginalSize = 0; ALBuf->mAccess = 0; ALBuf->mBlockAlign = (DstType == FmtIMA4 || DstType == FmtMSADPCM) ? samplesPerBlock : 1_u32; ALBuf->mSampleRate = gsl::narrow_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(gsl::not_null const context [[maybe_unused]], gsl::not_null const ALBuf, i32 const freq, FmtChannels const DstChannels, FmtType const DstType, void *const usrdata, u32 const usrdatalen) { if(ALBuf->mRef.load(std::memory_order_relaxed) != 0 || ALBuf->mMappedAccess != 0) context->throw_error(AL_INVALID_OPERATION, "Modifying storage for in-use buffer {}", ALBuf->mId); const auto samplesPerBlock = SanitizeAlignment(DstType, ALBuf->mUnpackAlign); if(samplesPerBlock < 1) context->throw_error(AL_INVALID_VALUE, "Invalid unpack alignment {} for {} samples", ALBuf->mUnpackAlign, NameFromFormat(DstType)); const auto typealign = std::invoke([DstType]() noexcept -> u32 { /* NOTE: This only needs to be the required alignment for the CPU to * read/write the given sample type in the mixer. */ switch(DstType) { 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(MulawSample); case FmtAlaw: return alignof(AlawSample); case FmtIMA4: break; case FmtMSADPCM: break; } return 1; }); /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) */ if((reinterpret_cast(usrdata) & (typealign-1)) != 0) context->throw_error(AL_INVALID_VALUE, "Pointer {} is misaligned for {} samples ({})", usrdata, NameFromFormat(DstType), typealign); const auto ambiorder = IsBFormat(DstChannels) ? ALBuf->mUnpackAmbiOrder : (IsUHJ(DstChannels) ? 1_u32 : 0_u32); /* Convert the size in bytes to blocks using the unpack block alignment. */ const auto NumChannels = ChannelsFromFmt(DstChannels, ambiorder); const auto bytesPerBlock = NumChannels * ((DstType == FmtIMA4) ? (samplesPerBlock-1_u32)/2_u32 + 4_u32 : (DstType == FmtMSADPCM) ? (samplesPerBlock-2_u32)/2_u32 + 7_u32 : (samplesPerBlock * BytesFromFmt(DstType))); if((usrdatalen%bytesPerBlock) != 0) context->throw_error(AL_INVALID_VALUE, "Data size {} is not a multiple of frame size {} ({} unpack alignment)", usrdatalen, bytesPerBlock, samplesPerBlock); const auto blocks = usrdatalen / bytesPerBlock; if(blocks > std::numeric_limits::max()/samplesPerBlock) context->throw_error(AL_OUT_OF_MEMORY, "Buffer size overflow, {} blocks x {} samples per block", blocks, samplesPerBlock); if(blocks > std::numeric_limits::max()/bytesPerBlock) context->throw_error(AL_OUT_OF_MEMORY, "Buffer size overflow, {} frames x {} bytes per frame", blocks, bytesPerBlock); #if ALSOFT_EAX if(ALBuf->mEaxXRamMode == EaxStorage::Hardware) { auto &device = *context->mALDevice; if(!eax_x_ram_check_availability(device, *ALBuf, usrdatalen)) context->throw_error(AL_OUT_OF_MEMORY, "Out of X-RAM memory (avail: {}, needed: {})", device.eax_x_ram_free_size, usrdatalen); } #endif auto do_realloc = [ALBuf,usrdata,usrdatalen](T value [[maybe_unused]]) { ALBuf->mDataStorage.emplace>(); ALBuf->mData = std::span{static_cast(usrdata), usrdatalen/sizeof(T)}; }; switch(DstType) { case FmtUByte: do_realloc(SampleInfo::silence()); break; case FmtShort: do_realloc(SampleInfo::silence()); break; case FmtInt: do_realloc(SampleInfo::silence()); break; case FmtFloat: do_realloc(SampleInfo::silence()); break; case FmtDouble: do_realloc(SampleInfo::silence()); break; case FmtMulaw: do_realloc(SampleInfo::silence()); break; case FmtAlaw: do_realloc(SampleInfo::silence()); break; case FmtIMA4: do_realloc(SampleInfo::silence()); break; case FmtMSADPCM: do_realloc(SampleInfo::silence()); break; } #if ALSOFT_EAX eax_x_ram_clear(*context->mALDevice, *ALBuf); #endif ALBuf->mCallback = nullptr; ALBuf->mUserData = nullptr; ALBuf->mOriginalSize = usrdatalen; ALBuf->mAccess = 0; ALBuf->mBlockAlign = (DstType == FmtIMA4 || DstType == FmtMSADPCM) ? samplesPerBlock : 1_u32; ALBuf->mSampleRate = gsl::narrow_cast(freq); ALBuf->mChannels = DstChannels; ALBuf->mType = DstType; ALBuf->mAmbiOrder = ambiorder; ALBuf->mSampleLen = blocks * samplesPerBlock; ALBuf->mLoopStart = 0; ALBuf->mLoopEnd = ALBuf->mSampleLen; #if ALSOFT_EAX if(ALBuf->mEaxXRamMode == EaxStorage::Hardware) eax_x_ram_apply(*context->mALDevice, *ALBuf); #endif } struct DecompResult { FmtChannels channels; FmtType type; }; auto DecomposeUserFormat(ALenum const 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_BFORMAT3D_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} }, }; if(const auto iter = std::ranges::find(UserFmtList, format, &FormatMap::format); iter != UserFmtList.end()) return iter->result; return std::nullopt; } void alGenBuffers(gsl::not_null const context, ALsizei const n, u32 *const buffers) noexcept try { if(n < 0) context->throw_error(AL_INVALID_VALUE, "Generating {} buffers", n); if(n <= 0) [[unlikely]] return; auto const device = al::get_not_null(context->mALDevice); auto const buflock = std::lock_guard{device->BufferLock}; const auto bids = std::views::counted(buffers, n); if(!EnsureBuffers(device, bids.size())) context->throw_error(AL_OUT_OF_MEMORY, "Failed to allocate {} buffer{}", n, (n==1) ? "" : "s"); std::ranges::generate(bids, [device]{ return AllocBuffer(device)->mId; }); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } void alDeleteBuffers(gsl::not_null const context, ALsizei const n, const u32 *const buffers) noexcept try { if(n < 0) context->throw_error(AL_INVALID_VALUE, "Deleting {} buffers", n); if(n <= 0) [[unlikely]] return; auto const device = al::get_not_null(context->mALDevice); auto const buflock = std::lock_guard{device->BufferLock}; /* First try to find any buffers that are invalid or in-use. */ auto const bids = std::views::counted(buffers, n); std::ranges::for_each(bids, [context](u32 const bid) { if(!bid) return; auto const albuf = LookupBuffer(context, bid); if(albuf->mRef.load(std::memory_order_relaxed) != 0) context->throw_error(AL_INVALID_OPERATION, "Deleting in-use buffer {}", bid); }); /* All good. Delete non-0 buffer IDs. */ std::ranges::for_each(bids, [device](u32 const bid) -> void { if(auto *const buffer = LookupBuffer(std::nothrow, device, bid)) FreeBuffer(device, gsl::make_not_null(buffer)); }); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } auto alIsBuffer(gsl::not_null const context, u32 const buffer) noexcept -> ALboolean { auto const device = al::get_not_null(context->mALDevice); auto const buflock = std::lock_guard{device->BufferLock}; if(buffer == 0 || LookupBuffer(std::nothrow, device, buffer) != nullptr) return AL_TRUE; return AL_FALSE; } void alBufferStorageSOFT(gsl::not_null const context, u32 const buffer, ALenum const format, void const *const data, ALsizei const size, i32 const freq, ALbitfieldSOFT const flags) noexcept try { auto const device = al::get_not_null(context->mALDevice); auto const buflock = std::lock_guard{device->BufferLock}; auto const albuf = LookupBuffer(context, 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 const usrfmt = DecomposeUserFormat(format); if(!usrfmt) context->throw_error(AL_INVALID_ENUM, "Invalid format {:#04x}", as_unsigned(format)); auto *const bdata = static_cast(data); auto const usize = gsl::narrow(size); LoadData(context, albuf, freq, usize, usrfmt->channels, usrfmt->type, std::span{bdata, bdata ? usize : 0_u32}, flags); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } void alBufferData(gsl::not_null const context, u32 const buffer, ALenum const format, void const *const data, ALsizei const size, i32 const freq) noexcept { alBufferStorageSOFT(context, buffer, format, data, size, freq, 0); } void alBufferDataStatic(gsl::not_null const context, u32 const buffer, ALenum const format, void *const data, ALsizei const size, i32 const freq) noexcept try { auto const device = al::get_not_null(context->mALDevice); auto const buflock = std::lock_guard{device->BufferLock}; auto const albuf = LookupBuffer(context, 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, data, gsl::narrow(size)); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } void alBufferCallbackSOFT(gsl::not_null const context, u32 const buffer, ALenum const format, i32 const freq, ALBUFFERCALLBACKTYPESOFT const callback, void *const userptr) noexcept try { auto const device = al::get_not_null(context->mALDevice); auto const buflock = std::lock_guard{device->BufferLock}; auto const albuf = LookupBuffer(context, 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()); } void alBufferSubDataSOFT(gsl::not_null const context, u32 const buffer, ALenum const format, void const *const data, ALsizei const offset, ALsizei const length) noexcept try { auto const device = al::get_not_null(context->mALDevice); auto const buflock = std::lock_guard{device->BufferLock}; auto const albuf = LookupBuffer(context, buffer); auto const usrfmt = DecomposeUserFormat(format); if(!usrfmt) context->throw_error(AL_INVALID_ENUM, "Invalid format {:#04x}", as_unsigned(format)); const auto unpack_align = albuf->mUnpackAlign; const auto 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->mUnpackAmbiOrder != albuf->mAmbiOrder) context->throw_error(AL_INVALID_VALUE, "Unpacking data with mismatched ambisonic order"); if(albuf->mMappedAccess != 0) context->throw_error(AL_INVALID_OPERATION, "Unpacking data into mapped buffer {}", buffer); const auto num_chans = albuf->channelsFromFmt(); const auto byte_align = (albuf->mType == FmtIMA4) ? ((align-1u)/2u + 4u) * num_chans : (albuf->mType == FmtMSADPCM) ? ((align-2u)/2u + 7u) * num_chans : (align * albuf->bytesFromFmt() * num_chans); if(offset < 0 || length < 0 || gsl::narrow_cast(offset) > albuf->mOriginalSize || gsl::narrow_cast(length) > albuf->mOriginalSize - gsl::narrow_cast(offset)) context->throw_error(AL_INVALID_VALUE, "Invalid data sub-range {}+{} on buffer {}", offset, length, buffer); if((gsl::narrow_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((gsl::narrow_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); auto bufferbytes = std::visit([](auto &datavec) { return std::as_writable_bytes(datavec); }, albuf->mData); std::ranges::copy(std::views::counted(static_cast(data), length), (bufferbytes | std::views::drop(offset)).begin()); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } auto alMapBufferSOFT(gsl::not_null const context, u32 const buffer, ALsizei const offset, ALsizei const length, ALbitfieldSOFT const access) noexcept -> void* try { auto const device = al::get_not_null(context->mALDevice); auto const buflock = std::lock_guard{device->BufferLock}; auto const albuf = LookupBuffer(context, 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); auto const unavailable = (albuf->mAccess^access) & access; if(albuf->mRef.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->mMappedAccess != 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 || gsl::narrow_cast(offset) >= albuf->mOriginalSize || gsl::narrow_cast(length) > albuf->mOriginalSize - gsl::narrow_cast(offset)) context->throw_error(AL_INVALID_VALUE, "Mapping invalid range {}+{} for buffer {}", offset, length, buffer); auto *const retval = std::visit([ptroff=gsl::narrow_cast(offset)](auto &datavec) { return &std::as_writable_bytes(datavec)[ptroff]; }, albuf->mData); albuf->mMappedAccess = access; albuf->mMappedOffset = offset; albuf->mMappedSize = length; return retval; } catch(al::base_exception&) { return nullptr; } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); return nullptr; } void alUnmapBufferSOFT(gsl::not_null const context, u32 const buffer) noexcept try { auto const device = al::get_not_null(context->mALDevice); auto const buflock = std::lock_guard{device->BufferLock}; auto const albuf = LookupBuffer(context, buffer); if(albuf->mMappedAccess == 0) context->throw_error(AL_INVALID_OPERATION, "Unmapping unmapped buffer {}", buffer); albuf->mMappedAccess = 0; albuf->mMappedOffset = 0; albuf->mMappedSize = 0; } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } void alFlushMappedBufferSOFT(gsl::not_null const context, u32 const buffer, ALsizei const offset, ALsizei const length) noexcept try { auto const device = al::get_not_null(context->mALDevice); auto const buflock = std::lock_guard{device->BufferLock}; auto const albuf = LookupBuffer(context, buffer); if(!(albuf->mMappedAccess&AL_MAP_WRITE_BIT_SOFT)) context->throw_error(AL_INVALID_OPERATION, "Flushing buffer {} while not mapped for writing", buffer); if(offset < albuf->mMappedOffset || length <= 0 || offset >= albuf->mMappedOffset+albuf->mMappedSize || length > albuf->mMappedOffset+albuf->mMappedSize-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()); } void alBufferf(gsl::not_null const context, u32 const buffer, ALenum const param, f32 const value [[maybe_unused]]) noexcept try { auto const device = al::get_not_null(context->mALDevice); auto const buflock [[maybe_unused]] = std::lock_guard{device->BufferLock}; std::ignore = LookupBuffer(context, 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()); } void alBuffer3f(gsl::not_null const context, u32 const buffer, ALenum const param, f32 const value1 [[maybe_unused]], f32 const value2 [[maybe_unused]], f32 const value3 [[maybe_unused]]) noexcept try { auto const device = al::get_not_null(context->mALDevice); auto const buflock [[maybe_unused]] = std::lock_guard{device->BufferLock}; std::ignore = LookupBuffer(context, 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()); } void alBufferfv(gsl::not_null const context, u32 const buffer, ALenum const param, f32 const *const values) noexcept try { auto const device = al::get_not_null(context->mALDevice); auto const buflock [[maybe_unused]] = std::lock_guard{device->BufferLock}; std::ignore = LookupBuffer(context, 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()); } void alBufferi(gsl::not_null const context, u32 const buffer, ALenum const param, i32 const value) noexcept try { auto const device = al::get_not_null(context->mALDevice); auto const buflock = std::lock_guard{device->BufferLock}; auto const albuf = LookupBuffer(context, buffer); switch(param) { case AL_UNPACK_BLOCK_ALIGNMENT_SOFT: if(value < 0) context->throw_error(AL_INVALID_VALUE, "Invalid unpack block alignment {}", value); albuf->mUnpackAlign = gsl::narrow_cast(value); return; case AL_PACK_BLOCK_ALIGNMENT_SOFT: if(value < 0) context->throw_error(AL_INVALID_VALUE, "Invalid pack block alignment {}", value); albuf->mPackAlign = gsl::narrow_cast(value); return; case AL_AMBISONIC_LAYOUT_SOFT: if(albuf->mRef.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->mRef.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->mUnpackAmbiOrder = gsl::narrow_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()); } void alBuffer3i(gsl::not_null const context, u32 const buffer, ALenum const param, i32 const value1 [[maybe_unused]], i32 const value2 [[maybe_unused]], i32 const value3 [[maybe_unused]]) noexcept try { auto const device = al::get_not_null(context->mALDevice); auto const buflock [[maybe_unused]] = std::lock_guard{device->BufferLock}; std::ignore = LookupBuffer(context, 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()); } void alBufferiv(gsl::not_null const context, u32 const buffer, ALenum const param, i32 const *const 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: alBufferi(context, buffer, param, *values); return; } auto const device = al::get_not_null(context->mALDevice); auto const buflock = std::lock_guard{device->BufferLock}; auto const albuf = LookupBuffer(context, buffer); switch(param) { case AL_LOOP_POINTS_SOFT: const auto vals = std::span{values, 2_uz}; if(albuf->mRef.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] || gsl::narrow_cast(vals[1]) > albuf->mSampleLen) context->throw_error(AL_INVALID_VALUE, "Invalid loop point range {} -> {} on buffer {}", vals[0], vals[1], buffer); albuf->mLoopStart = gsl::narrow_cast(vals[0]); albuf->mLoopEnd = gsl::narrow_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()); } void alGetBufferf(gsl::not_null const context, u32 const buffer, ALenum const param, f32 *const value) noexcept try { auto const device = al::get_not_null(context->mALDevice); auto const buflock = std::lock_guard{device->BufferLock}; auto const albuf = LookupBuffer(context, buffer); if(!value) context->throw_error(AL_INVALID_VALUE, "NULL pointer"); switch(param) { case AL_SEC_LENGTH_SOFT: *value = (albuf->mSampleRate < 1) ? 0.0_f32 : (gsl::narrow_cast(albuf->mSampleLen)/gsl::narrow_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()); } void alGetBuffer3f(gsl::not_null const context, u32 const buffer, ALenum const param, f32 *const value1, f32 *const value2, f32 *const value3) noexcept try { auto const device = al::get_not_null(context->mALDevice); auto const buflock [[maybe_unused]] = std::lock_guard{device->BufferLock}; std::ignore = LookupBuffer(context, 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()); } void alGetBufferfv(gsl::not_null const context, u32 const buffer, ALenum const param, f32 *const values) noexcept try { switch(param) { case AL_SEC_LENGTH_SOFT: alGetBufferf(context, buffer, param, values); return; } auto const device = al::get_not_null(context->mALDevice); auto const buflock [[maybe_unused]] = std::lock_guard{device->BufferLock}; std::ignore = LookupBuffer(context, 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()); } void alGetBufferi(gsl::not_null const context, u32 const buffer, ALenum const param, i32 *const value) noexcept try { auto const device = al::get_not_null(context->mALDevice); auto const buflock = std::lock_guard{device->BufferLock}; auto const albuf = LookupBuffer(context, buffer); if(!value) context->throw_error(AL_INVALID_VALUE, "NULL pointer"); switch(param) { case AL_FREQUENCY: *value = gsl::narrow_cast(albuf->mSampleRate); return; case AL_BITS: *value = (albuf->mType == FmtIMA4 || albuf->mType == FmtMSADPCM) ? 4_i32 : gsl::narrow_cast(albuf->bytesFromFmt() * 8_u32); return; case AL_CHANNELS: *value = gsl::narrow_cast(albuf->channelsFromFmt()); return; case AL_SIZE: if(albuf->mCallback) *value = 0; else *value = std::visit([](auto &dataspan) -> i32 { return gsl::narrow_cast(dataspan.size_bytes()); }, albuf->mData); return; case AL_BYTE_LENGTH_SOFT: *value = gsl::narrow_cast(albuf->mSampleLen / albuf->mBlockAlign * albuf->blockSizeFromFmt()); return; case AL_SAMPLE_LENGTH_SOFT: *value = gsl::narrow_cast(albuf->mSampleLen); return; case AL_UNPACK_BLOCK_ALIGNMENT_SOFT: *value = gsl::narrow_cast(albuf->mUnpackAlign); return; case AL_PACK_BLOCK_ALIGNMENT_SOFT: *value = gsl::narrow_cast(albuf->mPackAlign); 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 = gsl::narrow_cast(albuf->mUnpackAmbiOrder); 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()); } void alGetBuffer3i(gsl::not_null const context, u32 const buffer, ALenum const param, i32 *const value1, i32 *const value2, i32 *const value3) noexcept try { auto const device = al::get_not_null(context->mALDevice); auto const buflock [[maybe_unused]] = std::lock_guard{device->BufferLock}; std::ignore = LookupBuffer(context, 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()); } void alGetBufferiv(gsl::not_null const context, u32 const buffer, ALenum const param, i32 *const 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: alGetBufferi(context, buffer, param, values); return; } auto const device = al::get_not_null(context->mALDevice); auto const buflock = std::lock_guard{device->BufferLock}; auto const albuf = LookupBuffer(context, buffer); if(!values) context->throw_error(AL_INVALID_VALUE, "NULL pointer"); switch(param) { case AL_LOOP_POINTS_SOFT: const auto vals = std::span{values, 2_uz}; vals[0] = gsl::narrow_cast(albuf->mLoopStart); vals[1] = gsl::narrow_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()); } void alGetBufferPtrSOFT(gsl::not_null const context, u32 const buffer, ALenum const param, void **const value) noexcept try { auto const device = al::get_not_null(context->mALDevice); auto const buflock = std::lock_guard{device->BufferLock}; auto const albuf = LookupBuffer(context, buffer); if(!value) context->throw_error(AL_INVALID_VALUE, "NULL pointer"); switch(param) { case AL_BUFFER_CALLBACK_FUNCTION_SOFT: /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) */ *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()); } void alGetBuffer3PtrSOFT(gsl::not_null const context, u32 const buffer, ALenum const param, void **const value1, void **const value2, void **const value3) noexcept try { auto const device = al::get_not_null(context->mALDevice); auto const buflock [[maybe_unused]] = std::lock_guard{device->BufferLock}; std::ignore = LookupBuffer(context, 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()); } void alGetBufferPtrvSOFT(gsl::not_null const context, u32 const buffer, ALenum const param, void **const values) noexcept try { switch(param) { case AL_BUFFER_CALLBACK_FUNCTION_SOFT: case AL_BUFFER_CALLBACK_USER_PARAM_SOFT: alGetBufferPtrSOFT(context, buffer, param, values); return; } auto const device = al::get_not_null(context->mALDevice); auto const buflock [[maybe_unused]] = std::lock_guard{device->BufferLock}; std::ignore = LookupBuffer(context, 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()); } #if ALSOFT_EAX auto EAXSetBufferMode(gsl::not_null const context, ALsizei const n, u32 const *const buffers, i32 const value) noexcept -> ALboolean 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 const device = al::get_not_null(context->mALDevice); auto const devlock = std::lock_guard{device->BufferLock}; /* Special-case setting a single buffer, to avoid extraneous allocations. */ if(n == 1) { auto const bufid = *buffers; if(bufid == AL_NONE) return AL_TRUE; auto const buffer = LookupBuffer(context, 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->mEaxXRamIsHardware && buffer->mOriginalSize > device->eax_x_ram_free_size) context->throw_error(AL_OUT_OF_MEMORY, "Out of X-RAM memory (need: {}, avail: {})", buffer->mOriginalSize, device->eax_x_ram_free_size); eax_x_ram_apply(*device, *buffer); } else eax_x_ram_clear(*device, *buffer); buffer->mEaxXRamMode = *storage; return AL_TRUE; } /* Validate the buffers. */ auto buflist = std::unordered_set>{}; for(u32 const bufid : std::views::counted(buffers, n)) { if(bufid == AL_NONE) continue; auto const buffer = LookupBuffer(context, 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) { auto total_needed = 0_uz; for(auto const &buffer : buflist) { if(!buffer->mEaxXRamIsHardware) { if(std::numeric_limits::max() - buffer->mOriginalSize < total_needed) context->throw_error(AL_OUT_OF_MEMORY, "Size overflow ({} + {})", buffer->mOriginalSize, total_needed); total_needed += buffer->mOriginalSize; } } 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(auto const buffer : buflist) { if(*storage == EaxStorage::Hardware) eax_x_ram_apply(*device, *buffer); else eax_x_ram_clear(*device, *buffer); buffer->mEaxXRamMode = *storage; } return AL_TRUE; } catch(al::base_exception&) { return AL_FALSE; } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); return AL_FALSE; } auto EAXGetBufferMode(gsl::not_null const context, u32 const buffer, i32 *const pReserved) noexcept -> ALenum 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 const device = al::get_not_null(context->mALDevice); auto const devlock = std::lock_guard{device->BufferLock}; auto const al_buffer = LookupBuffer(context, buffer); return EnumFromEaxStorage(al_buffer->mEaxXRamMode); } catch(al::base_exception&) { return AL_NONE; } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); return AL_NONE; } #endif /* ALSOFT_EAX */ } // namespace AL_API DECL_FUNC2(void, alGenBuffers, ALsizei,n, ALuint*,buffers) AL_API DECL_FUNC2(void, alDeleteBuffers, ALsizei,n, const ALuint*,buffers) AL_API DECL_FUNC1(ALboolean, alIsBuffer, ALuint,buffer) AL_API DECL_FUNC5(void, alBufferData, ALuint,buffer, ALenum,format, const ALvoid*,data, ALsizei,size, ALsizei,freq) AL_API DECL_FUNCEXT6(void, alBufferStorage,SOFT, ALuint,buffer, ALenum,format, const ALvoid*,data, ALsizei,size, ALsizei,freq, ALbitfieldSOFT,flags) FORCE_ALIGN DECL_FUNC5(void, alBufferDataStatic, ALuint,buffer, ALenum,format, ALvoid*,data, ALsizei,size, ALsizei,freq) AL_API DECL_FUNCEXT5(void, alBufferCallback,SOFT, ALuint,buffer, ALenum,format, ALsizei,freq, ALBUFFERCALLBACKTYPESOFT,callback, ALvoid*,userptr) AL_API DECL_FUNCEXT5(void, alBufferSubData,SOFT, ALuint,buffer, ALenum,format, const ALvoid*,data, ALsizei,offset, ALsizei,length) AL_API DECL_FUNCEXT4(void*, alMapBuffer,SOFT, ALuint,buffer, ALsizei,offset, ALsizei,length, ALbitfieldSOFT,access) AL_API DECL_FUNCEXT1(void, alUnmapBuffer,SOFT, ALuint,buffer) AL_API DECL_FUNCEXT3(void, alFlushMappedBuffer,SOFT, ALuint,buffer, ALsizei,offset, ALsizei,length) AL_API DECL_FUNC3(void, alBufferf, ALuint,buffer, ALenum,param, ALfloat,value) AL_API DECL_FUNC5(void, alBuffer3f, ALuint,buffer, ALenum,param, ALfloat,value1, ALfloat,value2, ALfloat,value3) AL_API DECL_FUNC3(void, alBufferfv, ALuint,buffer, ALenum,param, const ALfloat*,values) AL_API DECL_FUNC3(void, alBufferi, ALuint,buffer, ALenum,param, ALint,value) AL_API DECL_FUNC5(void, alBuffer3i, ALuint,buffer, ALenum,param, ALint,value1, ALint,value2, ALint,value3) AL_API DECL_FUNC3(void, alBufferiv, ALuint,buffer, ALenum,param, const ALint*,values) AL_API DECL_FUNC3(void, alGetBufferf, ALuint,buffer, ALenum,param, ALfloat*,value) AL_API DECL_FUNC5(void, alGetBuffer3f, ALuint,buffer, ALenum,param, ALfloat*,value1, ALfloat*,value2, ALfloat*,value3) AL_API DECL_FUNC3(void, alGetBufferfv, ALuint,buffer, ALenum,param, ALfloat*,values) AL_API DECL_FUNC3(void, alGetBufferi, ALuint,buffer, ALenum,param, ALint*,value) AL_API DECL_FUNC5(void, alGetBuffer3i, ALuint,buffer, ALenum,param, ALint*,value1, ALint*,value2, ALint*,value3) AL_API DECL_FUNC3(void, alGetBufferiv, ALuint,buffer, ALenum,param, ALint*,values) AL_API DECL_FUNCEXT3(void, alGetBufferPtr,SOFT, ALuint,buffer, ALenum,param, ALvoid**,value) AL_API DECL_FUNCEXT5(void, alGetBuffer3Ptr,SOFT, ALuint,buffer, ALenum,param, ALvoid**,value1, ALvoid**,value2, ALvoid**,value3) AL_API DECL_FUNCEXT3(void, alGetBufferPtrv,SOFT, ALuint,buffer, ALenum,param, ALvoid**,values) #if ALSOFT_EAX FORCE_ALIGN DECL_FUNC3(ALboolean, EAXSetBufferMode, ALsizei,n, const ALuint*,buffers, ALint,value) FORCE_ALIGN DECL_FUNC2(ALenum, EAXGetBufferMode, ALuint,buffer, ALint*,pReserved) #endif // ALSOFT_EAX AL_API void AL_APIENTRY alBufferSamplesSOFT(ALuint /*buffer*/, ALuint /*samplerate*/, ALenum /*internalformat*/, ALsizei /*samples*/, ALenum /*channels*/, ALenum /*type*/, const ALvoid* /*data*/) noexcept { const auto 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 { const auto 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 { const auto context = GetContextRef(); if(!context) [[unlikely]] return; context->setError(AL_INVALID_OPERATION, "alGetBufferSamplesSOFT not supported"); } AL_API auto AL_APIENTRY alIsBufferFormatSupportedSOFT(ALenum /*format*/) noexcept -> ALboolean { const auto context = GetContextRef(); if(!context) [[unlikely]] return AL_FALSE; context->setError(AL_INVALID_OPERATION, "alIsBufferFormatSupportedSOFT not supported"); return AL_FALSE; } void al::Buffer::SetName(gsl::not_null const context, u32 const id, std::string_view const name) { auto const device = al::get_not_null(context->mALDevice); auto const buflock = std::lock_guard{device->BufferLock}; std::ignore = LookupBuffer(context, id); device->mBufferNames.insert_or_assign(id, name); } BufferSubList::~BufferSubList() { if(!mBuffers) return; auto usemask = ~mFreeMask; while(usemask) { auto const idx = std::countr_zero(usemask); std::destroy_at(std::to_address(mBuffers->begin() + idx)); usemask &= ~(1_u64 << idx); } mFreeMask = ~usemask; SubListAllocator{}.deallocate(mBuffers, 1); mBuffers = nullptr; } kcat-openal-soft-75c0059/al/buffer.h000066400000000000000000000045501512220627100171570ustar00rootroot00000000000000#ifndef AL_BUFFER_H #define AL_BUFFER_H #include "config.h" #include #include #include #include #include #include #include "AL/al.h" #include "alc/inprogext.h" #include "almalloc.h" #include "alnumeric.h" #include "core/buffer_storage.h" #include "gsl/gsl" #include "intrusive_ptr.h" #include "vector.h" #if ALSOFT_EAX enum class EaxStorage : u8 { Automatic, Accessible, Hardware }; #endif // ALSOFT_EAX namespace al { struct Context; struct Buffer : BufferStorage { ALbitfieldSOFT mAccess{0u}; std::variant, al::vector, al::vector, al::vector, al::vector, al::vector, al::vector, al::vector, al::vector> mDataStorage; u32 mOriginalSize{0_u32}; u32 mUnpackAlign{0_u32}; u32 mPackAlign{0_u32}; u32 mUnpackAmbiOrder{1_u32}; u32 mMappedAccess{0_u32}; i32 mMappedOffset{0_i32}; i32 mMappedSize{0_i32}; u32 mLoopStart{0_u32}; u32 mLoopEnd{0_u32}; std::atomic mRef{0_u32}; /* Self ID */ u32 mId{0_u32}; auto inc_ref() noexcept { return mRef.fetch_add(1, std::memory_order_acq_rel)+1; } auto dec_ref() noexcept { return mRef.fetch_sub(1, std::memory_order_acq_rel)-1; } auto newReference() noexcept { mRef.fetch_add(1, std::memory_order_acq_rel); return al::intrusive_ptr{this}; } static void SetName(gsl::not_null context, u32 id, std::string_view name); DISABLE_ALLOC #if ALSOFT_EAX EaxStorage mEaxXRamMode{EaxStorage::Automatic}; bool mEaxXRamIsHardware{}; #endif // ALSOFT_EAX }; } /* namespace al */ struct BufferSubList { u64 mFreeMask{~0_u64}; gsl::owner*> mBuffers{nullptr}; BufferSubList() noexcept = default; BufferSubList(const BufferSubList&) = delete; BufferSubList(BufferSubList&& rhs) noexcept : mFreeMask{rhs.mFreeMask}, mBuffers{rhs.mBuffers} { rhs.mFreeMask = ~0_u64; rhs.mBuffers = nullptr; } ~BufferSubList(); BufferSubList& operator=(const BufferSubList&) = delete; BufferSubList& operator=(BufferSubList&& rhs) noexcept { std::swap(mFreeMask, rhs.mFreeMask); std::swap(mBuffers, rhs.mBuffers); return *this; } }; #endif kcat-openal-soft-75c0059/al/debug.cpp000066400000000000000000000613211512220627100173260ustar00rootroot00000000000000#include "config.h" #include "debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "AL/al.h" #include "AL/alext.h" #include "alc/context.h" #include "alc/device.h" #include "alformat.hpp" #include "alnumeric.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 "gsl/gsl" #include "opthelpers.h" #include "source.h" 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{al::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{al::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{al::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; } void alDebugMessageCallbackEXT(gsl::not_null context, ALDEBUGPROCEXT callback, void *userParam) noexcept { auto debuglock = std::lock_guard{context->mDebugCbLock}; context->mDebugCb = callback; context->mDebugParam = userParam; } void alDebugMessageInsertEXT(gsl::not_null 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"); const auto msgview = (length < 0) ? std::string_view{message} : std::string_view{message, gsl::narrow_cast(length)}; if(msgview.size() >= MaxDebugMessageLength) context->throw_error(AL_INVALID_VALUE, "Debug message too long ({} >= {})", msgview.size(), MaxDebugMessageLength); const 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)); const auto dtype = GetDebugType(type); if(!dtype) context->throw_error(AL_INVALID_ENUM, "Invalid debug type {:#04x}", as_unsigned(type)); const 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()); } void alDebugMessageControlEXT(gsl::not_null 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 auto ElemCount = DebugSourceCount + DebugTypeCount + DebugSeverityCount; static constexpr auto Values = make_array_sequence(); auto srcIdxs = std::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)); srcIdxs = srcIdxs.subspan(al::to_underlying(*dsource), 1); } auto typeIdxs = std::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)); typeIdxs = typeIdxs.subspan(al::to_underlying(*dtype), 1); } auto svrIdxs = std::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)); svrIdxs = svrIdxs.subspan(al::to_underlying(*dseverity), 1); } auto debuglock = std::lock_guard{context->mDebugCbLock}; auto &debug = context->mDebugGroups.back(); if(count > 0) { const auto filterbase = (1u< context, ALenum source, ALuint id, ALsizei length, const ALchar *message) noexcept try { if(length < 0) { const auto newlen = std::strlen(message); if(newlen >= MaxDebugMessageLength) context->throw_error(AL_INVALID_VALUE, "Debug message too long ({} >= {})", newlen, MaxDebugMessageLength); length = gsl::narrow_cast(newlen); } else if(length >= MaxDebugMessageLength) context->throw_error(AL_INVALID_VALUE, "Debug message too long ({} >= {})", length, MaxDebugMessageLength); const 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 debuglock = std::unique_lock{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, gsl::narrow_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()); } void alPopDebugGroupEXT(gsl::not_null context) noexcept try { auto debuglock = std::unique_lock{context->mDebugCbLock}; if(context->mDebugGroups.size() <= 1) context->throw_error(AL_STACK_UNDERFLOW_EXT, "Attempting to pop the default debug group"); auto &debug = context->mDebugGroups.back(); const auto source = debug.mSource; const auto id = debug.mId; auto 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()); } auto alGetDebugMessageLogEXT(gsl::not_null context, ALuint count, ALsizei logBufSize, ALenum *sources, ALenum *types, ALuint *ids, ALenum *severities, ALsizei *lengths, ALchar *logBuf) noexcept -> ALuint try { if(logBuf && logBufSize < 0) context->throw_error(AL_INVALID_VALUE, "Negative debug log buffer size"); auto debuglock = std::lock_guard{context->mDebugCbLock}; /* Calculate the number of log entries to get, depending on the log buffer * size (if applicable), the number of logged messages, and the requested * count. */ const auto toget = std::invoke([context,count,logBuf,logBufSize]() -> ALuint { /* NOTE: The log buffer size is relevant when the log buffer is * non-NULL, including when the size is 0. */ if(logBuf) { const auto logSpan = std::views::counted(logBuf, logBufSize); auto counter = 0_uz; auto todo = 0u; std::ignore = std::ranges::find_if(context->mDebugLog | std::views::take(count), [logSpan,&counter,&todo](const DebugLogEntry &entry) noexcept -> bool { const auto tocopy = size_t{entry.mMessage.size() + 1}; if(tocopy > logSpan.size()-counter) return true; counter += tocopy; ++todo; return false; }); return todo; } return gsl::narrow_cast(std::min(context->mDebugLog.size(), size_t{count})); }); if(toget < 1) return 0; auto logrange = context->mDebugLog | std::views::take(toget); if(sources) std::ranges::transform(logrange | std::views::transform(&DebugLogEntry::mSource), std::span{sources, toget}.begin(), GetDebugSourceEnum); if(types) std::ranges::transform(logrange | std::views::transform(&DebugLogEntry::mType), std::span{types, toget}.begin(), GetDebugTypeEnum); if(ids) std::ranges::transform(logrange, std::span{ids, toget}.begin(), &DebugLogEntry::mId); if(severities) std::ranges::transform(logrange | std::views::transform(&DebugLogEntry::mSeverity), std::span{severities, toget}.begin(), GetDebugSeverityEnum); if(lengths) { std::ranges::transform(logrange, std::span{lengths, toget}.begin(), [](const std::string_view msg) { return gsl::narrow_cast(msg.size()+1); }, &DebugLogEntry::mMessage); } if(logBuf) { const auto logSpan = std::views::counted(logBuf, logBufSize); /* C++23... std::ranges::copy(logrange | std::views::transform(&DebugLogEntry::mMessage) | std::views::join_with('\0'), logSpan.begin()); */ auto logiter = logSpan.begin(); std::ranges::for_each(logrange, [&logiter](const std::string_view msg) { logiter = std::ranges::copy(msg, logiter).out; *(logiter++) = '\0'; }, &DebugLogEntry::mMessage); } /* FIXME: Ugh. Calling erase(begin(), begin()+toget) causes an error since * DebugLogEntry can't be moved/copied. Not sure how else to pop a number * of elements from the front of a deque without it trying to instantiate a * move/copy. */ std::ranges::for_each(std::views::iota(0u, toget), [context](auto&&){ context->mDebugLog.pop_front(); }); return toget; } catch(al::base_exception&) { return 0; } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); return 0; } void alObjectLabelEXT(gsl::not_null 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, gsl::narrow_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: al::Source::SetName(context, name, objname); return; case AL_BUFFER: al::Buffer::SetName(context, name, objname); return; case AL_FILTER_EXT: al::Filter::SetName(context, name, objname); return; case AL_EFFECT_EXT: al::Effect::SetName(context, name, objname); return; case AL_AUXILIARY_EFFECT_SLOT_EXT: al::EffectSlot::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()); } void alGetObjectLabelEXT(gsl::not_null 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 = std::views::counted(label, label ? bufSize : 0); auto copy_name = [name,length,labelOut](std::unordered_map &names) { const auto objname = std::invoke([name,&names] { if(auto iter = names.find(name); iter != names.end()) return std::string_view{iter->second}; return std::string_view{}; }); if(labelOut.empty()) *length = gsl::narrow_cast(objname.size()); else { const auto namerange = objname | std::views::take(labelOut.size()-1); auto oiter = std::ranges::copy(namerange, labelOut.begin()).out; *oiter = '\0'; if(length) *length = gsl::narrow_cast(namerange.size()); } }; if(identifier == AL_SOURCE_EXT) { auto srclock = std::lock_guard{context->mSourceLock}; copy_name(context->mSourceNames); } else if(identifier == AL_BUFFER) { auto const device = al::get_not_null(context->mALDevice); auto buflock = std::lock_guard{device->BufferLock}; copy_name(device->mBufferNames); } else if(identifier == AL_FILTER_EXT) { auto const device = al::get_not_null(context->mALDevice); auto buflock = std::lock_guard{device->FilterLock}; copy_name(device->mFilterNames); } else if(identifier == AL_EFFECT_EXT) { auto const device = al::get_not_null(context->mALDevice); auto buflock = std::lock_guard{device->EffectLock}; copy_name(device->mEffectNames); } else if(identifier == AL_AUXILIARY_EFFECT_SLOT_EXT) { auto slotlock = std::lock_guard{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()); } } // namespace void al::Context::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; } auto &debug = mDebugGroups.back(); const auto idfilter = (1_u64 << (DebugSourceBase+al::to_underlying(source))) | (1_u64 << (DebugTypeBase+al::to_underlying(type))) | (uint64_t{id} << 32); const auto iditer = std::ranges::lower_bound(debug.mIdFilters, idfilter); if(iditer != debug.mIdFilters.cend() && *iditer == idfilter) return; const auto filter = (1u << (DebugSourceBase+al::to_underlying(source))) | (1u << (DebugTypeBase+al::to_underlying(type))) | (1u << (DebugSeverityBase+al::to_underlying(severity))); const auto iter = std::ranges::lower_bound(debug.mFilters, 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), gsl::narrow_cast(message.size()), message.data(), param); /* NOLINT(bugprone-suspicious-stringview-data-usage) */ } 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 DECL_FUNCEXT6(void, alDebugMessageInsert,EXT, ALenum,source, ALenum,type, ALuint,id, ALenum,severity, ALsizei,length, const ALchar*,message) FORCE_ALIGN DECL_FUNCEXT6(void, alDebugMessageControl,EXT, ALenum,source, ALenum,type, ALenum,severity, ALsizei,count, const ALuint*,ids, ALboolean,enable) FORCE_ALIGN DECL_FUNCEXT4(void, alPushDebugGroup,EXT, ALenum,source, ALuint,id, ALsizei,length, const ALchar*,message) FORCE_ALIGN DECL_FUNCEXT(void, alPopDebugGroup,EXT) 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 DECL_FUNCEXT4(void, alObjectLabel,EXT, ALenum,identifier, ALuint,name, ALsizei,length, const ALchar*,label) FORCE_ALIGN DECL_FUNCEXT5(void, alGetObjectLabel,EXT, ALenum,identifier, ALuint,name, ALsizei,bufSize, ALsizei*,length, ALchar*,label) kcat-openal-soft-75c0059/al/debug.h000066400000000000000000000032301512220627100167660ustar00rootroot00000000000000#ifndef AL_DEBUG_H #define AL_DEBUG_H #include #include #include #include #include "alnumeric.h" #include "opthelpers.h" /* Somewhat arbitrary. Avoid letting it get out of control if the app enables * logging but never reads it. */ inline constexpr auto MaxDebugLoggedMessages = 64_u8; inline constexpr auto MaxDebugMessageLength = 1024_u16; inline constexpr auto MaxDebugGroupDepth = 64_u8; inline constexpr auto MaxObjectLabelLength = 1024_u16; inline constexpr auto DebugSourceBase = 0_u32; enum class DebugSource : u8 { API = 0, System, ThirdParty, Application, Other, }; inline constexpr auto DebugSourceCount = 5_u32; inline constexpr auto DebugTypeBase = DebugSourceBase + DebugSourceCount; enum class DebugType : u8 { Error = 0, DeprecatedBehavior, UndefinedBehavior, Portability, Performance, Marker, PushGroup, PopGroup, Other, }; inline constexpr auto DebugTypeCount = 9_u32; inline constexpr auto DebugSeverityBase = DebugTypeBase + DebugTypeCount; enum class DebugSeverity : u8 { High = 0, Medium, Low, Notification, }; inline constexpr auto DebugSeverityCount = 4_u32; struct DebugGroup { u32 const mId; DebugSource const mSource; std::string mMessage; std::vector mFilters; std::vector mIdFilters; template DebugGroup(DebugSource const source, u32 const id, T&& message) : mId{id}, mSource{source}, mMessage{std::forward(message)} { } DebugGroup(const DebugGroup&) = default; DebugGroup(DebugGroup&&) = default; NOINLINE ~DebugGroup() = default; }; #endif /* AL_DEBUG_H */ kcat-openal-soft-75c0059/al/direct_defs.h000066400000000000000000000322631512220627100201630ustar00rootroot00000000000000#ifndef AL_DIRECT_DEFS_H #define AL_DIRECT_DEFS_H #include "alc/context.h" #include "gsl/gsl" namespace al { inline auto verify_context(ALCcontext *context) -> gsl::not_null { /* TODO: A debug/non-optimized build should essentially do * al::get_not_null(VerifyContext(context)) to ensure the ALCcontext handle * is valid, not just non-null. */ /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-static-cast-downcast) */ return gsl::make_not_null(static_cast(context)); } } 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 const context = GetContextRef(); \ if(!context) [[unlikely]] return detail_::DefaultVal(); \ return Name(gsl::make_not_null(context.get())); \ } \ FORCE_ALIGN auto AL_APIENTRY Name##Direct(ALCcontext *context) noexcept -> R \ { \ return Name(al::verify_context(context)); \ } #define DECL_FUNC1(R, Name, T1,n1) \ auto AL_APIENTRY Name(T1 n1) noexcept -> R \ { \ auto const context = GetContextRef(); \ if(!context) [[unlikely]] return detail_::DefaultVal(); \ return Name(gsl::make_not_null(context.get()), n1); \ } \ FORCE_ALIGN auto AL_APIENTRY Name##Direct(ALCcontext *context, T1 n1) noexcept\ -> R \ { \ return Name(al::verify_context(context), n1); \ } #define DECL_FUNC2(R, Name, T1,n1, T2,n2) \ auto AL_APIENTRY Name(T1 n1, T2 n2) noexcept -> R \ { \ auto const context = GetContextRef(); \ if(!context) [[unlikely]] return detail_::DefaultVal(); \ return Name(gsl::make_not_null(context.get()), n1, n2); \ } \ FORCE_ALIGN auto AL_APIENTRY Name##Direct(ALCcontext *context, T1 n1, T2 n2) \ noexcept -> R \ { \ return Name(al::verify_context(context), 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 const context = GetContextRef(); \ if(!context) [[unlikely]] return detail_::DefaultVal(); \ return Name(gsl::make_not_null(context.get()), n1, n2, n3); \ } \ FORCE_ALIGN auto AL_APIENTRY Name##Direct(ALCcontext *context, T1 n1, T2 n2, \ T3 n3) noexcept -> R \ { \ return Name(al::verify_context(context), 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 const context = GetContextRef(); \ if(!context) [[unlikely]] return detail_::DefaultVal(); \ return Name(gsl::make_not_null(context.get()), n1, n2, n3, n4); \ } \ FORCE_ALIGN auto AL_APIENTRY Name##Direct(ALCcontext *context, T1 n1, T2 n2, \ T3 n3, T4 n4) noexcept -> R \ { \ return Name(al::verify_context(context), 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 const context = GetContextRef(); \ if(!context) [[unlikely]] return detail_::DefaultVal(); \ return Name(gsl::make_not_null(context.get()), n1, n2, n3, n4, n5); \ } \ FORCE_ALIGN auto AL_APIENTRY Name##Direct(ALCcontext *context, T1 n1, T2 n2, \ T3 n3, T4 n4, T5 n5) noexcept -> R \ { \ return Name(al::verify_context(context), n1, n2, n3, n4, n5); \ } #define DECL_FUNCEXT(R, Name,Ext) \ auto AL_APIENTRY Name##Ext() noexcept -> R \ { \ auto const context = GetContextRef(); \ if(!context) [[unlikely]] return detail_::DefaultVal(); \ return Name##Ext(gsl::make_not_null(context.get())); \ } \ FORCE_ALIGN auto AL_APIENTRY Name##Direct##Ext(ALCcontext *context) noexcept \ -> R \ { \ return Name##Ext(al::verify_context(context)); \ } #define DECL_FUNCEXT1(R, Name,Ext, T1,n1) \ auto AL_APIENTRY Name##Ext(T1 n1) noexcept -> R \ { \ auto const context = GetContextRef(); \ if(!context) [[unlikely]] return detail_::DefaultVal(); \ return Name##Ext(gsl::make_not_null(context.get()), n1); \ } \ FORCE_ALIGN auto AL_APIENTRY Name##Direct##Ext(ALCcontext *context, T1 n1) \ noexcept -> R \ { \ return Name##Ext(al::verify_context(context), n1); \ } #define DECL_FUNCEXT2(R, Name,Ext, T1,n1, T2,n2) \ auto AL_APIENTRY Name##Ext(T1 n1, T2 n2) noexcept -> R \ { \ auto const context = GetContextRef(); \ if(!context) [[unlikely]] return detail_::DefaultVal(); \ return Name##Ext(gsl::make_not_null(context.get()), n1, n2); \ } \ FORCE_ALIGN auto AL_APIENTRY Name##Direct##Ext(ALCcontext *context, T1 n1, \ T2 n2) noexcept -> R \ { \ return Name##Ext(al::verify_context(context), 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 const context = GetContextRef(); \ if(!context) [[unlikely]] return detail_::DefaultVal(); \ return Name##Ext(gsl::make_not_null(context.get()), n1, n2, n3); \ } \ FORCE_ALIGN auto AL_APIENTRY Name##Direct##Ext(ALCcontext *context, T1 n1, \ T2 n2, T3 n3) noexcept -> R \ { \ return Name##Ext(al::verify_context(context), 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 const context = GetContextRef(); \ if(!context) [[unlikely]] return detail_::DefaultVal(); \ return Name##Ext(gsl::make_not_null(context.get()), n1, n2, n3, n4); \ } \ FORCE_ALIGN auto AL_APIENTRY Name##Direct##Ext(ALCcontext *context, T1 n1, \ T2 n2, T3 n3, T4 n4) noexcept -> R \ { \ return Name##Ext(al::verify_context(context), 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 const context = GetContextRef(); \ if(!context) [[unlikely]] return detail_::DefaultVal(); \ return Name##Ext(gsl::make_not_null(context.get()), n1, n2, n3, n4, n5); \ } \ FORCE_ALIGN auto AL_APIENTRY Name##Direct##Ext(ALCcontext *context, T1 n1, \ T2 n2, T3 n3, T4 n4, T5 n5) noexcept -> R \ { \ return Name##Ext(al::verify_context(context), 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 const context = GetContextRef(); \ if(!context) [[unlikely]] return detail_::DefaultVal(); \ return Name##Ext(gsl::make_not_null(context.get()), n1, n2, n3, n4, n5, \ n6); \ } \ FORCE_ALIGN auto AL_APIENTRY Name##Direct##Ext(ALCcontext *context, T1 n1, \ T2 n2, T3 n3, T4 n4, T5 n5, T6 n6) noexcept -> R \ { \ return Name##Ext(al::verify_context(context), 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 const context = GetContextRef(); \ if(!context) [[unlikely]] return detail_::DefaultVal(); \ return Name##Ext(gsl::make_not_null(context.get()), n1, n2, n3, n4, n5, \ n6, n7, n8); \ } \ FORCE_ALIGN auto AL_APIENTRY Name##Direct##Ext(ALCcontext *context, T1 n1, \ T2 n2, T3 n3, T4 n4, T5 n5, T6 n6, T7 n7, T8 n8) noexcept -> R \ { \ return Name##Ext(al::verify_context(context), n1, n2, n3, n4, n5, n6, n7, \ n8); \ } #endif /* AL_DIRECT_DEFS_H */ kcat-openal-soft-75c0059/al/eax.cpp000066400000000000000000000025411512220627100170140ustar00rootroot00000000000000 #include "config.h" #include "AL/al.h" #include "alc/context.h" #include "direct_defs.h" #include "eax/utils.h" #include "gsl/gsl" namespace { auto EAXSet(gsl::not_null context, const GUID *property_set_id, ALuint property_id, ALuint source_id, ALvoid *value, ALuint value_size) noexcept -> ALenum try { const auto proplock = std::lock_guard{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; } auto EAXGet(gsl::not_null context, const GUID *property_set_id, ALuint property_id, ALuint source_id, ALvoid *value, ALuint value_size) noexcept -> ALenum try { const auto proplock = std::lock_guard{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; } } // namespace FORCE_ALIGN DECL_FUNC5(ALenum, EAXSet, const GUID*,property_set_id, ALuint,property_id, ALuint,source_id, ALvoid*,value, ALuint,value_size) FORCE_ALIGN DECL_FUNC5(ALenum, EAXGet, const GUID*,property_set_id, ALuint,property_id, ALuint,source_id, ALvoid*,value, ALuint,value_size) kcat-openal-soft-75c0059/al/eax/000077500000000000000000000000001512220627100163065ustar00rootroot00000000000000kcat-openal-soft-75c0059/al/eax/api.cpp000066400000000000000000000755131512220627100175760ustar00rootroot00000000000000// // 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 "api.h" static_assert(sizeof(EAX30SOURCEPROPERTIES) == 72); static_assert(sizeof(EAXSOURCESENDPROPERTIES) == 24); static_assert(sizeof(EAXSOURCEOCCLUSIONSENDPROPERTIES) == 32); static_assert(sizeof(EAXSOURCEEXCLUSIONSENDPROPERTIES) == 24); static_assert(sizeof(EAXSOURCEALLSENDPROPERTIES) == 48); 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'000_eax_long, -6'000_eax_long, 0.0F, 0.17F, 0.1F, -1'204_eax_long, 0.001F, 207_eax_long, 0.002F, EAX2_ENVIRONMENT_PADDEDCELL, 1.4F, 1.0F, -5.0F, EAX2LISTENER_DEFAULTFLAGS, }; constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_ROOM{ -1'000_eax_long, -454_eax_long, 0.0F, 0.4F, 0.83F, -1'646_eax_long, 0.002F, 53_eax_long, 0.003F, EAX2_ENVIRONMENT_ROOM, 1.9F, 1.0F, -5.0F, EAX2LISTENER_DEFAULTFLAGS, }; constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_BATHROOM{ -1'000_eax_long, -1'200_eax_long, 0.0F, 1.49F, 0.54F, -370_eax_long, 0.007F, 1'030_eax_long, 0.011F, EAX2_ENVIRONMENT_BATHROOM, 1.4F, 1.0F, -5.0F, EAX2LISTENER_DEFAULTFLAGS, }; constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_LIVINGROOM{ -1'000_eax_long, -6'000_eax_long, 0.0F, 0.5F, 0.1F, -1'376_eax_long, 0.003F, -1'104_eax_long, 0.004F, EAX2_ENVIRONMENT_LIVINGROOM, 2.5F, 1.0F, -5.0F, EAX2LISTENER_DEFAULTFLAGS, }; constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_STONEROOM{ -1'000_eax_long, -300_eax_long, 0.0F, 2.31F, 0.64F, -711_eax_long, 0.012F, 83_eax_long, 0.017F, EAX2_ENVIRONMENT_STONEROOM, 11.6F, 1.0F, -5.0F, EAX2LISTENER_DEFAULTFLAGS, }; constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_AUDITORIUM{ -1'000_eax_long, -476_eax_long, 0.0F, 4.32F, 0.59F, -789_eax_long, 0.02F, -289_eax_long, 0.03F, EAX2_ENVIRONMENT_AUDITORIUM, 21.6F, 1.0F, -5.0F, EAX2LISTENER_DEFAULTFLAGS, }; constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_CONCERTHALL{ -1'000_eax_long, -500_eax_long, 0.0F, 3.92F, 0.7F, -1'230_eax_long, 0.02F, -2_eax_long, 0.029F, EAX2_ENVIRONMENT_CONCERTHALL, 19.6F, 1.0F, -5.0F, EAX2LISTENER_DEFAULTFLAGS, }; constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_CAVE{ -1'000_eax_long, 0_eax_long, 0.0F, 2.91F, 1.3F, -602_eax_long, 0.015F, -302_eax_long, 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'000_eax_long, -698_eax_long, 0.0F, 7.24F, 0.33F, -1'166_eax_long, 0.02F, 16_eax_long, 0.03F, EAX2_ENVIRONMENT_ARENA, 36.2F, 1.0F, -5.0F, EAX2LISTENER_DEFAULTFLAGS, }; constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_HANGAR{ -1'000_eax_long, -1'000_eax_long, 0.0F, 10.05F, 0.23F, -602_eax_long, 0.02F, 198_eax_long, 0.03F, EAX2_ENVIRONMENT_HANGAR, 50.3F, 1.0F, -5.0F, EAX2LISTENER_DEFAULTFLAGS, }; constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_CARPETTEDHALLWAY{ -1'000_eax_long, -4'000_eax_long, 0.0F, 0.3F, 0.1F, -1'831_eax_long, 0.002F, -1'630_eax_long, 0.03F, EAX2_ENVIRONMENT_CARPETEDHALLWAY, 1.9F, 1.0F, -5.0F, EAX2LISTENER_DEFAULTFLAGS, }; constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_HALLWAY{ -1'000_eax_long, -300_eax_long, 0.0F, 1.49F, 0.59F, -1'219_eax_long, 0.007F, 441_eax_long, 0.011F, EAX2_ENVIRONMENT_HALLWAY, 1.8F, 1.0F, -5.0F, EAX2LISTENER_DEFAULTFLAGS, }; constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_STONECORRIDOR{ -1'000_eax_long, -237_eax_long, 0.0F, 2.7F, 0.79F, -1'214_eax_long, 0.013F, 395_eax_long, 0.02F, EAX2_ENVIRONMENT_STONECORRIDOR, 13.5F, 1.0F, -5.0F, EAX2LISTENER_DEFAULTFLAGS, }; constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_ALLEY{ -1'000_eax_long, -270_eax_long, 0.0F, 1.49F, 0.86F, -1'204_eax_long, 0.007F, -4_eax_long, 0.011F, EAX2_ENVIRONMENT_ALLEY, 7.5F, 0.3F, -5.0F, EAX2LISTENER_DEFAULTFLAGS, }; constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_FOREST{ -1'000_eax_long, -3'300_eax_long, 0.0F, 1.49F, 0.54F, -2'560_eax_long, 0.162F, -229_eax_long, 0.088F, EAX2_ENVIRONMENT_FOREST, 38.0F, 0.3F, -5.0F, EAX2LISTENER_DEFAULTFLAGS, }; constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_CITY{ -1'000_eax_long, -800_eax_long, 0.0F, 1.49F, 0.67F, -2'273_eax_long, 0.007F, -1'691_eax_long, 0.011F, EAX2_ENVIRONMENT_CITY, 7.5F, 0.5F, -5.0F, EAX2LISTENER_DEFAULTFLAGS, }; constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_MOUNTAINS{ -1'000_eax_long, -2'500_eax_long, 0.0F, 1.49F, 0.21F, -2'780_eax_long, 0.3F, -1'434_eax_long, 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'000_eax_long, -1'000_eax_long, 0.0F, 1.49F, 0.83F, -10'000_eax_long, 0.061F, 500_eax_long, 0.025F, EAX2_ENVIRONMENT_QUARRY, 17.5F, 1.0F, -5.0F, EAX2LISTENER_DEFAULTFLAGS, }; constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_PLAIN{ -1'000_eax_long, -2'000_eax_long, 0.0F, 1.49F, 0.5F, -2'466_eax_long, 0.179F, -1'926_eax_long, 0.1F, EAX2_ENVIRONMENT_PLAIN, 42.5F, 0.21F, -5.0F, EAX2LISTENER_DEFAULTFLAGS, }; constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_PARKINGLOT{ -1'000_eax_long, 0_eax_long, 0.0F, 1.65F, 1.5F, -1'363_eax_long, 0.008F, -1'153_eax_long, 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'000_eax_long, -1'000_eax_long, 0.0F, 2.81F, 0.14F, 429_eax_long, 0.014F, 1'023_eax_long, 0.021F, EAX2_ENVIRONMENT_SEWERPIPE, 1.7F, 0.8F, -5.0F, EAX2LISTENER_DEFAULTFLAGS, }; constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_UNDERWATER{ -1'000_eax_long, -4'000_eax_long, 0.0F, 1.49F, 0.1F, -449_eax_long, 0.007F, 1'700_eax_long, 0.011F, EAX2_ENVIRONMENT_UNDERWATER, 1.8F, 1.0F, -5.0F, EAX2LISTENER_DEFAULTFLAGS, }; constexpr EAX20LISTENERPROPERTIES EAX2REVERB_PRESET_DRUGGED{ -1'000_eax_long, 0_eax_long, 0.0F, 8.39F, 1.39F, -115_eax_long, 0.002F, 985_eax_long, 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'000_eax_long, -400_eax_long, 0.0F, 17.23F, 0.56F, -1'713_eax_long, 0.02F, -613_eax_long, 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'000_eax_long, -151_eax_long, 0.0F, 7.56F, 0.91F, -626_eax_long, 0.02F, 774_eax_long, 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'000_eax_long, -6'000_eax_long, 0_eax_long, 0.17F, 0.10F, 1.0F, -1'204_eax_long, 0.001F, EAXVECTOR{}, 207_eax_long, 0.002F, EAXVECTOR{}, 0.250F, 0.0F, 0.250F, 0.0F, -5.0F, 5'000.0F, 250.0F, 0.0F, 0x3F_eax_ulong, }; constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_ROOM = { EAX_ENVIRONMENT_ROOM, 1.9F, 1.0F, -1'000_eax_long, -454_eax_long, 0_eax_long, 0.40F, 0.83F, 1.0F, -1'646_eax_long, 0.002F, EAXVECTOR{}, 53_eax_long, 0.003F, EAXVECTOR{}, 0.250F, 0.0F, 0.250F, 0.0F, -5.0F, 5'000.0F, 250.0F, 0.0F, 0x3F_eax_ulong, }; constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_BATHROOM = { EAX_ENVIRONMENT_BATHROOM, 1.4F, 1.0F, -1'000_eax_long, -1'200_eax_long, 0_eax_long, 1.49F, 0.54F, 1.0F, -370_eax_long, 0.007F, EAXVECTOR{}, 1'030_eax_long, 0.011F, EAXVECTOR{}, 0.250F, 0.0F, 0.250F, 0.0F, -5.0F, 5'000.0F, 250.0F, 0.0F, 0x3F_eax_ulong, }; constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_LIVINGROOM = { EAX_ENVIRONMENT_LIVINGROOM, 2.5F, 1.0F, -1'000_eax_long, -6'000_eax_long, 0_eax_long, 0.50F, 0.10F, 1.0F, -1'376, 0.003F, EAXVECTOR{}, -1'104_eax_long, 0.004F, EAXVECTOR{}, 0.250F, 0.0F, 0.250F, 0.0F, -5.0F, 5'000.0F, 250.0F, 0.0F, 0x3F_eax_ulong, }; constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_STONEROOM = { EAX_ENVIRONMENT_STONEROOM, 11.6F, 1.0F, -1'000_eax_long, -300_eax_long, 0_eax_long, 2.31F, 0.64F, 1.0F, -711_eax_long, 0.012F, EAXVECTOR{}, 83_eax_long, 0.017F, EAXVECTOR{}, 0.250F, 0.0F, 0.250F, 0.0F, -5.0F, 5'000.0F, 250.0F, 0.0F, 0x3F_eax_ulong, }; constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_AUDITORIUM = { EAX_ENVIRONMENT_AUDITORIUM, 21.6F, 1.0F, -1'000_eax_long, -476_eax_long, 0_eax_long, 4.32F, 0.59F, 1.0F, -789_eax_long, 0.020F, EAXVECTOR{}, -289_eax_long, 0.030F, EAXVECTOR{}, 0.250F, 0.0F, 0.250F, 0.0F, -5.0F, 5'000.0F, 250.0F, 0.0F, 0x3F_eax_ulong, }; constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_CONCERTHALL = { EAX_ENVIRONMENT_CONCERTHALL, 19.6F, 1.0F, -1'000_eax_long, -500_eax_long, 0_eax_long, 3.92F, 0.70F, 1.0F, -1'230_eax_long, 0.020F, EAXVECTOR{}, -2_eax_long, 0.029F, EAXVECTOR{}, 0.250F, 0.0F, 0.250F, 0.0F, -5.0F, 5'000.0F, 250.0F, 0.0F, 0x3F_eax_ulong, }; constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_CAVE = { EAX_ENVIRONMENT_CAVE, 14.6F, 1.0F, -1'000_eax_long, 0_eax_long, 0_eax_long, 2.91F, 1.30F, 1.0F, -602_eax_long, 0.015F, EAXVECTOR{}, -302_eax_long, 0.022F, EAXVECTOR{}, 0.250F, 0.0F, 0.250F, 0.0F, -5.0F, 5'000.0F, 250.0F, 0.0F, 0x1F_eax_ulong, }; constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_ARENA = { EAX_ENVIRONMENT_ARENA, 36.2F, 1.0F, -1'000_eax_long, -698_eax_long, 0_eax_long, 7.24F, 0.33F, 1.0F, -1'166_eax_long, 0.020F, EAXVECTOR{}, 16_eax_long, 0.030F, EAXVECTOR{}, 0.250F, 0.0F, 0.250F, 0.0F, -5.0F, 5'000.0F, 250.0F, 0.0F, 0x3F_eax_ulong, }; constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_HANGAR = { EAX_ENVIRONMENT_HANGAR, 50.3F, 1.0F, -1'000_eax_long, -1'000_eax_long, 0_eax_long, 10.05F, 0.23F, 1.0F, -602_eax_long, 0.020F, EAXVECTOR{}, 198_eax_long, 0.030F, EAXVECTOR{}, 0.250F, 0.0F, 0.250F, 0.0F, -5.0F, 5'000.0F, 250.0F, 0.0F, 0x3F_eax_ulong, }; constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_CARPETTEDHALLWAY = { EAX_ENVIRONMENT_CARPETEDHALLWAY, 1.9F, 1.0F, -1'000_eax_long, -4'000_eax_long, 0_eax_long, 0.30F, 0.10F, 1.0F, -1'831_eax_long, 0.002F, EAXVECTOR{}, -1'630_eax_long, 0.030F, EAXVECTOR{}, 0.250F, 0.0F, 0.250F, 0.0F, -5.0F, 5'000.0F, 250.0F, 0.0F, 0x3F_eax_ulong, }; constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_HALLWAY = { EAX_ENVIRONMENT_HALLWAY, 1.8F, 1.0F, -1'000_eax_long, -300_eax_long, 0_eax_long, 1.49F, 0.59F, 1.0F, -1'219_eax_long, 0.007F, EAXVECTOR{}, 441_eax_long, 0.011F, EAXVECTOR{}, 0.250F, 0.0F, 0.250F, 0.0F, -5.0F, 5'000.0F, 250.0F, 0.0F, 0x3F_eax_ulong, }; constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_STONECORRIDOR = { EAX_ENVIRONMENT_STONECORRIDOR, 13.5F, 1.0F, -1'000_eax_long, -237_eax_long, 0_eax_long, 2.70F, 0.79F, 1.0F, -1'214_eax_long, 0.013F, EAXVECTOR{}, 395_eax_long, 0.020F, EAXVECTOR{}, 0.250F, 0.0F, 0.250F, 0.0F, -5.0F, 5'000.0F, 250.0F, 0.0F, 0x3F_eax_ulong, }; constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_ALLEY = { EAX_ENVIRONMENT_ALLEY, 7.5F, 0.300F, -1'000_eax_long, -270_eax_long, 0_eax_long, 1.49F, 0.86F, 1.0F, -1'204_eax_long, 0.007F, EAXVECTOR{}, -4_eax_long, 0.011F, EAXVECTOR{}, 0.125F, 0.950F, 0.250F, 0.0F, -5.0F, 5'000.0F, 250.0F, 0.0F, 0x3F_eax_ulong, }; constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_FOREST = { EAX_ENVIRONMENT_FOREST, 38.0F, 0.300F, -1'000_eax_long, -3'300_eax_long, 0_eax_long, 1.49F, 0.54F, 1.0F, -2'560_eax_long, 0.162F, EAXVECTOR{}, -229_eax_long, 0.088F, EAXVECTOR{}, 0.125F, 1.0F, 0.250F, 0.0F, -5.0F, 5'000.0F, 250.0F, 0.0F, 0x3F_eax_ulong, }; constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_CITY = { EAX_ENVIRONMENT_CITY, 7.5F, 0.500F, -1'000_eax_long, -800_eax_long, 0_eax_long, 1.49F, 0.67F, 1.0F, -2'273_eax_long, 0.007F, EAXVECTOR{}, -1'691_eax_long, 0.011F, EAXVECTOR{}, 0.250F, 0.0F, 0.250F, 0.0F, -5.0F, 5'000.0F, 250.0F, 0.0F, 0x3F_eax_ulong, }; constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_MOUNTAINS = { EAX_ENVIRONMENT_MOUNTAINS, 100.0F, 0.270F, -1'000_eax_long, -2'500_eax_long, 0_eax_long, 1.49F, 0.21F, 1.0F, -2'780_eax_long, 0.300F, EAXVECTOR{}, -1'434_eax_long, 0.100F, EAXVECTOR{}, 0.250F, 1.0F, 0.250F, 0.0F, -5.0F, 5'000.0F, 250.0F, 0.0F, 0x1F_eax_ulong, }; constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_QUARRY = { EAX_ENVIRONMENT_QUARRY, 17.5F, 1.0F, -1'000_eax_long, -1'000_eax_long, 0_eax_long, 1.49F, 0.83F, 1.0F, -10'000_eax_long, 0.061F, EAXVECTOR{}, 500_eax_long, 0.025F, EAXVECTOR{}, 0.125F, 0.700F, 0.250F, 0.0F, -5.0F, 5'000.0F, 250.0F, 0.0F, 0x3F_eax_ulong, }; constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_PLAIN = { EAX_ENVIRONMENT_PLAIN, 42.5F, 0.210F, -1'000_eax_long, -2'000_eax_long, 0_eax_long, 1.49F, 0.50F, 1.0F, -2'466_eax_long, 0.179F, EAXVECTOR{}, -1'926_eax_long, 0.100F, EAXVECTOR{}, 0.250F, 1.0F, 0.250F, 0.0F, -5.0F, 5'000.0F, 250.0F, 0.0F, 0x3F_eax_ulong, }; constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_PARKINGLOT = { EAX_ENVIRONMENT_PARKINGLOT, 8.3F, 1.0F, -1'000_eax_long, 0_eax_long, 0_eax_long, 1.65F, 1.50F, 1.0F, -1'363_eax_long, 0.008F, EAXVECTOR{}, -1'153_eax_long, 0.012F, EAXVECTOR{}, 0.250F, 0.0F, 0.250F, 0.0F, -5.0F, 5'000.0F, 250.0F, 0.0F, 0x1F_eax_ulong, }; constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_SEWERPIPE = { EAX_ENVIRONMENT_SEWERPIPE, 1.7F, 0.800F, -1'000_eax_long, -1'000_eax_long, 0_eax_long, 2.81F, 0.14F, 1.0F, 429_eax_long, 0.014F, EAXVECTOR{}, 1'023_eax_long, 0.021F, EAXVECTOR{}, 0.250F, 0.0F, 0.250F, 0.0F, -5.0F, 5'000.0F, 250.0F, 0.0F, 0x3F_eax_ulong, }; constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_UNDERWATER = { EAX_ENVIRONMENT_UNDERWATER, 1.8F, 1.0F, -1'000_eax_long, -4'000_eax_long, 0_eax_long, 1.49F, 0.10F, 1.0F, -449_eax_long, 0.007F, EAXVECTOR{}, 1'700_eax_long, 0.011F, EAXVECTOR{}, 0.250F, 0.0F, 1.180F, 0.348F, -5.0F, 5'000.0F, 250.0F, 0.0F, 0x3F_eax_ulong, }; constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_DRUGGED = { EAX_ENVIRONMENT_DRUGGED, 1.9F, 0.500F, -1'000_eax_long, 0_eax_long, 0_eax_long, 8.39F, 1.39F, 1.0F, -115_eax_long, 0.002F, EAXVECTOR{}, 985_eax_long, 0.030F, EAXVECTOR{}, 0.250F, 0.0F, 0.250F, 1.0F, -5.0F, 5'000.0F, 250.0F, 0.0F, 0x1F_eax_ulong, }; constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_DIZZY = { EAX_ENVIRONMENT_DIZZY, 1.8F, 0.600F, -1'000_eax_long, -400_eax_long, 0_eax_long, 17.23F, 0.56F, 1.0F, -1'713_eax_long, 0.020F, EAXVECTOR{}, -613_eax_long, 0.030F, EAXVECTOR{}, 0.250F, 1.0F, 0.810F, 0.310F, -5.0F, 5'000.0F, 250.0F, 0.0F, 0x1F_eax_ulong, }; constexpr EAXREVERBPROPERTIES EAXREVERB_PRESET_PSYCHOTIC = { EAX_ENVIRONMENT_PSYCHOTIC, 1.0F, 0.500F, -1'000_eax_long, -151_eax_long, 0_eax_long, 7.56F, 0.91F, 1.0F, -626_eax_long, 0.020F, EAXVECTOR{}, 774_eax_long, 0.030F, EAXVECTOR{}, 0.250F, 0.0F, 4.0F, 1.0F, -5.0F, 5'000.0F, 250.0F, 0.0F, 0x1F_eax_ulong, }; } // 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, }}; kcat-openal-soft-75c0059/al/eax/api.h000066400000000000000000001504751512220627100172440ustar00rootroot00000000000000#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 #else #include #endif #include "AL/al.h" #include "gsl/gsl" #include "opthelpers.h" using eax_long = std::conditional_t; using eax_ulong = std::make_unsigned_t; consteval auto operator ""_eax_long(unsigned long long const x) { return gsl::narrow(x); } consteval auto operator ""_eax_ulong(unsigned long long const x) { return gsl::narrow(x); } #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 DECL_HIDDEN extern const GUID DSPROPSETID_EAX_ReverbProperties; enum DSPROPERTY_EAX_REVERBPROPERTY : unsigned { DSPROPERTY_EAX_ALL, DSPROPERTY_EAX_ENVIRONMENT, DSPROPERTY_EAX_VOLUME, DSPROPERTY_EAX_DECAYTIME, DSPROPERTY_EAX_DAMPING, }; // DSPROPERTY_EAX_REVERBPROPERTY struct EAX_REVERBPROPERTIES { eax_ulong environment; float fVolume; float fDecayTime_sec; float fDamping; }; // EAX_REVERBPROPERTIES DECL_HIDDEN extern const GUID DSPROPSETID_EAXBUFFER_ReverbProperties; enum DSPROPERTY_EAXBUFFER_REVERBPROPERTY : unsigned { 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 { 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 { eax_long lRoom; // room effect level at low frequencies eax_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 eax_long lReflections; // early reflections level relative to room effect float flReflectionsDelay; // initial reflection delay time eax_long lReverb; // late reverberation level relative to room effect float flReverbDelay; // late reverberation delay time relative to initial reflection eax_ulong 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 eax_ulong dwFlags; // modifies the behavior of properties }; // EAX20LISTENERPROPERTIES enum : eax_ulong { 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 = 0x00000001_eax_ulong; constexpr auto EAX2LISTENERFLAGS_REFLECTIONSSCALE = 0x00000002_eax_ulong; constexpr auto EAX2LISTENERFLAGS_REFLECTIONSDELAYSCALE = 0x00000004_eax_ulong; constexpr auto EAX2LISTENERFLAGS_REVERBSCALE = 0x00000008_eax_ulong; constexpr auto EAX2LISTENERFLAGS_REVERBDELAYSCALE = 0x00000010_eax_ulong; constexpr auto EAX2LISTENERFLAGS_DECAYHFLIMIT = 0x00000020_eax_ulong; constexpr auto EAX2LISTENERFLAGS_RESERVED = 0xFFFFFFC0_eax_ulong; constexpr auto EAX2LISTENER_MINROOM = -10'000_eax_long; constexpr auto EAX2LISTENER_MAXROOM = 0_eax_long; constexpr auto EAX2LISTENER_DEFAULTROOM = -1'000_eax_long; constexpr auto EAX2LISTENER_MINROOMHF = -10'000_eax_long; constexpr auto EAX2LISTENER_MAXROOMHF = 0_eax_long; constexpr auto EAX2LISTENER_DEFAULTROOMHF = -100_eax_long; 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'000_eax_long; constexpr auto EAX2LISTENER_MAXREFLECTIONS = 1'000_eax_long; constexpr auto EAX2LISTENER_DEFAULTREFLECTIONS = -2'602_eax_long; constexpr auto EAX2LISTENER_MINREFLECTIONSDELAY = 0.0F; constexpr auto EAX2LISTENER_MAXREFLECTIONSDELAY = 0.3F; constexpr auto EAX2LISTENER_DEFAULTREFLECTIONSDELAY = 0.007F; constexpr auto EAX2LISTENER_MINREVERB = -10'000_eax_long; constexpr auto EAX2LISTENER_MAXREVERB = 2'000_eax_long; constexpr auto EAX2LISTENER_DEFAULTREVERB = 200_eax_long; constexpr auto EAX2LISTENER_MINREVERBDELAY = 0.0F; constexpr auto EAX2LISTENER_MAXREVERBDELAY = 0.1F; constexpr auto EAX2LISTENER_DEFAULTREVERBDELAY = 0.011F; constexpr auto EAX2LISTENER_MINENVIRONMENT = 0_eax_ulong; 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 { 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 { eax_long lDirect; // direct path level eax_long lDirectHF; // direct path level at high frequencies eax_long lRoom; // room effect level eax_long lRoomHF; // room effect level at high frequencies float flRoomRolloffFactor; // like DS3D flRolloffFactor but for room effect eax_long lObstruction; // main obstruction control (attenuation at high frequencies) float flObstructionLFRatio; // obstruction low-frequency level re. main control eax_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 eax_long lOutsideVolumeHF; // outside sound cone level at high frequencies float flAirAbsorptionFactor; // multiplies DSPROPERTY_EAXLISTENER_AIRABSORPTIONHF eax_ulong 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 = 0_eax_long; constexpr auto EAXERR_INVALID_OPERATION = -1_eax_long; constexpr auto EAXERR_INVALID_VALUE = -2_eax_long; constexpr auto EAXERR_NO_EFFECT_LOADED = -3_eax_long; constexpr auto EAXERR_UNKNOWN_EFFECT = -4_eax_long; constexpr auto EAXERR_INCOMPATIBLE_SOURCE_TYPE = -5_eax_long; constexpr auto EAXERR_INCOMPATIBLE_EAX_VERSION = -6_eax_long; DECL_HIDDEN extern const GUID EAX_NULL_GUID; DECL_HIDDEN extern const GUID EAX_PrimaryFXSlotID; struct EAXVECTOR { float x; float y; float z; [[nodiscard]] friend auto operator<=>(const EAXVECTOR& lhs, const EAXVECTOR& rhs) noexcept -> std::partial_ordering = default; }; DECL_HIDDEN extern const GUID EAXPROPERTYID_EAX40_Context; DECL_HIDDEN extern const GUID EAXPROPERTYID_EAX50_Context; // EAX50 constexpr auto HEADPHONES = 0_eax_ulong; constexpr auto SPEAKERS_2 = 1_eax_ulong; constexpr auto SPEAKERS_4 = 2_eax_ulong; constexpr auto SPEAKERS_5 = 3_eax_ulong; // 5.1 speakers constexpr auto SPEAKERS_6 = 4_eax_ulong; // 6.1 speakers constexpr auto SPEAKERS_7 = 5_eax_ulong; // 7.1 speakers constexpr auto EAXCONTEXT_MINSPEAKERCONFIG = HEADPHONES; constexpr auto EAXCONTEXT_MAXSPEAKERCONFIG = SPEAKERS_7; // EAX50 constexpr auto EAX_40 = 5_eax_ulong; // EAX 4.0 constexpr auto EAX_50 = 6_eax_ulong; // 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 = 2_eax_ulong; constexpr auto EAXCONTEXT_MAXMAXACTIVESENDS = 4_eax_ulong; constexpr auto EAXCONTEXT_DEFAULTMAXACTIVESENDS = 2_eax_ulong; // EAX50 struct EAXSESSIONPROPERTIES { eax_ulong ulEAXVersion; eax_ulong ulMaxActiveSends; }; // EAXSESSIONPROPERTIES enum EAXCONTEXT_PROPERTY : unsigned { 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 { 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 = 0x00000001_eax_ulong; // EAX50 constexpr auto EAXFXSLOTFLAGS_UPMIX = 0x00000002_eax_ulong; constexpr auto EAX40FXSLOTFLAGS_RESERVED = 0xFFFFFFFE_eax_ulong; // reserved future use constexpr auto EAX50FXSLOTFLAGS_RESERVED = 0xFFFFFFFC_eax_ulong; // reserved future use constexpr auto EAXFXSLOT_MINVOLUME = -10'000_eax_long; constexpr auto EAXFXSLOT_MAXVOLUME = 0_eax_long; constexpr auto EAXFXSLOT_DEFAULTVOLUME = 0_eax_long; constexpr auto EAXFXSLOT_MINLOCK = 0_eax_long; constexpr auto EAXFXSLOT_MAXLOCK = 1_eax_long; enum : eax_long { EAXFXSLOT_UNLOCKED = 0, EAXFXSLOT_LOCKED = 1 }; constexpr auto EAXFXSLOT_MINOCCLUSION = -10'000_eax_long; constexpr auto EAXFXSLOT_MAXOCCLUSION = 0_eax_long; constexpr auto EAXFXSLOT_DEFAULTOCCLUSION = 0_eax_long; 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; eax_long lVolume; eax_long lLock; eax_ulong ulFlags; }; // EAX40FXSLOTPROPERTIES struct EAX50FXSLOTPROPERTIES : EAX40FXSLOTPROPERTIES { eax_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 { // 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 = 0x00000001_eax_ulong; // relates to EAXSOURCE_DIRECTHF constexpr auto EAXSOURCEFLAGS_ROOMAUTO = 0x00000002_eax_ulong; // relates to EAXSOURCE_ROOM constexpr auto EAXSOURCEFLAGS_ROOMHFAUTO = 0x00000004_eax_ulong; // relates to EAXSOURCE_ROOMHF // EAX50 constexpr auto EAXSOURCEFLAGS_3DELEVATIONFILTER = 0x00000008_eax_ulong; constexpr auto EAXSOURCEFLAGS_UPMIX = 0x00000010_eax_ulong; constexpr auto EAXSOURCEFLAGS_APPLYSPEAKERLEVELS = 0x00000020_eax_ulong; constexpr auto EAX20SOURCEFLAGS_RESERVED = 0xFFFFFFF8_eax_ulong; // reserved future use constexpr auto EAX50SOURCEFLAGS_RESERVED = 0xFFFFFFC0_eax_ulong; // reserved future use constexpr auto EAXSOURCE_MINSEND = -10'000_eax_long; constexpr auto EAXSOURCE_MAXSEND = 0_eax_long; constexpr auto EAXSOURCE_DEFAULTSEND = 0_eax_long; constexpr auto EAXSOURCE_MINSENDHF = -10'000_eax_long; constexpr auto EAXSOURCE_MAXSENDHF = 0_eax_long; constexpr auto EAXSOURCE_DEFAULTSENDHF = 0_eax_long; constexpr auto EAXSOURCE_MINDIRECT = -10'000_eax_long; constexpr auto EAXSOURCE_MAXDIRECT = 1'000_eax_long; constexpr auto EAXSOURCE_DEFAULTDIRECT = 0_eax_long; constexpr auto EAXSOURCE_MINDIRECTHF = -10'000_eax_long; constexpr auto EAXSOURCE_MAXDIRECTHF = 0_eax_long; constexpr auto EAXSOURCE_DEFAULTDIRECTHF = 0_eax_long; constexpr auto EAXSOURCE_MINROOM = -10'000_eax_long; constexpr auto EAXSOURCE_MAXROOM = 1'000_eax_long; constexpr auto EAXSOURCE_DEFAULTROOM = 0_eax_long; constexpr auto EAXSOURCE_MINROOMHF = -10'000_eax_long; constexpr auto EAXSOURCE_MAXROOMHF = 0_eax_long; constexpr auto EAXSOURCE_DEFAULTROOMHF = 0_eax_long; constexpr auto EAXSOURCE_MINOBSTRUCTION = -10'000_eax_long; constexpr auto EAXSOURCE_MAXOBSTRUCTION = 0_eax_long; constexpr auto EAXSOURCE_DEFAULTOBSTRUCTION = 0_eax_long; constexpr auto EAXSOURCE_MINOBSTRUCTIONLFRATIO = 0.0F; constexpr auto EAXSOURCE_MAXOBSTRUCTIONLFRATIO = 1.0F; constexpr auto EAXSOURCE_DEFAULTOBSTRUCTIONLFRATIO = 0.0F; constexpr auto EAXSOURCE_MINOCCLUSION = -10'000_eax_long; constexpr auto EAXSOURCE_MAXOCCLUSION = 0_eax_long; constexpr auto EAXSOURCE_DEFAULTOCCLUSION = 0_eax_long; 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'000_eax_long; constexpr auto EAXSOURCE_MAXEXCLUSION = 0_eax_long; constexpr auto EAXSOURCE_DEFAULTEXCLUSION = 0_eax_long; constexpr auto EAXSOURCE_MINEXCLUSIONLFRATIO = 0.0F; constexpr auto EAXSOURCE_MAXEXCLUSIONLFRATIO = 1.0F; constexpr auto EAXSOURCE_DEFAULTEXCLUSIONLFRATIO = 1.0F; constexpr auto EAXSOURCE_MINOUTSIDEVOLUMEHF = -10'000_eax_long; constexpr auto EAXSOURCE_MAXOUTSIDEVOLUMEHF = 0_eax_long; constexpr auto EAXSOURCE_DEFAULTOUTSIDEVOLUMEHF = 0_eax_long; 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'000_eax_long; constexpr auto EAXSOURCE_MAXSPEAKERLEVEL = 0_eax_long; constexpr auto EAXSOURCE_DEFAULTSPEAKERLEVEL = -10'000_eax_long; constexpr auto EAXSOURCE_DEFAULTFLAGS = EAXSOURCEFLAGS_DIRECTHFAUTO | EAXSOURCEFLAGS_ROOMAUTO | EAXSOURCEFLAGS_ROOMHFAUTO; enum : eax_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 EAXSENDPROPERTIES { eax_long lSend; // send level (at low and mid frequencies) eax_long lSendHF; // relative send level at high frequencies }; // Use this structure for EAXSOURCE_OBSTRUCTIONPARAMETERS property. struct EAXOBSTRUCTIONPROPERTIES { eax_long lObstruction; // main obstruction control (attenuation at high frequencies) float flObstructionLFRatio; // obstruction low-frequency level re. main control }; // Use this structure for EAXSOURCE_OCCLUSIONPARAMETERS property. struct EAXOCCLUSIONPROPERTIES { eax_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 }; // Use this structure for EAXSOURCE_EXCLUSIONPARAMETERS property. struct EAXEXCLUSIONPROPERTIES { eax_long lExclusion; // main exclusion control (attenuation at high frequencies) float flExclusionLFRatio; // exclusion low-frequency level re. main control }; struct EAX30SOURCEPROPERTIES { eax_long lDirect; // direct path level (at low and mid frequencies) eax_long lDirectHF; // relative direct path level at high frequencies eax_long lRoom; // room effect level (at low and mid frequencies) eax_long lRoomHF; // relative room effect level at high frequencies EAXOBSTRUCTIONPROPERTIES mObstruction; EAXOCCLUSIONPROPERTIES mOcclusion; EAXEXCLUSIONPROPERTIES mExclusion; eax_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 eax_ulong ulFlags; // modifies the behavior of properties }; struct EAX50SOURCEPROPERTIES : public EAX30SOURCEPROPERTIES { float flMacroFXFactor; }; struct EAXSOURCE2DPROPERTIES { eax_long lDirect; // direct path level (at low and mid frequencies) eax_long lDirectHF; // relative direct path level at high frequencies eax_long lRoom; // room effect level (at low and mid frequencies) eax_long lRoomHF; // relative room effect level at high frequencies eax_ulong ulFlags; // modifies the behavior of properties }; struct EAXSPEAKERLEVELPROPERTIES { eax_long lSpeakerID; eax_long lLevel; }; struct EAX40ACTIVEFXSLOTS { std::array guidActiveFXSlots; }; struct EAX50ACTIVEFXSLOTS { std::array guidActiveFXSlots; }; // Use this structure for EAXSOURCE_SENDPARAMETERS properties. struct EAXSOURCESENDPROPERTIES { GUID guidReceivingFXSlotID; EAXSENDPROPERTIES mSend; }; // Use this structure for EAXSOURCE_OCCLUSIONSENDPARAMETERS struct EAXSOURCEOCCLUSIONSENDPROPERTIES { GUID guidReceivingFXSlotID; EAXOCCLUSIONPROPERTIES mOcclusion; }; // Use this structure for EAXSOURCE_EXCLUSIONSENDPARAMETERS struct EAXSOURCEEXCLUSIONSENDPROPERTIES { GUID guidReceivingFXSlotID; EAXEXCLUSIONPROPERTIES mExclusion; }; struct EAXSOURCEALLSENDPROPERTIES { GUID guidReceivingFXSlotID; EAXSENDPROPERTIES mSend; EAXOCCLUSIONPROPERTIES mOcclusion; EAXEXCLUSIONPROPERTIES mExclusion; }; 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 { 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 : eax_ulong { 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 = 0x00000001_eax_ulong; // reflection level constexpr auto EAXREVERBFLAGS_REFLECTIONSSCALE = 0x00000002_eax_ulong; // initial reflection delay time constexpr auto EAXREVERBFLAGS_REFLECTIONSDELAYSCALE = 0x00000004_eax_ulong; // reflections level constexpr auto EAXREVERBFLAGS_REVERBSCALE = 0x00000008_eax_ulong; // late reverberation delay time constexpr auto EAXREVERBFLAGS_REVERBDELAYSCALE = 0x00000010_eax_ulong; // echo time // EAX30+ constexpr auto EAXREVERBFLAGS_ECHOTIMESCALE = 0x00000040_eax_ulong; // modulation time // EAX30+ constexpr auto EAXREVERBFLAGS_MODULATIONTIMESCALE = 0x00000080_eax_ulong; // This flag limits high-frequency decay time according to air absorption. constexpr auto EAXREVERBFLAGS_DECAYHFLIMIT = 0x00000020_eax_ulong; constexpr auto EAXREVERBFLAGS_RESERVED = 0xFFFFFF00_eax_ulong; // reserved future use struct EAXREVERBPROPERTIES { eax_ulong ulEnvironment; // sets all reverb properties float flEnvironmentSize; // environment size in meters float flEnvironmentDiffusion; // environment diffusion eax_long lRoom; // room effect level (at mid frequencies) eax_long lRoomHF; // relative room effect level at high frequencies eax_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 eax_long lReflections; // early reflections level relative to room effect float flReflectionsDelay; // initial reflection delay time EAXVECTOR vReflectionsPan; // early reflections panning vector eax_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 eax_ulong ulFlags; // modifies the behavior of properties [[nodiscard]] friend auto operator<=>(const EAXREVERBPROPERTIES& lhs, const EAXREVERBPROPERTIES& rhs) noexcept -> std::partial_ordering = default; }; // EAXREVERBPROPERTIES constexpr auto EAXREVERB_MINENVIRONMENT = eax_ulong{EAX_ENVIRONMENT_GENERIC}; constexpr auto EAX1REVERB_MAXENVIRONMENT = eax_ulong{EAX_ENVIRONMENT_PSYCHOTIC}; constexpr auto EAX30REVERB_MAXENVIRONMENT = eax_ulong{EAX_ENVIRONMENT_UNDEFINED}; constexpr auto EAXREVERB_DEFAULTENVIRONMENT = eax_ulong{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'000_eax_long; constexpr auto EAXREVERB_MAXROOM = 0_eax_long; constexpr auto EAXREVERB_DEFAULTROOM = -1'000_eax_long; constexpr auto EAXREVERB_MINROOMHF = -10'000_eax_long; constexpr auto EAXREVERB_MAXROOMHF = 0_eax_long; constexpr auto EAXREVERB_DEFAULTROOMHF = -100_eax_long; constexpr auto EAXREVERB_MINROOMLF = -10'000_eax_long; constexpr auto EAXREVERB_MAXROOMLF = 0_eax_long; constexpr auto EAXREVERB_DEFAULTROOMLF = 0_eax_long; 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'000_eax_long; constexpr auto EAXREVERB_MAXREFLECTIONS = 1'000_eax_long; constexpr auto EAXREVERB_DEFAULTREFLECTIONS = -2'602_eax_long; 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'000_eax_long; constexpr auto EAXREVERB_MAXREVERB = 2'000_eax_long; constexpr auto EAXREVERB_DEFAULTREVERB = 200_eax_long; 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 { EAXAGCCOMPRESSOR_NONE, EAXAGCCOMPRESSOR_ALLPARAMETERS, EAXAGCCOMPRESSOR_ONOFF, }; // EAXAGCCOMPRESSOR_PROPERTY struct EAXAGCCOMPRESSORPROPERTIES { eax_ulong ulOnOff; // Switch Compressor on or off [[nodiscard]] friend auto operator<=>(const EAXAGCCOMPRESSORPROPERTIES& lhs, const EAXAGCCOMPRESSORPROPERTIES& rhs) noexcept -> std::strong_ordering = default; }; // EAXAGCCOMPRESSORPROPERTIES constexpr auto EAXAGCCOMPRESSOR_MINONOFF = 0_eax_ulong; constexpr auto EAXAGCCOMPRESSOR_MAXONOFF = 1_eax_ulong; constexpr auto EAXAGCCOMPRESSOR_DEFAULTONOFF = EAXAGCCOMPRESSOR_MAXONOFF; // Autowah Effect DECL_HIDDEN extern const GUID EAX_AUTOWAH_EFFECT; enum EAXAUTOWAH_PROPERTY : unsigned { 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) eax_long lResonance; // Resonance (mB) eax_long lPeakLevel; // Peak level (mB) [[nodiscard]] friend auto operator<=>(const EAXAUTOWAHPROPERTIES& lhs, const EAXAUTOWAHPROPERTIES& rhs) noexcept -> std::partial_ordering = default; }; // 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 = 600_eax_long; constexpr auto EAXAUTOWAH_MAXRESONANCE = 6000_eax_long; constexpr auto EAXAUTOWAH_DEFAULTRESONANCE = 6000_eax_long; constexpr auto EAXAUTOWAH_MINPEAKLEVEL = -9000_eax_long; constexpr auto EAXAUTOWAH_MAXPEAKLEVEL = 9000_eax_long; constexpr auto EAXAUTOWAH_DEFAULTPEAKLEVEL = 2100_eax_long; // Chorus Effect DECL_HIDDEN extern const GUID EAX_CHORUS_EFFECT; enum EAXCHORUS_PROPERTY : unsigned { EAXCHORUS_NONE, EAXCHORUS_ALLPARAMETERS, EAXCHORUS_WAVEFORM, EAXCHORUS_PHASE, EAXCHORUS_RATE, EAXCHORUS_DEPTH, EAXCHORUS_FEEDBACK, EAXCHORUS_DELAY, }; // EAXCHORUS_PROPERTY enum : eax_ulong { EAX_CHORUS_SINUSOID, EAX_CHORUS_TRIANGLE, }; struct EAXCHORUSPROPERTIES { eax_ulong ulWaveform; // Waveform selector - see enum above eax_long lPhase; // Phase (Degrees) float flRate; // Rate (Hz) float flDepth; // Depth (0 to 1) float flFeedback; // Feedback (-1 to 1) float flDelay; // Delay (seconds) [[nodiscard]] friend auto operator<=>(const EAXCHORUSPROPERTIES& lhs, const EAXCHORUSPROPERTIES& rhs) noexcept -> std::partial_ordering = default; }; // EAXCHORUSPROPERTIES constexpr auto EAXCHORUS_MINWAVEFORM = 0_eax_ulong; constexpr auto EAXCHORUS_MAXWAVEFORM = 1_eax_ulong; constexpr auto EAXCHORUS_DEFAULTWAVEFORM = 1_eax_ulong; constexpr auto EAXCHORUS_MINPHASE = -180_eax_long; constexpr auto EAXCHORUS_MAXPHASE = 180_eax_long; constexpr auto EAXCHORUS_DEFAULTPHASE = 90_eax_long; 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 { 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) eax_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) [[nodiscard]] friend auto operator<=>(const EAXDISTORTIONPROPERTIES& lhs, const EAXDISTORTIONPROPERTIES& rhs) noexcept -> std::partial_ordering = default; }; // EAXDISTORTIONPROPERTIES constexpr auto EAXDISTORTION_MINEDGE = 0.0F; constexpr auto EAXDISTORTION_MAXEDGE = 1.0F; constexpr auto EAXDISTORTION_DEFAULTEDGE = 0.2F; constexpr auto EAXDISTORTION_MINGAIN = -6000_eax_long; constexpr auto EAXDISTORTION_MAXGAIN = 0_eax_long; constexpr auto EAXDISTORTION_DEFAULTGAIN = -2600_eax_long; 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 { 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 [[nodiscard]] friend auto operator<=>(const EAXECHOPROPERTIES& lhs, const EAXECHOPROPERTIES& rhs) noexcept -> std::partial_ordering = default; }; // 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 { 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 { eax_long lLowGain; // (mB) float flLowCutOff; // (Hz) eax_long lMid1Gain; // (mB) float flMid1Center; // (Hz) float flMid1Width; // (octaves) eax_long lMid2Gain; // (mB) float flMid2Center; // (Hz) float flMid2Width; // (octaves) eax_long lHighGain; // (mB) float flHighCutOff; // (Hz) [[nodiscard]] friend auto operator<=>(const EAXEQUALIZERPROPERTIES& lhs, const EAXEQUALIZERPROPERTIES& rhs) noexcept -> std::partial_ordering = default; }; // EAXEQUALIZERPROPERTIES constexpr auto EAXEQUALIZER_MINLOWGAIN = -1800_eax_long; constexpr auto EAXEQUALIZER_MAXLOWGAIN = 1800_eax_long; constexpr auto EAXEQUALIZER_DEFAULTLOWGAIN = 0_eax_long; constexpr auto EAXEQUALIZER_MINLOWCUTOFF = 50.0F; constexpr auto EAXEQUALIZER_MAXLOWCUTOFF = 800.0F; constexpr auto EAXEQUALIZER_DEFAULTLOWCUTOFF = 200.0F; constexpr auto EAXEQUALIZER_MINMID1GAIN = -1800_eax_long; constexpr auto EAXEQUALIZER_MAXMID1GAIN = 1800_eax_long; constexpr auto EAXEQUALIZER_DEFAULTMID1GAIN = 0_eax_long; 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 = -1800_eax_long; constexpr auto EAXEQUALIZER_MAXMID2GAIN = 1800_eax_long; constexpr auto EAXEQUALIZER_DEFAULTMID2GAIN = 0_eax_long; 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 = -1800_eax_long; constexpr auto EAXEQUALIZER_MAXHIGHGAIN = 1800_eax_long; constexpr auto EAXEQUALIZER_DEFAULTHIGHGAIN = 0_eax_long; 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 { EAXFLANGER_NONE, EAXFLANGER_ALLPARAMETERS, EAXFLANGER_WAVEFORM, EAXFLANGER_PHASE, EAXFLANGER_RATE, EAXFLANGER_DEPTH, EAXFLANGER_FEEDBACK, EAXFLANGER_DELAY, }; // EAXFLANGER_PROPERTY enum : eax_ulong { EAX_FLANGER_SINUSOID, EAX_FLANGER_TRIANGLE, }; struct EAXFLANGERPROPERTIES { eax_ulong ulWaveform; // Waveform selector - see enum above eax_long lPhase; // Phase (Degrees) float flRate; // Rate (Hz) float flDepth; // Depth (0 to 1) float flFeedback; // Feedback (0 to 1) float flDelay; // Delay (seconds) [[nodiscard]] friend auto operator<=>(const EAXFLANGERPROPERTIES& lhs, const EAXFLANGERPROPERTIES& rhs) noexcept -> std::partial_ordering = default; }; // EAXFLANGERPROPERTIES constexpr auto EAXFLANGER_MINWAVEFORM = 0_eax_ulong; constexpr auto EAXFLANGER_MAXWAVEFORM = 1_eax_ulong; constexpr auto EAXFLANGER_DEFAULTWAVEFORM = 1_eax_ulong; constexpr auto EAXFLANGER_MINPHASE = -180_eax_long; constexpr auto EAXFLANGER_MAXPHASE = 180_eax_long; constexpr auto EAXFLANGER_DEFAULTPHASE = 0_eax_long; 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 { EAXFREQUENCYSHIFTER_NONE, EAXFREQUENCYSHIFTER_ALLPARAMETERS, EAXFREQUENCYSHIFTER_FREQUENCY, EAXFREQUENCYSHIFTER_LEFTDIRECTION, EAXFREQUENCYSHIFTER_RIGHTDIRECTION, }; // EAXFREQUENCYSHIFTER_PROPERTY enum : eax_ulong { EAX_FREQUENCYSHIFTER_DOWN, EAX_FREQUENCYSHIFTER_UP, EAX_FREQUENCYSHIFTER_OFF }; struct EAXFREQUENCYSHIFTERPROPERTIES { float flFrequency; // (Hz) eax_ulong ulLeftDirection; // see enum above eax_ulong ulRightDirection; // see enum above [[nodiscard]] friend auto operator<=>(const EAXFREQUENCYSHIFTERPROPERTIES& lhs, const EAXFREQUENCYSHIFTERPROPERTIES& rhs) noexcept -> std::partial_ordering = default; }; // EAXFREQUENCYSHIFTERPROPERTIES constexpr auto EAXFREQUENCYSHIFTER_MINFREQUENCY = 0.0F; constexpr auto EAXFREQUENCYSHIFTER_MAXFREQUENCY = 24000.0F; constexpr auto EAXFREQUENCYSHIFTER_DEFAULTFREQUENCY = EAXFREQUENCYSHIFTER_MINFREQUENCY; constexpr auto EAXFREQUENCYSHIFTER_MINLEFTDIRECTION = 0_eax_ulong; constexpr auto EAXFREQUENCYSHIFTER_MAXLEFTDIRECTION = 2_eax_ulong; constexpr auto EAXFREQUENCYSHIFTER_DEFAULTLEFTDIRECTION = EAXFREQUENCYSHIFTER_MINLEFTDIRECTION; constexpr auto EAXFREQUENCYSHIFTER_MINRIGHTDIRECTION = 0_eax_ulong; constexpr auto EAXFREQUENCYSHIFTER_MAXRIGHTDIRECTION = 2_eax_ulong; constexpr auto EAXFREQUENCYSHIFTER_DEFAULTRIGHTDIRECTION = EAXFREQUENCYSHIFTER_MINRIGHTDIRECTION; // Vocal Morpher Effect DECL_HIDDEN extern const GUID EAX_VOCALMORPHER_EFFECT; enum EAXVOCALMORPHER_PROPERTY : unsigned { EAXVOCALMORPHER_NONE, EAXVOCALMORPHER_ALLPARAMETERS, EAXVOCALMORPHER_PHONEMEA, EAXVOCALMORPHER_PHONEMEACOARSETUNING, EAXVOCALMORPHER_PHONEMEB, EAXVOCALMORPHER_PHONEMEBCOARSETUNING, EAXVOCALMORPHER_WAVEFORM, EAXVOCALMORPHER_RATE, }; // EAXVOCALMORPHER_PROPERTY enum : eax_ulong { EAX_VOCALMORPHER_PHONEME_A, EAX_VOCALMORPHER_PHONEME_E, EAX_VOCALMORPHER_PHONEME_I, EAX_VOCALMORPHER_PHONEME_O, EAX_VOCALMORPHER_PHONEME_U, EAX_VOCALMORPHER_PHONEME_AA, EAX_VOCALMORPHER_PHONEME_AE, EAX_VOCALMORPHER_PHONEME_AH, EAX_VOCALMORPHER_PHONEME_AO, EAX_VOCALMORPHER_PHONEME_EH, EAX_VOCALMORPHER_PHONEME_ER, EAX_VOCALMORPHER_PHONEME_IH, EAX_VOCALMORPHER_PHONEME_IY, EAX_VOCALMORPHER_PHONEME_UH, EAX_VOCALMORPHER_PHONEME_UW, EAX_VOCALMORPHER_PHONEME_B, EAX_VOCALMORPHER_PHONEME_D, EAX_VOCALMORPHER_PHONEME_F, EAX_VOCALMORPHER_PHONEME_G, EAX_VOCALMORPHER_PHONEME_J, EAX_VOCALMORPHER_PHONEME_K, EAX_VOCALMORPHER_PHONEME_L, EAX_VOCALMORPHER_PHONEME_M, EAX_VOCALMORPHER_PHONEME_N, EAX_VOCALMORPHER_PHONEME_P, EAX_VOCALMORPHER_PHONEME_R, EAX_VOCALMORPHER_PHONEME_S, EAX_VOCALMORPHER_PHONEME_T, EAX_VOCALMORPHER_PHONEME_V, EAX_VOCALMORPHER_PHONEME_Z, }; enum : eax_ulong { EAX_VOCALMORPHER_SINUSOID, EAX_VOCALMORPHER_TRIANGLE, EAX_VOCALMORPHER_SAWTOOTH }; // Use this structure for EAXVOCALMORPHER_ALLPARAMETERS struct EAXVOCALMORPHERPROPERTIES { eax_ulong ulPhonemeA; // see enum above eax_long lPhonemeACoarseTuning; // (semitones) eax_ulong ulPhonemeB; // see enum above eax_long lPhonemeBCoarseTuning; // (semitones) eax_ulong ulWaveform; // Waveform selector - see enum above float flRate; // (Hz) [[nodiscard]] friend auto operator<=>(const EAXVOCALMORPHERPROPERTIES& lhs, const EAXVOCALMORPHERPROPERTIES& rhs) noexcept -> std::partial_ordering = default; }; // EAXVOCALMORPHERPROPERTIES constexpr auto EAXVOCALMORPHER_MINPHONEMEA = 0_eax_ulong; constexpr auto EAXVOCALMORPHER_MAXPHONEMEA = 29_eax_ulong; constexpr auto EAXVOCALMORPHER_DEFAULTPHONEMEA = EAXVOCALMORPHER_MINPHONEMEA; constexpr auto EAXVOCALMORPHER_MINPHONEMEACOARSETUNING = -24_eax_long; constexpr auto EAXVOCALMORPHER_MAXPHONEMEACOARSETUNING = 24_eax_long; constexpr auto EAXVOCALMORPHER_DEFAULTPHONEMEACOARSETUNING = 0_eax_long; constexpr auto EAXVOCALMORPHER_MINPHONEMEB = 0_eax_ulong; constexpr auto EAXVOCALMORPHER_MAXPHONEMEB = 29_eax_ulong; constexpr auto EAXVOCALMORPHER_DEFAULTPHONEMEB = 10_eax_ulong; constexpr auto EAXVOCALMORPHER_MINPHONEMEBCOARSETUNING = -24_eax_long; constexpr auto EAXVOCALMORPHER_MAXPHONEMEBCOARSETUNING = 24_eax_long; constexpr auto EAXVOCALMORPHER_DEFAULTPHONEMEBCOARSETUNING = 0_eax_long; constexpr auto EAXVOCALMORPHER_MINWAVEFORM = 0_eax_ulong; constexpr auto EAXVOCALMORPHER_MAXWAVEFORM = 2_eax_ulong; 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 { EAXPITCHSHIFTER_NONE, EAXPITCHSHIFTER_ALLPARAMETERS, EAXPITCHSHIFTER_COARSETUNE, EAXPITCHSHIFTER_FINETUNE, }; // EAXPITCHSHIFTER_PROPERTY struct EAXPITCHSHIFTERPROPERTIES { eax_long lCoarseTune; // Amount of pitch shift (semitones) eax_long lFineTune; // Amount of pitch shift (cents) [[nodiscard]] friend auto operator<=>(const EAXPITCHSHIFTERPROPERTIES& lhs, const EAXPITCHSHIFTERPROPERTIES& rhs) noexcept -> std::strong_ordering = default; }; // EAXPITCHSHIFTERPROPERTIES constexpr auto EAXPITCHSHIFTER_MINCOARSETUNE = -12_eax_long; constexpr auto EAXPITCHSHIFTER_MAXCOARSETUNE = 12_eax_long; constexpr auto EAXPITCHSHIFTER_DEFAULTCOARSETUNE = 12_eax_long; constexpr auto EAXPITCHSHIFTER_MINFINETUNE = -50_eax_long; constexpr auto EAXPITCHSHIFTER_MAXFINETUNE = 50_eax_long; constexpr auto EAXPITCHSHIFTER_DEFAULTFINETUNE = 0_eax_long; // Ring Modulator Effect DECL_HIDDEN extern const GUID EAX_RINGMODULATOR_EFFECT; enum EAXRINGMODULATOR_PROPERTY : unsigned { EAXRINGMODULATOR_NONE, EAXRINGMODULATOR_ALLPARAMETERS, EAXRINGMODULATOR_FREQUENCY, EAXRINGMODULATOR_HIGHPASSCUTOFF, EAXRINGMODULATOR_WAVEFORM, }; // EAXRINGMODULATOR_PROPERTY enum : eax_ulong { 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) eax_ulong ulWaveform; // Waveform selector - see enum above [[nodiscard]] friend auto operator<=>(const EAXRINGMODULATORPROPERTIES& lhs, const EAXRINGMODULATORPROPERTIES& rhs) noexcept -> std::partial_ordering = default; }; // 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 = 0_eax_ulong; constexpr auto EAXRINGMODULATOR_MAXWAVEFORM = 2_eax_ulong; 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); #endif // !EAX_API_INCLUDED kcat-openal-soft-75c0059/al/eax/call.cpp000066400000000000000000000141571512220627100177350ustar00rootroot00000000000000#include "config.h" #include "call.h" #include #include "exception.h" namespace { constexpr auto deferred_flag = 0x80000000U; /* NOLINTNEXTLINE(clazy-copyable-polymorphic) Exceptions must be copyable. */ class EaxCallException final : public EaxException { public: explicit EaxCallException(const std::string_view message) : EaxException{"EAX_CALL", message} { } }; } // 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(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 std::string_view 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 }; } kcat-openal-soft-75c0059/al/eax/call.h000066400000000000000000000052011512220627100173700ustar00rootroot00000000000000#ifndef EAX_EAX_CALL_INCLUDED #define EAX_EAX_CALL_INCLUDED #include #include #include "AL/al.h" #include "alnumeric.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 load() const -> TValue& { if(mPropertyBufferSize < sizeof(TValue)) fail_too_small(); return *static_cast(mPropertyBuffer); } template [[nodiscard]] auto as_span(size_t max_count=~0_uz) const -> std::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 auto store(const TValue &value) const -> void { load() = 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 std::string_view 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 kcat-openal-soft-75c0059/al/eax/effect.h000066400000000000000000000333011512220627100177130ustar00rootroot00000000000000#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" inline bool EaxTraceCommits{false}; 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()...; }; 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 std::string_view 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.load(); TValidator{}(value); property = value; } template static void defer(const EaxCall& call, TProperties& properties, TProperty&) { const auto &value = call.load(); TValidator{}(value); TDeferrer{}(properties, value); } template static void defer3(const EaxCall& call, EAXREVERBPROPERTIES& properties, TProperty& property) { const auto& value = call.load(); TValidator{}(value); if(value == property) return; property = value; properties.ulEnvironment = EAX_ENVIRONMENT_UNDEFINED; } [[nodiscard]] auto commit(const EAX_REVERBPROPERTIES &props) const -> bool; [[nodiscard]] auto commit(const EAX20LISTENERPROPERTIES &props) const -> bool; [[nodiscard]] auto commit(const EAXREVERBPROPERTIES &props) const -> bool; 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.load(); TValidator{}(value); property = value; } [[noreturn]] static void fail(const std::string_view 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; }; #define DECL_COMMITTER(T, P) struct T : EaxCommitter { \ T(EaxEffectProps &eaxprops, EffectProps &alprops) \ : EaxCommitter{eaxprops, alprops} \ { } \ \ [[nodiscard]] auto commit(const P &props) const -> bool; \ \ static void SetDefaults(EaxEffectProps &props); \ static void Get(const EaxCall &call, const P &props); \ static void Set(const EaxCall &call, P &props); \ }; DECL_COMMITTER(EaxAutowahCommitter, EAXAUTOWAHPROPERTIES) DECL_COMMITTER(EaxChorusCommitter, EAXCHORUSPROPERTIES) DECL_COMMITTER(EaxCompressorCommitter, EAXAGCCOMPRESSORPROPERTIES) DECL_COMMITTER(EaxDistortionCommitter, EAXDISTORTIONPROPERTIES) DECL_COMMITTER(EaxEchoCommitter, EAXECHOPROPERTIES) DECL_COMMITTER(EaxEqualizerCommitter, EAXEQUALIZERPROPERTIES) DECL_COMMITTER(EaxFlangerCommitter, EAXFLANGERPROPERTIES) DECL_COMMITTER(EaxFrequencyShifterCommitter, EAXFREQUENCYSHIFTERPROPERTIES) DECL_COMMITTER(EaxModulatorCommitter, EAXRINGMODULATORPROPERTIES) DECL_COMMITTER(EaxPitchShifterCommitter, EAXPITCHSHIFTERPROPERTIES) DECL_COMMITTER(EaxVocalMorpherCommitter, EAXVOCALMORPHERPROPERTIES) DECL_COMMITTER(EaxNullCommitter, std::monostate) #undef DECL_COMMITTER 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 = 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([&](T &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([&](T &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([&](T &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 kcat-openal-soft-75c0059/al/eax/exception.cpp000066400000000000000000000011631512220627100210110ustar00rootroot00000000000000#include "config.h" #include "exception.h" #include EaxException::EaxException(std::string_view context, std::string_view message) : std::runtime_error{make_message(context, message)} { } 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; } kcat-openal-soft-75c0059/al/eax/exception.h000066400000000000000000000014251512220627100204570ustar00rootroot00000000000000#ifndef EAX_EXCEPTION_INCLUDED #define EAX_EXCEPTION_INCLUDED #include #include #include #include "opthelpers.h" /* NOLINTNEXTLINE(clazy-copyable-polymorphic) Exceptions must be copyable. */ 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); NOINLINE ~EaxException() override = default; auto operator=(const EaxException&) -> EaxException& = default; auto operator=(EaxException&&) -> EaxException& = default; }; #endif /* EAX_EXCEPTION_INCLUDED */ kcat-openal-soft-75c0059/al/eax/fx_slot_index.cpp000066400000000000000000000023371512220627100216640ustar00rootroot00000000000000#include "config.h" #include "fx_slot_index.h" #include "exception.h" namespace { /* NOLINTNEXTLINE(clazy-copyable-polymorphic) Exceptions must be copyable. */ class EaxFxSlotIndexException final : public EaxException { public: explicit EaxFxSlotIndexException(const std::string_view message) : EaxException{"EAX_FX_SLOT_INDEX", message} { } }; } // 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(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 std::string_view message) { throw EaxFxSlotIndexException{message}; } kcat-openal-soft-75c0059/al/eax/fx_slot_index.h000066400000000000000000000020351512220627100213240ustar00rootroot00000000000000#ifndef EAX_FX_SLOT_INDEX_INCLUDED #define EAX_FX_SLOT_INDEX_INCLUDED #include #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 std::string_view 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 kcat-openal-soft-75c0059/al/eax/fx_slots.cpp000066400000000000000000000023401512220627100206520ustar00rootroot00000000000000#include "config.h" #include "fx_slots.h" #include #include "exception.h" namespace { /* NOLINTNEXTLINE(clazy-copyable-polymorphic) Exceptions must be copyable. */ class EaxFxSlotsException final : public EaxException { public: explicit EaxFxSlotsException(const std::string_view message) : EaxException{"EAX_FX_SLOTS", message} { } }; } // namespace void EaxFxSlots::initialize(gsl::not_null 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(fx_slot_index); fx_slot_index += 1; } } void EaxFxSlots::uninitialize() noexcept { for(auto &fx_slot : fx_slots_) fx_slot = nullptr; } auto EaxFxSlots::get(EaxFxSlotIndex const index) const -> al::EffectSlot const& { if(!index.has_value()) fail("Empty index."); return *fx_slots_[index.value()]; } auto EaxFxSlots::get(EaxFxSlotIndex const index) -> al::EffectSlot& { if(!index.has_value()) fail("Empty index."); return *fx_slots_[index.value()]; } [[noreturn]] void EaxFxSlots::fail(std::string_view const message) { throw EaxFxSlotsException{message}; } kcat-openal-soft-75c0059/al/eax/fx_slots.h000066400000000000000000000015101512220627100203150ustar00rootroot00000000000000#ifndef EAX_FX_SLOTS_INCLUDED #define EAX_FX_SLOTS_INCLUDED #include #include #include "al/auxeffectslot.h" #include "fx_slot_index.h" #include "gsl/gsl" namespace al { struct Context; } // namespace al class EaxFxSlots { public: void initialize(gsl::not_null al_context); void uninitialize() noexcept; void commit() const { for(auto& fx_slot : fx_slots_) fx_slot->eax_commit(); } [[nodiscard]] auto get(EaxFxSlotIndex index) const -> const al::EffectSlot&; [[nodiscard]] auto get(EaxFxSlotIndex index) -> al::EffectSlot&; private: using Items = std::array; Items fx_slots_{}; [[noreturn]] static void fail(const std::string_view message); }; // EaxFxSlots #endif // !EAX_FX_SLOTS_INCLUDED kcat-openal-soft-75c0059/al/eax/globals.h000066400000000000000000000002021512220627100200740ustar00rootroot00000000000000#ifndef EAX_GLOBALS_INCLUDED #define EAX_GLOBALS_INCLUDED inline bool eax_g_is_enabled{true}; #endif /* EAX_GLOBALS_INCLUDED */ kcat-openal-soft-75c0059/al/eax/utils.cpp000066400000000000000000000007351512220627100201570ustar00rootroot00000000000000#include "config.h" #include "utils.h" #include #include "core/logging.h" #include "gsl/gsl" void eax_log_exception(std::string_view message) noexcept { const auto exception_ptr = std::current_exception(); Expects(exception_ptr); try { std::rethrow_exception(exception_ptr); } catch(std::exception& ex) { ERR("{} {}", message, ex.what()); } catch(...) { ERR("{} {}", message, "Generic exception."); } } kcat-openal-soft-75c0059/al/eax/utils.h000066400000000000000000000012551512220627100176220ustar00rootroot00000000000000#ifndef EAX_UTILS_INCLUDED #define EAX_UTILS_INCLUDED #include #include "alformat.hpp" 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 = al::format("{} out of range (value: {}; min: {}; max: {}).", value_name, value, min_value, max_value); throw TException{message}; } #endif // !EAX_UTILS_INCLUDED kcat-openal-soft-75c0059/al/eax/x_ram.h000066400000000000000000000017331512220627100175710ustar00rootroot00000000000000#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 kcat-openal-soft-75c0059/al/effect.cpp000066400000000000000000000576741512220627100175140ustar00rootroot00000000000000/** * 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 #include #include "AL/al.h" #include "AL/alext.h" #include "AL/efx-presets.h" #include "AL/efx.h" #include "al/effects/effects.h" #include "alc/context.h" #include "alc/device.h" #include "alc/inprogext.h" #include "almalloc.h" #include "alnumeric.h" #include "alstring.h" #include "core/except.h" #include "core/logging.h" #include "direct_defs.h" #include "gsl/gsl" #include "opthelpers.h" using uint = unsigned int; constinit 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 namespace std::string_view_literals; using SubListAllocator = al::allocator>; constexpr auto GetDefaultProps(ALenum const 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(al::Effect *const effect, ALenum const type) noexcept { switch(type) { case AL_EFFECT_NULL: effect->mPropsVariant.emplace(); break; case AL_EFFECT_EAXREVERB: effect->mPropsVariant.emplace(); break; case AL_EFFECT_REVERB: effect->mPropsVariant.emplace(); break; case AL_EFFECT_AUTOWAH: effect->mPropsVariant.emplace(); break; case AL_EFFECT_CHORUS: effect->mPropsVariant.emplace(); break; case AL_EFFECT_COMPRESSOR: effect->mPropsVariant.emplace(); break; case AL_EFFECT_DISTORTION: effect->mPropsVariant.emplace(); break; case AL_EFFECT_ECHO: effect->mPropsVariant.emplace(); break; case AL_EFFECT_EQUALIZER: effect->mPropsVariant.emplace(); break; case AL_EFFECT_FLANGER: effect->mPropsVariant.emplace(); break; case AL_EFFECT_FREQUENCY_SHIFTER: effect->mPropsVariant.emplace(); break; case AL_EFFECT_RING_MODULATOR: effect->mPropsVariant.emplace(); break; case AL_EFFECT_PITCH_SHIFTER: effect->mPropsVariant.emplace(); break; case AL_EFFECT_VOCAL_MORPHER: effect->mPropsVariant.emplace(); break; case AL_EFFECT_DEDICATED_DIALOGUE: effect->mPropsVariant.emplace(); break; case AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT: effect->mPropsVariant.emplace(); break; case AL_EFFECT_CONVOLUTION_SOFT: effect->mPropsVariant.emplace(); break; } effect->mProps = GetDefaultProps(type); effect->mType = type; } [[nodiscard]] auto EnsureEffects(gsl::not_null const device, usize const needed) noexcept -> bool try { auto count = std::accumulate(device->EffectList.cbegin(), device->EffectList.cend(), 0_uz, [](usize const cur, const EffectSubList &sublist) noexcept -> usize { return cur + gsl::narrow_cast(std::popcount(sublist.mFreeMask)); }); while(needed > count) { if(device->EffectList.size() >= 1<<25) [[unlikely]] return false; auto sublist = EffectSubList{}; sublist.mFreeMask = ~0_u64; sublist.mEffects = SubListAllocator{}.allocate(1); device->EffectList.emplace_back(std::move(sublist)); count += std::tuple_size_v; } return true; } catch(...) { return false; } [[nodiscard]] auto AllocEffect(gsl::not_null const device) noexcept -> gsl::not_null { auto const sublist = std::ranges::find_if(device->EffectList, &EffectSubList::mFreeMask); auto const lidx = gsl::narrow_cast(std::distance(device->EffectList.begin(), sublist)); auto const slidx = gsl::narrow_cast(std::countr_zero(sublist->mFreeMask)); ASSUME(slidx < 64); auto effect = gsl::make_not_null(std::construct_at( std::to_address(std::next(sublist->mEffects->begin(), slidx)))); InitEffectParams(effect, AL_EFFECT_NULL); /* Add 1 to avoid effect ID 0. */ effect->mId = ((lidx<<6) | slidx) + 1; sublist->mFreeMask &= ~(1_u64 << slidx); return effect; } void FreeEffect(gsl::not_null const device, gsl::not_null const effect) { device->mEffectNames.erase(effect->mId); const auto id = effect->mId - 1; const auto lidx = id >> 6; const auto slidx = id & 0x3f; std::destroy_at(std::to_address(effect)); device->EffectList[lidx].mFreeMask |= 1_u64 << slidx; } [[nodiscard]] auto LookupEffect(std::nothrow_t, gsl::not_null const device, u32 const id) noexcept -> al::Effect* { const auto lidx = (id-1) >> 6; const auto slidx = (id-1) & 0x3f; if(lidx >= device->EffectList.size()) [[unlikely]] return nullptr; auto &sublist = device->EffectList[lidx]; if(sublist.mFreeMask & (1_u64 << slidx)) [[unlikely]] return nullptr; return std::to_address(std::next(sublist.mEffects->begin(), slidx)); } [[nodiscard]] auto LookupEffect(gsl::not_null const context, u32 const id) -> gsl::not_null { if(auto *const effect = LookupEffect(std::nothrow, al::get_not_null(context->mALDevice), id)) [[likely]] return gsl::make_not_null(effect); context->throw_error(AL_INVALID_NAME, "Invalid effect ID {}", id); } void alGenEffects(gsl::not_null 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 const device = al::get_not_null(context->mALDevice); auto effectlock = std::lock_guard{device->EffectLock}; const auto eids = std::views::counted(effects, n); if(!EnsureEffects(device, eids.size())) context->throw_error(AL_OUT_OF_MEMORY, "Failed to allocate {} effect{}", n, (n==1) ? "" : "s"); std::ranges::generate(eids, [device]{ return AllocEffect(device)->mId; }); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } void alDeleteEffects(gsl::not_null 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 const device = al::get_not_null(context->mALDevice); auto effectlock = std::lock_guard{device->EffectLock}; /* First try to find any effects that are invalid. */ const auto eids = std::views::counted(effects, n); std::ranges::for_each(eids, [context](const ALuint eid) { if(eid != 0) std::ignore = LookupEffect(context, eid); }); /* All good. Delete non-0 effect IDs. */ std::ranges::for_each(eids, [device](ALuint eid) { if(auto *effect = LookupEffect(std::nothrow, device, eid)) FreeEffect(device, gsl::make_not_null(effect)); }); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } auto alIsEffect(gsl::not_null context, ALuint effect) noexcept -> ALboolean { auto const device = al::get_not_null(context->mALDevice); auto effectlock = std::lock_guard{device->EffectLock}; if(effect == 0 || LookupEffect(std::nothrow, device, effect) != nullptr) return AL_TRUE; return AL_FALSE; } void alEffecti(gsl::not_null context, ALuint effect, ALenum param, ALint value) noexcept try { auto const device = al::get_not_null(context->mALDevice); auto effectlock = std::lock_guard{device->EffectLock}; auto const aleffect = LookupEffect(context, 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::ranges::any_of(gEffectList, 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](T &arg) { using PropType = T::prop_type; return arg.SetParami(context, std::get(aleffect->mProps), param, value); }, aleffect->mPropsVariant); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } void alEffectiv(gsl::not_null context, ALuint effect, ALenum param, const ALint *values) noexcept try { switch(param) { case AL_EFFECT_TYPE: alEffecti(context, effect, param, *values); return; } auto const device = al::get_not_null(context->mALDevice); auto effectlock = std::lock_guard{device->EffectLock}; auto const aleffect = LookupEffect(context, effect); /* Call the appropriate handler */ std::visit([context,aleffect,param,values](T &arg) { using PropType = T::prop_type; return arg.SetParamiv(context, std::get(aleffect->mProps), param, values); }, aleffect->mPropsVariant); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } void alEffectf(gsl::not_null context, ALuint effect, ALenum param, ALfloat value) noexcept try { auto const device = al::get_not_null(context->mALDevice); auto effectlock = std::lock_guard{device->EffectLock}; auto const aleffect = LookupEffect(context, effect); /* Call the appropriate handler */ std::visit([context,aleffect,param,value](T &arg) { using PropType = T::prop_type; return arg.SetParamf(context, std::get(aleffect->mProps), param, value); }, aleffect->mPropsVariant); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } void alEffectfv(gsl::not_null context, ALuint effect, ALenum param, const ALfloat *values) noexcept try { auto const device = al::get_not_null(context->mALDevice); auto effectlock = std::lock_guard{device->EffectLock}; auto const aleffect = LookupEffect(context, effect); /* Call the appropriate handler */ std::visit([context,aleffect,param,values](T &arg) { using PropType = T::prop_type; return arg.SetParamfv(context, std::get(aleffect->mProps), param, values); }, aleffect->mPropsVariant); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } void alGetEffecti(gsl::not_null context, ALuint effect, ALenum param, ALint *value) noexcept try { auto const device = al::get_not_null(context->mALDevice); auto effectlock = std::lock_guard{device->EffectLock}; auto const aleffect = LookupEffect(context, effect); switch(param) { case AL_EFFECT_TYPE: *value = aleffect->mType; return; } /* Call the appropriate handler */ std::visit([context,aleffect,param,value](T &arg) { using PropType = T::prop_type; return arg.GetParami(context, std::get(aleffect->mProps), param, value); }, aleffect->mPropsVariant); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } void alGetEffectiv(gsl::not_null context, ALuint effect, ALenum param, ALint *values) noexcept try { switch(param) { case AL_EFFECT_TYPE: alGetEffecti(context, effect, param, values); return; } auto const device = al::get_not_null(context->mALDevice); auto effectlock = std::lock_guard{device->EffectLock}; auto const aleffect = LookupEffect(context, effect); /* Call the appropriate handler */ std::visit([context,aleffect,param,values](T &arg) { using PropType = T::prop_type; return arg.GetParamiv(context, std::get(aleffect->mProps), param, values); }, aleffect->mPropsVariant); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } void alGetEffectf(gsl::not_null context, ALuint effect, ALenum param, ALfloat *value) noexcept try { auto const device = al::get_not_null(context->mALDevice); auto effectlock = std::lock_guard{device->EffectLock}; auto const aleffect = LookupEffect(context, effect); /* Call the appropriate handler */ std::visit([context,aleffect,param,value](T &arg) { using PropType = T::prop_type; return arg.GetParamf(context, std::get(aleffect->mProps), param, value); }, aleffect->mPropsVariant); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } void alGetEffectfv(gsl::not_null context, ALuint effect, ALenum param, ALfloat *values) noexcept try { auto const device = al::get_not_null(context->mALDevice); auto effectlock = std::lock_guard{device->EffectLock}; auto const aleffect = LookupEffect(context, effect); /* Call the appropriate handler */ std::visit([context,aleffect,param,values](T &arg) { using PropType = T::prop_type; return arg.GetParamfv(context, std::get(aleffect->mProps), param, values); }, aleffect->mPropsVariant); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } } // namespace AL_API DECL_FUNC2(void, alGenEffects, ALsizei,n, ALuint*,effects) AL_API DECL_FUNC2(void, alDeleteEffects, ALsizei,n, const ALuint*,effects) AL_API DECL_FUNC1(ALboolean, alIsEffect, ALuint,effect) AL_API DECL_FUNC3(void, alEffecti, ALuint,effect, ALenum,param, ALint,value) AL_API DECL_FUNC3(void, alEffectiv, ALuint,effect, ALenum,param, const ALint*,values) AL_API DECL_FUNC3(void, alEffectf, ALuint,effect, ALenum,param, ALfloat,value) AL_API DECL_FUNC3(void, alEffectfv, ALuint,effect, ALenum,param, const ALfloat*,values) AL_API DECL_FUNC3(void, alGetEffecti, ALuint,effect, ALenum,param, ALint*,value) AL_API DECL_FUNC3(void, alGetEffectiv, ALuint,effect, ALenum,param, ALint*,values) AL_API DECL_FUNC3(void, alGetEffectf, ALuint,effect, ALenum,param, ALfloat*,value) AL_API DECL_FUNC3(void, alGetEffectfv, ALuint,effect, ALenum,param, ALfloat*,values) void InitEffect(al::Effect *const effect) { InitEffectParams(effect, AL_EFFECT_NULL); } void al::Effect::SetName(gsl::not_null const context, u32 const id, std::string_view const name) { auto const device = al::get_not_null(context->mALDevice); auto const effectlock = std::lock_guard{device->EffectLock}; std::ignore = LookupEffect(context, id); device->mEffectNames.insert_or_assign(id, name); } EffectSubList::~EffectSubList() { if(!mEffects) return; auto usemask = ~mFreeMask; while(usemask) { const auto idx = std::countr_zero(usemask); std::destroy_at(std::to_address(std::next(mEffects->begin(), idx))); usemask &= ~(1_u64 << idx); } mFreeMask = ~usemask; SubListAllocator{}.deallocate(mEffects, 1); mEffects = nullptr; } struct EffectPreset { std::string_view name; EFXEAXREVERBPROPERTIES props; }; #define DECL(x) EffectPreset{#x##sv, EFX_REVERB_PRESET_##x} static constexpr auto reverblist = std::array{ 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(std::string_view const name, al::Effect *const effect) { 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 { TRACE("Reverb disabled, ignoring preset '{}'", name); InitEffectParams(effect, AL_EFFECT_NULL); return; } const auto preset = std::ranges::find_if(reverblist, [name](EffectPreset const &item) -> bool { return al::case_compare(name, item.name) == 0; }); if(preset == reverblist.end()) { WARN("Reverb preset '{}' not found", name); return; } TRACE("Loading reverb '{}'", preset->name); const auto &props = preset->props; auto &dst = std::get(effect->mProps); 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; } auto IsValidEffectType(ALenum const type) noexcept -> bool { if(type == AL_EFFECT_NULL) return true; return std::ranges::any_of(gEffectList, [type](EffectList const &item) noexcept -> bool { return type == item.val && !DisabledEffects.test(item.type); }); } kcat-openal-soft-75c0059/al/effect.h000066400000000000000000000045631512220627100171460ustar00rootroot00000000000000#ifndef AL_EFFECT_H #define AL_EFFECT_H #include #include #include #include #include #include "AL/al.h" #include "AL/efx.h" #include "almalloc.h" #include "alnumeric.h" #include "core/effects/base.h" #include "effects/effects.h" #include "gsl/gsl" 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 { std::string_view name; ALuint type; ALenum val; }; DECL_HIDDEN constinit extern const std::array gEffectList; using EffectHandlerVariant = std::variant; namespace al { struct Effect { // Effect type (AL_EFFECT_NULL, ...) ALenum mType{AL_EFFECT_NULL}; EffectHandlerVariant mPropsVariant; EffectProps mProps; /* Self ID */ u32 mId{0u}; static void SetName(gsl::not_null context, u32 id, std::string_view name); DISABLE_ALLOC }; } /* namespace al */ void InitEffect(al::Effect *effect); void LoadReverbPreset(std::string_view name, al::Effect *effect); bool IsValidEffectType(ALenum type) noexcept; struct EffectSubList { u64 mFreeMask{~0_u64}; gsl::owner*> mEffects{nullptr}; /* 64 */ EffectSubList() noexcept = default; EffectSubList(const EffectSubList&) = delete; EffectSubList(EffectSubList&& rhs) noexcept : mFreeMask{rhs.mFreeMask}, mEffects{rhs.mEffects} { rhs.mFreeMask = ~0_u64; rhs.mEffects = nullptr; } ~EffectSubList(); EffectSubList& operator=(const EffectSubList&) = delete; EffectSubList& operator=(EffectSubList&& rhs) noexcept { std::swap(mFreeMask, rhs.mFreeMask); std::swap(mEffects, rhs.mEffects); return *this; } }; #endif kcat-openal-soft-75c0059/al/effects/000077500000000000000000000000001512220627100171505ustar00rootroot00000000000000kcat-openal-soft-75c0059/al/effects/autowah.cpp000066400000000000000000000167731512220627100213420ustar00rootroot00000000000000 #include "config.h" #include #include #include "AL/efx.h" #include "alc/context.h" #include "alnumeric.h" #include "effects.h" #include "gsl/gsl" #if ALSOFT_EAX #include "al/eax/effect.h" #include "al/eax/exception.h" #include "al/eax/utils.h" #endif // ALSOFT_EAX namespace { consteval auto genDefaultProps() noexcept -> EffectProps { return AutowahProps{ .AttackTime = AL_AUTOWAH_DEFAULT_ATTACK_TIME, .ReleaseTime = AL_AUTOWAH_DEFAULT_RELEASE_TIME, .Resonance = AL_AUTOWAH_DEFAULT_RESONANCE, .PeakGain = AL_AUTOWAH_DEFAULT_PEAK_GAIN}; } } // namespace constinit const EffectProps AutowahEffectProps(genDefaultProps()); void AutowahEffectHandler::SetParami(al::Context *context, AutowahProps&, ALenum param, int) { context->throw_error(AL_INVALID_ENUM, "Invalid autowah integer property {:#04x}", as_unsigned(param)); } void AutowahEffectHandler::SetParamiv(al::Context *context, AutowahProps&, ALenum param, const int*) { context->throw_error(AL_INVALID_ENUM, "Invalid autowah integer vector property {:#04x}", as_unsigned(param)); } void AutowahEffectHandler::SetParamf(al::Context *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(al::Context *context, AutowahProps &props, ALenum param, const float *vals) { SetParamf(context, props, param, *vals); } void AutowahEffectHandler::GetParami(al::Context *context, const AutowahProps&, ALenum param, int*) { context->throw_error(AL_INVALID_ENUM, "Invalid autowah integer property {:#04x}", as_unsigned(param)); } void AutowahEffectHandler::GetParamiv(al::Context *context, const AutowahProps&, ALenum param, int*) { context->throw_error(AL_INVALID_ENUM, "Invalid autowah integer vector property {:#04x}", as_unsigned(param)); } void AutowahEffectHandler::GetParamf(al::Context *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(al::Context *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()(eax_long const lResonance) const { eax_validate_range( "Resonance", lResonance, EAXAUTOWAH_MINRESONANCE, EAXAUTOWAH_MAXRESONANCE); } }; // ResonanceValidator struct PeakLevelValidator { void operator()(eax_long const 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<> /* NOLINTNEXTLINE(clazy-copyable-polymorphic) Exceptions must be copyable. */ struct AutowahCommitter::Exception final : EaxException { explicit Exception(const std::string_view message) : EaxException{"EAX_AUTOWAH_EFFECT", message} { } }; template<> [[noreturn]] void AutowahCommitter::fail(const std::string_view message) { throw Exception{message}; } auto EaxAutowahCommitter::commit(const EAXAUTOWAHPROPERTIES &props) const -> bool { if(auto *cur = std::get_if(&mEaxProps); cur && *cur == props) return false; mEaxProps = props; mAlProps = AutowahProps{ .AttackTime = props.flAttackTime, .ReleaseTime = props.flReleaseTime, .Resonance = level_mb_to_gain(gsl::narrow_cast(props.lResonance)), .PeakGain = level_mb_to_gain(gsl::narrow_cast(props.lPeakLevel))}; return true; } void EaxAutowahCommitter::SetDefaults(EaxEffectProps &props) { props = EAXAUTOWAHPROPERTIES{ .flAttackTime = EAXAUTOWAH_DEFAULTATTACKTIME, .flReleaseTime = EAXAUTOWAH_DEFAULTRELEASETIME, .lResonance = EAXAUTOWAH_DEFAULTRESONANCE, .lPeakLevel = EAXAUTOWAH_DEFAULTPEAKLEVEL}; } void EaxAutowahCommitter::Get(const EaxCall &call, const EAXAUTOWAHPROPERTIES &props) { switch(call.get_property_id()) { case EAXAUTOWAH_NONE: break; case EAXAUTOWAH_ALLPARAMETERS: call.store(props); break; case EAXAUTOWAH_ATTACKTIME: call.store(props.flAttackTime); break; case EAXAUTOWAH_RELEASETIME: call.store(props.flReleaseTime); break; case EAXAUTOWAH_RESONANCE: call.store(props.lResonance); break; case EAXAUTOWAH_PEAKLEVEL: call.store(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 kcat-openal-soft-75c0059/al/effects/chorus.cpp000066400000000000000000000540641512220627100211700ustar00rootroot00000000000000 #include "config.h" #include #include #include "AL/al.h" #include "AL/efx.h" #include "alc/context.h" #include "alformat.hpp" #include "alnumeric.h" #include "core/logging.h" #include "effects.h" #include "gsl/gsl" #if ALSOFT_EAX #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{al::format("Invalid chorus waveform: {}", int{al::to_underlying(type)})}; } consteval auto genDefaultChorusProps() noexcept -> EffectProps { return ChorusProps{ /* NOLINTNEXTLINE(bugprone-unchecked-optional-access) */ .Waveform = WaveformFromEnum(AL_CHORUS_DEFAULT_WAVEFORM).value(), .Phase = AL_CHORUS_DEFAULT_PHASE, .Rate = AL_CHORUS_DEFAULT_RATE, .Depth = AL_CHORUS_DEFAULT_DEPTH, .Feedback = AL_CHORUS_DEFAULT_FEEDBACK, .Delay = AL_CHORUS_DEFAULT_DELAY}; } consteval auto genDefaultFlangerProps() noexcept -> EffectProps { return ChorusProps{ /* NOLINTNEXTLINE(bugprone-unchecked-optional-access) */ .Waveform = WaveformFromEnum(AL_FLANGER_DEFAULT_WAVEFORM).value(), .Phase = AL_FLANGER_DEFAULT_PHASE, .Rate = AL_FLANGER_DEFAULT_RATE, .Depth = AL_FLANGER_DEFAULT_DEPTH, .Feedback = AL_FLANGER_DEFAULT_FEEDBACK, .Delay = AL_FLANGER_DEFAULT_DELAY}; } } // namespace constinit const EffectProps ChorusEffectProps(genDefaultChorusProps()); void ChorusEffectHandler::SetParami(al::Context *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(al::Context *context, ChorusProps &props, ALenum param, const int *vals) { SetParami(context, props, param, *vals); } void ChorusEffectHandler::SetParamf(al::Context *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: {}", 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: {}", 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: {}", 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: {}", val); props.Delay = val; return; } context->throw_error(AL_INVALID_ENUM, "Invalid chorus float property {:#04x}", as_unsigned(param)); } void ChorusEffectHandler::SetParamfv(al::Context *context, ChorusProps &props, ALenum param, const float *vals) { SetParamf(context, props, param, *vals); } void ChorusEffectHandler::GetParami(al::Context *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(al::Context *context, const ChorusProps &props, ALenum param, int *vals) { GetParami(context, props, param, vals); } void ChorusEffectHandler::GetParamf(al::Context *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(al::Context *context, const ChorusProps &props, ALenum param, float *vals) { GetParamf(context, props, param, vals); } constinit const EffectProps FlangerEffectProps(genDefaultFlangerProps()); void FlangerEffectHandler::SetParami(al::Context *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(al::Context *context, ChorusProps &props, ALenum param, const int *vals) { SetParami(context, props, param, *vals); } void FlangerEffectHandler::SetParamf(al::Context *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: {}", 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: {}", 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: {}", 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: {}", val); props.Delay = val; return; } context->throw_error(AL_INVALID_ENUM, "Invalid flanger float property {:#04x}", as_unsigned(param)); } void FlangerEffectHandler::SetParamfv(al::Context *context, ChorusProps &props, ALenum param, const float *vals) { SetParamf(context, props, param, *vals); } void FlangerEffectHandler::GetParami(al::Context *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(al::Context *context, const ChorusProps &props, ALenum param, int *vals) { GetParami(context, props, param, vals); } void FlangerEffectHandler::GetParamf(al::Context *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(al::Context *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 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 eax_waveform(eax_ulong const type) -> ChorusWaveform { 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 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 eax_waveform(eax_ulong const type) -> ChorusWaveform { 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 = Traits::EaxProps; using Committer = Traits::Committer; using Exception = Committer::Exception; struct WaveformValidator { void operator()(eax_ulong const ulWaveform) const { eax_validate_range( "Waveform", ulWaveform, Traits::eax_min_waveform(), Traits::eax_max_waveform()); } }; // WaveformValidator struct PhaseValidator { void operator()(eax_long const lPhase) const { eax_validate_range( "Phase", lPhase, Traits::eax_min_phase(), Traits::eax_max_phase()); } }; // PhaseValidator struct RateValidator { void operator()(float const flRate) const { eax_validate_range( "Rate", flRate, Traits::eax_min_rate(), Traits::eax_max_rate()); } }; // RateValidator struct DepthValidator { void operator()(float const flDepth) const { eax_validate_range( "Depth", flDepth, Traits::eax_min_depth(), Traits::eax_max_depth()); } }; // DepthValidator struct FeedbackValidator { void operator()(float const flFeedback) const { eax_validate_range( "Feedback", flFeedback, Traits::eax_min_feedback(), Traits::eax_max_feedback()); } }; // FeedbackValidator struct DelayValidator { void operator()(float const 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 static void SetDefaults(EaxEffectProps &props) { props = EaxProps{ .ulWaveform = Traits::eax_default_waveform(), .lPhase = Traits::eax_default_phase(), .flRate = Traits::eax_default_rate(), .flDepth = Traits::eax_default_depth(), .flFeedback = Traits::eax_default_feedback(), .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.store(all); break; case Traits::eax_waveform_param_id(): call.store(all.ulWaveform); break; case Traits::eax_phase_param_id(): call.store(all.lPhase); break; case Traits::eax_rate_param_id(): call.store(all.flRate); break; case Traits::eax_depth_param_id(): call.store(all.flDepth); break; case Traits::eax_feedback_param_id(): call.store(all.flFeedback); break; case Traits::eax_delay_param_id(): call.store(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 = gsl::narrow_cast(props.lPhase); al_props_.Rate = props.flRate; al_props_.Depth = props.flDepth; al_props_.Feedback = props.flFeedback; al_props_.Delay = props.flDelay; if(EaxTraceCommits) [[unlikely]] { TRACE("Chorus/flanger commit:\n" " Waveform: {}\n" " Phase: {}\n" " Rate: {:f}\n" " Depth: {:f}\n" " Feedback: {:f}\n" " Delay: {:f}", al::to_underlying(al_props_.Waveform), al_props_.Phase, al_props_.Rate, al_props_.Depth, al_props_.Feedback, al_props_.Delay); } return true; } }; // EaxChorusFlangerEffect using ChorusCommitter = EaxCommitter; using FlangerCommitter = EaxCommitter; } // namespace template<> /* NOLINTNEXTLINE(clazy-copyable-polymorphic) Exceptions must be copyable. */ struct ChorusCommitter::Exception final : EaxException { explicit Exception(const std::string_view message) : EaxException{"EAX_CHORUS_EFFECT", message} { } }; template<> [[noreturn]] void ChorusCommitter::fail(const std::string_view message) { throw Exception{message}; } auto EaxChorusCommitter::commit(const EAXCHORUSPROPERTIES &props) const -> bool { 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<> /* NOLINTNEXTLINE(clazy-copyable-polymorphic) Exceptions must be copyable. */ struct FlangerCommitter::Exception final : EaxException { explicit Exception(const std::string_view message) : EaxException{"EAX_FLANGER_EFFECT",message} { } }; template<> [[noreturn]] void FlangerCommitter::fail(const std::string_view message) { throw Exception{message}; } auto EaxFlangerCommitter::commit(const EAXFLANGERPROPERTIES &props) const -> bool { 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 kcat-openal-soft-75c0059/al/effects/compressor.cpp000066400000000000000000000111331512220627100220470ustar00rootroot00000000000000 #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 { consteval auto genDefaultProps() noexcept -> EffectProps { return CompressorProps{.OnOff = AL_COMPRESSOR_DEFAULT_ONOFF}; } } // namespace constinit const EffectProps CompressorEffectProps(genDefaultProps()); void CompressorEffectHandler::SetParami(al::Context *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(al::Context *context, CompressorProps &props, ALenum param, const int *vals) { SetParami(context, props, param, *vals); } void CompressorEffectHandler::SetParamf(al::Context *context, CompressorProps&, ALenum param, float) { context->throw_error(AL_INVALID_ENUM, "Invalid compressor float property {:#04x}", as_unsigned(param)); } void CompressorEffectHandler::SetParamfv(al::Context *context, CompressorProps&, ALenum param, const float*) { context->throw_error(AL_INVALID_ENUM, "Invalid compressor float-vector property {:#04x}", as_unsigned(param)); } void CompressorEffectHandler::GetParami(al::Context *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(al::Context *context, const CompressorProps &props, ALenum param, int *vals) { GetParami(context, props, param, vals); } void CompressorEffectHandler::GetParamf(al::Context *context, const CompressorProps&, ALenum param, float*) { context->throw_error(AL_INVALID_ENUM, "Invalid compressor float property {:#04x}", as_unsigned(param)); } void CompressorEffectHandler::GetParamfv(al::Context *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()(eax_ulong const 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<> /* NOLINTNEXTLINE(clazy-copyable-polymorphic) Exceptions must be copyable. */ struct CompressorCommitter::Exception final : EaxException { explicit Exception(const std::string_view message) : EaxException{"EAX_CHORUS_EFFECT", message} { } }; template<> [[noreturn]] void CompressorCommitter::fail(const std::string_view message) { throw Exception{message}; } auto EaxCompressorCommitter::commit(const EAXAGCCOMPRESSORPROPERTIES &props) const -> bool { if(auto *cur = std::get_if(&mEaxProps); cur && *cur == props) return false; mEaxProps = props; mAlProps = CompressorProps{.OnOff = props.ulOnOff != 0}; return true; } void EaxCompressorCommitter::SetDefaults(EaxEffectProps &props) { props = EAXAGCCOMPRESSORPROPERTIES{.ulOnOff = EAXAGCCOMPRESSOR_DEFAULTONOFF}; } void EaxCompressorCommitter::Get(const EaxCall &call, const EAXAGCCOMPRESSORPROPERTIES &props) { switch(call.get_property_id()) { case EAXAGCCOMPRESSOR_NONE: break; case EAXAGCCOMPRESSOR_ALLPARAMETERS: call.store(props); break; case EAXAGCCOMPRESSOR_ONOFF: call.store(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 kcat-openal-soft-75c0059/al/effects/convolution.cpp000066400000000000000000000056071512220627100222430ustar00rootroot00000000000000 #include "config.h" #include #include #include #include #include "AL/al.h" #include "alc/context.h" #include "alc/inprogext.h" #include "alnumeric.h" #include "effects.h" namespace { consteval auto genDefaultProps() noexcept -> EffectProps { return ConvolutionProps{ .OrientAt = {0.0f, 0.0f, -1.0f}, .OrientUp = {0.0f, 1.0f, 0.0f}}; } } // namespace constinit const EffectProps ConvolutionEffectProps(genDefaultProps()); void ConvolutionEffectHandler::SetParami(al::Context *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(al::Context *context, ConvolutionProps &props, ALenum param, const int *vals) { SetParami(context, props, param, *vals); } void ConvolutionEffectHandler::SetParamf(al::Context *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(al::Context *context, ConvolutionProps &props, ALenum param, const float *values) { static constexpr auto is_finite = [](float val) -> bool { return std::isfinite(val); }; switch(param) { case AL_CONVOLUTION_ORIENTATION_SOFT: const auto vals = std::span{values, 6_uz}; if(!std::ranges::all_of(vals, is_finite)) context->throw_error(AL_INVALID_VALUE, "Convolution orientation out of range", param); std::copy_n(vals.begin(), props.OrientAt.size(), props.OrientAt.begin()); std::copy_n(vals.begin()+3, props.OrientUp.size(), props.OrientUp.begin()); return; } SetParamf(context, props, param, *values); } void ConvolutionEffectHandler::GetParami(al::Context *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(al::Context *context, const ConvolutionProps &props, ALenum param, int *vals) { GetParami(context, props, param, vals); } void ConvolutionEffectHandler::GetParamf(al::Context *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(al::Context *context, const ConvolutionProps &props, ALenum param, float *values) { switch(param) { case AL_CONVOLUTION_ORIENTATION_SOFT: const auto vals = std::span{values, 6_uz}; const auto oiter = std::ranges::copy(props.OrientAt, vals.begin()).out; std::ranges::copy(props.OrientUp, oiter); return; } GetParamf(context, props, param, values); } kcat-openal-soft-75c0059/al/effects/dedicated.cpp000066400000000000000000000110721512220627100215630ustar00rootroot00000000000000 #include "config.h" #include #include "AL/al.h" #include "AL/alext.h" #include "alc/context.h" #include "alnumeric.h" #include "effects.h" namespace { consteval auto genDefaultDialogProps() noexcept -> EffectProps { return DedicatedProps{ .Target = DedicatedProps::Dialog, .Gain = 1.0f}; } consteval auto genDefaultLfeProps() noexcept -> EffectProps { return DedicatedProps{ .Target = DedicatedProps::Lfe, .Gain = 1.0f}; } } // namespace constinit const EffectProps DedicatedDialogEffectProps(genDefaultDialogProps()); void DedicatedDialogEffectHandler::SetParami(al::Context *context, DedicatedProps&, ALenum param, int) { context->throw_error(AL_INVALID_ENUM, "Invalid dedicated integer property {:#04x}", as_unsigned(param)); } void DedicatedDialogEffectHandler::SetParamiv(al::Context *context, DedicatedProps&, ALenum param, const int*) { context->throw_error(AL_INVALID_ENUM, "Invalid dedicated integer-vector property {:#04x}", as_unsigned(param)); } void DedicatedDialogEffectHandler::SetParamf(al::Context *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(al::Context *context, DedicatedProps &props, ALenum param, const float *vals) { SetParamf(context, props, param, *vals); } void DedicatedDialogEffectHandler::GetParami(al::Context *context, const DedicatedProps&, ALenum param, int*) { context->throw_error(AL_INVALID_ENUM, "Invalid dedicated integer property {:#04x}", as_unsigned(param)); } void DedicatedDialogEffectHandler::GetParamiv(al::Context *context, const DedicatedProps&, ALenum param, int*) { context->throw_error(AL_INVALID_ENUM, "Invalid dedicated integer-vector property {:#04x}", as_unsigned(param)); } void DedicatedDialogEffectHandler::GetParamf(al::Context *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(al::Context *context, const DedicatedProps &props, ALenum param, float *vals) { GetParamf(context, props, param, vals); } constinit const EffectProps DedicatedLfeEffectProps(genDefaultLfeProps()); void DedicatedLfeEffectHandler::SetParami(al::Context *context, DedicatedProps&, ALenum param, int) { context->throw_error(AL_INVALID_ENUM, "Invalid dedicated integer property {:#04x}", as_unsigned(param)); } void DedicatedLfeEffectHandler::SetParamiv(al::Context *context, DedicatedProps&, ALenum param, const int*) { context->throw_error(AL_INVALID_ENUM, "Invalid dedicated integer-vector property {:#04x}", as_unsigned(param)); } void DedicatedLfeEffectHandler::SetParamf(al::Context *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(al::Context *context, DedicatedProps &props, ALenum param, const float *vals) { SetParamf(context, props, param, *vals); } void DedicatedLfeEffectHandler::GetParami(al::Context *context, const DedicatedProps&, ALenum param, int*) { context->throw_error(AL_INVALID_ENUM, "Invalid dedicated integer property {:#04x}", as_unsigned(param)); } void DedicatedLfeEffectHandler::GetParamiv(al::Context *context, const DedicatedProps&, ALenum param, int*) { context->throw_error(AL_INVALID_ENUM, "Invalid dedicated integer-vector property {:#04x}", as_unsigned(param)); } void DedicatedLfeEffectHandler::GetParamf(al::Context *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(al::Context *context, const DedicatedProps &props, ALenum param, float *vals) { GetParamf(context, props, param, vals); } kcat-openal-soft-75c0059/al/effects/distortion.cpp000066400000000000000000000207601512220627100220570ustar00rootroot00000000000000 #include "config.h" #include "AL/al.h" #include "AL/efx.h" #include "alc/context.h" #include "alnumeric.h" #include "effects.h" #include "gsl/gsl" #if ALSOFT_EAX #include "al/eax/effect.h" #include "al/eax/exception.h" #include "al/eax/utils.h" #endif // ALSOFT_EAX namespace { consteval auto genDefaultProps() noexcept -> EffectProps { return DistortionProps{ .Edge = AL_DISTORTION_DEFAULT_EDGE, .Gain = AL_DISTORTION_DEFAULT_GAIN, .LowpassCutoff = AL_DISTORTION_DEFAULT_LOWPASS_CUTOFF, .EQCenter = AL_DISTORTION_DEFAULT_EQCENTER, .EQBandwidth = AL_DISTORTION_DEFAULT_EQBANDWIDTH}; } } // namespace constinit const EffectProps DistortionEffectProps(genDefaultProps()); void DistortionEffectHandler::SetParami(al::Context *context, DistortionProps&, ALenum param, int) { context->throw_error(AL_INVALID_ENUM, "Invalid distortion integer property {:#04x}", as_unsigned(param)); } void DistortionEffectHandler::SetParamiv(al::Context *context, DistortionProps&, ALenum param, const int*) { context->throw_error(AL_INVALID_ENUM, "Invalid distortion integer-vector property {:#04x}", as_unsigned(param)); } void DistortionEffectHandler::SetParamf(al::Context *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(al::Context *context, DistortionProps &props, ALenum param, const float *vals) { SetParamf(context, props, param, *vals); } void DistortionEffectHandler::GetParami(al::Context *context, const DistortionProps&, ALenum param, int*) { context->throw_error(AL_INVALID_ENUM, "Invalid distortion integer property {:#04x}", as_unsigned(param)); } void DistortionEffectHandler::GetParamiv(al::Context *context, const DistortionProps&, ALenum param, int*) { context->throw_error(AL_INVALID_ENUM, "Invalid distortion integer-vector property {:#04x}", as_unsigned(param)); } void DistortionEffectHandler::GetParamf(al::Context *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(al::Context *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 const flEdge) const { eax_validate_range( "Edge", flEdge, EAXDISTORTION_MINEDGE, EAXDISTORTION_MAXEDGE); } }; // EdgeValidator struct GainValidator { void operator()(eax_long const lGain) const { eax_validate_range( "Gain", lGain, EAXDISTORTION_MINGAIN, EAXDISTORTION_MAXGAIN); } }; // GainValidator struct LowPassCutOffValidator { void operator()(float const flLowPassCutOff) const { eax_validate_range( "Low-pass Cut-off", flLowPassCutOff, EAXDISTORTION_MINLOWPASSCUTOFF, EAXDISTORTION_MAXLOWPASSCUTOFF); } }; // LowPassCutOffValidator struct EqCenterValidator { void operator()(float const flEQCenter) const { eax_validate_range( "EQ Center", flEQCenter, EAXDISTORTION_MINEQCENTER, EAXDISTORTION_MAXEQCENTER); } }; // EqCenterValidator struct EqBandwidthValidator { void operator()(float const 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<> /* NOLINTNEXTLINE(clazy-copyable-polymorphic) Exceptions must be copyable. */ struct DistortionCommitter::Exception final : EaxException { explicit Exception(const std::string_view message) : EaxException{"EAX_DISTORTION_EFFECT", message} { } }; template<> [[noreturn]] void DistortionCommitter::fail(const std::string_view message) { throw Exception{message}; } auto EaxDistortionCommitter::commit(const EAXDISTORTIONPROPERTIES &props) const -> bool { if(auto *cur = std::get_if(&mEaxProps); cur && *cur == props) return false; mEaxProps = props; mAlProps = DistortionProps{ .Edge = props.flEdge, .Gain = level_mb_to_gain(gsl::narrow_cast(props.lGain)), .LowpassCutoff = props.flLowPassCutOff, .EQCenter = props.flEQCenter, .EQBandwidth = props.flEdge}; return true; } void EaxDistortionCommitter::SetDefaults(EaxEffectProps &props) { props = EAXDISTORTIONPROPERTIES{ .flEdge = EAXDISTORTION_DEFAULTEDGE, .lGain = EAXDISTORTION_DEFAULTGAIN, .flLowPassCutOff = EAXDISTORTION_DEFAULTLOWPASSCUTOFF, .flEQCenter = EAXDISTORTION_DEFAULTEQCENTER, .flEQBandwidth = EAXDISTORTION_DEFAULTEQBANDWIDTH}; } void EaxDistortionCommitter::Get(const EaxCall &call, const EAXDISTORTIONPROPERTIES &props) { switch(call.get_property_id()) { case EAXDISTORTION_NONE: break; case EAXDISTORTION_ALLPARAMETERS: call.store(props); break; case EAXDISTORTION_EDGE: call.store(props.flEdge); break; case EAXDISTORTION_GAIN: call.store(props.lGain); break; case EAXDISTORTION_LOWPASSCUTOFF: call.store(props.flLowPassCutOff); break; case EAXDISTORTION_EQCENTER: call.store(props.flEQCenter); break; case EAXDISTORTION_EQBANDWIDTH: call.store(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 kcat-openal-soft-75c0059/al/effects/echo.cpp000066400000000000000000000174431512220627100206030ustar00rootroot00000000000000 #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"); consteval auto genDefaultProps() noexcept -> EffectProps { return EchoProps{ .Delay = AL_ECHO_DEFAULT_DELAY, .LRDelay = AL_ECHO_DEFAULT_LRDELAY, .Damping = AL_ECHO_DEFAULT_DAMPING, .Feedback = AL_ECHO_DEFAULT_FEEDBACK, .Spread = AL_ECHO_DEFAULT_SPREAD}; } } // namespace constinit const EffectProps EchoEffectProps(genDefaultProps()); void EchoEffectHandler::SetParami(al::Context *context, EchoProps&, ALenum param, int) { context->throw_error(AL_INVALID_ENUM, "Invalid echo integer property {:#04x}", as_unsigned(param)); } void EchoEffectHandler::SetParamiv(al::Context *context, EchoProps&, ALenum param, const int*) { context->throw_error(AL_INVALID_ENUM, "Invalid echo integer-vector property {:#04x}", as_unsigned(param)); } void EchoEffectHandler::SetParamf(al::Context *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(al::Context *context, EchoProps &props, ALenum param, const float *vals) { SetParamf(context, props, param, *vals); } void EchoEffectHandler::GetParami(al::Context *context, const EchoProps&, ALenum param, int*) { context->throw_error(AL_INVALID_ENUM, "Invalid echo integer property {:#04x}", as_unsigned(param)); } void EchoEffectHandler::GetParamiv(al::Context *context, const EchoProps&, ALenum param, int*) { context->throw_error(AL_INVALID_ENUM, "Invalid echo integer-vector property {:#04x}", as_unsigned(param)); } void EchoEffectHandler::GetParamf(al::Context *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(al::Context *context, const EchoProps &props, ALenum param, float *vals) { GetParamf(context, props, param, vals); } #if ALSOFT_EAX namespace { using EchoCommitter = EaxCommitter; struct DelayValidator { void operator()(f32 const flDelay) const { eax_validate_range( "Delay", flDelay, EAXECHO_MINDELAY, EAXECHO_MAXDELAY); } }; // DelayValidator struct LrDelayValidator { void operator()(f32 const flLRDelay) const { eax_validate_range( "LR Delay", flLRDelay, EAXECHO_MINLRDELAY, EAXECHO_MAXLRDELAY); } }; // LrDelayValidator struct DampingValidator { void operator()(f32 const flDamping) const { eax_validate_range( "Damping", flDamping, EAXECHO_MINDAMPING, EAXECHO_MAXDAMPING); } }; // DampingValidator struct FeedbackValidator { void operator()(f32 const flFeedback) const { eax_validate_range( "Feedback", flFeedback, EAXECHO_MINFEEDBACK, EAXECHO_MAXFEEDBACK); } }; // FeedbackValidator struct SpreadValidator { void operator()(f32 const 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<> /* NOLINTNEXTLINE(clazy-copyable-polymorphic) Exceptions must be copyable. */ struct EchoCommitter::Exception final : EaxException { explicit Exception(const std::string_view message) : EaxException{"EAX_ECHO_EFFECT", message} { } }; template<> [[noreturn]] void EchoCommitter::fail(const std::string_view message) { throw Exception{message}; } auto EaxEchoCommitter::commit(const EAXECHOPROPERTIES &props) const -> bool { if(auto *cur = std::get_if(&mEaxProps); cur && *cur == props) return false; mEaxProps = props; mAlProps = EchoProps{ .Delay = props.flDelay, .LRDelay = props.flLRDelay, .Damping = props.flDamping, .Feedback = props.flFeedback, .Spread = props.flSpread}; return true; } void EaxEchoCommitter::SetDefaults(EaxEffectProps &props) { props = EAXECHOPROPERTIES{ .flDelay = EAXECHO_DEFAULTDELAY, .flLRDelay = EAXECHO_DEFAULTLRDELAY, .flDamping = EAXECHO_DEFAULTDAMPING, .flFeedback = EAXECHO_DEFAULTFEEDBACK, .flSpread = EAXECHO_DEFAULTSPREAD}; } void EaxEchoCommitter::Get(const EaxCall &call, const EAXECHOPROPERTIES &props) { switch(call.get_property_id()) { case EAXECHO_NONE: break; case EAXECHO_ALLPARAMETERS: call.store(props); break; case EAXECHO_DELAY: call.store(props.flDelay); break; case EAXECHO_LRDELAY: call.store(props.flLRDelay); break; case EAXECHO_DAMPING: call.store(props.flDamping); break; case EAXECHO_FEEDBACK: call.store(props.flFeedback); break; case EAXECHO_SPREAD: call.store(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 kcat-openal-soft-75c0059/al/effects/effects.cpp000066400000000000000000000000521512220627100212700ustar00rootroot00000000000000#include "config.h" #include "effects.h" kcat-openal-soft-75c0059/al/effects/effects.h000066400000000000000000000066421512220627100207500ustar00rootroot00000000000000#ifndef AL_EFFECTS_EFFECTS_H #define AL_EFFECTS_EFFECTS_H #include #include "AL/al.h" #include "core/effects/base.h" #include "opthelpers.h" namespace al { struct Context; } // namespace al #define DECL_HANDLER(N, T) \ struct N { \ using prop_type = T; \ \ static void SetParami(al::Context *context, prop_type &props, ALenum param, int val); \ static void SetParamiv(al::Context *context, prop_type &props, ALenum param, const int *vals); \ static void SetParamf(al::Context *context, prop_type &props, ALenum param, float val); \ static void SetParamfv(al::Context *context, prop_type &props, ALenum param, const float *vals);\ static void GetParami(al::Context *context, const prop_type &props, ALenum param, int *val); \ static void GetParamiv(al::Context *context, const prop_type &props, ALenum param, int *vals); \ static void GetParamf(al::Context *context, const prop_type &props, ALenum param, float *val); \ static void GetParamfv(al::Context *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 constinit const EffectProps NullEffectProps; DECL_HIDDEN extern constinit const EffectProps ReverbEffectProps; DECL_HIDDEN extern constinit const EffectProps StdReverbEffectProps; DECL_HIDDEN extern constinit const EffectProps AutowahEffectProps; DECL_HIDDEN extern constinit const EffectProps ChorusEffectProps; DECL_HIDDEN extern constinit const EffectProps CompressorEffectProps; DECL_HIDDEN extern constinit const EffectProps DistortionEffectProps; DECL_HIDDEN extern constinit const EffectProps EchoEffectProps; DECL_HIDDEN extern constinit const EffectProps EqualizerEffectProps; DECL_HIDDEN extern constinit const EffectProps FlangerEffectProps; DECL_HIDDEN extern constinit const EffectProps FshifterEffectProps; DECL_HIDDEN extern constinit const EffectProps ModulatorEffectProps; DECL_HIDDEN extern constinit const EffectProps PshifterEffectProps; DECL_HIDDEN extern constinit const EffectProps VmorpherEffectProps; DECL_HIDDEN extern constinit const EffectProps DedicatedDialogEffectProps; DECL_HIDDEN extern constinit const EffectProps DedicatedLfeEffectProps; DECL_HIDDEN extern constinit const EffectProps ConvolutionEffectProps; #endif /* AL_EFFECTS_EFFECTS_H */ kcat-openal-soft-75c0059/al/effects/equalizer.cpp000066400000000000000000000327531512220627100216670ustar00rootroot00000000000000 #include "config.h" #include "AL/al.h" #include "AL/efx.h" #include "alc/context.h" #include "alnumeric.h" #include "effects.h" #include "gsl/gsl" #if ALSOFT_EAX #include "al/eax/effect.h" #include "al/eax/exception.h" #include "al/eax/utils.h" #endif // ALSOFT_EAX namespace { consteval auto genDefaultProps() noexcept -> EffectProps { return EqualizerProps{ .LowCutoff = AL_EQUALIZER_DEFAULT_LOW_CUTOFF, .LowGain = AL_EQUALIZER_DEFAULT_LOW_GAIN, .Mid1Center = AL_EQUALIZER_DEFAULT_MID1_CENTER, .Mid1Gain = AL_EQUALIZER_DEFAULT_MID1_GAIN, .Mid1Width = AL_EQUALIZER_DEFAULT_MID1_WIDTH, .Mid2Center = AL_EQUALIZER_DEFAULT_MID2_CENTER, .Mid2Gain = AL_EQUALIZER_DEFAULT_MID2_GAIN, .Mid2Width = AL_EQUALIZER_DEFAULT_MID2_WIDTH, .HighCutoff = AL_EQUALIZER_DEFAULT_HIGH_CUTOFF, .HighGain = AL_EQUALIZER_DEFAULT_HIGH_GAIN}; } } // namespace constinit const EffectProps EqualizerEffectProps(genDefaultProps()); void EqualizerEffectHandler::SetParami(al::Context *context, EqualizerProps&, ALenum param, int) { context->throw_error(AL_INVALID_ENUM, "Invalid equalizer integer property {:#04x}", as_unsigned(param)); } void EqualizerEffectHandler::SetParamiv(al::Context *context, EqualizerProps&, ALenum param, const int*) { context->throw_error(AL_INVALID_ENUM, "Invalid equalizer integer-vector property {:#04x}", as_unsigned(param)); } void EqualizerEffectHandler::SetParamf(al::Context *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(al::Context *context, EqualizerProps &props, ALenum param, const float *vals) { SetParamf(context, props, param, *vals); } void EqualizerEffectHandler::GetParami(al::Context *context, const EqualizerProps&, ALenum param, int*) { context->throw_error(AL_INVALID_ENUM, "Invalid equalizer integer property {:#04x}", as_unsigned(param)); } void EqualizerEffectHandler::GetParamiv(al::Context *context, const EqualizerProps&, ALenum param, int*) { context->throw_error(AL_INVALID_ENUM, "Invalid equalizer integer-vector property {:#04x}", as_unsigned(param)); } void EqualizerEffectHandler::GetParamf(al::Context *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(al::Context *context, const EqualizerProps &props, ALenum param, float *vals) { GetParamf(context, props, param, vals); } #if ALSOFT_EAX namespace { using EqualizerCommitter = EaxCommitter; struct LowGainValidator { void operator()(eax_long const lLowGain) const { eax_validate_range( "Low Gain", lLowGain, EAXEQUALIZER_MINLOWGAIN, EAXEQUALIZER_MAXLOWGAIN); } }; // LowGainValidator struct LowCutOffValidator { void operator()(float const flLowCutOff) const { eax_validate_range( "Low Cutoff", flLowCutOff, EAXEQUALIZER_MINLOWCUTOFF, EAXEQUALIZER_MAXLOWCUTOFF); } }; // LowCutOffValidator struct Mid1GainValidator { void operator()(eax_long const lMid1Gain) const { eax_validate_range( "Mid1 Gain", lMid1Gain, EAXEQUALIZER_MINMID1GAIN, EAXEQUALIZER_MAXMID1GAIN); } }; // Mid1GainValidator struct Mid1CenterValidator { void operator()(float const flMid1Center) const { eax_validate_range( "Mid1 Center", flMid1Center, EAXEQUALIZER_MINMID1CENTER, EAXEQUALIZER_MAXMID1CENTER); } }; // Mid1CenterValidator struct Mid1WidthValidator { void operator()(float const flMid1Width) const { eax_validate_range( "Mid1 Width", flMid1Width, EAXEQUALIZER_MINMID1WIDTH, EAXEQUALIZER_MAXMID1WIDTH); } }; // Mid1WidthValidator struct Mid2GainValidator { void operator()(eax_long const lMid2Gain) const { eax_validate_range( "Mid2 Gain", lMid2Gain, EAXEQUALIZER_MINMID2GAIN, EAXEQUALIZER_MAXMID2GAIN); } }; // Mid2GainValidator struct Mid2CenterValidator { void operator()(float const flMid2Center) const { eax_validate_range( "Mid2 Center", flMid2Center, EAXEQUALIZER_MINMID2CENTER, EAXEQUALIZER_MAXMID2CENTER); } }; // Mid2CenterValidator struct Mid2WidthValidator { void operator()(float const flMid2Width) const { eax_validate_range( "Mid2 Width", flMid2Width, EAXEQUALIZER_MINMID2WIDTH, EAXEQUALIZER_MAXMID2WIDTH); } }; // Mid2WidthValidator struct HighGainValidator { void operator()(eax_long const lHighGain) const { eax_validate_range( "High Gain", lHighGain, EAXEQUALIZER_MINHIGHGAIN, EAXEQUALIZER_MAXHIGHGAIN); } }; // HighGainValidator struct HighCutOffValidator { void operator()(float const 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<> /* NOLINTNEXTLINE(clazy-copyable-polymorphic) Exceptions must be copyable. */ struct EqualizerCommitter::Exception final : EaxException { explicit Exception(const std::string_view message) : EaxException{"EAX_EQUALIZER_EFFECT", message} { } }; template<> [[noreturn]] void EqualizerCommitter::fail(const std::string_view message) { throw Exception{message}; } auto EaxEqualizerCommitter::commit(const EAXEQUALIZERPROPERTIES &props) const -> bool { if(auto *cur = std::get_if(&mEaxProps); cur && *cur == props) return false; mEaxProps = props; mAlProps = EqualizerProps{ .LowCutoff = props.flLowCutOff, .LowGain = level_mb_to_gain(gsl::narrow_cast(props.lLowGain)), .Mid1Center = props.flMid1Center, .Mid1Gain = level_mb_to_gain(gsl::narrow_cast(props.lMid1Gain)), .Mid1Width = props.flMid1Width, .Mid2Center = props.flMid2Center, .Mid2Gain = level_mb_to_gain(gsl::narrow_cast(props.lMid2Gain)), .Mid2Width = props.flMid2Width, .HighCutoff = props.flHighCutOff, .HighGain = level_mb_to_gain(gsl::narrow_cast(props.lHighGain))}; return true; } void EaxEqualizerCommitter::SetDefaults(EaxEffectProps &props) { props = EAXEQUALIZERPROPERTIES{ .lLowGain = EAXEQUALIZER_DEFAULTLOWGAIN, .flLowCutOff = EAXEQUALIZER_DEFAULTLOWCUTOFF, .lMid1Gain = EAXEQUALIZER_DEFAULTMID1GAIN, .flMid1Center = EAXEQUALIZER_DEFAULTMID1CENTER, .flMid1Width = EAXEQUALIZER_DEFAULTMID1WIDTH, .lMid2Gain = EAXEQUALIZER_DEFAULTMID2GAIN, .flMid2Center = EAXEQUALIZER_DEFAULTMID2CENTER, .flMid2Width = EAXEQUALIZER_DEFAULTMID2WIDTH, .lHighGain = EAXEQUALIZER_DEFAULTHIGHGAIN, .flHighCutOff = EAXEQUALIZER_DEFAULTHIGHCUTOFF}; } void EaxEqualizerCommitter::Get(const EaxCall &call, const EAXEQUALIZERPROPERTIES &props) { switch(call.get_property_id()) { case EAXEQUALIZER_NONE: break; case EAXEQUALIZER_ALLPARAMETERS: call.store(props); break; case EAXEQUALIZER_LOWGAIN: call.store(props.lLowGain); break; case EAXEQUALIZER_LOWCUTOFF: call.store(props.flLowCutOff); break; case EAXEQUALIZER_MID1GAIN: call.store(props.lMid1Gain); break; case EAXEQUALIZER_MID1CENTER: call.store(props.flMid1Center); break; case EAXEQUALIZER_MID1WIDTH: call.store(props.flMid1Width); break; case EAXEQUALIZER_MID2GAIN: call.store(props.lMid2Gain); break; case EAXEQUALIZER_MID2CENTER: call.store(props.flMid2Center); break; case EAXEQUALIZER_MID2WIDTH: call.store(props.flMid2Width); break; case EAXEQUALIZER_HIGHGAIN: call.store(props.lHighGain); break; case EAXEQUALIZER_HIGHCUTOFF: call.store(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 kcat-openal-soft-75c0059/al/effects/fshifter.cpp000066400000000000000000000211711512220627100214700ustar00rootroot00000000000000 #include "config.h" #include #include #include "AL/al.h" #include "AL/efx.h" #include "alc/context.h" #include "alformat.hpp" #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 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{al::format("Invalid direction: {}", int{al::to_underlying(dir)})}; } consteval EffectProps genDefaultProps() noexcept { /* NOLINTBEGIN(bugprone-unchecked-optional-access) */ 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; /* NOLINTEND(bugprone-unchecked-optional-access) */ } } // namespace constinit const EffectProps FshifterEffectProps(genDefaultProps()); void FshifterEffectHandler::SetParami(al::Context *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(al::Context *context, FshifterProps &props, ALenum param, const int *vals) { SetParami(context, props, param, *vals); } void FshifterEffectHandler::SetParamf(al::Context *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(al::Context *context, FshifterProps &props, ALenum param, const float *vals) { SetParamf(context, props, param, *vals); } void FshifterEffectHandler::GetParami(al::Context *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(al::Context *context, const FshifterProps &props, ALenum param, int *vals) { GetParami(context, props, param, vals); } void FshifterEffectHandler::GetParamf(al::Context *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(al::Context *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 const flFrequency) const { eax_validate_range( "Frequency", flFrequency, EAXFREQUENCYSHIFTER_MINFREQUENCY, EAXFREQUENCYSHIFTER_MAXFREQUENCY); } }; // FrequencyValidator struct LeftDirectionValidator { void operator()(eax_ulong const ulLeftDirection) const { eax_validate_range( "Left Direction", ulLeftDirection, EAXFREQUENCYSHIFTER_MINLEFTDIRECTION, EAXFREQUENCYSHIFTER_MAXLEFTDIRECTION); } }; // LeftDirectionValidator struct RightDirectionValidator { void operator()(eax_ulong const 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<> /* NOLINTNEXTLINE(clazy-copyable-polymorphic) Exceptions must be copyable. */ struct FrequencyShifterCommitter::Exception final : EaxException { explicit Exception(const std::string_view message) : EaxException{"EAX_FREQUENCY_SHIFTER_EFFECT", message} { } }; template<> [[noreturn]] void FrequencyShifterCommitter::fail(const std::string_view message) { throw Exception{message}; } auto EaxFrequencyShifterCommitter::commit(const EAXFREQUENCYSHIFTERPROPERTIES &props) const -> bool { if(auto *cur = std::get_if(&mEaxProps); cur && *cur == props) return false; static constexpr auto get_direction = [](eax_ulong const dir) noexcept { switch(dir) { case EAX_FREQUENCYSHIFTER_DOWN: return FShifterDirection::Down; case EAX_FREQUENCYSHIFTER_UP: return FShifterDirection::Up; default: break; } return FShifterDirection::Off; }; mEaxProps = props; mAlProps = FshifterProps{ .Frequency = props.flFrequency, .LeftDirection = get_direction(props.ulLeftDirection), .RightDirection = get_direction(props.ulRightDirection)}; return true; } void EaxFrequencyShifterCommitter::SetDefaults(EaxEffectProps &props) { props = EAXFREQUENCYSHIFTERPROPERTIES{ .flFrequency = EAXFREQUENCYSHIFTER_DEFAULTFREQUENCY, .ulLeftDirection = EAXFREQUENCYSHIFTER_DEFAULTLEFTDIRECTION, .ulRightDirection = EAXFREQUENCYSHIFTER_DEFAULTRIGHTDIRECTION}; } void EaxFrequencyShifterCommitter::Get(const EaxCall &call, const EAXFREQUENCYSHIFTERPROPERTIES &props) { switch(call.get_property_id()) { case EAXFREQUENCYSHIFTER_NONE: break; case EAXFREQUENCYSHIFTER_ALLPARAMETERS: call.store(props); break; case EAXFREQUENCYSHIFTER_FREQUENCY: call.store(props.flFrequency); break; case EAXFREQUENCYSHIFTER_LEFTDIRECTION: call.store(props.ulLeftDirection); break; case EAXFREQUENCYSHIFTER_RIGHTDIRECTION: call.store(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 kcat-openal-soft-75c0059/al/effects/modulator.cpp000066400000000000000000000211701512220627100216630ustar00rootroot00000000000000 #include "config.h" #include #include #include "AL/al.h" #include "AL/efx.h" #include "alc/context.h" #include "alformat.hpp" #include "alnumeric.h" #include "effects.h" #include "gsl/gsl" #if ALSOFT_EAX #include "al/eax/effect.h" #include "al/eax/exception.h" #include "al/eax/utils.h" #endif // ALSOFT_EAX namespace { constexpr auto WaveformFromEmum(ALenum value) noexcept -> std::optional { 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 auto EnumFromWaveform(ModulatorWaveform type) -> ALenum { 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{al::format("Invalid modulator waveform: {}", int{al::to_underlying(type)})}; } consteval auto genDefaultProps() noexcept -> EffectProps { return ModulatorProps{ .Frequency = AL_RING_MODULATOR_DEFAULT_FREQUENCY, .HighPassCutoff = AL_RING_MODULATOR_DEFAULT_HIGHPASS_CUTOFF, /* NOLINTNEXTLINE(bugprone-unchecked-optional-access) */ .Waveform = WaveformFromEmum(AL_RING_MODULATOR_DEFAULT_WAVEFORM).value()}; } } // namespace constinit const EffectProps ModulatorEffectProps(genDefaultProps()); void ModulatorEffectHandler::SetParami(al::Context *context, ModulatorProps &props, ALenum param, int val) { switch(param) { case AL_RING_MODULATOR_FREQUENCY: case AL_RING_MODULATOR_HIGHPASS_CUTOFF: SetParamf(context, props, param, gsl::narrow_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(al::Context *context, ModulatorProps &props, ALenum param, const int *vals) { SetParami(context, props, param, *vals); } void ModulatorEffectHandler::SetParamf(al::Context *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(al::Context *context, ModulatorProps &props, ALenum param, const float *vals) { SetParamf(context, props, param, *vals); } void ModulatorEffectHandler::GetParami(al::Context *context, const ModulatorProps &props, ALenum param, int *val) { switch(param) { case AL_RING_MODULATOR_FREQUENCY: *val = gsl::narrow_cast(props.Frequency); return; case AL_RING_MODULATOR_HIGHPASS_CUTOFF: *val = gsl::narrow_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(al::Context *context, const ModulatorProps &props, ALenum param, int *vals) { GetParami(context, props, param, vals); } void ModulatorEffectHandler::GetParamf(al::Context *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(al::Context *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 const flFrequency) const { eax_validate_range( "Frequency", flFrequency, EAXRINGMODULATOR_MINFREQUENCY, EAXRINGMODULATOR_MAXFREQUENCY); } }; // FrequencyValidator struct HighPassCutOffValidator { void operator()(float const flHighPassCutOff) const { eax_validate_range( "High-Pass Cutoff", flHighPassCutOff, EAXRINGMODULATOR_MINHIGHPASSCUTOFF, EAXRINGMODULATOR_MAXHIGHPASSCUTOFF); } }; // HighPassCutOffValidator struct WaveformValidator { void operator()(eax_ulong const 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<> /* NOLINTNEXTLINE(clazy-copyable-polymorphic) Exceptions must be copyable. */ struct ModulatorCommitter::Exception final : EaxException { explicit Exception(const std::string_view message) : EaxException{"EAX_RING_MODULATOR_EFFECT", message} { } }; template<> [[noreturn]] void ModulatorCommitter::fail(const std::string_view message) { throw Exception{message}; } auto EaxModulatorCommitter::commit(const EAXRINGMODULATORPROPERTIES &props) const -> bool { if(auto *cur = std::get_if(&mEaxProps); cur && *cur == props) return false; static constexpr auto get_waveform = [](eax_ulong const form) { switch(form) { case EAX_RINGMODULATOR_SINUSOID: return ModulatorWaveform::Sinusoid; case EAX_RINGMODULATOR_SAWTOOTH: return ModulatorWaveform::Sawtooth; case EAX_RINGMODULATOR_SQUARE: return ModulatorWaveform::Square; default: break; } return ModulatorWaveform::Sinusoid; }; mEaxProps = props; mAlProps = ModulatorProps{ .Frequency = props.flFrequency, .HighPassCutoff = props.flHighPassCutOff, .Waveform = get_waveform(props.ulWaveform)}; return true; } void EaxModulatorCommitter::SetDefaults(EaxEffectProps &props) { props = EAXRINGMODULATORPROPERTIES{ .flFrequency = EAXRINGMODULATOR_DEFAULTFREQUENCY, .flHighPassCutOff = EAXRINGMODULATOR_DEFAULTHIGHPASSCUTOFF, .ulWaveform = EAXRINGMODULATOR_DEFAULTWAVEFORM}; } void EaxModulatorCommitter::Get(const EaxCall &call, const EAXRINGMODULATORPROPERTIES &props) { switch(call.get_property_id()) { case EAXRINGMODULATOR_NONE: break; case EAXRINGMODULATOR_ALLPARAMETERS: call.store(props); break; case EAXRINGMODULATOR_FREQUENCY: call.store(props.flFrequency); break; case EAXRINGMODULATOR_HIGHPASSCUTOFF: call.store(props.flHighPassCutOff); break; case EAXRINGMODULATOR_WAVEFORM: call.store(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 kcat-openal-soft-75c0059/al/effects/null.cpp000066400000000000000000000060251512220627100206310ustar00rootroot00000000000000 #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 { consteval auto genDefaultProps() noexcept -> EffectProps { return std::monostate{}; } } // namespace constinit const EffectProps NullEffectProps(genDefaultProps()); void NullEffectHandler::SetParami(al::Context *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(al::Context *context, std::monostate &props, ALenum param, const int *vals) { SetParami(context, props, param, *vals); } void NullEffectHandler::SetParamf(al::Context *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(al::Context *context, std::monostate &props, ALenum param, const float *vals) { SetParamf(context, props, param, *vals); } void NullEffectHandler::GetParami(al::Context *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(al::Context *context, const std::monostate &props, ALenum param, int *vals) { GetParami(context, props, param, vals); } void NullEffectHandler::GetParamf(al::Context *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(al::Context *context, const std::monostate &props, ALenum param, float *vals) { GetParamf(context, props, param, vals); } #if ALSOFT_EAX namespace { using NullCommitter = EaxCommitter; } // namespace template<> /* NOLINTNEXTLINE(clazy-copyable-polymorphic) Exceptions must be copyable. */ struct NullCommitter::Exception final : EaxException { explicit Exception(const std::string_view message) : EaxException{"EAX_NULL_EFFECT", message} { } }; template<> [[noreturn]] void NullCommitter::fail(const std::string_view message) { throw Exception{message}; } auto EaxNullCommitter::commit(const std::monostate &props) const -> bool { 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 kcat-openal-soft-75c0059/al/effects/pshifter.cpp000066400000000000000000000132451512220627100215050ustar00rootroot00000000000000 #include "config.h" #include "AL/al.h" #include "AL/efx.h" #include "alc/context.h" #include "alnumeric.h" #include "effects.h" #include "gsl/gsl" #if ALSOFT_EAX #include "al/eax/effect.h" #include "al/eax/exception.h" #include "al/eax/utils.h" #endif // ALSOFT_EAX namespace { consteval auto genDefaultProps() noexcept -> EffectProps { return PshifterProps{ .CoarseTune = AL_PITCH_SHIFTER_DEFAULT_COARSE_TUNE, .FineTune = AL_PITCH_SHIFTER_DEFAULT_FINE_TUNE}; } } // namespace constinit const EffectProps PshifterEffectProps(genDefaultProps()); void PshifterEffectHandler::SetParami(al::Context *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(al::Context *context, PshifterProps &props, ALenum param, const int *vals) { SetParami(context, props, param, *vals); } void PshifterEffectHandler::SetParamf(al::Context *context, PshifterProps&, ALenum param, float) { context->throw_error(AL_INVALID_ENUM, "Invalid pitch shifter float property {:#04x}", as_unsigned(param)); } void PshifterEffectHandler::SetParamfv(al::Context *context, PshifterProps &props, ALenum param, const float *vals) { SetParamf(context, props, param, *vals); } void PshifterEffectHandler::GetParami(al::Context *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(al::Context *context, const PshifterProps &props, ALenum param, int *vals) { GetParami(context, props, param, vals); } void PshifterEffectHandler::GetParamf(al::Context *context, const PshifterProps&, ALenum param, float*) { context->throw_error(AL_INVALID_ENUM, "Invalid pitch shifter float property {:#04x}", as_unsigned(param)); } void PshifterEffectHandler::GetParamfv(al::Context *context, const PshifterProps &props, ALenum param, float *vals) { GetParamf(context, props, param, vals); } #if ALSOFT_EAX namespace { using PitchShifterCommitter = EaxCommitter; struct CoarseTuneValidator { void operator()(eax_long const lCoarseTune) const { eax_validate_range( "Coarse Tune", lCoarseTune, EAXPITCHSHIFTER_MINCOARSETUNE, EAXPITCHSHIFTER_MAXCOARSETUNE); } }; // CoarseTuneValidator struct FineTuneValidator { void operator()(eax_long const 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<> /* NOLINTNEXTLINE(clazy-copyable-polymorphic) Exceptions must be copyable. */ struct PitchShifterCommitter::Exception final : EaxException { explicit Exception(const std::string_view message) : EaxException{"EAX_PITCH_SHIFTER_EFFECT", message} { } }; template<> [[noreturn]] void PitchShifterCommitter::fail(const std::string_view message) { throw Exception{message}; } auto EaxPitchShifterCommitter::commit(const EAXPITCHSHIFTERPROPERTIES &props) const -> bool { if(auto *cur = std::get_if(&mEaxProps); cur && *cur == props) return false; mEaxProps = props; mAlProps = PshifterProps{ .CoarseTune = gsl::narrow_cast(props.lCoarseTune), .FineTune = gsl::narrow_cast(props.lFineTune)}; return true; } void EaxPitchShifterCommitter::SetDefaults(EaxEffectProps &props) { props = EAXPITCHSHIFTERPROPERTIES{ .lCoarseTune = EAXPITCHSHIFTER_DEFAULTCOARSETUNE, .lFineTune = EAXPITCHSHIFTER_DEFAULTFINETUNE}; } void EaxPitchShifterCommitter::Get(const EaxCall &call, const EAXPITCHSHIFTERPROPERTIES &props) { switch(call.get_property_id()) { case EAXPITCHSHIFTER_NONE: break; case EAXPITCHSHIFTER_ALLPARAMETERS: call.store(props); break; case EAXPITCHSHIFTER_COARSETUNE: call.store(props.lCoarseTune); break; case EAXPITCHSHIFTER_FINETUNE: call.store(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 kcat-openal-soft-75c0059/al/effects/reverb.cpp000066400000000000000000001427601512220627100211530ustar00rootroot00000000000000 #include "config.h" #include #include #include #include #include #include "AL/al.h" #include "AL/efx.h" #include "alc/context.h" #include "alnumeric.h" #include "core/logging.h" #include "effects.h" #include "gsl/gsl" #if ALSOFT_EAX #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 { consteval auto genDefaultProps() noexcept -> EffectProps { return ReverbProps{ .Density = AL_EAXREVERB_DEFAULT_DENSITY, .Diffusion = AL_EAXREVERB_DEFAULT_DIFFUSION, .Gain = AL_EAXREVERB_DEFAULT_GAIN, .GainHF = AL_EAXREVERB_DEFAULT_GAINHF, .GainLF = AL_EAXREVERB_DEFAULT_GAINLF, .DecayTime = AL_EAXREVERB_DEFAULT_DECAY_TIME, .DecayHFRatio = AL_EAXREVERB_DEFAULT_DECAY_HFRATIO, .DecayLFRatio = AL_EAXREVERB_DEFAULT_DECAY_LFRATIO, .ReflectionsGain = AL_EAXREVERB_DEFAULT_REFLECTIONS_GAIN, .ReflectionsDelay = AL_EAXREVERB_DEFAULT_REFLECTIONS_DELAY, .ReflectionsPan = {AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ, AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ, AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ}, .LateReverbGain = AL_EAXREVERB_DEFAULT_LATE_REVERB_GAIN, .LateReverbDelay = AL_EAXREVERB_DEFAULT_LATE_REVERB_DELAY, .LateReverbPan = {AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ, AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ, AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ}, .EchoTime = AL_EAXREVERB_DEFAULT_ECHO_TIME, .EchoDepth = AL_EAXREVERB_DEFAULT_ECHO_DEPTH, .ModulationTime = AL_EAXREVERB_DEFAULT_MODULATION_TIME, .ModulationDepth = AL_EAXREVERB_DEFAULT_MODULATION_DEPTH, .AirAbsorptionGainHF = AL_EAXREVERB_DEFAULT_AIR_ABSORPTION_GAINHF, .HFReference = AL_EAXREVERB_DEFAULT_HFREFERENCE, .LFReference = AL_EAXREVERB_DEFAULT_LFREFERENCE, .RoomRolloffFactor = AL_EAXREVERB_DEFAULT_ROOM_ROLLOFF_FACTOR, .DecayHFLimit = AL_EAXREVERB_DEFAULT_DECAY_HFLIMIT}; } consteval auto genDefaultStdProps() noexcept -> EffectProps { return ReverbProps{ .Density = AL_REVERB_DEFAULT_DENSITY, .Diffusion = AL_REVERB_DEFAULT_DIFFUSION, .Gain = AL_REVERB_DEFAULT_GAIN, .GainHF = AL_REVERB_DEFAULT_GAINHF, .GainLF = 1.0f, .DecayTime = AL_REVERB_DEFAULT_DECAY_TIME, .DecayHFRatio = AL_REVERB_DEFAULT_DECAY_HFRATIO, .DecayLFRatio = 1.0f, .ReflectionsGain = AL_REVERB_DEFAULT_REFLECTIONS_GAIN, .ReflectionsDelay = AL_REVERB_DEFAULT_REFLECTIONS_DELAY, .ReflectionsPan = {0.0f, 0.0f, 0.0f}, .LateReverbGain = AL_REVERB_DEFAULT_LATE_REVERB_GAIN, .LateReverbDelay = AL_REVERB_DEFAULT_LATE_REVERB_DELAY, .LateReverbPan = {0.0f, 0.0f, 0.0f}, .EchoTime = 0.25f, .EchoDepth = 0.0f, .ModulationTime = 0.25f, .ModulationDepth = 0.0f, .AirAbsorptionGainHF = AL_REVERB_DEFAULT_AIR_ABSORPTION_GAINHF, .HFReference = 5'000.0f, .LFReference = 250.0f, .RoomRolloffFactor = AL_REVERB_DEFAULT_ROOM_ROLLOFF_FACTOR, .DecayHFLimit = AL_REVERB_DEFAULT_DECAY_HFLIMIT}; } } // namespace constinit const EffectProps ReverbEffectProps(genDefaultProps()); void ReverbEffectHandler::SetParami(al::Context *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(al::Context *context, ReverbProps &props, ALenum param, const int *vals) { SetParami(context, props, param, *vals); } void ReverbEffectHandler::SetParamf(al::Context *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(al::Context *context, ReverbProps &props, ALenum param, const float *vals) { static constexpr auto is_finite = [](const float f) -> bool { return std::isfinite(f); }; auto values = std::span{}; switch(param) { case AL_EAXREVERB_REFLECTIONS_PAN: values = {vals, 3_uz}; if(!std::ranges::all_of(values, is_finite)) context->throw_error(AL_INVALID_VALUE, "EAX Reverb reflections pan out of range"); std::ranges::copy(values, props.ReflectionsPan.begin()); return; case AL_EAXREVERB_LATE_REVERB_PAN: values = {vals, 3_uz}; if(!std::ranges::all_of(values, is_finite)) context->throw_error(AL_INVALID_VALUE, "EAX Reverb late reverb pan out of range"); std::ranges::copy(values, props.LateReverbPan.begin()); return; } SetParamf(context, props, param, *vals); } void ReverbEffectHandler::GetParami(al::Context *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(al::Context *context, const ReverbProps &props, ALenum param, int *vals) { GetParami(context, props, param, vals); } void ReverbEffectHandler::GetParamf(al::Context *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(al::Context *context, const ReverbProps &props, ALenum param, float *vals) { auto values = std::span{}; switch(param) { case AL_EAXREVERB_REFLECTIONS_PAN: values = {vals, 3_uz}; std::ranges::copy(props.ReflectionsPan, values.begin()); return; case AL_EAXREVERB_LATE_REVERB_PAN: values = {vals, 3_uz}; std::ranges::copy(props.LateReverbPan, values.begin()); return; } GetParamf(context, props, param, vals); } constinit const EffectProps StdReverbEffectProps(genDefaultStdProps()); void StdReverbEffectHandler::SetParami(al::Context *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(al::Context *context, ReverbProps &props, ALenum param, const int *vals) { SetParami(context, props, param, *vals); } void StdReverbEffectHandler::SetParamf(al::Context *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(al::Context *context, ReverbProps &props, ALenum param, const float *vals) { SetParamf(context, props, param, *vals); } void StdReverbEffectHandler::GetParami(al::Context *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(al::Context *context, const ReverbProps &props, ALenum param, int *vals) { GetParami(context, props, param, vals); } void StdReverbEffectHandler::GetParamf(al::Context *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(al::Context *context, const ReverbProps &props, ALenum param, float *vals) { GetParamf(context, props, param, vals); } #if ALSOFT_EAX namespace { /* NOLINTNEXTLINE(clazy-copyable-polymorphic) Exceptions must be copyable. */ class EaxReverbEffectException : public EaxException { public: explicit EaxReverbEffectException(const std::string_view message) : EaxException{"EAX_REVERB_EFFECT", message} { } }; struct EnvironmentValidator1 { void operator()(eax_ulong const ulEnvironment) const { eax_validate_range( "Environment", ulEnvironment, EAXREVERB_MINENVIRONMENT, EAX1REVERB_MAXENVIRONMENT); } }; // EnvironmentValidator1 struct VolumeValidator { void operator()(float const volume) const { eax_validate_range( "Volume", volume, EAX1REVERB_MINVOLUME, EAX1REVERB_MAXVOLUME); } }; // VolumeValidator struct DecayTimeValidator { void operator()(float const flDecayTime) const { eax_validate_range( "Decay Time", flDecayTime, EAXREVERB_MINDECAYTIME, EAXREVERB_MAXDECAYTIME); } }; // DecayTimeValidator struct DampingValidator { void operator()(float const 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()(eax_long const lRoom) const { eax_validate_range( "Room", lRoom, EAXREVERB_MINROOM, EAXREVERB_MAXROOM); } }; // RoomValidator struct RoomHFValidator { void operator()(eax_long const lRoomHF) const { eax_validate_range( "Room HF", lRoomHF, EAXREVERB_MINROOMHF, EAXREVERB_MAXROOMHF); } }; // RoomHFValidator struct RoomRolloffFactorValidator { void operator()(float const flRoomRolloffFactor) const { eax_validate_range( "Room Rolloff Factor", flRoomRolloffFactor, EAXREVERB_MINROOMROLLOFFFACTOR, EAXREVERB_MAXROOMROLLOFFFACTOR); } }; // RoomRolloffFactorValidator struct DecayHFRatioValidator { void operator()(float const flDecayHFRatio) const { eax_validate_range( "Decay HF Ratio", flDecayHFRatio, EAXREVERB_MINDECAYHFRATIO, EAXREVERB_MAXDECAYHFRATIO); } }; // DecayHFRatioValidator struct ReflectionsValidator { void operator()(eax_long const lReflections) const { eax_validate_range( "Reflections", lReflections, EAXREVERB_MINREFLECTIONS, EAXREVERB_MAXREFLECTIONS); } }; // ReflectionsValidator struct ReflectionsDelayValidator { void operator()(float const flReflectionsDelay) const { eax_validate_range( "Reflections Delay", flReflectionsDelay, EAXREVERB_MINREFLECTIONSDELAY, EAXREVERB_MAXREFLECTIONSDELAY); } }; // ReflectionsDelayValidator struct ReverbValidator { void operator()(eax_long const lReverb) const { eax_validate_range( "Reverb", lReverb, EAXREVERB_MINREVERB, EAXREVERB_MAXREVERB); } }; // ReverbValidator struct ReverbDelayValidator { void operator()(float const flReverbDelay) const { eax_validate_range( "Reverb Delay", flReverbDelay, EAXREVERB_MINREVERBDELAY, EAXREVERB_MAXREVERBDELAY); } }; // ReverbDelayValidator struct EnvironmentSizeValidator { void operator()(float const flEnvironmentSize) const { eax_validate_range( "Environment Size", flEnvironmentSize, EAXREVERB_MINENVIRONMENTSIZE, EAXREVERB_MAXENVIRONMENTSIZE); } }; // EnvironmentSizeValidator struct EnvironmentDiffusionValidator { void operator()(float const flEnvironmentDiffusion) const { eax_validate_range( "Environment Diffusion", flEnvironmentDiffusion, EAXREVERB_MINENVIRONMENTDIFFUSION, EAXREVERB_MAXENVIRONMENTDIFFUSION); } }; // EnvironmentDiffusionValidator struct AirAbsorptionHFValidator { void operator()(float const flAirAbsorptionHF) const { eax_validate_range( "Air Absorbtion HF", flAirAbsorptionHF, EAXREVERB_MINAIRABSORPTIONHF, EAXREVERB_MAXAIRABSORPTIONHF); } }; // AirAbsorptionHFValidator struct FlagsValidator2 { void operator()(eax_ulong const ulFlags) const { eax_validate_range( "Flags", ulFlags, 0_eax_ulong, ~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()(eax_ulong const ulEnvironment) const { eax_validate_range( "Environment", ulEnvironment, EAXREVERB_MINENVIRONMENT, EAX30REVERB_MAXENVIRONMENT); } }; // EnvironmentValidator1 struct RoomLFValidator { void operator()(eax_long const lRoomLF) const { eax_validate_range( "Room LF", lRoomLF, EAXREVERB_MINROOMLF, EAXREVERB_MAXROOMLF); } }; // RoomLFValidator struct DecayLFRatioValidator { void operator()(float const 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 const flEchoTime) const { eax_validate_range( "Echo Time", flEchoTime, EAXREVERB_MINECHOTIME, EAXREVERB_MAXECHOTIME); } }; // EchoTimeValidator struct EchoDepthValidator { void operator()(float const flEchoDepth) const { eax_validate_range( "Echo Depth", flEchoDepth, EAXREVERB_MINECHODEPTH, EAXREVERB_MAXECHODEPTH); } }; // EchoDepthValidator struct ModulationTimeValidator { void operator()(float const flModulationTime) const { eax_validate_range( "Modulation Time", flModulationTime, EAXREVERB_MINMODULATIONTIME, EAXREVERB_MAXMODULATIONTIME); } }; // ModulationTimeValidator struct ModulationDepthValidator { void operator()(float const flModulationDepth) const { eax_validate_range( "Modulation Depth", flModulationDepth, EAXREVERB_MINMODULATIONDEPTH, EAXREVERB_MAXMODULATIONDEPTH); } }; // ModulationDepthValidator struct HFReferenceValidator { void operator()(float const flHFReference) const { eax_validate_range( "HF Reference", flHFReference, EAXREVERB_MINHFREFERENCE, EAXREVERB_MAXHFREFERENCE); } }; // HFReferenceValidator struct LFReferenceValidator { void operator()(float const flLFReference) const { eax_validate_range( "LF Reference", flLFReference, EAXREVERB_MINLFREFERENCE, EAXREVERB_MAXLFREFERENCE); } }; // LFReferenceValidator struct FlagsValidator3 { void operator()(eax_ulong const ulFlags) const { eax_validate_range( "Flags", ulFlags, 0_eax_ulong, ~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, eax_ulong const dwEnvironment) const { props = EAX2REVERB_PRESETS[dwEnvironment]; } }; // EnvironmentDeferrer2 struct EnvironmentSizeDeferrer2 { void operator()(EAX20LISTENERPROPERTIES& props, float const 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 - gsl::narrow_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 - gsl::narrow_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, eax_ulong const 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 const 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 - gsl::narrow_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 - gsl::narrow_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 /* NOLINTNEXTLINE(clazy-copyable-polymorphic) Exceptions must be copyable. */ struct EaxReverbCommitter::Exception final : EaxReverbEffectException { using EaxReverbEffectException::EaxReverbEffectException; }; [[noreturn]] void EaxReverbCommitter::fail(const std::string_view message) { throw Exception{message}; } void EaxReverbCommitter::translate(const EAX_REVERBPROPERTIES& src, EAXREVERBPROPERTIES& dst) noexcept { Expects(src.environment <= EAX1REVERB_MAXENVIRONMENT); dst = EAXREVERB_PRESETS[src.environment]; dst.flDecayTime = src.fDecayTime_sec; dst.flDecayHFRatio = src.fDamping; dst.lReverb = gsl::narrow_cast(std::min(gain_to_level_mb(src.fVolume), 0.0f)); } void EaxReverbCommitter::translate(const EAX20LISTENERPROPERTIES& src, EAXREVERBPROPERTIES& dst) noexcept { Expects(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; } auto EaxReverbCommitter::commit(const EAX_REVERBPROPERTIES &props) const -> bool { EAXREVERBPROPERTIES dst{}; translate(props, dst); return commit(dst); } auto EaxReverbCommitter::commit(const EAX20LISTENERPROPERTIES &props) const -> bool { EAXREVERBPROPERTIES dst{}; translate(props, dst); return commit(dst); } auto EaxReverbCommitter::commit(const EAXREVERBPROPERTIES &props) const -> bool { 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{ .Density = std::min(density, AL_EAXREVERB_MAX_DENSITY), .Diffusion = props.flEnvironmentDiffusion, .Gain = level_mb_to_gain(gsl::narrow_cast(props.lRoom)), .GainHF = level_mb_to_gain(gsl::narrow_cast(props.lRoomHF)), .GainLF = level_mb_to_gain(gsl::narrow_cast(props.lRoomLF)), .DecayTime = props.flDecayTime, .DecayHFRatio = props.flDecayHFRatio, .DecayLFRatio = props.flDecayLFRatio, .ReflectionsGain = level_mb_to_gain(gsl::narrow_cast(props.lReflections)), .ReflectionsDelay = props.flReflectionsDelay, .ReflectionsPan = {props.vReflectionsPan.x, props.vReflectionsPan.y, props.vReflectionsPan.z}, .LateReverbGain = level_mb_to_gain(gsl::narrow_cast(props.lReverb)), .LateReverbDelay = props.flReverbDelay, .LateReverbPan = {props.vReverbPan.x, props.vReverbPan.y, props.vReverbPan.z}, .EchoTime = props.flEchoTime, .EchoDepth = props.flEchoDepth, .ModulationTime = props.flModulationTime, .ModulationDepth = props.flModulationDepth, .AirAbsorptionGainHF = level_mb_to_gain(props.flAirAbsorptionHF), .HFReference = props.flHFReference, .LFReference = props.flLFReference, .RoomRolloffFactor = props.flRoomRolloffFactor, .DecayHFLimit = ((props.ulFlags & EAXREVERBFLAGS_DECAYHFLIMIT) != 0)}; if(EaxTraceCommits) [[unlikely]] { const auto &ret = std::get(mAlProps); TRACE("Reverb commit:\n" " Density: {:f}\n" " Diffusion: {:f}\n" " Gain: {:f}\n" " GainHF: {:f}\n" " GainLF: {:f}\n" " DecayTime: {:f}\n" " DecayHFRatio: {:f}\n" " DecayLFRatio: {:f}\n" " ReflectionsGain: {:f}\n" " ReflectionsDelay: {:f}\n" " ReflectionsPan: [{}, {}, {}]\n" " LateReverbGain: {:f}\n" " LateReverbDelay: {:f}\n" " LateReverbPan: [{}, {}, {}]\n" " EchoTime: {:f}\n" " EchoDepth: {:f}\n" " ModulationTime: {:f}\n" " ModulationDepth: {:f}\n" " AirAbsorptionGainHF: {:f}\n" " HFReference: {:f}\n" " LFReference: {:f}\n" " RoomRolloffFactor: {:f}\n" " DecayHFLimit: {}", ret.Density, ret.Diffusion, ret.Gain, ret.GainHF, ret.GainLF, ret.DecayTime, ret.DecayHFRatio, ret.DecayLFRatio, ret.ReflectionsGain, ret.ReflectionsDelay, ret.ReflectionsPan[0], ret.ReflectionsPan[1], ret.ReflectionsPan[2], ret.LateReverbGain, ret.LateReverbDelay, ret.LateReverbPan[0], ret.LateReverbPan[1], ret.LateReverbPan[2], ret.EchoTime, ret.EchoDepth, ret.ModulationTime, ret.ModulationDepth, ret.AirAbsorptionGainHF, ret.HFReference, ret.LFReference, ret.RoomRolloffFactor, ret.DecayHFLimit ? "true" : "false"); } 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'000_eax_long; } 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.store(props); break; case DSPROPERTY_EAX_ENVIRONMENT: call.store(props.environment); break; case DSPROPERTY_EAX_VOLUME: call.store(props.fVolume); break; case DSPROPERTY_EAX_DECAYTIME: call.store(props.fDecayTime_sec); break; case DSPROPERTY_EAX_DAMPING: call.store(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.store(props); break; case DSPROPERTY_EAX20LISTENER_ROOM: call.store(props.lRoom); break; case DSPROPERTY_EAX20LISTENER_ROOMHF: call.store(props.lRoomHF); break; case DSPROPERTY_EAX20LISTENER_ROOMROLLOFFFACTOR: call.store(props.flRoomRolloffFactor); break; case DSPROPERTY_EAX20LISTENER_DECAYTIME: call.store(props.flDecayTime); break; case DSPROPERTY_EAX20LISTENER_DECAYHFRATIO: call.store(props.flDecayHFRatio); break; case DSPROPERTY_EAX20LISTENER_REFLECTIONS: call.store(props.lReflections); break; case DSPROPERTY_EAX20LISTENER_REFLECTIONSDELAY: call.store(props.flReflectionsDelay); break; case DSPROPERTY_EAX20LISTENER_REVERB: call.store(props.lReverb); break; case DSPROPERTY_EAX20LISTENER_REVERBDELAY: call.store(props.flReverbDelay); break; case DSPROPERTY_EAX20LISTENER_ENVIRONMENT: call.store(props.dwEnvironment); break; case DSPROPERTY_EAX20LISTENER_ENVIRONMENTSIZE: call.store(props.flEnvironmentSize); break; case DSPROPERTY_EAX20LISTENER_ENVIRONMENTDIFFUSION: call.store(props.flEnvironmentDiffusion); break; case DSPROPERTY_EAX20LISTENER_AIRABSORPTIONHF: call.store(props.flAirAbsorptionHF); break; case DSPROPERTY_EAX20LISTENER_FLAGS: call.store(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.store(props); break; case EAXREVERB_ENVIRONMENT: call.store(props.ulEnvironment); break; case EAXREVERB_ENVIRONMENTSIZE: call.store(props.flEnvironmentSize); break; case EAXREVERB_ENVIRONMENTDIFFUSION: call.store(props.flEnvironmentDiffusion); break; case EAXREVERB_ROOM: call.store(props.lRoom); break; case EAXREVERB_ROOMHF: call.store(props.lRoomHF); break; case EAXREVERB_ROOMLF: call.store(props.lRoomLF); break; case EAXREVERB_DECAYTIME: call.store(props.flDecayTime); break; case EAXREVERB_DECAYHFRATIO: call.store(props.flDecayHFRatio); break; case EAXREVERB_DECAYLFRATIO: call.store(props.flDecayLFRatio); break; case EAXREVERB_REFLECTIONS: call.store(props.lReflections); break; case EAXREVERB_REFLECTIONSDELAY: call.store(props.flReflectionsDelay); break; case EAXREVERB_REFLECTIONSPAN: call.store(props.vReflectionsPan); break; case EAXREVERB_REVERB: call.store(props.lReverb); break; case EAXREVERB_REVERBDELAY: call.store(props.flReverbDelay); break; case EAXREVERB_REVERBPAN: call.store(props.vReverbPan); break; case EAXREVERB_ECHOTIME: call.store(props.flEchoTime); break; case EAXREVERB_ECHODEPTH: call.store(props.flEchoDepth); break; case EAXREVERB_MODULATIONTIME: call.store(props.flModulationTime); break; case EAXREVERB_MODULATIONDEPTH: call.store(props.flModulationDepth); break; case EAXREVERB_AIRABSORPTIONHF: call.store(props.flAirAbsorptionHF); break; case EAXREVERB_HFREFERENCE: call.store(props.flHFReference); break; case EAXREVERB_LFREFERENCE: call.store(props.flLFReference); break; case EAXREVERB_ROOMROLLOFFFACTOR: call.store(props.flRoomRolloffFactor); break; case EAXREVERB_FLAGS: call.store(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 kcat-openal-soft-75c0059/al/effects/vmorpher.cpp000066400000000000000000000356351512220627100215320ustar00rootroot00000000000000 #include "config.h" #include #include #include "AL/al.h" #include "AL/efx.h" #include "alc/context.h" #include "alformat.hpp" #include "alnumeric.h" #include "effects.h" #include "gsl/gsl" #if ALSOFT_EAX #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{al::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{al::format("Invalid vocal morpher waveform: {}", int{al::to_underlying(type)})}; } consteval auto genDefaultProps() noexcept -> EffectProps { /* NOLINTBEGIN(bugprone-unchecked-optional-access) */ return VmorpherProps{ .Rate = AL_VOCAL_MORPHER_DEFAULT_RATE, .PhonemeA = PhenomeFromEnum(AL_VOCAL_MORPHER_DEFAULT_PHONEMEA).value(), .PhonemeB = PhenomeFromEnum(AL_VOCAL_MORPHER_DEFAULT_PHONEMEB).value(), .PhonemeACoarseTuning = AL_VOCAL_MORPHER_DEFAULT_PHONEMEA_COARSE_TUNING, .PhonemeBCoarseTuning = AL_VOCAL_MORPHER_DEFAULT_PHONEMEB_COARSE_TUNING, .Waveform = WaveformFromEmum(AL_VOCAL_MORPHER_DEFAULT_WAVEFORM).value()}; /* NOLINTEND(bugprone-unchecked-optional-access) */ } } // namespace constinit const EffectProps VmorpherEffectProps(genDefaultProps()); void VmorpherEffectHandler::SetParami(al::Context *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(al::Context *context, VmorpherProps &props, ALenum param, const int *vals) { SetParami(context, props, param, *vals); } void VmorpherEffectHandler::SetParamf(al::Context *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(al::Context *context, VmorpherProps &props, ALenum param, const float *vals) { SetParamf(context, props, param, *vals); } void VmorpherEffectHandler::GetParami(al::Context *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(al::Context *context, const VmorpherProps &props, ALenum param, int *vals) { GetParami(context, props, param, vals); } void VmorpherEffectHandler::GetParamf(al::Context *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(al::Context *context, const VmorpherProps &props, ALenum param, float *vals) { GetParamf(context, props, param, vals); } #if ALSOFT_EAX namespace { using VocalMorpherCommitter = EaxCommitter; struct PhonemeAValidator { void operator()(eax_ulong const ulPhonemeA) const { eax_validate_range( "Phoneme A", ulPhonemeA, EAXVOCALMORPHER_MINPHONEMEA, EAXVOCALMORPHER_MAXPHONEMEA); } }; // PhonemeAValidator struct PhonemeACoarseTuningValidator { void operator()(eax_long const lPhonemeACoarseTuning) const { eax_validate_range( "Phoneme A Coarse Tuning", lPhonemeACoarseTuning, EAXVOCALMORPHER_MINPHONEMEACOARSETUNING, EAXVOCALMORPHER_MAXPHONEMEACOARSETUNING); } }; // PhonemeACoarseTuningValidator struct PhonemeBValidator { void operator()(eax_ulong const ulPhonemeB) const { eax_validate_range( "Phoneme B", ulPhonemeB, EAXVOCALMORPHER_MINPHONEMEB, EAXVOCALMORPHER_MAXPHONEMEB); } }; // PhonemeBValidator struct PhonemeBCoarseTuningValidator { void operator()(eax_long const lPhonemeBCoarseTuning) const { eax_validate_range( "Phoneme B Coarse Tuning", lPhonemeBCoarseTuning, EAXVOCALMORPHER_MINPHONEMEBCOARSETUNING, EAXVOCALMORPHER_MAXPHONEMEBCOARSETUNING); } }; // PhonemeBCoarseTuningValidator struct WaveformValidator { void operator()(eax_ulong const ulWaveform) const { eax_validate_range( "Waveform", ulWaveform, EAXVOCALMORPHER_MINWAVEFORM, EAXVOCALMORPHER_MAXWAVEFORM); } }; // WaveformValidator struct RateValidator { void operator()(float const 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<> /* NOLINTNEXTLINE(clazy-copyable-polymorphic) Exceptions must be copyable. */ struct VocalMorpherCommitter::Exception final : EaxException { explicit Exception(const std::string_view message) : EaxException{"EAX_VOCAL_MORPHER_EFFECT", message} { } }; template<> [[noreturn]] void VocalMorpherCommitter::fail(const std::string_view message) { throw Exception{message}; } auto EaxVocalMorpherCommitter::commit(const EAXVOCALMORPHERPROPERTIES &props) const -> bool { if(auto *cur = std::get_if(&mEaxProps); cur && *cur == props) return false; static constexpr auto get_phoneme = [](eax_ulong const phoneme) noexcept { #define HANDLE_PHENOME(x) case EAX_VOCALMORPHER_PHONEME_##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); default: break; } return VMorpherPhenome::A; #undef HANDLE_PHENOME }; static constexpr auto get_waveform = [](eax_ulong const form) noexcept { switch(form) { case EAX_VOCALMORPHER_SINUSOID: return VMorpherWaveform::Sinusoid; case EAX_VOCALMORPHER_TRIANGLE: return VMorpherWaveform::Triangle; case EAX_VOCALMORPHER_SAWTOOTH: return VMorpherWaveform::Sawtooth; default: break; } return VMorpherWaveform::Sinusoid; }; mEaxProps = props; mAlProps = VmorpherProps{ .Rate = props.flRate, .PhonemeA = get_phoneme(props.ulPhonemeA), .PhonemeB = get_phoneme(props.ulPhonemeB), .PhonemeACoarseTuning = gsl::narrow_cast(props.lPhonemeACoarseTuning), .PhonemeBCoarseTuning = gsl::narrow_cast(props.lPhonemeBCoarseTuning), .Waveform = get_waveform(props.ulWaveform)}; return true; } void EaxVocalMorpherCommitter::SetDefaults(EaxEffectProps &props) { props = EAXVOCALMORPHERPROPERTIES{ .ulPhonemeA = EAXVOCALMORPHER_DEFAULTPHONEMEA, .lPhonemeACoarseTuning = EAXVOCALMORPHER_DEFAULTPHONEMEACOARSETUNING, .ulPhonemeB = EAXVOCALMORPHER_DEFAULTPHONEMEB, .lPhonemeBCoarseTuning = EAXVOCALMORPHER_DEFAULTPHONEMEBCOARSETUNING, .ulWaveform = EAXVOCALMORPHER_DEFAULTWAVEFORM, .flRate = EAXVOCALMORPHER_DEFAULTRATE}; } void EaxVocalMorpherCommitter::Get(const EaxCall &call, const EAXVOCALMORPHERPROPERTIES &props) { switch(call.get_property_id()) { case EAXVOCALMORPHER_NONE: break; case EAXVOCALMORPHER_ALLPARAMETERS: call.store(props); break; case EAXVOCALMORPHER_PHONEMEA: call.store(props.ulPhonemeA); break; case EAXVOCALMORPHER_PHONEMEACOARSETUNING: call.store(props.lPhonemeACoarseTuning); break; case EAXVOCALMORPHER_PHONEMEB: call.store(props.ulPhonemeB); break; case EAXVOCALMORPHER_PHONEMEBCOARSETUNING: call.store(props.lPhonemeBCoarseTuning); break; case EAXVOCALMORPHER_WAVEFORM: call.store(props.ulWaveform); break; case EAXVOCALMORPHER_RATE: call.store(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 kcat-openal-soft-75c0059/al/error.cpp000066400000000000000000000077231512220627100173770ustar00rootroot00000000000000/** * 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 #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 "alformat.hpp" #include "alnumeric.h" #include "core/except.h" #include "core/logging.h" #include "direct_defs.h" #include "gsl/gsl" #include "strutils.hpp" namespace { auto alGetError(gsl::not_null context) noexcept -> ALenum { auto ret = context->mLastThreadError.get(); if(ret != AL_NO_ERROR) [[unlikely]] context->mLastThreadError.set(AL_NO_ERROR); return ret; } } // namespace void al::Context::setErrorImpl(ALenum const errorCode, al::string_view const fmt, al::format_args args) { const auto message = al::vformat(fmt, std::move(args)); WARN("Error generated on context {}, code {:#04x}, \"{}\"", decltype(std::declval()){this}, as_unsigned(errorCode), message); 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, as_unsigned(errorCode), DebugSeverity::High, message); } void al::Context::throw_error_impl(ALenum const errorCode, al::string_view const fmt, al::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 alGetError(gsl::make_not_null(context.get())); static constexpr auto get_value = [](gsl::czstring envname, std::string_view 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 value; } catch(...) { } ERR("Invalid default error value: \"{}\"", *optstr); } return AL_INVALID_OPERATION; }; static const auto 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 auto AL_APIENTRY alGetErrorDirect(ALCcontext *context) noexcept -> ALenum { return alGetError(al::verify_context(context)); } kcat-openal-soft-75c0059/al/event.cpp000066400000000000000000000177411512220627100173700ustar00rootroot00000000000000 #include "config.h" #include "event.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "AL/al.h" #include "AL/alext.h" #include "alc/context.h" #include "alformat.hpp" #include "alnumeric.h" #include "core/async_event.h" #include "core/context.h" #include "core/effects/base.h" #include "core/except.h" #include "core/logging.h" #include "direct_defs.h" #include "gsl/gsl" #include "intrusive_ptr.h" #include "opthelpers.h" #include "ringbuffer.h" namespace { using namespace std::string_view_literals; template struct overloaded : Ts... { using Ts::operator()...; }; auto EventThread(gsl::not_null const context) -> void { auto const ring = gsl::not_null{context->mAsyncEvents.get()}; auto quitnow = false; while(!quitnow) { auto const evt_span = ring->getReadVector()[0]; if(evt_span.empty()) { al::atomic_wait(context->mEventsPending, 0_u32, std::memory_order_acquire); context->mEventsPending.store(0_u32, std::memory_order_release); continue; } auto eventlock = std::lock_guard{context->mEventCbLock}; const auto enabledevts = context->mEnabledEvts.load(std::memory_order_acquire); for(auto &event : evt_span) { quitnow = std::holds_alternative(event); if(quitnow) [[unlikely]] break; std::visit(overloaded { [](AsyncKillThread&) { }, [](AsyncEffectReleaseEvent const &evt) { al::intrusive_ptr{evt.mEffectState}; }, [context,enabledevts](AsyncSourceStateEvent const &evt) { if(!context->mEventCb || !enabledevts.test(al::to_underlying(AsyncEnableBits::SourceState))) return; auto state = ALuint{}; auto state_sv = std::string_view{}; switch(evt.mState) { case AsyncSrcState::Reset: state_sv = "AL_INITIAL"sv; state = AL_INITIAL; break; case AsyncSrcState::Stop: state_sv = "AL_STOPPED"sv; state = AL_STOPPED; break; case AsyncSrcState::Play: state_sv = "AL_PLAYING"sv; state = AL_PLAYING; break; case AsyncSrcState::Pause: state_sv = "AL_PAUSED"sv; state = AL_PAUSED; break; } const auto msg = al::format("Source ID {} state has changed to {}", evt.mId, state_sv); context->mEventCb(AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT, evt.mId, state, al::saturate_cast(msg.size()), msg.c_str(), context->mEventParam); }, [context,enabledevts](AsyncBufferCompleteEvent const &evt) { if(!context->mEventCb || !enabledevts.test(al::to_underlying(AsyncEnableBits::BufferCompleted))) return; const auto msg = al::format("{} buffer{} completed", evt.mCount, (evt.mCount == 1) ? "" : "s"); context->mEventCb(AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT, evt.mId, evt.mCount, al::saturate_cast(msg.size()), msg.c_str(), context->mEventParam); }, [context,enabledevts](AsyncDisconnectEvent const &evt) { if(!context->mEventCb || !enabledevts.test(al::to_underlying(AsyncEnableBits::Disconnected))) return; context->mEventCb(AL_EVENT_TYPE_DISCONNECTED_SOFT, 0, 0, al::saturate_cast(evt.msg.size()), evt.msg.c_str(), context->mEventParam); } }, event); } ring->readAdvance(evt_span.size()); } } [[nodiscard]] constexpr auto GetEventType(ALenum const etype) noexcept -> std::optional { 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; } void alEventControlSOFT(gsl::not_null const context, ALsizei const count, ALenum const *const types, ALboolean const 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"); auto flags = ContextBase::AsyncEventBitset{}; std::ranges::for_each(std::views::counted(types, count), [context,&flags](ALenum const evttype) { auto const 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::ignore = std::lock_guard{context->mEventCbLock}; } } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } void alEventCallbackSOFT(gsl::not_null const context, ALEVENTPROCSOFT const callback, void *const userParam) noexcept try { auto const eventlock = std::lock_guard{context->mEventCbLock}; context->mEventCb = callback; context->mEventParam = userParam; } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } } // namespace void StartEventThrd(al::Context *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(al::Context *ctx) { if(!ctx->mEventThread.joinable()) return; auto const ring = gsl::not_null{ctx->mAsyncEvents.get()}; auto evt_span = ring->getWriteVector()[0]; if(evt_span.empty()) { do { std::this_thread::yield(); evt_span = ring->getWriteVector()[0]; } while(evt_span.empty()); } std::ignore = InitAsyncEvent(evt_span[0]); ring->writeAdvance(1); ctx->mEventsPending.store(1_u32, std::memory_order_release); al::atomic_notify_all(ctx->mEventsPending); ctx->mEventThread.join(); } AL_API DECL_FUNCEXT3(void, alEventControl,SOFT, ALsizei,count, const ALenum*,types, ALboolean,enable) AL_API DECL_FUNCEXT2(void, alEventCallback,SOFT, ALEVENTPROCSOFT,callback, void*,userParam) kcat-openal-soft-75c0059/al/event.h000066400000000000000000000002561512220627100170260ustar00rootroot00000000000000#ifndef AL_EVENT_H #define AL_EVENT_H namespace al { struct Context; } // namespace al void StartEventThrd(al::Context *ctx); void StopEventThrd(al::Context *ctx); #endif kcat-openal-soft-75c0059/al/extension.cpp000066400000000000000000000045521512220627100202570ustar00rootroot00000000000000/** * 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" namespace { auto alIsExtensionPresent(gsl::not_null context, const ALchar *extName) noexcept -> ALboolean { if(!extName) [[unlikely]] { context->setError(AL_INVALID_VALUE, "NULL pointer"); return AL_FALSE; } const auto tofind = std::string_view{extName}; const auto found = std::ranges::any_of(context->mExtensions, [tofind](std::string_view ext) { return tofind.size() == ext.size() && al::case_compare(ext, tofind) == 0; }); return found ? AL_TRUE : AL_FALSE; } } // namespace AL_API DECL_FUNC1(ALboolean, alIsExtensionPresent, const ALchar*,extName) AL_API auto AL_APIENTRY alGetProcAddress(const ALchar *funcName) noexcept -> ALvoid* { if(!funcName) return nullptr; return alcGetProcAddress(nullptr, funcName); } FORCE_ALIGN auto AL_APIENTRY alGetProcAddressDirect(ALCcontext*, const ALchar *funcName) noexcept -> ALvoid* { if(!funcName) return nullptr; return alcGetProcAddress(nullptr, funcName); } AL_API auto AL_APIENTRY alGetEnumValue(const ALchar *enumName) noexcept -> ALenum { if(!enumName) return ALenum{0}; return alcGetEnumValue(nullptr, enumName); } FORCE_ALIGN auto AL_APIENTRY alGetEnumValueDirect(ALCcontext*, const ALchar *enumName) noexcept -> ALenum { if(!enumName) return ALenum{0}; return alcGetEnumValue(nullptr, enumName); } kcat-openal-soft-75c0059/al/filter.cpp000066400000000000000000000604041512220627100175260ustar00rootroot00000000000000/** * 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 #include #include "AL/al.h" #include "AL/efx.h" #include "alc/context.h" #include "alc/device.h" #include "almalloc.h" #include "alnumeric.h" #include "core/except.h" #include "core/logging.h" #include "direct_defs.h" #include "gsl/gsl" #include "opthelpers.h" using uint = unsigned int; /* Null filter parameter handlers */ template<> void FilterTable::setParami(gsl::not_null context, gsl::not_null, ALenum param, i32) { context->throw_error(AL_INVALID_ENUM, "Invalid null filter property {:#04x}", as_unsigned(param)); } template<> void FilterTable::setParamiv(gsl::not_null context, gsl::not_null, ALenum param, i32 const*) { context->throw_error(AL_INVALID_ENUM, "Invalid null filter property {:#04x}", as_unsigned(param)); } template<> void FilterTable::setParamf(gsl::not_null context, gsl::not_null, ALenum param, f32) { context->throw_error(AL_INVALID_ENUM, "Invalid null filter property {:#04x}", as_unsigned(param)); } template<> void FilterTable::setParamfv(gsl::not_null context, gsl::not_null, ALenum param, f32 const*) { context->throw_error(AL_INVALID_ENUM, "Invalid null filter property {:#04x}", as_unsigned(param)); } template<> void FilterTable::getParami(gsl::not_null context, gsl::not_null, ALenum param, i32*) { context->throw_error(AL_INVALID_ENUM, "Invalid null filter property {:#04x}", as_unsigned(param)); } template<> void FilterTable::getParamiv(gsl::not_null context, gsl::not_null, ALenum param, i32*) { context->throw_error(AL_INVALID_ENUM, "Invalid null filter property {:#04x}", as_unsigned(param)); } template<> void FilterTable::getParamf(gsl::not_null context, gsl::not_null, ALenum param, f32*) { context->throw_error(AL_INVALID_ENUM, "Invalid null filter property {:#04x}", as_unsigned(param)); } template<> void FilterTable::getParamfv(gsl::not_null context, gsl::not_null, ALenum param, f32*) { context->throw_error(AL_INVALID_ENUM, "Invalid null filter property {:#04x}", as_unsigned(param)); } /* Lowpass parameter handlers */ template<> void FilterTable::setParami(gsl::not_null context, gsl::not_null, ALenum param, i32) { context->throw_error(AL_INVALID_ENUM, "Invalid low-pass integer property {:#04x}", as_unsigned(param)); } template<> void FilterTable::setParamiv(gsl::not_null context, gsl::not_null filter, ALenum param, i32 const *values) { setParami(context, filter, param, *values); } template<> void FilterTable::setParamf(gsl::not_null context, gsl::not_null filter, ALenum param, f32 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 {} out of range", val); filter->mGain = 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 {} out of range", val); filter->mGainHF = val; return; } context->throw_error(AL_INVALID_ENUM, "Invalid low-pass float property {:#04x}", as_unsigned(param)); } template<> void FilterTable::setParamfv(gsl::not_null context, gsl::not_null filter, ALenum param, f32 const *vals) { setParamf(context, filter, param, *vals); } template<> void FilterTable::getParami(gsl::not_null context, gsl::not_null, ALenum param, i32*) { context->throw_error(AL_INVALID_ENUM, "Invalid low-pass integer property {:#04x}", as_unsigned(param)); } template<> void FilterTable::getParamiv(gsl::not_null context, gsl::not_null filter, ALenum param, i32 *values) { getParami(context, filter, param, values); } template<> void FilterTable::getParamf(gsl::not_null context, gsl::not_null filter, ALenum param, f32 *val) { switch(param) { case AL_LOWPASS_GAIN: *val = filter->mGain; return; case AL_LOWPASS_GAINHF: *val = filter->mGainHF; return; } context->throw_error(AL_INVALID_ENUM, "Invalid low-pass float property {:#04x}", as_unsigned(param)); } template<> void FilterTable::getParamfv(gsl::not_null context, gsl::not_null filter, ALenum param, f32 *vals) { getParamf(context, filter, param, vals); } /* Highpass parameter handlers */ template<> void FilterTable::setParami(gsl::not_null context, gsl::not_null, ALenum param, i32) { context->throw_error(AL_INVALID_ENUM, "Invalid high-pass integer property {:#04x}", as_unsigned(param)); } template<> void FilterTable::setParamiv(gsl::not_null context, gsl::not_null filter, ALenum param, i32 const *values) { setParami(context, filter, param, *values); } template<> void FilterTable::setParamf(gsl::not_null context, gsl::not_null filter, ALenum param, f32 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 {} out of range", val); filter->mGain = 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 {} out of range", val); filter->mGainLF = val; return; } context->throw_error(AL_INVALID_ENUM, "Invalid high-pass float property {:#04x}", as_unsigned(param)); } template<> void FilterTable::setParamfv(gsl::not_null context, gsl::not_null filter, ALenum param, f32 const *vals) { setParamf(context, filter, param, *vals); } template<> void FilterTable::getParami(gsl::not_null context, gsl::not_null, ALenum param, i32*) { context->throw_error(AL_INVALID_ENUM, "Invalid high-pass integer property {:#04x}", as_unsigned(param)); } template<> void FilterTable::getParamiv(gsl::not_null context, gsl::not_null filter, ALenum param, i32 *values) { getParami(context, filter, param, values); } template<> void FilterTable::getParamf(gsl::not_null context, gsl::not_null filter, ALenum param, f32 *val) { switch(param) { case AL_HIGHPASS_GAIN: *val = filter->mGain; return; case AL_HIGHPASS_GAINLF: *val = filter->mGainLF; return; } context->throw_error(AL_INVALID_ENUM, "Invalid high-pass float property {:#04x}", as_unsigned(param)); } template<> void FilterTable::getParamfv(gsl::not_null context, gsl::not_null filter, ALenum param, f32 *vals) { getParamf(context, filter, param, vals); } /* Bandpass parameter handlers */ template<> void FilterTable::setParami(gsl::not_null context, gsl::not_null, ALenum param, i32) { context->throw_error(AL_INVALID_ENUM, "Invalid band-pass integer property {:#04x}", as_unsigned(param)); } template<> void FilterTable::setParamiv(gsl::not_null context, gsl::not_null filter, ALenum param, i32 const *values) { setParami(context, filter, param, *values); } template<> void FilterTable::setParamf(gsl::not_null context, gsl::not_null filter, ALenum param, f32 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 {} out of range", val); filter->mGain = 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 {} out of range", val); filter->mGainHF = 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 {} out of range", val); filter->mGainLF = val; return; } context->throw_error(AL_INVALID_ENUM, "Invalid band-pass float property {:#04x}", as_unsigned(param)); } template<> void FilterTable::setParamfv(gsl::not_null context, gsl::not_null filter, ALenum param, f32 const *vals) { setParamf(context, filter, param, *vals); } template<> void FilterTable::getParami(gsl::not_null context, gsl::not_null, ALenum param, i32*) { context->throw_error(AL_INVALID_ENUM, "Invalid band-pass integer property {:#04x}", as_unsigned(param)); } template<> void FilterTable::getParamiv(gsl::not_null context, gsl::not_null filter, ALenum param, i32 *values) { getParami(context, filter, param, values); } template<> void FilterTable::getParamf(gsl::not_null context, gsl::not_null filter, ALenum param, f32 *val) { switch(param) { case AL_BANDPASS_GAIN: *val = filter->mGain; return; case AL_BANDPASS_GAINHF: *val = filter->mGainHF; return; case AL_BANDPASS_GAINLF: *val = filter->mGainLF; return; } context->throw_error(AL_INVALID_ENUM, "Invalid band-pass float property {:#04x}", as_unsigned(param)); } template<> void FilterTable::getParamfv(gsl::not_null context, gsl::not_null filter, ALenum param, f32 *vals) { getParamf(context, filter, param, vals); } namespace { using SubListAllocator = al::allocator>; void InitFilterParams(gsl::not_null const filter, ALenum const type) { if(type == AL_FILTER_LOWPASS) { filter->mGain = AL_LOWPASS_DEFAULT_GAIN; filter->mGainHF = AL_LOWPASS_DEFAULT_GAINHF; filter->mHFReference = LowPassFreqRef; filter->mGainLF = 1.0f; filter->mLFReference = HighPassFreqRef; filter->mTypeVariant.emplace(); } else if(type == AL_FILTER_HIGHPASS) { filter->mGain = AL_HIGHPASS_DEFAULT_GAIN; filter->mGainHF = 1.0f; filter->mHFReference = LowPassFreqRef; filter->mGainLF = AL_HIGHPASS_DEFAULT_GAINLF; filter->mLFReference = HighPassFreqRef; filter->mTypeVariant.emplace(); } else if(type == AL_FILTER_BANDPASS) { filter->mGain = AL_BANDPASS_DEFAULT_GAIN; filter->mGainHF = AL_BANDPASS_DEFAULT_GAINHF; filter->mHFReference = LowPassFreqRef; filter->mGainLF = AL_BANDPASS_DEFAULT_GAINLF; filter->mLFReference = HighPassFreqRef; filter->mTypeVariant.emplace(); } else { filter->mGain = 1.0f; filter->mGainHF = 1.0f; filter->mHFReference = LowPassFreqRef; filter->mGainLF = 1.0f; filter->mLFReference = HighPassFreqRef; filter->mTypeVariant.emplace(); } filter->mType = type; } [[nodiscard]] auto EnsureFilters(gsl::not_null device, size_t needed) noexcept -> bool try { auto count = std::accumulate(device->FilterList.cbegin(), device->FilterList.cend(), 0_uz, [](size_t cur, const FilterSubList &sublist) noexcept -> size_t { return cur + gsl::narrow_cast(std::popcount(sublist.mFreeMask)); }); while(needed > count) { if(device->FilterList.size() >= 1<<25) [[unlikely]] return false; auto sublist = FilterSubList{}; sublist.mFreeMask = ~0_u64; sublist.mFilters = SubListAllocator{}.allocate(1); device->FilterList.emplace_back(std::move(sublist)); count += std::tuple_size_v; } return true; } catch(...) { return false; } [[nodiscard]] auto AllocFilter(gsl::not_null const device) noexcept -> gsl::not_null { auto const sublist = std::ranges::find_if(device->FilterList, &FilterSubList::mFreeMask); auto const lidx = gsl::narrow_cast(std::distance(device->FilterList.begin(), sublist)); auto const slidx = gsl::narrow_cast(std::countr_zero(sublist->mFreeMask)); ASSUME(slidx < 64); auto filter = gsl::make_not_null(std::construct_at( std::to_address(std::next(sublist->mFilters->begin(), slidx)))); InitFilterParams(filter, AL_FILTER_NULL); /* Add 1 to avoid filter ID 0. */ filter->mId = ((lidx<<6) | slidx) + 1; sublist->mFreeMask &= ~(1_u64 << slidx); return filter; } void FreeFilter(gsl::not_null const device, gsl::not_null const filter) { device->mFilterNames.erase(filter->mId); const auto id = filter->mId - 1; const auto lidx = id >> 6; const auto slidx = id & 0x3f; std::destroy_at(filter.get()); device->FilterList[lidx].mFreeMask |= 1_u64 << slidx; } [[nodiscard]] auto LookupFilter(std::nothrow_t, gsl::not_null const device, u32 const id) noexcept -> al::Filter* { const auto lidx = (id-1) >> 6; const auto slidx = (id-1) & 0x3f; if(lidx >= device->FilterList.size()) [[unlikely]] return nullptr; auto &sublist = device->FilterList[lidx]; if(sublist.mFreeMask & (1_u64 << slidx)) [[unlikely]] return nullptr; return std::to_address(std::next(sublist.mFilters->begin(), slidx)); } [[nodiscard]] auto LookupFilter(gsl::not_null const context, u32 const id) -> gsl::not_null { if(auto *const filter = LookupFilter(std::nothrow, al::get_not_null(context->mALDevice), id)) [[likely]] return gsl::make_not_null(filter); context->throw_error(AL_INVALID_NAME, "Invalid filter ID {}", id); } void alGenFilters(gsl::not_null 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 const device = al::get_not_null(context->mALDevice); auto filterlock = std::lock_guard{device->FilterLock}; const auto fids = std::views::counted(filters, n); if(!EnsureFilters(device, fids.size())) context->throw_error(AL_OUT_OF_MEMORY, "Failed to allocate {} filter{}", n, (n==1) ? "" : "s"); std::ranges::generate(fids, [device]{ return AllocFilter(device)->mId; }); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } void alDeleteFilters(gsl::not_null 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 const device = al::get_not_null(context->mALDevice); auto filterlock = std::lock_guard{device->FilterLock}; /* First try to find any filters that are invalid. */ const auto fids = std::views::counted(filters, n); std::ranges::for_each(fids, [context](const ALuint fid) { if(fid != 0) std::ignore = LookupFilter(context, fid); }); /* All good. Delete non-0 filter IDs. */ std::ranges::for_each(fids, [device](const ALuint fid) { if(auto *filter = LookupFilter(std::nothrow, device, fid)) FreeFilter(device, gsl::make_not_null(filter)); }); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } auto alIsFilter(gsl::not_null context, ALuint filter) noexcept -> ALboolean { auto const device = al::get_not_null(context->mALDevice); auto filterlock = std::lock_guard{device->FilterLock}; if(filter == 0 || LookupFilter(std::nothrow, device, filter) != nullptr) return AL_TRUE; return AL_FALSE; } void alFilteri(gsl::not_null context, ALuint filter, ALenum param, ALint value) noexcept try { auto const device = al::get_not_null(context->mALDevice); auto filterlock = std::lock_guard{device->FilterLock}; auto const alfilt = LookupFilter(context, 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()); } void alFilteriv(gsl::not_null context, ALuint filter, ALenum param, const ALint *values) noexcept try { switch(param) { case AL_FILTER_TYPE: alFilteri(context, filter, param, *values); return; } auto const device = al::get_not_null(context->mALDevice); auto filterlock = std::lock_guard{device->FilterLock}; auto const alfilt = LookupFilter(context, 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()); } void alFilterf(gsl::not_null context, ALuint filter, ALenum param, ALfloat value) noexcept try { auto const device = al::get_not_null(context->mALDevice); auto filterlock = std::lock_guard{device->FilterLock}; auto const alfilt = LookupFilter(context, 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()); } void alFilterfv(gsl::not_null context, ALuint filter, ALenum param, const ALfloat *values) noexcept try { auto const device = al::get_not_null(context->mALDevice); auto filterlock = std::lock_guard{device->FilterLock}; auto const alfilt = LookupFilter(context, 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()); } void alGetFilteri(gsl::not_null context, ALuint filter, ALenum param, ALint *value) noexcept try { auto const device = al::get_not_null(context->mALDevice); auto filterlock = std::lock_guard{device->FilterLock}; auto const alfilt = LookupFilter(context, filter); switch(param) { case AL_FILTER_TYPE: *value = alfilt->mType; 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()); } void alGetFilteriv(gsl::not_null context, ALuint filter, ALenum param, ALint *values) noexcept try { switch(param) { case AL_FILTER_TYPE: alGetFilteri(context, filter, param, values); return; } auto const device = al::get_not_null(context->mALDevice); auto filterlock = std::lock_guard{device->FilterLock}; auto const alfilt = LookupFilter(context, 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()); } void alGetFilterf(gsl::not_null context, ALuint filter, ALenum param, ALfloat *value) noexcept try { auto const device = al::get_not_null(context->mALDevice); auto filterlock = std::lock_guard{device->FilterLock}; auto const alfilt = LookupFilter(context, 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()); } void alGetFilterfv(gsl::not_null context, ALuint filter, ALenum param, ALfloat *values) noexcept try { auto const device = al::get_not_null(context->mALDevice); auto filterlock = std::lock_guard{device->FilterLock}; auto const alfilt = LookupFilter(context, 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()); } } // namespace AL_API DECL_FUNC2(void, alGenFilters, ALsizei,n, ALuint*,filters) AL_API DECL_FUNC2(void, alDeleteFilters, ALsizei,n, const ALuint*,filters) AL_API DECL_FUNC1(ALboolean, alIsFilter, ALuint,filter) AL_API DECL_FUNC3(void, alFilteri, ALuint,filter, ALenum,param, ALint,value) AL_API DECL_FUNC3(void, alFilteriv, ALuint,filter, ALenum,param, const ALint*,values) AL_API DECL_FUNC3(void, alFilterf, ALuint,filter, ALenum,param, ALfloat,value) AL_API DECL_FUNC3(void, alFilterfv, ALuint,filter, ALenum,param, const ALfloat*,values) AL_API DECL_FUNC3(void, alGetFilteri, ALuint,filter, ALenum,param, ALint*,value) AL_API DECL_FUNC3(void, alGetFilteriv, ALuint,filter, ALenum,param, ALint*,values) AL_API DECL_FUNC3(void, alGetFilterf, ALuint,filter, ALenum,param, ALfloat*,value) AL_API DECL_FUNC3(void, alGetFilterfv, ALuint,filter, ALenum,param, ALfloat*,values) void al::Filter::SetName(gsl::not_null const context, u32 const id, std::string_view const name) { auto const device = get_not_null(context->mALDevice); auto const filterlock = std::lock_guard{device->FilterLock}; std::ignore = LookupFilter(context, id); device->mFilterNames.insert_or_assign(id, name); } FilterSubList::~FilterSubList() { if(!mFilters) return; auto usemask = ~mFreeMask; while(usemask) { auto const idx = std::countr_zero(usemask); std::destroy_at(std::to_address(std::next(mFilters->begin(), idx))); usemask &= ~(1_u64 << idx); } mFreeMask = ~usemask; SubListAllocator{}.deallocate(mFilters, 1); mFilters = nullptr; } kcat-openal-soft-75c0059/al/filter.h000066400000000000000000000052471512220627100171770ustar00rootroot00000000000000#ifndef AL_FILTER_H #define AL_FILTER_H #include #include #include #include #include #include "AL/al.h" #include "AL/efx.h" #include "almalloc.h" #include "alnumeric.h" #include "gsl/gsl" namespace al { struct Context; struct Filter; } // namespace al inline constexpr auto LowPassFreqRef = 5000.0f; inline constexpr auto HighPassFreqRef = 250.0f; template struct FilterTable { static void setParami(gsl::not_null, gsl::not_null, ALenum, i32); static void setParamiv(gsl::not_null, gsl::not_null, ALenum, i32 const*); static void setParamf(gsl::not_null, gsl::not_null, ALenum, f32); static void setParamfv(gsl::not_null, gsl::not_null, ALenum, f32 const*); static void getParami(gsl::not_null, gsl::not_null, ALenum, i32*); static void getParamiv(gsl::not_null, gsl::not_null, ALenum, i32*); static void getParamf(gsl::not_null, gsl::not_null, ALenum, f32*); static void getParamfv(gsl::not_null, gsl::not_null, ALenum, f32*); private: FilterTable() = default; friend T; }; struct NullFilterTable : FilterTable { }; struct LowpassFilterTable : FilterTable { }; struct HighpassFilterTable : FilterTable { }; struct BandpassFilterTable : FilterTable { }; namespace al { struct Filter { ALenum mType{AL_FILTER_NULL}; f32 mGain{1.0f}; f32 mGainHF{1.0f}; f32 mHFReference{LowPassFreqRef}; f32 mGainLF{1.0f}; f32 mLFReference{HighPassFreqRef}; using TableTypes = std::variant; TableTypes mTypeVariant; /* Self ID */ u32 mId{0}; static void SetName(gsl::not_null context, u32 id, std::string_view name); DISABLE_ALLOC }; } /* namespace al */ struct FilterSubList { u64 mFreeMask{~0_u64}; gsl::owner*> mFilters{nullptr}; FilterSubList() noexcept = default; FilterSubList(const FilterSubList&) = delete; FilterSubList(FilterSubList&& rhs) noexcept : mFreeMask{rhs.mFreeMask}, mFilters{rhs.mFilters} { rhs.mFreeMask = ~0_u64; rhs.mFilters = nullptr; } ~FilterSubList(); FilterSubList& operator=(const FilterSubList&) = delete; FilterSubList& operator=(FilterSubList&& rhs) noexcept { std::swap(mFreeMask, rhs.mFreeMask); std::swap(mFilters, rhs.mFilters); return *this; } }; #endif kcat-openal-soft-75c0059/al/listener.cpp000066400000000000000000000360001512220627100200610ustar00rootroot00000000000000/** * 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 #include #include "AL/al.h" #include "AL/efx.h" #include "alc/context.h" #include "alnumeric.h" #include "core/except.h" #include "core/logging.h" #include "direct_defs.h" #include "gsl/gsl" using uint = unsigned int; namespace { inline void UpdateProps(gsl::not_null context) { if(!context->mDeferUpdates) { UpdateContextProps(context); return; } context->mPropsDirty = true; } inline void CommitAndUpdateProps(gsl::not_null context) { if(!context->mDeferUpdates) { #if ALSOFT_EAX if(context->eaxNeedsCommit()) { context->mPropsDirty = true; context->applyAllUpdates(); return; } #endif UpdateContextProps(context); return; } context->mPropsDirty = true; } void alListenerf(gsl::not_null context, ALenum param, ALfloat value) noexcept try { const auto proplock = std::lock_guard{context->mPropLock}; auto &listener = context->mListener; switch(param) { case AL_GAIN: if(!(value >= 0.0f && std::isfinite(value))) context->throw_error(AL_INVALID_VALUE, "Listener gain {} out of range", value); listener.mGain = 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 {} 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()); } void alListener3f(gsl::not_null context, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3) noexcept try { auto &listener = context->mListener; const auto proplock = std::lock_guard{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.mPosition[0] = value1; listener.mPosition[1] = value2; listener.mPosition[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.mVelocity[0] = value1; listener.mVelocity[1] = value2; listener.mVelocity[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()); } void alListenerfv(gsl::not_null 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: alListenerf(context, param, *values); return; case AL_POSITION: case AL_VELOCITY: const auto vals = std::span{values, 3_uz}; alListener3f(context, param, vals[0], vals[1], vals[2]); return; } const auto proplock = std::lock_guard{context->mPropLock}; auto &listener = context->mListener; switch(param) { case AL_ORIENTATION: const auto vals = std::span{values, 6_uz}; if(!std::ranges::all_of(vals, [](float f){ return std::isfinite(f); })) context->throw_error(AL_INVALID_VALUE, "Listener orientation out of range"); /* AT then UP */ std::ranges::copy(vals | std::views::take(3), listener.mOrientAt.begin()); std::ranges::copy(vals | std::views::drop(3), listener.mOrientUp.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()); } void alListeneri(gsl::not_null context, ALenum param, ALint value) noexcept try { const auto proplock = std::lock_guard{context->mPropLock}; auto &listener = context->mListener; switch(param) { case AL_GAIN: if(value < 0) context->throw_error(AL_INVALID_VALUE, "Listener gain {} out of range", value); listener.mGain = gsl::narrow_cast(value); UpdateProps(context); return; case AL_METERS_PER_UNIT: if(value < 1) context->throw_error(AL_INVALID_VALUE, "Listener meters per unit {} out of range", value); listener.mMetersPerUnit = gsl::narrow_cast(value); UpdateProps(context); return; } 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()); } void alListener3i(gsl::not_null context, ALenum param, ALint value1, ALint value2, ALint value3) noexcept try { switch(param) { case AL_POSITION: case AL_VELOCITY: alListener3f(context, param, gsl::narrow_cast(value1), gsl::narrow_cast(value2), gsl::narrow_cast(value3)); return; } const auto proplock [[maybe_unused]] = std::lock_guard{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()); } void alListeneriv(gsl::not_null context, ALenum param, const ALint *values) noexcept try { if(!values) context->throw_error(AL_INVALID_VALUE, "NULL pointer"); auto vals = std::span{}; switch(param) { case AL_GAIN: case AL_METERS_PER_UNIT: alListeneri(context, param, *values); return; case AL_POSITION: case AL_VELOCITY: vals = {values, 3_uz}; alListener3f(context, param, gsl::narrow_cast(vals[0]), gsl::narrow_cast(vals[1]), gsl::narrow_cast(vals[2])); return; case AL_ORIENTATION: vals = {values, 6_uz}; const auto fvals = std::array{gsl::narrow_cast(vals[0]), gsl::narrow_cast(vals[1]), gsl::narrow_cast(vals[2]), gsl::narrow_cast(vals[3]), gsl::narrow_cast(vals[4]), gsl::narrow_cast(vals[5]), }; alListenerfv(context, param, fvals.data()); return; } const auto proplock [[maybe_unused]] = std::lock_guard{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()); } void alGetListenerf(gsl::not_null context, ALenum param, ALfloat *value) noexcept try { if(!value) context->throw_error(AL_INVALID_VALUE, "NULL pointer"); const auto proplock = std::lock_guard{context->mPropLock}; const auto &listener = context->mListener; switch(param) { case AL_GAIN: *value = listener.mGain; 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()); } void alGetListener3f(gsl::not_null context, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3) noexcept try { if(!value1 || !value2 || !value3) context->throw_error(AL_INVALID_VALUE, "NULL pointer"); const auto proplock = std::lock_guard{context->mPropLock}; const auto &listener = context->mListener; switch(param) { case AL_POSITION: *value1 = listener.mPosition[0]; *value2 = listener.mPosition[1]; *value3 = listener.mPosition[2]; return; case AL_VELOCITY: *value1 = listener.mVelocity[0]; *value2 = listener.mVelocity[1]; *value3 = listener.mVelocity[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()); } void alGetListenerfv(gsl::not_null 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: alGetListenerf(context, param, values); return; case AL_POSITION: case AL_VELOCITY: const auto vals = std::span{values, 3_uz}; alGetListener3f(context, param, &vals[0], &vals[1], &vals[2]); return; } const auto proplock = std::lock_guard{context->mPropLock}; const auto &listener = context->mListener; switch(param) { case AL_ORIENTATION: const auto vals = std::span{values, 6_uz}; // AT then UP auto oiter = std::ranges::copy(listener.mOrientAt, vals.begin()).out; std::ranges::copy(listener.mOrientUp, oiter); 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()); } void alGetListeneri(gsl::not_null context, ALenum param, ALint *value) noexcept try { /* The largest float value that can fit in an int. */ static constexpr auto float_int_max = 2147483520.0f; if(!value) context->throw_error(AL_INVALID_VALUE, "NULL pointer"); const auto proplock = std::lock_guard{context->mPropLock}; const auto &listener = context->mListener; switch(param) { case AL_GAIN: *value = gsl::narrow_cast(std::min(listener.mGain, float_int_max)); return; case AL_METERS_PER_UNIT: *value = gsl::narrow_cast(std::clamp(listener.mMetersPerUnit, 1.0f, float_int_max)); return; } 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()); } void alGetListener3i(gsl::not_null context, ALenum param, ALint *value1, ALint *value2, ALint *value3) noexcept try { if(!value1 || !value2 || !value3) context->throw_error(AL_INVALID_VALUE, "NULL pointer"); const auto proplock = std::lock_guard{context->mPropLock}; const auto &listener = context->mListener; switch(param) { case AL_POSITION: *value1 = gsl::narrow_cast(listener.mPosition[0]); *value2 = gsl::narrow_cast(listener.mPosition[1]); *value3 = gsl::narrow_cast(listener.mPosition[2]); return; case AL_VELOCITY: *value1 = gsl::narrow_cast(listener.mVelocity[0]); *value2 = gsl::narrow_cast(listener.mVelocity[1]); *value3 = gsl::narrow_cast(listener.mVelocity[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()); } void alGetListeneriv(gsl::not_null context, ALenum param, ALint *values) noexcept try { if(!values) context->throw_error(AL_INVALID_VALUE, "NULL pointer"); switch(param) { case AL_GAIN: case AL_METERS_PER_UNIT: alGetListeneri(context, param, values); return; case AL_POSITION: case AL_VELOCITY: const auto vals = std::span{values, 3_uz}; alGetListener3i(context, param, &vals[0], &vals[1], &vals[2]); return; } const auto proplock = std::lock_guard{context->mPropLock}; const auto &listener = context->mListener; static constexpr auto f2i = [](const float val) { return gsl::narrow_cast(val); }; switch(param) { case AL_ORIENTATION: const auto vals = std::span{values, 6_uz}; // AT then UP auto oiter = std::ranges::transform(listener.mOrientAt, vals.begin(), f2i).out; std::ranges::transform(listener.mOrientUp, oiter, 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()); } } // namespace AL_API DECL_FUNC2(void, alListenerf, ALenum,param, ALfloat,value) AL_API DECL_FUNC4(void, alListener3f, ALenum,param, ALfloat,value1, ALfloat,value2, ALfloat,value3) AL_API DECL_FUNC2(void, alListenerfv, ALenum,param, const ALfloat*,values) AL_API DECL_FUNC2(void, alListeneri, ALenum,param, ALint,value) AL_API DECL_FUNC4(void, alListener3i, ALenum,param, ALint,value1, ALint,value2, ALint,value3) AL_API DECL_FUNC2(void, alListeneriv, ALenum,param, const ALint*,values) AL_API DECL_FUNC2(void, alGetListenerf, ALenum,param, ALfloat*,value) AL_API DECL_FUNC4(void, alGetListener3f, ALenum,param, ALfloat*,value1, ALfloat*,value2, ALfloat*,value3) AL_API DECL_FUNC2(void, alGetListenerfv, ALenum,param, ALfloat*,values) AL_API DECL_FUNC2(void, alGetListeneri, ALenum,param, ALint*,value) AL_API DECL_FUNC4(void, alGetListener3i, ALenum,param, ALint*,value1, ALint*,value2, ALint*,value3) AL_API DECL_FUNC2(void, alGetListeneriv, ALenum,param, ALint*,values) kcat-openal-soft-75c0059/al/listener.h000066400000000000000000000007711512220627100175340ustar00rootroot00000000000000#ifndef AL_LISTENER_H #define AL_LISTENER_H #include #include "AL/efx.h" #include "almalloc.h" #include "altypes.hpp" namespace al { struct Listener { std::array mPosition{{0.0f, 0.0f, 0.0f}}; std::array mVelocity{{0.0f, 0.0f, 0.0f}}; std::array mOrientAt{{0.0f, 0.0f, -1.0f}}; std::array mOrientUp{{0.0f, 1.0f, 0.0f}}; f32 mGain{1.0f}; f32 mMetersPerUnit{AL_DEFAULT_METERS_PER_UNIT}; DISABLE_ALLOC }; } /* namespace al */ #endif kcat-openal-soft-75c0059/al/source.cpp000066400000000000000000005017741512220627100175530ustar00rootroot00000000000000/** * 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 #include #include #include "AL/al.h" #include "AL/alext.h" #include "AL/efx.h" #include "alc/backends/base.h" #include "alc/context.h" #include "alc/device.h" #include "alc/inprogext.h" #include "alformat.hpp" #include "almalloc.h" #include "alnumeric.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 "gsl/gsl" #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 using uint = unsigned int; namespace { using SubListAllocator = al::allocator>; using std::chrono::nanoseconds; using seconds_d = std::chrono::duration; using namespace std::string_view_literals; constexpr auto HasBuffer(al::BufferQueueItem const &item) noexcept -> bool { return bool{item.mBuffer}; } auto GetSourceVoice(gsl::not_null const source, gsl::not_null const context) -> Voice* { auto const voicelist = context->getVoicesSpan(); if(auto const idx = source->mVoiceIdx; idx < voicelist.size()) { if(auto *const voice = voicelist[idx]; voice->mSourceID.load(std::memory_order_acquire) == source->mId) return voice; } source->mVoiceIdx = InvalidVoiceIndex; return nullptr; } void UpdateSourceProps(gsl::not_null const source, Voice *const voice, gsl::not_null const context) { /* Get an unused property container, or allocate a new one as needed. */ auto *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->mPitch; props->Gain = source->mGain; props->OuterGain = source->mOuterGain; props->MinGain = source->mMinGain; props->MaxGain = source->mMaxGain; props->InnerAngle = source->mInnerAngle; props->OuterAngle = source->mOuterAngle; props->RefDistance = source->mRefDistance; props->MaxDistance = source->mMaxDistance; props->RolloffFactor = source->mRolloffFactor #if ALSOFT_EAX + source->mRolloffFactor2 #endif ; props->Position = source->mPosition; props->Velocity = source->mVelocity; props->Direction = source->mDirection; props->OrientAt = source->mOrientAt; props->OrientUp = source->mOrientUp; props->HeadRelative = source->mHeadRelative; props->mDistanceModel = source->mDistanceModel; props->mResampler = source->mResampler; props->DirectChannels = source->DirectChannels; props->mSpatializeMode = source->mSpatialize; props->mPanningEnabled = source->mPanningEnabled; props->DryGainHFAuto = source->mDryGainHFAuto; props->WetGainAuto = source->mWetGainAuto; props->WetGainHFAuto = source->mWetGainHFAuto; props->OuterGainHF = source->mOuterGainHF; props->AirAbsorptionFactor = source->mAirAbsorptionFactor; props->RoomRolloffFactor = source->mRoomRolloffFactor; props->DopplerFactor = source->mDopplerFactor; props->StereoPan = source->mStereoPan; props->Radius = source->mRadius; props->EnhWidth = source->mEnhWidth; props->Panning = source->mPanningEnabled ? source->mPan : 0.0f; props->Direct.Gain = source->mDirect.mGain; props->Direct.GainHF = source->mDirect.mGainHF; props->Direct.HFReference = source->mDirect.mHFReference; props->Direct.GainLF = source->mDirect.mGainLF; props->Direct.LFReference = source->mDirect.mLFReference; std::ranges::transform(source->mSend, props->Send.begin(), [](al::Source::SendData const &srcsend) noexcept { auto ret = VoiceProps::SendData{}; ret.Slot = srcsend.mSlot ? srcsend.mSlot->mSlot.get() : nullptr; ret.Gain = srcsend.mGain; ret.GainHF = srcsend.mGainHF; ret.HFReference = srcsend.mHFReference; ret.GainLF = srcsend.mGainLF; ret.LFReference = srcsend.mLFReference; return ret; }); 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). */ auto GetSourceSampleOffset(gsl::not_null const Source, gsl::not_null const context, nanoseconds *const clocktime) -> i64 { auto const device = al::get_not_null(context->mALDevice); auto const *Current = LPVoiceBufferItem{}; auto readPos = i64{}; auto readPosFrac = uint{}; auto refcount = uint{}; do { refcount = device->waitForMix(); *clocktime = device->getClockTime(); auto const *const voice = GetSourceVoice(Source, context); if(not voice) return 0; 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(readPos < 0) return (readPos * (std::numeric_limits::max()+1_i64)) + (i64{readPosFrac} << (32-MixerFracBits)); std::ignore = std::ranges::find_if(Source->mQueue, [Current,&readPos](const VoiceBufferItem &item) { if(&item == Current) return true; readPos += item.mSampleLen; return false; }); if(readPos >= std::numeric_limits::max()>>32) return std::numeric_limits::max(); return (readPos<<32) + (i64{readPosFrac} << (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). */ auto GetSourceSecOffset(gsl::not_null const Source, gsl::not_null const context, nanoseconds *const clocktime) -> f64 { auto const device = al::get_not_null(context->mALDevice); auto const *Current = LPVoiceBufferItem{}; auto readPos = i64{}; auto readPosFrac = uint{}; auto refcount = uint{}; do { refcount = device->waitForMix(); *clocktime = device->getClockTime(); auto const *const voice = GetSourceVoice(Source, context); if(not voice) return 0.0; 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)); const auto BufferFmt = std::invoke([Source]() -> al::Buffer* { if(const auto iter = std::ranges::find_if(Source->mQueue, HasBuffer); iter != Source->mQueue.end()) return iter->mBuffer.get(); return nullptr; }); Ensures(BufferFmt != nullptr); std::ignore = std::ranges::find_if(Source->mQueue, [Current,&readPos](al::BufferQueueItem const &item) { if(&item == Current) return true; readPos += item.mSampleLen; return false; }); return (gsl::narrow_cast(readPosFrac)/f64{MixerFracOne} + gsl::narrow_cast(readPos)) / 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 auto GetSourceOffset(gsl::not_null const Source, ALenum const name, gsl::not_null const context) -> T { auto const device = al::get_not_null(context->mALDevice); auto const *Current = LPVoiceBufferItem{}; auto readPos = i64{}; auto readPosFrac = uint{}; auto refcount = uint{}; do { refcount = device->waitForMix(); auto const *const voice = GetSourceVoice(Source, context); if(not voice) return T{0}; 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)); const auto BufferFmt = std::invoke([Source]() -> al::Buffer* { if(const auto iter = std::ranges::find_if(Source->mQueue, HasBuffer); iter != Source->mQueue.end()) return iter->mBuffer.get(); return nullptr; }); std::ignore = std::ranges::find_if(Source->mQueue, [Current,&readPos](al::BufferQueueItem const &item) { if(&item == Current) return true; readPos += item.mSampleLen; return false; }); switch(name) { case AL_SEC_OFFSET: if constexpr(std::is_floating_point_v) { const auto offset = gsl::narrow_cast(readPos) + gsl::narrow_cast(readPosFrac)/T{MixerFracOne}; return offset / gsl::narrow_cast(BufferFmt->mSampleRate); } else return al::saturate_cast(readPos / BufferFmt->mSampleRate); case AL_SAMPLE_OFFSET: if constexpr(std::is_floating_point_v) return gsl::narrow_cast(readPos) + gsl::narrow_cast(readPosFrac)/T{MixerFracOne}; else return al::saturate_cast(readPos); case AL_BYTE_OFFSET: /* Round down to the block boundary. */ const auto BlockSize = uint{BufferFmt->blockSizeFromFmt()}; readPos = readPos / BufferFmt->mBlockAlign * BlockSize; if constexpr(std::is_floating_point_v) return gsl::narrow_cast(readPos); else { if(readPos > std::numeric_limits::max()) return RoundToZero(std::numeric_limits::max(), gsl::narrow_cast(BlockSize)); if(readPos < std::numeric_limits::min()) return RoundToZero(std::numeric_limits::min(), gsl::narrow_cast(BlockSize)); return gsl::narrow_cast(readPos); } } return T{0}; } /* GetSourceLength * * Gets the length of the given Source's buffer queue, in the appropriate * format (Bytes, Samples or Seconds). */ template NOINLINE auto GetSourceLength(gsl::not_null const source, ALenum const name) -> T { const auto BufferFmt = std::invoke([source]() -> al::Buffer* { if(auto const iter = std::ranges::find_if(source->mQueue, HasBuffer); iter != source->mQueue.end()) return iter->mBuffer.get(); return nullptr; }); if(!BufferFmt) return T{0}; const auto length = std::accumulate(source->mQueue.begin(), source->mQueue.end(), 0_u64, [](u64 const count, al::BufferQueueItem const &item) { return count + item.mSampleLen; }); if(length == 0) return T{0}; switch(name) { case AL_SEC_LENGTH_SOFT: if constexpr(std::is_floating_point_v) return gsl::narrow_cast(length) / gsl::narrow_cast(BufferFmt->mSampleRate); else return al::saturate_cast(length / BufferFmt->mSampleRate); case AL_SAMPLE_LENGTH_SOFT: if constexpr(std::is_floating_point_v) return gsl::narrow_cast(length); else return al::saturate_cast(length); case AL_BYTE_LENGTH_SOFT: /* Round down to the block boundary. */ const auto BlockSize = u32{BufferFmt->blockSizeFromFmt()}; const auto alignedlen = length / BufferFmt->mBlockAlign * BlockSize; if constexpr(std::is_floating_point_v) return gsl::narrow_cast(alignedlen); else { if(alignedlen > u64{std::numeric_limits::max()}) return RoundToZero(std::numeric_limits::max(), gsl::narrow_cast(BlockSize)); return gsl::narrow_cast(alignedlen); } } return T{0}; } struct VoicePos { i32 pos; u32 frac; al::BufferQueueItem *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. */ auto GetSampleOffset(std::deque &BufferList, ALenum const OffsetType, f64 const Offset) -> std::optional { /* Find the first valid Buffer in the Queue */ const auto BufferFmt = std::invoke([&BufferList]() -> al::Buffer* { if(auto const iter = std::ranges::find_if(BufferList, HasBuffer); iter != BufferList.end()) return iter->mBuffer.get(); return nullptr; }); if(!BufferFmt) [[unlikely]] return std::nullopt; /* Get sample frame offset */ auto [offset, frac] = std::invoke([OffsetType,Offset,BufferFmt]() -> std::pair { auto dbloff = f64{}; auto dblfrac = f64{}; 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 * greater value (e.g. -2.75 -> -2 + -0.75 -> -3 + 0.25). */ dbloff -= 1.0; dblfrac += 1.0; } return {gsl::narrow_cast(dbloff), gsl::narrow_cast(std::min(dblfrac*MixerFracOne, MixerFracOne-1.0))}; case AL_SAMPLE_OFFSET: dblfrac = std::modf(Offset, &dbloff); if(dblfrac < 0.0) { dbloff -= 1.0; dblfrac += 1.0; } return {gsl::narrow_cast(dbloff), gsl::narrow_cast(std::min(dblfrac*MixerFracOne, MixerFracOne-1.0))}; case AL_BYTE_OFFSET: /* Determine the ByteOffset (and ensure it is block aligned) */ const auto blockoffset = std::floor(Offset / BufferFmt->blockSizeFromFmt()); return {gsl::narrow_cast(blockoffset) * BufferFmt->mBlockAlign, 0u}; } return {0_i64, 0u}; }); /* Find the bufferlist item this offset belongs to. */ if(offset < 0) { if(offset < std::numeric_limits::min()) return std::nullopt; return VoicePos{gsl::narrow_cast(offset), frac, &BufferList.front()}; } if(BufferFmt->mCallback) return std::nullopt; const auto iter = std::ranges::find_if(BufferList, [&offset](al::BufferQueueItem const &item) { if(item.mSampleLen > offset) return true; offset -= item.mSampleLen; return false; }); if(iter != BufferList.end()) { /* Offset is in this buffer */ return VoicePos{gsl::narrow_cast(offset), frac, &*iter}; } /* Offset is out of range of the queue */ return std::nullopt; } void InitVoice(Voice *const voice, gsl::not_null const source, al::BufferQueueItem const *const BufferList, gsl::not_null const context, gsl::not_null const device) { voice->mLoopBuffer.store(source->mLooping ? &source->mQueue.front() : nullptr, std::memory_order_relaxed); auto const *const buffer = BufferList->mBuffer.get(); voice->mFrequency = buffer->mSampleRate; if(buffer->mChannels == FmtStereo && source->mStereoMode == SourceStereo::Enhanced) voice->mFmtChannels = FmtSuperStereo; else voice->mFmtChannels = buffer->mChannels; 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->mSourceType == AL_STATIC) voice->mFlags.set(VoiceIsStatic); voice->mNumCallbackBlocks = 0; voice->mCallbackBlockOffset = 0; voice->prepare(device); source->mPropsDirty = false; UpdateSourceProps(source, voice, context); voice->mSourceID.store(source->mId, std::memory_order_release); } auto GetVoiceChanger(gsl::not_null ctx) -> VoiceChange* { 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(gsl::not_null ctx, VoiceChange *tail) { auto const device = al::get_not_null(ctx->mALDevice); auto *oldhead = ctx->mCurrentVoiceChange.load(std::memory_order_acquire); while(auto *next = oldhead->mNext.load(std::memory_order_relaxed)) oldhead = next; oldhead->mNext.store(tail, std::memory_order_release); const auto 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. */ auto *cur = ctx->mCurrentVoiceChange.load(std::memory_order_acquire); while(auto *next = cur->mNext.load(std::memory_order_acquire)) { cur = next; if(auto *voice = cur->mVoice) voice->mSourceID.store(0, std::memory_order_relaxed); } ctx->mCurrentVoiceChange.store(cur, std::memory_order_release); } } } auto SetVoiceOffset(Voice *const oldvoice, const VoicePos &vpos, gsl::not_null const source, gsl::not_null const context, gsl::not_null const device) -> bool { /* First, get a free voice to start at the new offset. */ auto voicelist = context->getVoicesSpan(); Voice *newvoice{}; auto vidx = 0_u32; for(Voice *voice : voicelist) { if(voice->mPlayState.load(std::memory_order_acquire) == Voice::Stopped && voice->mSourceID.load(std::memory_order_relaxed) == 0_u32 && 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->mVoiceIdx = 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); auto *vchg = GetVoiceChanger(context); vchg->mOldVoice = oldvoice; vchg->mVoice = newvoice; vchg->mSourceID = source->mId; 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. */ auto IsPlayingOrPaused(gsl::not_null const source) noexcept -> bool { return source->mState == AL_PLAYING || source->mState == AL_PAUSED; } /** * Returns an updated source state using the matching voice's status (or lack * thereof). */ auto GetSourceState(gsl::not_null const source, Voice const *const voice) -> ALenum { if(!voice && source->mState == AL_PLAYING) source->mState = AL_STOPPED; return source->mState; } auto EnsureSources(gsl::not_null const context, usize const needed) -> bool { auto count = std::accumulate(context->mSourceList.cbegin(), context->mSourceList.cend(), 0_uz, [](usize const cur, const SourceSubList &sublist) noexcept -> usize { return cur + gsl::narrow_cast(std::popcount(sublist.mFreeMask)); }); try { while(needed > count) { if(context->mSourceList.size() >= 1<<25) [[unlikely]] return false; auto sublist = SourceSubList{}; sublist.mFreeMask = ~0_u64; sublist.mSources = SubListAllocator{}.allocate(1); context->mSourceList.emplace_back(std::move(sublist)); count += std::tuple_size_v; } } catch(...) { return false; } return true; } [[nodiscard]] auto AllocSource(gsl::not_null const context) noexcept -> gsl::not_null { auto const sublist = std::ranges::find_if(context->mSourceList, &SourceSubList::mFreeMask); auto const lidx = gsl::narrow_cast(std::distance(context->mSourceList.begin(), sublist)); auto const slidx = gsl::narrow_cast(std::countr_zero(sublist->mFreeMask)); ASSUME(slidx < 64); auto source = gsl::make_not_null(std::construct_at( std::to_address(std::next(sublist->mSources->begin(), slidx)))); #if ALSOFT_EAX source->eaxInitialize(context); #endif // ALSOFT_EAX /* Add 1 to avoid source ID 0. */ source->mId = ((lidx<<6) | slidx) + 1; context->mNumSources += 1; sublist->mFreeMask &= ~(1_u64 << slidx); return source; } void FreeSource(gsl::not_null const context, gsl::not_null const source) { context->mSourceNames.erase(source->mId); auto const id = source->mId - 1; auto const lidx = usize{id >> 6}; auto const slidx = id & 0x3f; if(auto *const voice = GetSourceVoice(source, context)) { auto *const vchg = GetVoiceChanger(context); voice->mPendingChange.store(true, std::memory_order_relaxed); vchg->mVoice = voice; vchg->mSourceID = source->mId; vchg->mState = VChangeState::Stop; SendVoiceChanges(context, vchg); } std::destroy_at(std::to_address(source)); context->mSourceList[lidx].mFreeMask |= 1_u64 << slidx; context->mNumSources--; } [[nodiscard]] auto LookupSource(std::nothrow_t, gsl::not_null const context, u32 const id) noexcept -> al::Source* { const auto lidx = (id-1) >> 6; const auto slidx = (id-1) & 0x3f; if(lidx >= context->mSourceList.size()) [[unlikely]] return nullptr; auto &sublist = context->mSourceList[lidx]; if(sublist.mFreeMask & (1_u64 << slidx)) [[unlikely]] return nullptr; return std::to_address(sublist.mSources->begin() + slidx); } [[nodiscard]] auto LookupSource(gsl::not_null const context, u32 const id) -> gsl::not_null { if(auto *source = LookupSource(std::nothrow, context, id)) [[likely]] return gsl::make_not_null(source); context->throw_error(AL_INVALID_NAME, "Invalid source ID {}", id); } [[nodiscard]] auto LookupBuffer(std::nothrow_t, gsl::not_null const device, std::unsigned_integral auto const id) noexcept -> al::Buffer* { const auto lidx = (id-1) >> 6; const auto slidx = (id-1) & 0x3f; if(lidx >= device->BufferList.size()) [[unlikely]] return nullptr; auto &sublist = device->BufferList[gsl::narrow_cast(lidx)]; if(sublist.mFreeMask & (1_u64 << slidx)) [[unlikely]] return nullptr; return std::to_address(std::next(sublist.mBuffers->begin(), gsl::narrow_cast(slidx))); } [[nodiscard]] auto LookupBuffer(gsl::not_null const context, std::unsigned_integral auto const id) -> gsl::not_null { if(auto *const buffer = LookupBuffer(std::nothrow, al::get_not_null(context->mALDevice), id)) [[likely]] return gsl::make_not_null(buffer); context->throw_error(AL_INVALID_NAME, "Invalid buffer ID {}", id); } [[nodiscard]] auto LookupFilter(std::nothrow_t, gsl::not_null const device, std::unsigned_integral auto const id) noexcept -> al::Filter* { const auto lidx = (id-1) >> 6; const auto slidx = (id-1) & 0x3f; if(lidx >= device->FilterList.size()) [[unlikely]] return nullptr; auto &sublist = device->FilterList[gsl::narrow_cast(lidx)]; if(sublist.mFreeMask & (1_u64 << slidx)) [[unlikely]] return nullptr; return std::to_address(std::next(sublist.mFilters->begin(), gsl::narrow_cast(slidx))); } [[nodiscard]] auto LookupFilter(gsl::not_null const context, std::unsigned_integral auto const id) -> gsl::not_null { if(auto *filter = LookupFilter(std::nothrow, al::get_not_null(context->mALDevice), id)) [[likely]] return gsl::make_not_null(filter); context->throw_error(AL_INVALID_NAME, "Invalid filter ID {}", id); } [[nodiscard]] auto LookupEffectSlot(std::nothrow_t, gsl::not_null const context, std::unsigned_integral auto const id) noexcept -> al::EffectSlot* { const auto lidx = (id-1) >> 6; const auto slidx = (id-1) & 0x3f; if(lidx >= context->mEffectSlotList.size()) [[unlikely]] return nullptr; auto &sublist = context->mEffectSlotList[gsl::narrow_cast(lidx)]; if(sublist.mFreeMask & (1_u64 << slidx)) [[unlikely]] return nullptr; return std::to_address(sublist.mEffectSlots->begin() + gsl::narrow_cast(slidx)); } [[nodiscard]] auto LookupEffectSlot(gsl::not_null const context, std::unsigned_integral auto const id) -> gsl::not_null { if(auto *const slot = LookupEffectSlot(std::nothrow, context, id)) [[likely]] return gsl::make_not_null(slot); context->throw_error(AL_INVALID_NAME, "Invalid effect slot ID {}", id); } auto StereoModeFromEnum(std::signed_integral auto const mode) noexcept -> std::optional { switch(mode) { case AL_NORMAL_SOFT: return SourceStereo::Normal; case AL_SUPER_STEREO_SOFT: return SourceStereo::Enhanced; } return std::nullopt; } auto EnumFromStereoMode(SourceStereo const mode) -> ALenum { switch(mode) { case SourceStereo::Normal: return AL_NORMAL_SOFT; case SourceStereo::Enhanced: return AL_SUPER_STEREO_SOFT; } throw std::runtime_error{al::format("Invalid SourceStereo: {:#x}", al::to_underlying(mode))}; } auto SpatializeModeFromEnum(std::signed_integral auto const 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; } auto EnumFromSpatializeMode(SpatializeMode const mode) -> ALenum { 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{al::format("Invalid SpatializeMode: {}", int{al::to_underlying(mode)})}; } auto DirectModeFromEnum(std::signed_integral auto const 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; } auto EnumFromDirectMode(DirectMode const mode) -> ALenum { 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{al::format("Invalid DirectMode: {}", int{al::to_underlying(mode)})}; } auto DistanceModelFromALenum(std::signed_integral auto const 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; } auto ALenumFromDistanceModel(DistanceModel const 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{al::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, }; [[nodiscard]] constexpr auto IntValsByProp(ALenum const prop) -> u32 { switch(SourceProp{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; [[fallthrough]]; 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; } [[nodiscard]] constexpr auto Int64ValsByProp(ALenum const prop) -> u32 { switch(SourceProp{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; [[fallthrough]]; 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; } [[nodiscard]] constexpr auto FloatValsByProp(ALenum const prop) -> u32 { switch(SourceProp{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; [[fallthrough]]; 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; } [[nodiscard]] constexpr auto DoubleValsByProp(ALenum const prop) -> u32 { switch(SourceProp{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; [[fallthrough]]; 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(gsl::not_null const source, gsl::not_null const context) { if(!context->mDeferUpdates) { if(auto *const voice = GetSourceVoice(source, context)) { UpdateSourceProps(source, voice, context); return; } } source->mPropsDirty = true; } #if ALSOFT_EAX void CommitAndUpdateSourceProps(gsl::not_null const source, gsl::not_null const context) { if(!context->mDeferUpdates) { if(context->hasEax()) source->eaxCommit(); if(auto *const voice = GetSourceVoice(source, context)) { UpdateSourceProps(source, voice, context); return; } } source->mPropsDirty = true; } #else void CommitAndUpdateSourceProps(gsl::not_null const source, gsl::not_null const 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 auto GetCheckers(gsl::not_null const context, SourceProp const prop, std::span const values) { return PairStruct{ [=](usize const 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 const passed) -> void { if(passed) return; context->throw_error(AL_INVALID_VALUE, "Value out of range"); } }; } template NOINLINE void SetProperty(const gsl::not_null Source, gsl::not_null const Context, SourceProp const prop, std::span const values) { static constexpr auto is_finite = [](U&& v) -> bool { return std::isfinite(gsl::narrow_cast(std::forward(v))); }; auto [CheckSize, CheckValue] = GetCheckers(Context, prop, values); auto const device = al::get_not_null(Context->mALDevice); 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} && is_finite(values[0])); else CheckValue(values[0] >= T{0}); Source->mPitch = gsl::narrow_cast(values[0]); return UpdateSourceProps(Source, Context); case AL_CONE_INNER_ANGLE: CheckSize(1); CheckValue(values[0] >= T{0} && values[0] <= T{360}); Source->mInnerAngle = gsl::narrow_cast(values[0]); return CommitAndUpdateSourceProps(Source, Context); case AL_CONE_OUTER_ANGLE: CheckSize(1); CheckValue(values[0] >= T{0} && values[0] <= T{360}); Source->mOuterAngle = gsl::narrow_cast(values[0]); return CommitAndUpdateSourceProps(Source, Context); case AL_GAIN: CheckSize(1); if constexpr(std::is_floating_point_v) CheckValue(values[0] >= T{0} && is_finite(values[0])); else CheckValue(values[0] >= T{0}); Source->mGain = gsl::narrow_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} && is_finite(values[0])); else CheckValue(values[0] >= T{0}); Source->mMaxDistance = gsl::narrow_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} && is_finite(values[0])); else CheckValue(values[0] >= T{0}); Source->mRolloffFactor = gsl::narrow_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} && is_finite(values[0])); else CheckValue(values[0] >= T{0}); Source->mRefDistance = gsl::narrow_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} && is_finite(values[0])); else CheckValue(values[0] >= T{0}); Source->mMinGain = gsl::narrow_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} && is_finite(values[0])); else CheckValue(values[0] >= T{0}); Source->mMaxGain = gsl::narrow_cast(values[0]); return UpdateSourceProps(Source, Context); case AL_CONE_OUTER_GAIN: CheckSize(1); CheckValue(values[0] >= T{0} && values[0] <= T{1}); Source->mOuterGain = gsl::narrow_cast(values[0]); return UpdateSourceProps(Source, Context); case AL_CONE_OUTER_GAINHF: CheckSize(1); CheckValue(values[0] >= T{0} && values[0] <= T{1}); Source->mOuterGainHF = gsl::narrow_cast(values[0]); return UpdateSourceProps(Source, Context); case AL_AIR_ABSORPTION_FACTOR: CheckSize(1); CheckValue(values[0] >= T{0} && values[0] <= T{10}); Source->mAirAbsorptionFactor = gsl::narrow_cast(values[0]); return UpdateSourceProps(Source, Context); case AL_ROOM_ROLLOFF_FACTOR: CheckSize(1); CheckValue(values[0] >= T{0} && values[0] <= T{1}); Source->mRoomRolloffFactor = gsl::narrow_cast(values[0]); return UpdateSourceProps(Source, Context); case AL_DOPPLER_FACTOR: CheckSize(1); CheckValue(values[0] >= T{0} && values[0] <= T{1}); Source->mDopplerFactor = gsl::narrow_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->mHeadRelative = 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->mLooping = values[0] != AL_FALSE; if(Voice *voice{GetSourceVoice(Source, Context)}) { if(Source->mLooping) 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 auto 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->mId); if(values[0]) { auto buflock = std::lock_guard{device->BufferLock}; auto buffer = LookupBuffer(Context, as_unsigned(values[0])); if(buffer->mMappedAccess && !(buffer->mMappedAccess&AL_MAP_PERSISTENT_BIT_SOFT)) Context->throw_error(AL_INVALID_OPERATION, "Setting non-persistently mapped buffer {}", buffer->mId); if(buffer->mCallback && buffer->mRef.load(std::memory_order_relaxed) != 0) Context->throw_error(AL_INVALID_OPERATION, "Setting already-set callback buffer {}", buffer->mId); /* Add the selected buffer to a one-item queue */ auto newlist = std::deque{}; auto &item = newlist.emplace_back(); item.mBuffer = buffer->newReference(); item.mCallback = buffer->mCallback; item.mUserData = buffer->mUserData; item.mBlockAlign = buffer->mBlockAlign; item.mSampleLen = buffer->mSampleLen; item.mLoopStart = buffer->mLoopStart; item.mLoopEnd = buffer->mLoopEnd; item.mSamples = buffer->mData; /* Source is now Static */ Source->mSourceType = AL_STATIC; Source->mQueue = std::move(newlist); } else { /* Source is now Undetermined */ Source->mSourceType = AL_UNDETERMINED; std::deque{}.swap(Source->mQueue); } 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(auto *voice = GetSourceVoice(Source, Context)) { auto vpos = GetSampleOffset(Source->mQueue, prop, gsl::narrow_cast(values[0])); if(!vpos) Context->throw_error(AL_INVALID_VALUE, "Invalid offset"); if(SetVoiceOffset(voice, *vpos, Source, Context, device)) return; } Source->mOffsetType = prop; Source->mOffset = gsl::narrow_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} && is_finite(values[0])); else CheckValue(values[0] >= T{0}); Source->mRadius = gsl::narrow_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->mEnhWidth = gsl::narrow_cast(values[0]); return UpdateSourceProps(Source, Context); case AL_PANNING_ENABLED_SOFT: CheckSize(1); 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 = gsl::narrow_cast(values[0]); return UpdateSourceProps(Source, Context); case AL_STEREO_ANGLES: CheckSize(2); if constexpr(std::is_floating_point_v) CheckValue(std::ranges::all_of(values, is_finite)); Source->mStereoPan[0] = gsl::narrow_cast(values[0]); Source->mStereoPan[1] = gsl::narrow_cast(values[1]); return UpdateSourceProps(Source, Context); case AL_POSITION: CheckSize(3); if constexpr(std::is_floating_point_v) CheckValue(std::ranges::all_of(values, is_finite)); Source->mPosition[0] = gsl::narrow_cast(values[0]); Source->mPosition[1] = gsl::narrow_cast(values[1]); Source->mPosition[2] = gsl::narrow_cast(values[2]); return CommitAndUpdateSourceProps(Source, Context); case AL_VELOCITY: CheckSize(3); if constexpr(std::is_floating_point_v) CheckValue(std::ranges::all_of(values, is_finite)); Source->mVelocity[0] = gsl::narrow_cast(values[0]); Source->mVelocity[1] = gsl::narrow_cast(values[1]); Source->mVelocity[2] = gsl::narrow_cast(values[2]); return CommitAndUpdateSourceProps(Source, Context); case AL_DIRECTION: CheckSize(3); if constexpr(std::is_floating_point_v) CheckValue(std::ranges::all_of(values, is_finite)); Source->mDirection[0] = gsl::narrow_cast(values[0]); Source->mDirection[1] = gsl::narrow_cast(values[1]); Source->mDirection[2] = gsl::narrow_cast(values[2]); return CommitAndUpdateSourceProps(Source, Context); case AL_ORIENTATION: CheckSize(6); if constexpr(std::is_floating_point_v) CheckValue(std::ranges::all_of(values, is_finite)); Source->mOrientAt[0] = gsl::narrow_cast(values[0]); Source->mOrientAt[1] = gsl::narrow_cast(values[1]); Source->mOrientAt[2] = gsl::narrow_cast(values[2]); Source->mOrientUp[0] = gsl::narrow_cast(values[3]); Source->mOrientUp[1] = gsl::narrow_cast(values[4]); Source->mOrientUp[2] = gsl::narrow_cast(values[5]); return UpdateSourceProps(Source, Context); case AL_DIRECT_FILTER: if constexpr(std::is_integral_v) { CheckSize(1); const auto filterid = as_unsigned(values[0]); if(values[0]) { const auto filterlock = std::lock_guard{device->FilterLock}; const auto filter = LookupFilter(Context, filterid); Source->mDirect.mGain = filter->mGain; Source->mDirect.mGainHF = filter->mGainHF; Source->mDirect.mHFReference = filter->mHFReference; Source->mDirect.mGainLF = filter->mGainLF; Source->mDirect.mLFReference = filter->mLFReference; } else { Source->mDirect.mGain = 1.0f; Source->mDirect.mGainHF = 1.0f; Source->mDirect.mHFReference = LowPassFreqRef; Source->mDirect.mGainLF = 1.0f; Source->mDirect.mLFReference = 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->mDryGainHFAuto = 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->mWetGainAuto = 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->mWetGainHFAuto = 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] <= al::to_underlying(Resampler::Max)); Source->mResampler = gsl::narrow_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->mId); 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 = as_unsigned(values[0]); const auto sendidx = as_unsigned(values[1]); const auto filterid = as_unsigned(values[2]); const auto slotlock = std::unique_lock{Context->mEffectSlotLock}; auto slot = al::intrusive_ptr{}; if(slotid) slot = LookupEffectSlot(Context, slotid)->newReference(); if(sendidx >= device->NumAuxSends) Context->throw_error(AL_INVALID_VALUE, "Invalid send {}", sendidx); auto &send = Source->mSend[gsl::narrow_cast(sendidx)]; if(filterid) { const auto filterlock = std::lock_guard{device->FilterLock}; const auto filter = LookupFilter(Context, filterid); send.mGain = filter->mGain; send.mGainHF = filter->mGainHF; send.mHFReference = filter->mHFReference; send.mGainLF = filter->mGainLF; send.mLFReference = filter->mLFReference; } else { /* Disable filter */ send.mGain = 1.0f; send.mGainHF = 1.0f; send.mHFReference = LowPassFreqRef; send.mGainLF = 1.0f; send.mLFReference = 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.mSlot && slot != send.mSlot && IsPlayingOrPaused(Source)) { send.mSlot = std::move(slot); if(auto *const voice = GetSourceVoice(Source, Context)) UpdateSourceProps(Source, voice, Context); else Source->mPropsDirty = true; } else { send.mSlot = std::move(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(gsl::not_null const context, SourceProp const prop, std::span const values) { return [=](usize const 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(const gsl::not_null Source, gsl::not_null const Context, SourceProp const prop, std::span const values) { using std::chrono::duration_cast; auto CheckSize = GetSizeChecker(Context, prop, values); auto const device = al::get_not_null(Context->mALDevice); switch(prop) { case AL_GAIN: CheckSize(1); values[0] = gsl::narrow_cast(Source->mGain); return; case AL_PITCH: CheckSize(1); values[0] = gsl::narrow_cast(Source->mPitch); return; case AL_MAX_DISTANCE: CheckSize(1); values[0] = gsl::narrow_cast(Source->mMaxDistance); return; case AL_ROLLOFF_FACTOR: CheckSize(1); values[0] = gsl::narrow_cast(Source->mRolloffFactor); return; case AL_REFERENCE_DISTANCE: CheckSize(1); values[0] = gsl::narrow_cast(Source->mRefDistance); return; case AL_CONE_INNER_ANGLE: CheckSize(1); values[0] = gsl::narrow_cast(Source->mInnerAngle); return; case AL_CONE_OUTER_ANGLE: CheckSize(1); values[0] = gsl::narrow_cast(Source->mOuterAngle); return; case AL_MIN_GAIN: CheckSize(1); values[0] = gsl::narrow_cast(Source->mMinGain); return; case AL_MAX_GAIN: CheckSize(1); values[0] = gsl::narrow_cast(Source->mMaxGain); return; case AL_CONE_OUTER_GAIN: CheckSize(1); values[0] = gsl::narrow_cast(Source->mOuterGain); 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] = gsl::narrow_cast(Source->mOuterGainHF); return; case AL_AIR_ABSORPTION_FACTOR: CheckSize(1); values[0] = gsl::narrow_cast(Source->mAirAbsorptionFactor); return; case AL_ROOM_ROLLOFF_FACTOR: CheckSize(1); values[0] = gsl::narrow_cast(Source->mRoomRolloffFactor); return; case AL_DOPPLER_FACTOR: CheckSize(1); values[0] = gsl::narrow_cast(Source->mDopplerFactor); 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] = Source->mRadius; } 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]; } else { CheckSize(1); values[0] = gsl::narrow_cast(Source->mRadius); } } return; case AL_SUPER_STEREO_WIDTH_SOFT: CheckSize(1); values[0] = gsl::narrow_cast(Source->mEnhWidth); 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] = gsl::narrow_cast(Source->mPan); return; case AL_STEREO_ANGLES: if constexpr(std::is_floating_point_v) { CheckSize(2); std::ranges::copy(Source->mStereoPan, values.begin()); 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. */ auto srcclock = nanoseconds{}; values[0] = GetSourceSampleOffset(Source, Context, &srcclock); const auto clocktime = std::invoke([device]() -> ClockLatency { auto statelock = std::lock_guard{device->StateLock}; return 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); auto srcclock = nanoseconds{}; 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. */ auto srcclock = nanoseconds{}; values[0] = GetSourceSecOffset(Source, Context, &srcclock); const auto clocktime = std::invoke([device]() -> ClockLatency { auto statelock = std::lock_guard{device->StateLock}; return 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); auto srcclock = nanoseconds{}; values[0] = GetSourceSecOffset(Source, Context, &srcclock); values[1] = duration_cast(srcclock).count(); return; } break; case AL_POSITION: CheckSize(3); values[0] = gsl::narrow_cast(Source->mPosition[0]); values[1] = gsl::narrow_cast(Source->mPosition[1]); values[2] = gsl::narrow_cast(Source->mPosition[2]); return; case AL_VELOCITY: CheckSize(3); values[0] = gsl::narrow_cast(Source->mVelocity[0]); values[1] = gsl::narrow_cast(Source->mVelocity[1]); values[2] = gsl::narrow_cast(Source->mVelocity[2]); return; case AL_DIRECTION: CheckSize(3); values[0] = gsl::narrow_cast(Source->mDirection[0]); values[1] = gsl::narrow_cast(Source->mDirection[1]); values[2] = gsl::narrow_cast(Source->mDirection[2]); return; case AL_ORIENTATION: CheckSize(6); values[0] = gsl::narrow_cast(Source->mOrientAt[0]); values[1] = gsl::narrow_cast(Source->mOrientAt[1]); values[2] = gsl::narrow_cast(Source->mOrientAt[2]); values[3] = gsl::narrow_cast(Source->mOrientUp[0]); values[4] = gsl::narrow_cast(Source->mOrientUp[1]); values[5] = gsl::narrow_cast(Source->mOrientUp[2]); return; case AL_SOURCE_RELATIVE: if constexpr(std::is_integral_v) { CheckSize(1); values[0] = Source->mHeadRelative; return; } break; case AL_LOOPING: if constexpr(std::is_integral_v) { CheckSize(1); values[0] = Source->mLooping; return; } break; case AL_BUFFER: if constexpr(std::is_integral_v) { CheckSize(1); al::BufferQueueItem const *buflist{}; /* 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->mSourceType == AL_STATIC || Source->mState == AL_INITIAL) { if(!Source->mQueue.empty()) buflist = &Source->mQueue.front(); } else if(auto const *const voice = GetSourceVoice(Source, Context)) { auto *Current = voice->mCurrentBuffer.load(std::memory_order_relaxed); const auto iter = std::ranges::find(Source->mQueue, Current, [](al::BufferQueueItem const &arg) { return &arg; }); buflist = (iter != Source->mQueue.end()) ? &*iter : nullptr; } auto *buffer = buflist ? buflist->mBuffer.get() : nullptr; values[0] = buffer ? static_cast(buffer->mId) : 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] = gsl::narrow_cast(Source->mQueue.size()); return; } break; case AL_BUFFERS_PROCESSED: if constexpr(std::is_integral_v) { CheckSize(1); auto played = 0_i32; /* Buffers on a looping source are in a perpetual state of PENDING, * so don't report any as PROCESSED. */ if(!Source->mLooping && Source->mSourceType == AL_STREAMING && Source->mState != AL_INITIAL) { const auto Current = std::invoke([Source,Context]() -> const VoiceBufferItem* { if(auto *voice = GetSourceVoice(Source, Context)) return voice->mCurrentBuffer.load(std::memory_order_relaxed); return nullptr; }); auto const qiter = std::ranges::find(Source->mQueue, Current, [](al::BufferQueueItem const &item) { return &item; }); played = gsl::narrow_cast(std::distance(Source->mQueue.begin(), qiter)); } values[0] = played; return; } break; case AL_SOURCE_TYPE: if constexpr(std::is_integral_v) { CheckSize(1); values[0] = Source->mSourceType; return; } break; case AL_DIRECT_FILTER_GAINHF_AUTO: if constexpr(std::is_integral_v) { CheckSize(1); values[0] = Source->mDryGainHFAuto; return; } break; case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO: if constexpr(std::is_integral_v) { CheckSize(1); values[0] = Source->mWetGainAuto; return; } break; case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO: if constexpr(std::is_integral_v) { CheckSize(1); values[0] = Source->mWetGainHFAuto; 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] = T{al::to_underlying(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))); } using source_store_single = std::array,1>; using source_store_vector = std::vector>; using source_store_variant = std::variant; constexpr auto get_srchandles(gsl::not_null const context, source_store_variant &source_store, std::span const sids) -> std::span> { if(sids.size() == 1) { auto source = std::array{LookupSource(context, sids[0])}; return source_store.emplace(source); } auto &sources = source_store.emplace(); sources.reserve(sids.size()); std::ranges::transform(sids, std::back_inserter(sources), [context](u32 const sid) { return LookupSource(context, sid); }); return std::span{sources}; } void StartSources(gsl::not_null const context, std::span const> const srchandles, nanoseconds const start_time=nanoseconds::min()) { auto const device = al::get_not_null(context->mALDevice); /* 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(const gsl::not_null source : srchandles) { /* TODO: Send state change event? */ source->mOffset = 0.0; source->mOffsetType = AL_NONE; source->mState = AL_STOPPED; } return; } } /* Count the number of reusable voices. */ auto voicelist = context->getVoicesSpan(); auto free_voices = 0_uz; std::ignore = std::ranges::find_if(voicelist, [srchandles,&free_voices](const Voice *voice) { 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); return (free_voices == srchandles.size()); }); if(srchandles.size() != free_voices) [[unlikely]] { const auto 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(); auto vidx = 0_u32; auto tail = LPVoiceChange{}; auto cur = LPVoiceChange{}; std::ranges::for_each(srchandles, [&](gsl::not_null const source) { /* Check that there is a queue containing at least one valid, non zero * length buffer. */ const auto BufferList = std::ranges::find_if(source->mQueue, [](al::BufferQueueItem &entry) { return entry.mSampleLen != 0 || entry.mCallback != nullptr; }); /* 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->mOffset = 0.0; source->mOffsetType = AL_NONE; source->mState = AL_STOPPED; return; } if(!cur) cur = tail = GetVoiceChanger(context); else { cur->mNext.store(GetVoiceChanger(context), std::memory_order_relaxed); cur = cur->mNext.load(std::memory_order_relaxed); } auto *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->mId; cur->mState = VChangeState::Play; source->mState = AL_PLAYING; #if ALSOFT_EAX if(context->hasEax()) source->eaxCommit(); #endif // ALSOFT_EAX return; 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: Expects(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) { if(auto *const v = *voiceiter; 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; } } Ensures(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 auto offsettype = source->mOffsetType) { auto const offset = source->mOffset; source->mOffsetType = AL_NONE; source->mOffset = 0.0; if(auto const 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, &*BufferList, context, device); source->mVoiceIdx = vidx; source->mState = AL_PLAYING; cur->mVoice = voice; cur->mSourceID = source->mId; cur->mState = VChangeState::Play; }); if(tail) [[likely]] SendVoiceChanges(context, tail); } void alGenSources(gsl::not_null const context, ALsizei const n, ALuint *const sources) noexcept try { if(n < 0) context->throw_error(AL_INVALID_VALUE, "Generating {} sources", n); if(n <= 0) [[unlikely]] return; auto const srclock = std::unique_lock{context->mSourceLock}; auto const device = al::get_not_null(context->mALDevice); const auto sids = std::views::counted(sources, 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::ranges::generate(sids, [context]{ return AllocSource(context)->mId; }); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } void alDeleteSources(gsl::not_null 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; auto srclock = std::lock_guard{context->mSourceLock}; /* Check that all Sources are valid */ const auto sids = std::views::counted(sources, n); std::ranges::for_each(sids, [context](const ALuint sid) { std::ignore = LookupSource(context, sid); }); /* All good. Delete source IDs. */ std::ranges::for_each(sids, [context](const ALuint sid) -> void { if(auto *src = LookupSource(std::nothrow, context, sid)) FreeSource(context, gsl::make_not_null(src)); }); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } auto alIsSource(gsl::not_null context, ALuint source) noexcept -> ALboolean { auto srclock = std::lock_guard{context->mSourceLock}; if(LookupSource(std::nothrow, context, source) != nullptr) return AL_TRUE; return AL_FALSE; } void alSourcef(gsl::not_null context, ALuint source, ALenum param, ALfloat value) noexcept try { auto proplock = std::lock_guard{context->mPropLock}; auto srclock = std::lock_guard{context->mSourceLock}; SetProperty(LookupSource(context, source), context, SourceProp{param}, {&value, 1u}); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } void alSource3f(gsl::not_null context, ALuint source, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3) noexcept try { auto proplock = std::lock_guard{context->mPropLock}; auto srclock = std::lock_guard{context->mSourceLock}; const auto fvals = std::array{value1, value2, value3}; SetProperty(LookupSource(context, source), context, SourceProp{param}, fvals); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } void alSourcefv(gsl::not_null context, ALuint source, ALenum param, const ALfloat *values) noexcept try { auto proplock = std::lock_guard{context->mPropLock}; auto srclock = std::lock_guard{context->mSourceLock}; auto const Source = LookupSource(context, source); if(!values) context->throw_error(AL_INVALID_VALUE, "NULL pointer"); const auto count = FloatValsByProp(param); SetProperty(Source, context, SourceProp{param}, std::span{values, count}); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } void alSourcedSOFT(gsl::not_null context, ALuint source, ALenum param, ALdouble value) noexcept try { auto proplock = std::lock_guard{context->mPropLock}; auto srclock = std::lock_guard{context->mSourceLock}; SetProperty(LookupSource(context, source), context, SourceProp{param}, {&value, 1}); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } void alSource3dSOFT(gsl::not_null context, ALuint source, ALenum param, ALdouble value1, ALdouble value2, ALdouble value3) noexcept try { auto proplock = std::lock_guard{context->mPropLock}; auto srclock = std::lock_guard{context->mSourceLock}; const auto dvals = std::array{value1, value2, value3}; SetProperty(LookupSource(context, source), context, SourceProp{param}, dvals); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } void alSourcedvSOFT(gsl::not_null context, ALuint source, ALenum param, const ALdouble *values) noexcept try { auto proplock = std::lock_guard{context->mPropLock}; auto srclock = std::lock_guard{context->mSourceLock}; auto const Source = LookupSource(context, source); if(!values) context->throw_error(AL_INVALID_VALUE, "NULL pointer"); const auto count = DoubleValsByProp(param); SetProperty(Source, context, SourceProp{param}, std::span{values, count}); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } void alSourcei(gsl::not_null context, ALuint source, ALenum param, ALint value) noexcept try { auto proplock = std::lock_guard{context->mPropLock}; auto srclock = std::lock_guard{context->mSourceLock}; SetProperty(LookupSource(context, source), context, SourceProp{param}, {&value, 1u}); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } void alSource3i(gsl::not_null context, ALuint source, ALenum param, ALint value1, ALint value2, ALint value3) noexcept try { auto proplock = std::lock_guard{context->mPropLock}; auto srclock = std::lock_guard{context->mSourceLock}; const auto ivals = std::array{value1, value2, value3}; SetProperty(LookupSource(context, source), context, SourceProp{param}, ivals); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } void alSourceiv(gsl::not_null context, ALuint source, ALenum param, const ALint *values) noexcept try { auto proplock = std::lock_guard{context->mPropLock}; auto srclock = std::lock_guard{context->mSourceLock}; auto const Source = LookupSource(context, source); if(!values) context->throw_error(AL_INVALID_VALUE, "NULL pointer"); const auto count = IntValsByProp(param); SetProperty(Source, context, SourceProp{param}, std::span{values, count}); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } void alSourcei64SOFT(gsl::not_null context, ALuint source, ALenum param, ALint64SOFT value) noexcept try { auto proplock = std::lock_guard{context->mPropLock}; auto srclock = std::lock_guard{context->mSourceLock}; SetProperty(LookupSource(context, source), context, SourceProp{param}, {&value, 1u}); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } void alSource3i64SOFT(gsl::not_null context, ALuint source, ALenum param, ALint64SOFT value1, ALint64SOFT value2, ALint64SOFT value3) noexcept try { auto proplock = std::lock_guard{context->mPropLock}; auto srclock = std::lock_guard{context->mSourceLock}; const auto i64vals = std::array{value1, value2, value3}; SetProperty(LookupSource(context, source), context, SourceProp{param}, i64vals); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } void alSourcei64vSOFT(gsl::not_null context, ALuint source, ALenum param, const ALint64SOFT *values) noexcept try { auto proplock = std::lock_guard{context->mPropLock}; auto srclock = std::lock_guard{context->mSourceLock}; auto const Source = LookupSource(context, source); if(!values) context->throw_error(AL_INVALID_VALUE, "NULL pointer"); const auto count = Int64ValsByProp(param); SetProperty(Source, context, SourceProp{param}, std::span{values, count}); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } void alGetSourcef(gsl::not_null context, ALuint source, ALenum param, ALfloat *value) noexcept try { auto srclock = std::lock_guard{context->mSourceLock}; auto const Source = LookupSource(context, source); if(!value) context->throw_error(AL_INVALID_VALUE, "NULL pointer"); GetProperty(Source, context, SourceProp{param}, std::span{value, 1u}); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } void alGetSource3f(gsl::not_null context, ALuint source, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3) noexcept try { auto srclock = std::lock_guard{context->mSourceLock}; auto const Source = LookupSource(context, source); if(!(value1 && value2 && value3)) context->throw_error(AL_INVALID_VALUE, "NULL pointer"); auto fvals = std::array{}; GetProperty(Source, context, SourceProp{param}, fvals); *value1 = fvals[0]; *value2 = fvals[1]; *value3 = fvals[2]; } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } void alGetSourcefv(gsl::not_null context, ALuint source, ALenum param, ALfloat *values) noexcept try { auto srclock = std::lock_guard{context->mSourceLock}; auto const Source = LookupSource(context, source); if(!values) context->throw_error(AL_INVALID_VALUE, "NULL pointer"); const auto count = FloatValsByProp(param); GetProperty(Source, context, SourceProp{param}, std::span{values, count}); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } void alGetSourcedSOFT(gsl::not_null context, ALuint source, ALenum param, ALdouble *value) noexcept try { auto srclock = std::lock_guard{context->mSourceLock}; auto const Source = LookupSource(context, source); if(!value) context->throw_error(AL_INVALID_VALUE, "NULL pointer"); GetProperty(Source, context, SourceProp{param}, std::span{value, 1u}); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } void alGetSource3dSOFT(gsl::not_null context, ALuint source, ALenum param, ALdouble *value1, ALdouble *value2, ALdouble *value3) noexcept try { auto srclock = std::lock_guard{context->mSourceLock}; auto const Source = LookupSource(context, source); if(!(value1 && value2 && value3)) context->throw_error(AL_INVALID_VALUE, "NULL pointer"); auto dvals = std::array{}; GetProperty(Source, context, SourceProp{param}, dvals); *value1 = dvals[0]; *value2 = dvals[1]; *value3 = dvals[2]; } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } void alGetSourcedvSOFT(gsl::not_null context, ALuint source, ALenum param, ALdouble *values) noexcept try { auto srclock = std::lock_guard{context->mSourceLock}; auto const Source = LookupSource(context, source); if(!values) context->throw_error(AL_INVALID_VALUE, "NULL pointer"); const auto count = DoubleValsByProp(param); GetProperty(Source, context, SourceProp{param}, std::span{values, count}); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } void alGetSourcei(gsl::not_null context, ALuint source, ALenum param, ALint *value) noexcept try { auto srclock = std::lock_guard{context->mSourceLock}; auto const Source = LookupSource(context, source); if(!value) context->throw_error(AL_INVALID_VALUE, "NULL pointer"); GetProperty(Source, context, SourceProp{param}, std::span{value, 1u}); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } void alGetSource3i(gsl::not_null context, ALuint source, ALenum param, ALint *value1, ALint *value2, ALint *value3) noexcept try { auto srclock = std::lock_guard{context->mSourceLock}; auto const Source = LookupSource(context, source); if(!(value1 && value2 && value3)) context->throw_error(AL_INVALID_VALUE, "NULL pointer"); auto ivals = std::array{}; GetProperty(Source, context, SourceProp{param}, ivals); *value1 = ivals[0]; *value2 = ivals[1]; *value3 = ivals[2]; } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } void alGetSourceiv(gsl::not_null context, ALuint source, ALenum param, ALint *values) noexcept try { auto srclock = std::lock_guard{context->mSourceLock}; auto const Source = LookupSource(context, source); if(!values) context->throw_error(AL_INVALID_VALUE, "NULL pointer"); const auto count = IntValsByProp(param); GetProperty(Source, context, SourceProp{param}, std::span{values, count}); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } void alGetSourcei64SOFT(gsl::not_null context, ALuint source, ALenum param, ALint64SOFT *value) noexcept try { auto srclock = std::lock_guard{context->mSourceLock}; auto const Source = LookupSource(context, source); if(!value) context->throw_error(AL_INVALID_VALUE, "NULL pointer"); GetProperty(Source, context, SourceProp{param}, std::span{value, 1u}); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } void alGetSource3i64SOFT(gsl::not_null context, ALuint source, ALenum param, ALint64SOFT *value1, ALint64SOFT *value2, ALint64SOFT *value3) noexcept try { auto srclock = std::lock_guard{context->mSourceLock}; auto const Source = LookupSource(context, source); if(!(value1 && value2 && value3)) context->throw_error(AL_INVALID_VALUE, "NULL pointer"); auto i64vals = std::array{}; GetProperty(Source, context, SourceProp{param}, i64vals); *value1 = i64vals[0]; *value2 = i64vals[1]; *value3 = i64vals[2]; } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } void alGetSourcei64vSOFT(gsl::not_null context, ALuint source, ALenum param, ALint64SOFT *values) noexcept try { auto srclock = std::lock_guard{context->mSourceLock}; auto const Source = LookupSource(context, source); if(!values) context->throw_error(AL_INVALID_VALUE, "NULL pointer"); const auto count = Int64ValsByProp(param); GetProperty(Source, context, SourceProp{param}, std::span{values, count}); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } void alSourcePlayv(gsl::not_null 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; const auto sids = std::views::counted(sources, n); auto source_store = source_store_variant{}; auto srclock = std::lock_guard{context->mSourceLock}; const auto srchandles = get_srchandles(context, source_store, sids); StartSources(context, srchandles); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } void alSourcePlay(gsl::not_null context, ALuint source) noexcept try { auto srclock = std::lock_guard{context->mSourceLock}; auto Source = LookupSource(context, source); StartSources(context, {&Source, 1}); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } void alSourcePlayAtTimevSOFT(gsl::not_null 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); const auto sids = std::views::counted(sources, n); auto source_store = source_store_variant{}; auto srclock = std::lock_guard{context->mSourceLock}; const auto srchandles = get_srchandles(context, source_store, sids); StartSources(context, srchandles, nanoseconds{start_time}); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } void alSourcePlayAtTimeSOFT(gsl::not_null context, ALuint source, ALint64SOFT start_time) noexcept try { if(start_time < 0) context->throw_error(AL_INVALID_VALUE, "Invalid time point {}", start_time); auto srclock = std::lock_guard{context->mSourceLock}; auto Source = LookupSource(context, source); StartSources(context, {&Source, 1}, nanoseconds{start_time}); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } void alSourcePausev(gsl::not_null 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; const auto sids = std::views::counted(sources, n); auto source_store = source_store_variant{}; auto srclock = std::lock_guard{context->mSourceLock}; const auto srchandles = get_srchandles(context, source_store, sids); /* Pausing has to be done in two steps. First, for each source that's * detected to be playing, change the voice (asynchronously) to * stopping/paused. */ auto tail = LPVoiceChange{}; auto cur = LPVoiceChange{}; std::ranges::for_each(srchandles, [context,&tail,&cur](gsl::not_null const source) { if(auto *const voice = GetSourceVoice(source, context); 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->mId; 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. */ std::ranges::for_each(srchandles, [context](gsl::not_null const source) { if(auto const *const voice = GetSourceVoice(source, context); GetSourceState(source, voice) == AL_PLAYING) source->mState = AL_PAUSED; }); } } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } void alSourcePause(gsl::not_null const context, ALuint const source) noexcept { alSourcePausev(context, 1, &source); } void alSourceStopv(gsl::not_null 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; const auto sids = std::views::counted(sources, n); auto source_store = source_store_variant{}; auto srclock = std::lock_guard{context->mSourceLock}; const auto srchandles = get_srchandles(context, source_store, sids); auto tail = LPVoiceChange{}; auto cur = LPVoiceChange{}; std::ranges::for_each(srchandles, [context,&tail,&cur](gsl::not_null const source) { if(auto *const 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->mId; cur->mState = VChangeState::Stop; source->mState = AL_STOPPED; } source->mOffset = 0.0; source->mOffsetType = AL_NONE; source->mVoiceIdx = InvalidVoiceIndex; }); if(tail) [[likely]] SendVoiceChanges(context, tail); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } void alSourceStop(gsl::not_null context, ALuint source) noexcept { alSourceStopv(context, 1, &source); } void alSourceRewindv(gsl::not_null 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; const auto sids = std::views::counted(sources, n); auto source_store = source_store_variant{}; auto srclock = std::lock_guard{context->mSourceLock}; const auto srchandles = get_srchandles(context, source_store, sids); auto tail = LPVoiceChange{}; auto cur = LPVoiceChange{}; std::ranges::for_each(srchandles, [context,&tail,&cur](gsl::not_null const source) { auto *const voice = GetSourceVoice(source, context); if(source->mState != 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->mId; cur->mState = VChangeState::Reset; source->mState = AL_INITIAL; } source->mOffset = 0.0; source->mOffsetType = AL_NONE; source->mVoiceIdx = InvalidVoiceIndex; }); if(tail) [[likely]] SendVoiceChanges(context, tail); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } void alSourceRewind(gsl::not_null context, ALuint source) noexcept { alSourceRewindv(context, 1, &source); } void alSourceQueueBuffers(gsl::not_null 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; auto srclock = std::lock_guard{context->mSourceLock}; auto const source = LookupSource(context, src); /* Can't queue on a Static Source */ if(source->mSourceType == AL_STATIC) context->throw_error(AL_INVALID_OPERATION, "Queueing onto static source {}", src); /* Check for a valid Buffer, for its frequency and format */ auto const device = al::get_not_null(context->mALDevice); auto BufferFmt = std::invoke([source]() -> al::Buffer* { const auto iter = std::ranges::find_if(source->mQueue, HasBuffer); if(iter != source->mQueue.end()) return iter->mBuffer.get(); return nullptr; }); auto buflock = std::unique_lock{device->BufferLock}; const auto bids = std::views::counted(buffers, nb); const auto NewListStart = std::ssize(source->mQueue); try { al::BufferQueueItem *BufferList{}; std::ranges::for_each(bids,[context,source,&BufferFmt,&BufferList](const ALuint bid) { auto *buffer = bid ? LookupBuffer(context, bid).get() : nullptr; if(buffer) { if(buffer->mSampleRate < 1) context->throw_error(AL_INVALID_OPERATION, "Queueing buffer {} with no format", buffer->mId); if(buffer->mCallback) context->throw_error(AL_INVALID_OPERATION, "Queueing callback buffer {}", buffer->mId); if(buffer->mMappedAccess != 0 && !(buffer->mMappedAccess&AL_MAP_PERSISTENT_BIT_SOFT)) context->throw_error(AL_INVALID_OPERATION, "Queueing non-persistently mapped buffer {}", buffer->mId); } 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->mBuffer = buffer->newReference(); BufferList->mBlockAlign = buffer->mBlockAlign; BufferList->mSampleLen = buffer->mSampleLen; BufferList->mLoopEnd = buffer->mSampleLen; BufferList->mSamples = buffer->mData; if(BufferFmt == nullptr) BufferFmt = buffer; else { auto fmt_mismatch = false; 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 release the buffers we had. */ source->mQueue.resize(gsl::narrow_cast(NewListStart)); throw; } /* All buffers good. */ buflock.unlock(); /* Source is now streaming */ source->mSourceType = AL_STREAMING; if(NewListStart > 0) { auto iter = std::next(source->mQueue.begin(), NewListStart); (iter-1)->mNext.store(&*iter, std::memory_order_release); } } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } void alSourceUnqueueBuffers(gsl::not_null 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; auto srclock = std::lock_guard{context->mSourceLock}; auto const source = LookupSource(context, src); if(source->mSourceType != AL_STREAMING) context->throw_error(AL_INVALID_VALUE, "Unqueueing from a non-streaming source {}", src); if(source->mLooping) context->throw_error(AL_INVALID_VALUE, "Unqueueing from looping source {}", src); /* Make sure enough buffers have been processed to unqueue. */ const auto bids = std::views::counted(buffers, nb); auto processed = 0_uz; if(source->mState != AL_INITIAL) [[likely]] { const auto Current = std::invoke([source,context]() -> const VoiceBufferItem* { if(auto *voice = GetSourceVoice(source, context)) return voice->mCurrentBuffer.load(std::memory_order_relaxed); return nullptr; }); const auto qiter = std::ranges::find(source->mQueue, Current, [](al::BufferQueueItem const &item) { return &item; }); processed = gsl::narrow_cast(std::distance(source->mQueue.begin(), qiter)); } if(processed < bids.size()) context->throw_error(AL_INVALID_VALUE, "Unqueueing {} buffer{} (only {} processed)", nb, (nb==1) ? "" : "s", processed); std::ranges::generate(bids, [source]() noexcept -> ALuint { auto bid = 0u; if(auto *buffer = source->mQueue.front().mBuffer.get()) bid = buffer->mId; source->mQueue.pop_front(); return bid; }); } catch(al::base_exception&) { } catch(std::exception &e) { ERR("Caught exception: {}", e.what()); } } // namespace AL_API DECL_FUNC2(void, alGenSources, ALsizei,n, ALuint*,sources) AL_API DECL_FUNC2(void, alDeleteSources, ALsizei,n, const ALuint*,sources) AL_API DECL_FUNC1(ALboolean, alIsSource, ALuint,source) AL_API DECL_FUNC3(void, alSourcef, ALuint,source, ALenum,param, ALfloat,value) AL_API DECL_FUNC5(void, alSource3f, ALuint,source, ALenum,param, ALfloat,value1, ALfloat,value2, ALfloat,value3) AL_API DECL_FUNC3(void, alSourcefv, ALuint,source, ALenum,param, const ALfloat*,values) AL_API DECL_FUNCEXT3(void, alSourced,SOFT, ALuint,source, ALenum,param, ALdouble,value) AL_API DECL_FUNCEXT5(void, alSource3d,SOFT, ALuint,source, ALenum,param, ALdouble,value1, ALdouble,value2, ALdouble,value3) AL_API DECL_FUNCEXT3(void, alSourcedv,SOFT, ALuint,source, ALenum,param, const ALdouble*,values) AL_API DECL_FUNC3(void, alSourcei, ALuint,source, ALenum,param, ALint,value) AL_API DECL_FUNC5(void, alSource3i, ALuint,buffer, ALenum,param, ALint,value1, ALint,value2, ALint,value3) AL_API DECL_FUNC3(void, alSourceiv, ALuint,source, ALenum,param, const ALint*,values) AL_API DECL_FUNCEXT3(void, alSourcei64,SOFT, ALuint,source, ALenum,param, ALint64SOFT,value) AL_API DECL_FUNCEXT5(void, alSource3i64,SOFT, ALuint,source, ALenum,param, ALint64SOFT,value1, ALint64SOFT,value2, ALint64SOFT,value3) AL_API DECL_FUNCEXT3(void, alSourcei64v,SOFT, ALuint,source, ALenum,param, const ALint64SOFT*,values) AL_API DECL_FUNC3(void, alGetSourcef, ALuint,source, ALenum,param, ALfloat*,value) AL_API DECL_FUNC5(void, alGetSource3f, ALuint,source, ALenum,param, ALfloat*,value1, ALfloat*,value2, ALfloat*,value3) AL_API DECL_FUNC3(void, alGetSourcefv, ALuint,source, ALenum,param, ALfloat*,values) AL_API DECL_FUNCEXT3(void, alGetSourced,SOFT, ALuint,source, ALenum,param, ALdouble*,value) AL_API DECL_FUNCEXT5(void, alGetSource3d,SOFT, ALuint,source, ALenum,param, ALdouble*,value1, ALdouble*,value2, ALdouble*,value3) AL_API DECL_FUNCEXT3(void, alGetSourcedv,SOFT, ALuint,source, ALenum,param, ALdouble*,values) AL_API DECL_FUNC3(void, alGetSourcei, ALuint,source, ALenum,param, ALint*,value) AL_API DECL_FUNC5(void, alGetSource3i, ALuint,source, ALenum,param, ALint*,value1, ALint*,value2, ALint*,value3) AL_API DECL_FUNC3(void, alGetSourceiv, ALuint,source, ALenum,param, ALint*,values) AL_API DECL_FUNCEXT3(void, alGetSourcei64,SOFT, ALuint,source, ALenum,param, ALint64SOFT*,value) AL_API DECL_FUNCEXT5(void, alGetSource3i64,SOFT, ALuint,source, ALenum,param, ALint64SOFT*,value1, ALint64SOFT*,value2, ALint64SOFT*,value3) AL_API DECL_FUNCEXT3(void, alGetSourcei64v,SOFT, ALuint,source, ALenum,param, ALint64SOFT*,values) AL_API DECL_FUNC1(void, alSourcePlay, ALuint,source) FORCE_ALIGN DECL_FUNCEXT2(void, alSourcePlayAtTime,SOFT, ALuint,source, ALint64SOFT,start_time) AL_API DECL_FUNC2(void, alSourcePlayv, ALsizei,n, const ALuint*,sources) FORCE_ALIGN DECL_FUNCEXT3(void, alSourcePlayAtTimev,SOFT, ALsizei,n, const ALuint*,sources, ALint64SOFT,start_time) AL_API DECL_FUNC1(void, alSourcePause, ALuint,source) AL_API DECL_FUNC2(void, alSourcePausev, ALsizei,n, const ALuint*,sources) AL_API DECL_FUNC1(void, alSourceStop, ALuint,source) AL_API DECL_FUNC2(void, alSourceStopv, ALsizei,n, const ALuint*,sources) AL_API DECL_FUNC1(void, alSourceRewind, ALuint,source) AL_API DECL_FUNC2(void, alSourceRewindv, ALsizei,n, const ALuint*,sources) AL_API DECL_FUNC3(void, alSourceQueueBuffers, ALuint,source, ALsizei,nb, const ALuint*,buffers) AL_API DECL_FUNC3(void, alSourceUnqueueBuffers, ALuint,source, ALsizei,nb, ALuint*,buffers) AL_API void AL_APIENTRY alSourceQueueBufferLayersSOFT(ALuint, ALsizei, const ALuint*) noexcept { const auto context = GetContextRef(); if(!context) [[unlikely]] return; context->setError(AL_INVALID_OPERATION, "alSourceQueueBufferLayersSOFT not supported"); } al::Source::Source() noexcept { mDirect.mGain = 1.0f; mDirect.mGainHF = 1.0f; mDirect.mHFReference = LowPassFreqRef; mDirect.mGainLF = 1.0f; mDirect.mLFReference = HighPassFreqRef; mSend.fill(SendData{.mSlot={}, .mGain=1.0f, .mGainHF=1.0f, .mHFReference=LowPassFreqRef, .mGainLF=1.0f, .mLFReference=HighPassFreqRef}); } al::Source::~Source() = default; void UpdateAllSourceProps(gsl::not_null const context) { auto const srclock = std::lock_guard{context->mSourceLock}; auto const voicelist = context->getVoicesSpan(); auto vidx = 0_u32; for(Voice *voice : voicelist) { auto const sid = voice->mSourceID.load(std::memory_order_acquire); if(auto *const source = sid ? LookupSource(std::nothrow, context, sid) : nullptr; source && source->mVoiceIdx == vidx) { if(std::exchange(source->mPropsDirty, false)) UpdateSourceProps(gsl::make_not_null(source), voice, context); } ++vidx; } } void al::Source::SetName(gsl::not_null const context, u32 const id, std::string_view const name) { auto const srclock = std::lock_guard{context->mSourceLock}; std::ignore = LookupSource(context, id); context->mSourceNames.insert_or_assign(id, name); } SourceSubList::~SourceSubList() { if(!mSources) return; auto usemask = ~mFreeMask; while(usemask) { const auto idx = std::countr_zero(usemask); usemask &= ~(1_u64 << idx); std::destroy_at(std::to_address(mSources->begin() + idx)); } mFreeMask = ~usemask; SubListAllocator{}.deallocate(mSources, 1); mSources = nullptr; } #if ALSOFT_EAX void al::Source::eaxInitialize(gsl::not_null const context) noexcept { mEaxAlContext = context; mEaxPrimaryFxSlotId = context->eaxGetPrimaryFxSlotIndex(); eax_set_defaults(); eax1_translate(mEax1.i, mEax); mEaxVersion = 1; mEaxChanged = true; } auto al::Source::EaxLookupSource(gsl::not_null const al_context, u32 const source_id) noexcept -> Source* { return LookupSource(std::nothrow, al_context, source_id); } [[noreturn]] void al::Source::eax_fail(const std::string_view message) { throw Exception{message}; } [[noreturn]] void al::Source::eax_fail_unknown_property_id() { eax_fail("Unknown property id."); } [[noreturn]] void al::Source::eax_fail_unknown_version() { eax_fail("Unknown version."); } [[noreturn]] void al::Source::eax_fail_unknown_active_fx_slot_id() { eax_fail("Unknown active FX slot ID."); } [[noreturn]] void al::Source::eax_fail_unknown_receiving_fx_slot_id() {eax_fail("Unknown receiving FX slot ID.");} void al::Source::eax_set_sends_defaults(EaxSends& sends, const EaxFxSlotIds& ids) noexcept { for(auto const i : std::views::iota(0_uz, usize{EAX_MAX_FXSLOTS})) { auto& send = sends[i]; send.guidReceivingFXSlotID = *(ids[i]); send.mSend.lSend = EAXSOURCE_DEFAULTSEND; send.mSend.lSendHF = EAXSOURCE_DEFAULTSENDHF; send.mOcclusion.lOcclusion = EAXSOURCE_DEFAULTOCCLUSION; send.mOcclusion.flOcclusionLFRatio = EAXSOURCE_DEFAULTOCCLUSIONLFRATIO; send.mOcclusion.flOcclusionRoomRatio = EAXSOURCE_DEFAULTOCCLUSIONROOMRATIO; send.mOcclusion.flOcclusionDirectRatio = EAXSOURCE_DEFAULTOCCLUSIONDIRECTRATIO; send.mExclusion.lExclusion = EAXSOURCE_DEFAULTEXCLUSION; send.mExclusion.flExclusionLFRatio = EAXSOURCE_DEFAULTEXCLUSIONLFRATIO; } } void al::Source::eax1_set_defaults(EAXBUFFER_REVERBPROPERTIES& props) noexcept { props.fMix = EAX_REVERBMIX_USEDISTANCE; } void al::Source::eax1_set_defaults() noexcept { eax1_set_defaults(mEax1.i); mEax1.d = mEax1.i; } void al::Source::eax2_set_defaults(EAX20BUFFERPROPERTIES& 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 al::Source::eax2_set_defaults() noexcept { eax2_set_defaults(mEax2.i); mEax2.d = mEax2.i; } void al::Source::eax3_set_defaults(EAX30SOURCEPROPERTIES& props) noexcept { props.lDirect = EAXSOURCE_DEFAULTDIRECT; props.lDirectHF = EAXSOURCE_DEFAULTDIRECTHF; props.lRoom = EAXSOURCE_DEFAULTROOM; props.lRoomHF = EAXSOURCE_DEFAULTROOMHF; props.mObstruction.lObstruction = EAXSOURCE_DEFAULTOBSTRUCTION; props.mObstruction.flObstructionLFRatio = EAXSOURCE_DEFAULTOBSTRUCTIONLFRATIO; props.mOcclusion.lOcclusion = EAXSOURCE_DEFAULTOCCLUSION; props.mOcclusion.flOcclusionLFRatio = EAXSOURCE_DEFAULTOCCLUSIONLFRATIO; props.mOcclusion.flOcclusionRoomRatio = EAXSOURCE_DEFAULTOCCLUSIONROOMRATIO; props.mOcclusion.flOcclusionDirectRatio = EAXSOURCE_DEFAULTOCCLUSIONDIRECTRATIO; props.mExclusion.lExclusion = EAXSOURCE_DEFAULTEXCLUSION; props.mExclusion.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 al::Source::eax3_set_defaults() noexcept { eax3_set_defaults(mEax3.i); mEax3.d = mEax3.i; } void al::Source::eax4_set_sends_defaults(EaxSends& sends) noexcept { eax_set_sends_defaults(sends, eax4_fx_slot_ids); } void al::Source::eax4_set_active_fx_slots_defaults(EAX40ACTIVEFXSLOTS& slots) noexcept { slots = EAX40SOURCE_DEFAULTACTIVEFXSLOTID; } void al::Source::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 al::Source::eax5_set_source_defaults(EAX50SOURCEPROPERTIES& props) noexcept { eax3_set_defaults(props); props.flMacroFXFactor = EAXSOURCE_DEFAULTMACROFXFACTOR; } void al::Source::eax5_set_sends_defaults(EaxSends& sends) noexcept { eax_set_sends_defaults(sends, eax5_fx_slot_ids); } void al::Source::eax5_set_active_fx_slots_defaults(EAX50ACTIVEFXSLOTS& slots) noexcept { slots = EAX50SOURCE_3DDEFAULTACTIVEFXSLOTID; } void al::Source::eax5_set_speaker_levels_defaults(EaxSpeakerLevels& speaker_levels) noexcept { for(auto const i : std::views::iota(0_uz, usize{eax_max_speakers})) { auto& speaker_level = speaker_levels[i]; speaker_level.lSpeakerID = gsl::narrow_cast(EAXSPEAKER_FRONT_LEFT + i); speaker_level.lLevel = EAXSOURCE_DEFAULTSPEAKERLEVEL; } } void al::Source::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 al::Source::eax5_set_defaults() noexcept { eax5_set_defaults(mEax5.i); mEax5.d = mEax5.i; } void al::Source::eax_set_defaults() noexcept { eax1_set_defaults(); eax2_set_defaults(); eax3_set_defaults(); eax4_set_defaults(); eax5_set_defaults(); } void al::Source::eax1_translate(const EAXBUFFER_REVERBPROPERTIES& src, Eax5Props& dst) noexcept { eax5_set_defaults(dst); if (src.fMix == EAX_REVERBMIX_USEDISTANCE) { dst.source.ulFlags |= EAXSOURCEFLAGS_ROOMAUTO; dst.sends[0].mSend.lSend = 0; } else { dst.source.ulFlags &= ~EAXSOURCEFLAGS_ROOMAUTO; dst.sends[0].mSend.lSend = std::clamp( gsl::narrow_cast(gain_to_level_mb(src.fMix)), EAXSOURCE_MINSEND, EAXSOURCE_MAXSEND); } } void al::Source::eax2_translate(const EAX20BUFFERPROPERTIES& 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.mObstruction.lObstruction = src.lObstruction; dst.source.mObstruction.flObstructionLFRatio = src.flObstructionLFRatio; dst.source.mOcclusion.lOcclusion = src.lOcclusion; dst.source.mOcclusion.flOcclusionLFRatio = src.flOcclusionLFRatio; dst.source.mOcclusion.flOcclusionRoomRatio = src.flOcclusionRoomRatio; dst.source.mOcclusion.flOcclusionDirectRatio = EAXSOURCE_DEFAULTOCCLUSIONDIRECTRATIO; dst.source.mExclusion.lExclusion = EAXSOURCE_DEFAULTEXCLUSION; dst.source.mExclusion.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 al::Source::eax3_translate(const EAX30SOURCEPROPERTIES& 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 al::Source::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(auto const i : std::views::iota(0_uz, usize{EAX_MAX_FXSLOTS})) dst.sends[i].guidReceivingFXSlotID = *(eax5_fx_slot_ids[i]); // Active FX slots. // const auto src_slots = std::span{src.active_fx_slots.guidActiveFXSlots}; const auto dst_slots = std::span{dst.active_fx_slots.guidActiveFXSlots}; auto dstiter = std::ranges::transform(src_slots, dst_slots.begin(), [](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; }).out; std::ranges::fill(dstiter, dst_slots.end(), EAX_NULL_GUID); // Speaker levels. // eax5_set_speaker_levels_defaults(dst.speaker_levels); } auto al::Source::eax_calculate_dst_occlusion_mb(eax_long const src_occlusion_mb, f32 const path_ratio, f32 const lf_ratio) noexcept -> f32 { const auto ratio_1 = path_ratio + lf_ratio - 1.0f; const auto ratio_2 = path_ratio * lf_ratio; return gsl::narrow_cast(src_occlusion_mb) * std::max(ratio_2, ratio_1); } auto al::Source::eax_create_direct_filter_param() const noexcept -> EaxAlLowPassParam { const auto &source = mEax.source; auto gain_mb = gsl::narrow_cast(source.mObstruction.lObstruction) * source.mObstruction.flObstructionLFRatio; auto gainhf_mb = gsl::narrow_cast(source.mObstruction.lObstruction); for(const auto i : std::views::iota(0_uz, usize{EAX_MAX_FXSLOTS})) { if(!mEaxActiveFxSlots.test(i)) continue; const auto &fx_slot = mEaxAlContext->eaxGetFxSlot(i); const auto &fx_slot_eax = fx_slot.eax_get_eax_fx_slot(); if(!(fx_slot_eax.ulFlags&EAXFXSLOTFLAGS_ENVIRONMENT)) continue; if(mEaxPrimaryFxSlotId.value_or(-1) == fx_slot.eax_get_index() && source.mOcclusion.lOcclusion != 0) { gain_mb += eax_calculate_dst_occlusion_mb(source.mOcclusion.lOcclusion, source.mOcclusion.flOcclusionDirectRatio, source.mOcclusion.flOcclusionLFRatio); gainhf_mb += gsl::narrow_cast(source.mOcclusion.lOcclusion) * source.mOcclusion.flOcclusionDirectRatio; } const auto &send = mEax.sends[i]; if(send.mOcclusion.lOcclusion != 0) { gain_mb += eax_calculate_dst_occlusion_mb(send.mOcclusion.lOcclusion, send.mOcclusion.flOcclusionDirectRatio, send.mOcclusion.flOcclusionLFRatio); gainhf_mb += gsl::narrow_cast(send.mOcclusion.lOcclusion) * send.mOcclusion.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; gain_mb += gsl::narrow_cast(source.lDirect); gainhf_mb += gsl::narrow_cast(source.lDirectHF); return EaxAlLowPassParam{level_mb_to_gain(gain_mb), level_mb_to_gain(gainhf_mb)}; } auto al::Source::eax_create_room_filter_param(const al::EffectSlot &fx_slot, const EAXSOURCEALLSENDPROPERTIES& send) const noexcept -> EaxAlLowPassParam { auto gain_mb = 0.0f; auto gainhf_mb = 0.0f; const auto &fx_slot_eax = fx_slot.eax_get_eax_fx_slot(); if((fx_slot_eax.ulFlags & EAXFXSLOTFLAGS_ENVIRONMENT) != 0) { gain_mb += gsl::narrow_cast(fx_slot_eax.lOcclusion)*fx_slot_eax.flOcclusionLFRatio + eax_calculate_dst_occlusion_mb(send.mOcclusion.lOcclusion, send.mOcclusion.flOcclusionRoomRatio, send.mOcclusion.flOcclusionLFRatio) + gsl::narrow_cast(send.mExclusion.lExclusion) *send.mExclusion.flExclusionLFRatio; gainhf_mb += gsl::narrow_cast(fx_slot_eax.lOcclusion) + gsl::narrow_cast(send.mOcclusion.lOcclusion) *send.mOcclusion.flOcclusionRoomRatio + gsl::narrow_cast(send.mExclusion.lExclusion); const auto &source = mEax.source; if(mEaxPrimaryFxSlotId.value_or(-1) == fx_slot.eax_get_index()) { gain_mb += eax_calculate_dst_occlusion_mb(source.mOcclusion.lOcclusion, source.mOcclusion.flOcclusionRoomRatio, source.mOcclusion.flOcclusionLFRatio); gain_mb += gsl::narrow_cast(source.mExclusion.lExclusion) * source.mExclusion.flExclusionLFRatio; gainhf_mb += gsl::narrow_cast(source.mOcclusion.lOcclusion) * source.mOcclusion.flOcclusionRoomRatio; gainhf_mb += gsl::narrow_cast(source.mExclusion.lExclusion); } gainhf_mb -= gain_mb; gain_mb += gsl::narrow_cast(source.lRoom); gainhf_mb += gsl::narrow_cast(source.lRoomHF); } gain_mb += gsl::narrow_cast(send.mSend.lSend); gainhf_mb += gsl::narrow_cast(send.mSend.lSendHF); return EaxAlLowPassParam{level_mb_to_gain(gain_mb), level_mb_to_gain(gainhf_mb)}; } void al::Source::eax_update_direct_filter() { const auto& direct_param = eax_create_direct_filter_param(); mDirect.mGain = direct_param.gain; mDirect.mGainHF = direct_param.gain_hf; mDirect.mHFReference = LowPassFreqRef; mDirect.mGainLF = 1.0f; mDirect.mLFReference = HighPassFreqRef; mPropsDirty = true; } void al::Source::eax_update_room_filters() { for(const auto i : std::views::iota(0_uz, usize{EAX_MAX_FXSLOTS})) { if(!mEaxActiveFxSlots.test(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.newReference(), i, room_param); } } void al::Source::eax_set_efx_outer_gain_hf() { mOuterGainHF = std::clamp( level_mb_to_gain(gsl::narrow_cast(mEax.source.lOutsideVolumeHF)), AL_MIN_CONE_OUTER_GAINHF, AL_MAX_CONE_OUTER_GAINHF); } void al::Source::eax_set_efx_doppler_factor() { mDopplerFactor = mEax.source.flDopplerFactor; } void al::Source::eax_set_efx_rolloff_factor() { mRolloffFactor2 = mEax.source.flRolloffFactor; } void al::Source::eax_set_efx_room_rolloff_factor() { mRoomRolloffFactor = mEax.source.flRoomRolloffFactor; } void al::Source::eax_set_efx_air_absorption_factor() { mAirAbsorptionFactor = mEax.source.flAirAbsorptionFactor; } void al::Source::eax_set_efx_dry_gain_hf_auto() { mDryGainHFAuto = ((mEax.source.ulFlags & EAXSOURCEFLAGS_DIRECTHFAUTO) != 0); } void al::Source::eax_set_efx_wet_gain_auto() { mWetGainAuto = ((mEax.source.ulFlags & EAXSOURCEFLAGS_ROOMAUTO) != 0); } void al::Source::eax_set_efx_wet_gain_hf_auto() { mWetGainHFAuto = ((mEax.source.ulFlags & EAXSOURCEFLAGS_ROOMHFAUTO) != 0); } void al::Source::eax1_set(const EaxCall& call, EAXBUFFER_REVERBPROPERTIES& props) { switch(call.get_property_id()) { case DSPROPERTY_EAXBUFFER_ALL: eax_defer(call, props, Eax1SourceAllValidator{}); break; case DSPROPERTY_EAXBUFFER_REVERBMIX: eax_defer(call, props.fMix, Eax1SourceReverbMixValidator{}); break; default: eax_fail_unknown_property_id(); } } void al::Source::eax2_set(const EaxCall& call, EAX20BUFFERPROPERTIES& props) { switch(call.get_property_id()) { case DSPROPERTY_EAX20BUFFER_NONE: break; case DSPROPERTY_EAX20BUFFER_ALLPARAMETERS: eax_defer(call, props, Eax2SourceAllValidator{}); break; case DSPROPERTY_EAX20BUFFER_DIRECT: eax_defer(call, props.lDirect, Eax2SourceDirectValidator{}); break; case DSPROPERTY_EAX20BUFFER_DIRECTHF: eax_defer(call, props.lDirectHF, Eax2SourceDirectHfValidator{}); break; case DSPROPERTY_EAX20BUFFER_ROOM: eax_defer(call, props.lRoom, Eax2SourceRoomValidator{}); break; case DSPROPERTY_EAX20BUFFER_ROOMHF: eax_defer(call, props.lRoomHF, Eax2SourceRoomHfValidator{}); break; case DSPROPERTY_EAX20BUFFER_ROOMROLLOFFFACTOR: eax_defer(call, props.flRoomRolloffFactor, Eax2SourceRoomRolloffFactorValidator{}); break; case DSPROPERTY_EAX20BUFFER_OBSTRUCTION: eax_defer(call, props.lObstruction, Eax2SourceObstructionValidator{}); break; case DSPROPERTY_EAX20BUFFER_OBSTRUCTIONLFRATIO: eax_defer(call, props.flObstructionLFRatio, Eax2SourceObstructionLfRatioValidator{}); break; case DSPROPERTY_EAX20BUFFER_OCCLUSION: eax_defer(call, props.lOcclusion, Eax2SourceOcclusionValidator{}); break; case DSPROPERTY_EAX20BUFFER_OCCLUSIONLFRATIO: eax_defer(call, props.flOcclusionLFRatio, Eax2SourceOcclusionLfRatioValidator{}); break; case DSPROPERTY_EAX20BUFFER_OCCLUSIONROOMRATIO: eax_defer(call, props.flOcclusionRoomRatio, Eax2SourceOcclusionRoomRatioValidator{}); break; case DSPROPERTY_EAX20BUFFER_OUTSIDEVOLUMEHF: eax_defer(call, props.lOutsideVolumeHF, Eax2SourceOutsideVolumeHfValidator{}); break; case DSPROPERTY_EAX20BUFFER_AIRABSORPTIONFACTOR: eax_defer(call, props.flAirAbsorptionFactor, Eax2SourceAirAbsorptionFactorValidator{}); break; case DSPROPERTY_EAX20BUFFER_FLAGS: eax_defer(call, props.dwFlags, Eax2SourceFlagsValidator{}); break; default: eax_fail_unknown_property_id(); } } void al::Source::eax3_set(const EaxCall& call, EAX30SOURCEPROPERTIES& props) { switch(call.get_property_id()) { case EAXSOURCE_NONE: break; case EAXSOURCE_ALLPARAMETERS: eax_defer(call, props, Eax3SourceAllValidator{}); break; case EAXSOURCE_OBSTRUCTIONPARAMETERS: eax_defer(call, props.mObstruction, Eax4ObstructionValidator{}); break; case EAXSOURCE_OCCLUSIONPARAMETERS: eax_defer(call, props.mOcclusion, Eax4OcclusionValidator{}); break; case EAXSOURCE_EXCLUSIONPARAMETERS: eax_defer(call, props.mExclusion, Eax4ExclusionValidator{}); break; case EAXSOURCE_DIRECT: eax_defer(call, props.lDirect, Eax2SourceDirectValidator{}); break; case EAXSOURCE_DIRECTHF: eax_defer(call, props.lDirectHF, Eax2SourceDirectHfValidator{}); break; case EAXSOURCE_ROOM: eax_defer(call, props.lRoom, Eax2SourceRoomValidator{}); break; case EAXSOURCE_ROOMHF: eax_defer(call, props.lRoomHF, Eax2SourceRoomHfValidator{}); break; case EAXSOURCE_OBSTRUCTION: eax_defer(call, props.mObstruction.lObstruction, Eax2SourceObstructionValidator{}); break; case EAXSOURCE_OBSTRUCTIONLFRATIO: eax_defer(call, props.mObstruction.flObstructionLFRatio, Eax2SourceObstructionLfRatioValidator{}); break; case EAXSOURCE_OCCLUSION: eax_defer(call, props.mOcclusion.lOcclusion, Eax2SourceOcclusionValidator{}); break; case EAXSOURCE_OCCLUSIONLFRATIO: eax_defer(call, props.mOcclusion.flOcclusionLFRatio, Eax2SourceOcclusionLfRatioValidator{}); break; case EAXSOURCE_OCCLUSIONROOMRATIO: eax_defer(call, props.mOcclusion.flOcclusionRoomRatio, Eax2SourceOcclusionRoomRatioValidator{}); break; case EAXSOURCE_OCCLUSIONDIRECTRATIO: eax_defer(call, props.mOcclusion.flOcclusionDirectRatio, Eax3SourceOcclusionDirectRatioValidator{}); break; case EAXSOURCE_EXCLUSION: eax_defer(call, props.mExclusion.lExclusion, Eax3SourceExclusionValidator{}); break; case EAXSOURCE_EXCLUSIONLFRATIO: eax_defer(call, props.mExclusion.flExclusionLFRatio, Eax3SourceExclusionLfRatioValidator{}); break; case EAXSOURCE_OUTSIDEVOLUMEHF: eax_defer(call, props.lOutsideVolumeHF, Eax2SourceOutsideVolumeHfValidator{}); break; case EAXSOURCE_DOPPLERFACTOR: eax_defer(call, props.flDopplerFactor, Eax3SourceDopplerFactorValidator{}); break; case EAXSOURCE_ROLLOFFFACTOR: eax_defer(call, props.flRolloffFactor, Eax3SourceRolloffFactorValidator{}); break; case EAXSOURCE_ROOMROLLOFFFACTOR: eax_defer(call, props.flRoomRolloffFactor, Eax2SourceRoomRolloffFactorValidator{}); break; case EAXSOURCE_AIRABSORPTIONFACTOR: eax_defer(call, props.flAirAbsorptionFactor, Eax2SourceAirAbsorptionFactorValidator{}); break; case EAXSOURCE_FLAGS: eax_defer(call, props.ulFlags, Eax2SourceFlagsValidator{}); break; default: eax_fail_unknown_property_id(); } } void al::Source::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, Eax4SendValidator{}); break; case EAXSOURCE_ALLSENDPARAMETERS: eax4_defer_sends(call, props.sends, Eax4AllSendValidator{}); break; case EAXSOURCE_OCCLUSIONSENDPARAMETERS: eax4_defer_sends(call, props.sends, Eax4OcclusionSendValidator{}); break; case EAXSOURCE_EXCLUSIONSENDPARAMETERS: eax4_defer_sends(call, props.sends, Eax4ExclusionSendValidator{}); break; case EAXSOURCE_ACTIVEFXSLOTID: eax4_defer_active_fx_slot_id(call, props.active_fx_slots.guidActiveFXSlots); break; default: eax_fail_unknown_property_id(); } } void al::Source::eax5_defer_all_2d(const EaxCall& call, EAX50SOURCEPROPERTIES& props) { const auto &src_props = call.load(); 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 al::Source::eax5_defer_speaker_levels(const EaxCall& call, EaxSpeakerLevels& props) { const auto values = call.as_span(eax_max_speakers); std::ranges::for_each(values, Eax5SpeakerAllValidator{}); for(const auto &value : values) { const auto index = gsl::narrow_cast(value.lSpeakerID - EAXSPEAKER_FRONT_LEFT); props[index].lLevel = value.lLevel; } } void al::Source::eax5_set(const EaxCall& call, Eax5Props& props) { switch(call.get_property_id()) { case EAXSOURCE_NONE: break; case EAXSOURCE_ALLPARAMETERS: eax_defer(call, props.source, Eax5SourceAllValidator{}); 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, Eax5SourceFlagsValidator{}); break; case EAXSOURCE_SENDPARAMETERS: eax5_defer_sends(call, props.sends, Eax5SendValidator{}); break; case EAXSOURCE_ALLSENDPARAMETERS: eax5_defer_sends(call, props.sends, Eax5AllSendValidator{}); break; case EAXSOURCE_OCCLUSIONSENDPARAMETERS: eax5_defer_sends(call, props.sends, Eax5OcclusionSendValidator{}); break; case EAXSOURCE_EXCLUSIONSENDPARAMETERS: eax5_defer_sends(call, props.sends, Eax5ExclusionSendValidator{}); break; case EAXSOURCE_ACTIVEFXSLOTID: eax5_defer_active_fx_slot_id(call, props.active_fx_slots.guidActiveFXSlots); break; case EAXSOURCE_MACROFXFACTOR: eax_defer(call, props.source.flMacroFXFactor, Eax5SourceMacroFXFactorValidator{}); 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 al::Source::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 al::Source::eax_get_active_fx_slot_id(const EaxCall& call, const std::span srcids) { Expects(srcids.size()==EAX40_MAX_ACTIVE_FXSLOTS || srcids.size()==EAX50_MAX_ACTIVE_FXSLOTS); const auto dst_ids = call.as_span(srcids.size()); std::uninitialized_copy_n(srcids.begin(), dst_ids.size(), dst_ids.begin()); } void al::Source::eax1_get(const EaxCall& call, const EAXBUFFER_REVERBPROPERTIES& props) { switch(call.get_property_id()) { case DSPROPERTY_EAXBUFFER_ALL: case DSPROPERTY_EAXBUFFER_REVERBMIX: call.store(props.fMix); break; default: eax_fail_unknown_property_id(); } } void al::Source::eax2_get(const EaxCall& call, const EAX20BUFFERPROPERTIES& props) { switch(call.get_property_id()) { case DSPROPERTY_EAX20BUFFER_NONE: break; case DSPROPERTY_EAX20BUFFER_ALLPARAMETERS: call.store(props); break; case DSPROPERTY_EAX20BUFFER_DIRECT: call.store(props.lDirect); break; case DSPROPERTY_EAX20BUFFER_DIRECTHF: call.store(props.lDirectHF); break; case DSPROPERTY_EAX20BUFFER_ROOM: call.store(props.lRoom); break; case DSPROPERTY_EAX20BUFFER_ROOMHF: call.store(props.lRoomHF); break; case DSPROPERTY_EAX20BUFFER_ROOMROLLOFFFACTOR: call.store(props.flRoomRolloffFactor); break; case DSPROPERTY_EAX20BUFFER_OBSTRUCTION: call.store(props.lObstruction); break; case DSPROPERTY_EAX20BUFFER_OBSTRUCTIONLFRATIO: call.store(props.flObstructionLFRatio); break; case DSPROPERTY_EAX20BUFFER_OCCLUSION: call.store(props.lOcclusion); break; case DSPROPERTY_EAX20BUFFER_OCCLUSIONLFRATIO: call.store(props.flOcclusionLFRatio); break; case DSPROPERTY_EAX20BUFFER_OCCLUSIONROOMRATIO: call.store(props.flOcclusionRoomRatio); break; case DSPROPERTY_EAX20BUFFER_OUTSIDEVOLUMEHF: call.store(props.lOutsideVolumeHF); break; case DSPROPERTY_EAX20BUFFER_AIRABSORPTIONFACTOR: call.store(props.flAirAbsorptionFactor);break; case DSPROPERTY_EAX20BUFFER_FLAGS: call.store(props.dwFlags); break; default: eax_fail_unknown_property_id(); } } void al::Source::eax3_get(const EaxCall &call, const EAX30SOURCEPROPERTIES &props) { switch(call.get_property_id()) { case EAXSOURCE_NONE: break; case EAXSOURCE_ALLPARAMETERS: call.store(props); break; case EAXSOURCE_OBSTRUCTIONPARAMETERS: call.store(props.mObstruction); break; case EAXSOURCE_OCCLUSIONPARAMETERS: call.store(props.mOcclusion); break; case EAXSOURCE_EXCLUSIONPARAMETERS: call.store(props.mExclusion); break; case EAXSOURCE_DIRECT: call.store(props.lDirect); break; case EAXSOURCE_DIRECTHF: call.store(props.lDirectHF); break; case EAXSOURCE_ROOM: call.store(props.lRoom); break; case EAXSOURCE_ROOMHF: call.store(props.lRoomHF); break; case EAXSOURCE_OBSTRUCTION: call.store(props.mObstruction.lObstruction); break; case EAXSOURCE_OBSTRUCTIONLFRATIO: call.store(props.mObstruction.flObstructionLFRatio); break; case EAXSOURCE_OCCLUSION: call.store(props.mOcclusion.lOcclusion); break; case EAXSOURCE_OCCLUSIONLFRATIO: call.store(props.mOcclusion.flOcclusionLFRatio); break; case EAXSOURCE_OCCLUSIONROOMRATIO: call.store(props.mOcclusion.flOcclusionRoomRatio); break; case EAXSOURCE_OCCLUSIONDIRECTRATIO: call.store(props.mOcclusion.flOcclusionDirectRatio);break; case EAXSOURCE_EXCLUSION: call.store(props.mExclusion.lExclusion); break; case EAXSOURCE_EXCLUSIONLFRATIO: call.store(props.mExclusion.flExclusionLFRatio); break; case EAXSOURCE_OUTSIDEVOLUMEHF: call.store(props.lOutsideVolumeHF); break; case EAXSOURCE_DOPPLERFACTOR: call.store(props.flDopplerFactor); break; case EAXSOURCE_ROLLOFFFACTOR: call.store(props.flRolloffFactor); break; case EAXSOURCE_ROOMROLLOFFFACTOR: call.store(props.flRoomRolloffFactor); break; case EAXSOURCE_AIRABSORPTIONFACTOR: call.store(props.flAirAbsorptionFactor); break; case EAXSOURCE_FLAGS: call.store(props.ulFlags); break; default: eax_fail_unknown_property_id(); } } void al::Source::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 al::Source::eax5_get_all_2d(const EaxCall &call, const EAX50SOURCEPROPERTIES &props) { auto &subprops = call.load(); subprops.lDirect = props.lDirect; subprops.lDirectHF = props.lDirectHF; subprops.lRoom = props.lRoom; subprops.lRoomHF = props.lRoomHF; subprops.ulFlags = props.ulFlags; } void al::Source::eax5_get_speaker_levels(const EaxCall &call, const EaxSpeakerLevels &props) { const auto subprops = call.as_span(eax_max_speakers); std::uninitialized_copy_n(props.cbegin(), subprops.size(), subprops.begin()); } void al::Source::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.store(props.source.flMacroFXFactor); break; case EAXSOURCE_SPEAKERLEVELS: call.store(props.speaker_levels); break; case EAXSOURCE_ALL2DPARAMETERS: eax5_get_all_2d(call, props.source); break; default: eax_fail_unknown_property_id(); } } void al::Source::eax_get(const EaxCall &call) const { 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 al::Source::eax_set_al_source_send(al::intrusive_ptr slot, usize const sendidx, const EaxAlLowPassParam &filter) { if(sendidx >= EAX_MAX_FXSLOTS) return; auto &send = mSend[sendidx]; send.mSlot = std::move(slot); send.mGain = filter.gain; send.mGainHF = filter.gain_hf; send.mHFReference = LowPassFreqRef; send.mGainLF = 1.0f; send.mLFReference = HighPassFreqRef; mPropsDirty = true; } void al::Source::eax_commit_active_fx_slots() { // Clear all slots to an inactive state. mEaxActiveFxSlots.reset(); // 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.set(*mEaxPrimaryFxSlotId); } else if(slot_id == EAXPROPERTYID_EAX50_FXSlot0) mEaxActiveFxSlots.set(0); else if(slot_id == EAXPROPERTYID_EAX50_FXSlot1) mEaxActiveFxSlots.set(1); else if(slot_id == EAXPROPERTYID_EAX50_FXSlot2) mEaxActiveFxSlots.set(2); else if(slot_id == EAXPROPERTYID_EAX50_FXSlot3) mEaxActiveFxSlots.set(3); } // Deactivate EFX auxiliary effect slots for inactive slots. Active slots // will be updated with the room filters. for(const auto i : std::views::iota(0_uz, usize{EAX_MAX_FXSLOTS})) { if(!mEaxActiveFxSlots.test(i)) eax_set_al_source_send({}, i, EaxAlLowPassParam{1.0f, 1.0f}); } } void al::Source::eax_commit_filters() { eax_update_direct_filter(); eax_update_room_filters(); } void al::Source::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 kcat-openal-soft-75c0059/al/source.h000066400000000000000000001075461512220627100172170ustar00rootroot00000000000000#ifndef AL_SOURCE_H #define AL_SOURCE_H #include "config.h" #include #include #include #include #include #include #include #include #include #include #include "AL/al.h" #include "AL/alext.h" #include "almalloc.h" #include "alnumeric.h" #include "core/context.h" #include "core/voice.h" #include "gsl/gsl" #include "intrusive_ptr.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 namespace al { struct Context; struct Buffer; struct EffectSlot; } // namespace al enum class Resampler : u8; enum class SourceStereo : bool { Normal = AL_NORMAL_SOFT, Enhanced = AL_SUPER_STEREO_SOFT }; inline constexpr auto DefaultSendCount = 2_uz; inline constexpr auto InvalidVoiceIndex = std::numeric_limits::max(); inline constinit auto sBufferSubDataCompat = false; #if ALSOFT_EAX /* NOLINTNEXTLINE(clazy-copyable-polymorphic) Exceptions must be copyable. */ class EaxSourceException final : public EaxException { public: explicit EaxSourceException(const std::string_view message) : EaxException{"EAX_SOURCE", message} { } }; #endif // ALSOFT_EAX namespace al { struct BufferQueueItem : VoiceBufferItem { al::intrusive_ptr mBuffer; }; struct Source { f32 mPitch{1.0f}; f32 mGain{1.0f}; f32 mOuterGain{0.0f}; f32 mMinGain{0.0f}; f32 mMaxGain{1.0f}; f32 mInnerAngle{360.0f}; f32 mOuterAngle{360.0f}; f32 mRefDistance{1.0f}; f32 mMaxDistance{std::numeric_limits::max()}; f32 mRolloffFactor{1.0f}; #if ALSOFT_EAX // For EAXSOURCE_ROLLOFFFACTOR, which is distinct from and added to // AL_ROLLOFF_FACTOR f32 mRolloffFactor2{0.0f}; #endif std::array mPosition{{0.0f, 0.0f, 0.0f}}; std::array mVelocity{{0.0f, 0.0f, 0.0f}}; std::array mDirection{{0.0f, 0.0f, 0.0f}}; std::array mOrientAt{{0.0f, 0.0f, -1.0f}}; std::array mOrientUp{{0.0f, 1.0f, 0.0f}}; bool mHeadRelative{false}; bool mLooping{false}; DistanceModel mDistanceModel{DistanceModel::Default}; Resampler mResampler{ResamplerDefault}; DirectMode DirectChannels{DirectMode::Off}; SpatializeMode mSpatialize{SpatializeMode::Auto}; SourceStereo mStereoMode{SourceStereo::Normal}; bool mPanningEnabled{false}; bool mDryGainHFAuto{true}; bool mWetGainAuto{true}; bool mWetGainHFAuto{true}; f32 mOuterGainHF{1.0f}; f32 mAirAbsorptionFactor{0.0f}; f32 mRoomRolloffFactor{0.0f}; f32 mDopplerFactor{1.0f}; /* NOTE: Stereo pan angles are specified in radians, counter-clockwise * rather than clockwise. */ std::array mStereoPan{{std::numbers::pi_v/6.0f, -std::numbers::pi_v/6.0f}}; f32 mRadius{0.0f}; f32 mEnhWidth{0.593f}; f32 mPan{0.0f}; /** Direct filter and auxiliary send info. */ struct DirectData { f32 mGain{}; f32 mGainHF{}; f32 mHFReference{}; f32 mGainLF{}; f32 mLFReference{}; }; DirectData mDirect; struct SendData { intrusive_ptr mSlot; f32 mGain{}; f32 mGainHF{}; f32 mHFReference{}; f32 mGainLF{}; f32 mLFReference{}; }; std::array mSend; /** * Last user-specified offset, and the offset type (bytes, samples, or * seconds). */ f64 mOffset{0.0}; ALenum mOffsetType{AL_NONE}; /** Source type (static, streaming, or undetermined) */ ALenum mSourceType{AL_UNDETERMINED}; /** Source state (initial, playing, paused, or stopped) */ ALenum mState{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. */ u32 mVoiceIdx{InvalidVoiceIndex}; /** Self ID */ u32 mId{0}; Source() noexcept; ~Source(); Source(const Source&) = delete; auto operator=(const Source&) -> Source& = delete; static void SetName(gsl::not_null context, u32 id, std::string_view name); DISABLE_ALLOC #if ALSOFT_EAX public: void eaxInitialize(gsl::not_null context) noexcept; void eaxDispatch(const EaxCall& call) { call.is_get() ? eax_get(call) : eax_set(call); } void eaxCommit(); void eaxMarkAsChanged() noexcept { mEaxChanged = true; } static auto EaxLookupSource(gsl::not_null al_context LIFETIMEBOUND, u32 source_id) noexcept -> Source*; private: using Exception = EaxSourceException; static constexpr auto eax_max_speakers = 9_u32; using EaxFxSlotIds = std::array; static constexpr auto eax4_fx_slot_ids = EaxFxSlotIds{ &EAXPROPERTYID_EAX40_FXSlot0, &EAXPROPERTYID_EAX40_FXSlot1, &EAXPROPERTYID_EAX40_FXSlot2, &EAXPROPERTYID_EAX40_FXSlot3, }; static constexpr auto eax5_fx_slot_ids = EaxFxSlotIds{ &EAXPROPERTYID_EAX50_FXSlot0, &EAXPROPERTYID_EAX50_FXSlot1, &EAXPROPERTYID_EAX50_FXSlot2, &EAXPROPERTYID_EAX50_FXSlot3, }; using EaxActiveFxSlots = std::bitset; using EaxSpeakerLevels = std::array; using EaxSends = std::array; struct Eax1State { EAXBUFFER_REVERBPROPERTIES i; // Immediate. EAXBUFFER_REVERBPROPERTIES d; // Deferred. }; struct Eax2State { EAX20BUFFERPROPERTIES i; // Immediate. EAX20BUFFERPROPERTIES d; // Deferred. }; struct Eax3State { EAX30SOURCEPROPERTIES i; // Immediate. EAX30SOURCEPROPERTIES d; // Deferred. }; struct Eax4Props { EAX30SOURCEPROPERTIES 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. }; Context *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()(f32 const 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()(eax_long const lDirect) const { eax_validate_range( "Direct", lDirect, EAXSOURCE_MINDIRECT, EAXSOURCE_MAXDIRECT); } }; struct Eax2SourceDirectHfValidator { void operator()(eax_long const lDirectHF) const { eax_validate_range( "Direct HF", lDirectHF, EAXSOURCE_MINDIRECTHF, EAXSOURCE_MAXDIRECTHF); } }; struct Eax2SourceRoomValidator { void operator()(eax_long const lRoom) const { eax_validate_range( "Room", lRoom, EAXSOURCE_MINROOM, EAXSOURCE_MAXROOM); } }; struct Eax2SourceRoomHfValidator { void operator()(eax_long const lRoomHF) const { eax_validate_range( "Room HF", lRoomHF, EAXSOURCE_MINROOMHF, EAXSOURCE_MAXROOMHF); } }; struct Eax2SourceRoomRolloffFactorValidator { void operator()(f32 const flRoomRolloffFactor) const { eax_validate_range( "Room Rolloff Factor", flRoomRolloffFactor, EAXSOURCE_MINROOMROLLOFFFACTOR, EAXSOURCE_MAXROOMROLLOFFFACTOR); } }; struct Eax2SourceObstructionValidator { void operator()(eax_long const lObstruction) const { eax_validate_range( "Obstruction", lObstruction, EAXSOURCE_MINOBSTRUCTION, EAXSOURCE_MAXOBSTRUCTION); } }; struct Eax2SourceObstructionLfRatioValidator { void operator()(f32 const flObstructionLFRatio) const { eax_validate_range( "Obstruction LF Ratio", flObstructionLFRatio, EAXSOURCE_MINOBSTRUCTIONLFRATIO, EAXSOURCE_MAXOBSTRUCTIONLFRATIO); } }; struct Eax2SourceOcclusionValidator { void operator()(eax_long const lOcclusion) const { eax_validate_range( "Occlusion", lOcclusion, EAXSOURCE_MINOCCLUSION, EAXSOURCE_MAXOCCLUSION); } }; struct Eax2SourceOcclusionLfRatioValidator { void operator()(f32 const flOcclusionLFRatio) const { eax_validate_range( "Occlusion LF Ratio", flOcclusionLFRatio, EAXSOURCE_MINOCCLUSIONLFRATIO, EAXSOURCE_MAXOCCLUSIONLFRATIO); } }; struct Eax2SourceOcclusionRoomRatioValidator { void operator()(f32 const flOcclusionRoomRatio) const { eax_validate_range( "Occlusion Room Ratio", flOcclusionRoomRatio, EAXSOURCE_MINOCCLUSIONROOMRATIO, EAXSOURCE_MAXOCCLUSIONROOMRATIO); } }; struct Eax2SourceOutsideVolumeHfValidator { void operator()(eax_long const lOutsideVolumeHF) const { eax_validate_range( "Outside Volume HF", lOutsideVolumeHF, EAXSOURCE_MINOUTSIDEVOLUMEHF, EAXSOURCE_MAXOUTSIDEVOLUMEHF); } }; struct Eax2SourceAirAbsorptionFactorValidator { void operator()(f32 const flAirAbsorptionFactor) const { eax_validate_range( "Air Absorption Factor", flAirAbsorptionFactor, EAXSOURCE_MINAIRABSORPTIONFACTOR, EAXSOURCE_MAXAIRABSORPTIONFACTOR); } }; struct Eax2SourceFlagsValidator { void operator()(eax_ulong const dwFlags) const { eax_validate_range( "Flags", dwFlags, 0_eax_ulong, ~EAX20SOURCEFLAGS_RESERVED); } }; struct Eax3SourceOcclusionDirectRatioValidator { void operator()(f32 const flOcclusionDirectRatio) const { eax_validate_range( "Occlusion Direct Ratio", flOcclusionDirectRatio, EAXSOURCE_MINOCCLUSIONDIRECTRATIO, EAXSOURCE_MAXOCCLUSIONDIRECTRATIO); } }; struct Eax3SourceExclusionValidator { void operator()(eax_long const lExclusion) const { eax_validate_range( "Exclusion", lExclusion, EAXSOURCE_MINEXCLUSION, EAXSOURCE_MAXEXCLUSION); } }; struct Eax3SourceExclusionLfRatioValidator { void operator()(f32 const flExclusionLFRatio) const { eax_validate_range( "Exclusion LF Ratio", flExclusionLFRatio, EAXSOURCE_MINEXCLUSIONLFRATIO, EAXSOURCE_MAXEXCLUSIONLFRATIO); } }; struct Eax3SourceDopplerFactorValidator { void operator()(f32 const flDopplerFactor) const { eax_validate_range( "Doppler Factor", flDopplerFactor, EAXSOURCE_MINDOPPLERFACTOR, EAXSOURCE_MAXDOPPLERFACTOR); } }; struct Eax3SourceRolloffFactorValidator { void operator()(f32 const flRolloffFactor) const { eax_validate_range( "Rolloff Factor", flRolloffFactor, EAXSOURCE_MINROLLOFFFACTOR, EAXSOURCE_MAXROLLOFFFACTOR); } }; struct Eax5SourceMacroFXFactorValidator { void operator()(f32 const flMacroFXFactor) const { eax_validate_range( "Macro FX Factor", flMacroFXFactor, EAXSOURCE_MINMACROFXFACTOR, EAXSOURCE_MAXMACROFXFACTOR); } }; struct Eax5SourceFlagsValidator { void operator()(eax_ulong const dwFlags) const { eax_validate_range( "Flags", dwFlags, 0_eax_ulong, ~EAX50SOURCEFLAGS_RESERVED); } }; struct Eax1SourceAllValidator { void operator()(const EAXBUFFER_REVERBPROPERTIES& props) const { Eax1SourceReverbMixValidator{}(props.fMix); } }; struct Eax2SourceAllValidator { void operator()(const EAX20BUFFERPROPERTIES& 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 EAX30SOURCEPROPERTIES& props) const { Eax2SourceDirectValidator{}(props.lDirect); Eax2SourceDirectHfValidator{}(props.lDirectHF); Eax2SourceRoomValidator{}(props.lRoom); Eax2SourceRoomHfValidator{}(props.lRoomHF); Eax2SourceObstructionValidator{}(props.mObstruction.lObstruction); Eax2SourceObstructionLfRatioValidator{}(props.mObstruction.flObstructionLFRatio); Eax2SourceOcclusionValidator{}(props.mOcclusion.lOcclusion); Eax2SourceOcclusionLfRatioValidator{}(props.mOcclusion.flOcclusionLFRatio); Eax2SourceOcclusionRoomRatioValidator{}(props.mOcclusion.flOcclusionRoomRatio); Eax3SourceOcclusionDirectRatioValidator{}(props.mOcclusion.flOcclusionDirectRatio); Eax3SourceExclusionValidator{}(props.mExclusion.lExclusion); Eax3SourceExclusionLfRatioValidator{}(props.mExclusion.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.mObstruction.lObstruction); Eax2SourceObstructionLfRatioValidator{}(props.mObstruction.flObstructionLFRatio); Eax2SourceOcclusionValidator{}(props.mOcclusion.lOcclusion); Eax2SourceOcclusionLfRatioValidator{}(props.mOcclusion.flOcclusionLFRatio); Eax2SourceOcclusionRoomRatioValidator{}(props.mOcclusion.flOcclusionRoomRatio); Eax3SourceOcclusionDirectRatioValidator{}(props.mOcclusion.flOcclusionDirectRatio); Eax3SourceExclusionValidator{}(props.mExclusion.lExclusion); Eax3SourceExclusionLfRatioValidator{}(props.mExclusion.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()(eax_long const lSend) const { eax_validate_range( "Send", lSend, EAXSOURCE_MINSEND, EAXSOURCE_MAXSEND); } }; struct Eax4SendSendHfValidator { void operator()(eax_long const 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.mSend.lSend); Eax4SendSendHfValidator{}(props.mSend.lSendHF); } }; using Eax4SendValidator = EaxSendValidator; using Eax5SendValidator = EaxSendValidator; template struct EaxOcclusionSendValidator { void operator()(const EAXSOURCEOCCLUSIONSENDPROPERTIES& props) const { TIdValidator{}(props.guidReceivingFXSlotID); Eax2SourceOcclusionValidator{}(props.mOcclusion.lOcclusion); Eax2SourceOcclusionLfRatioValidator{}(props.mOcclusion.flOcclusionLFRatio); Eax2SourceOcclusionRoomRatioValidator{}(props.mOcclusion.flOcclusionRoomRatio); Eax3SourceOcclusionDirectRatioValidator{}(props.mOcclusion.flOcclusionDirectRatio); } }; using Eax4OcclusionSendValidator = EaxOcclusionSendValidator; using Eax5OcclusionSendValidator = EaxOcclusionSendValidator; template struct EaxExclusionSendValidator { void operator()(const EAXSOURCEEXCLUSIONSENDPROPERTIES& props) const { TIdValidator{}(props.guidReceivingFXSlotID); Eax3SourceExclusionValidator{}(props.mExclusion.lExclusion); Eax3SourceExclusionLfRatioValidator{}(props.mExclusion.flExclusionLFRatio); } }; using Eax4ExclusionSendValidator = EaxExclusionSendValidator; using Eax5ExclusionSendValidator = EaxExclusionSendValidator; template struct EaxAllSendValidator { void operator()(const EAXSOURCEALLSENDPROPERTIES& props) const { TIdValidator{}(props.guidReceivingFXSlotID); Eax4SendSendValidator{}(props.mSend.lSend); Eax4SendSendHfValidator{}(props.mSend.lSendHF); Eax2SourceOcclusionValidator{}(props.mOcclusion.lOcclusion); Eax2SourceOcclusionLfRatioValidator{}(props.mOcclusion.flOcclusionLFRatio); Eax2SourceOcclusionRoomRatioValidator{}(props.mOcclusion.flOcclusionRoomRatio); Eax3SourceOcclusionDirectRatioValidator{}(props.mOcclusion.flOcclusionDirectRatio); Eax3SourceExclusionValidator{}(props.mExclusion.lExclusion); Eax3SourceExclusionLfRatioValidator{}(props.mExclusion.flExclusionLFRatio); } }; using Eax4AllSendValidator = EaxAllSendValidator; using 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()(eax_long const 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()(eax_long const 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(std::string_view 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(EAXBUFFER_REVERBPROPERTIES& props) noexcept; void eax1_set_defaults() noexcept; static void eax2_set_defaults(EAX20BUFFERPROPERTIES& props) noexcept; void eax2_set_defaults() noexcept; static void eax3_set_defaults(EAX30SOURCEPROPERTIES& 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 EAXBUFFER_REVERBPROPERTIES& src, Eax5Props& dst) noexcept; static void eax2_translate(const EAX20BUFFERPROPERTIES& src, Eax5Props& dst) noexcept; static void eax3_translate(const EAX30SOURCEPROPERTIES& src, Eax5Props& dst) noexcept; static void eax4_translate(const Eax4Props& src, Eax5Props& dst) noexcept; static auto eax_calculate_dst_occlusion_mb(eax_long src_occlusion_mb, f32 path_ratio, f32 lf_ratio) noexcept -> f32; [[nodiscard]] auto eax_create_direct_filter_param() const noexcept -> EaxAlLowPassParam; [[nodiscard]] auto eax_create_room_filter_param(al::EffectSlot const& 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.guidReceivingFXSlotID = src.guidReceivingFXSlotID; dst.mSend = src.mSend; } 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.mOcclusion = src.mOcclusion; } static void eax_copy_send_for_get(const EAXSOURCEALLSENDPROPERTIES& src, EAXSOURCEEXCLUSIONSENDPROPERTIES& dst) noexcept { dst.guidReceivingFXSlotID = src.guidReceivingFXSlotID; dst.mExclusion = src.mExclusion; } template static void eax_get_sends(const EaxCall &call, const EaxSends &src_sends) { const auto dst_sends = call.as_span(EAX_MAX_FXSLOTS); for(const auto i : std::views::iota(0_uz, dst_sends.size())) { 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 std::span srcids); static void eax1_get(const EaxCall &call, const EAXBUFFER_REVERBPROPERTIES &props); static void eax2_get(const EaxCall &call, const EAX20BUFFERPROPERTIES &props); static void eax3_get(const EaxCall &call, const EAX30SOURCEPROPERTIES &props); static 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); static void eax5_get(const EaxCall &call, const Eax5Props &props); void eax_get(const EaxCall &call) const; static void eax_copy_send_for_set(const EAXSOURCEALLSENDPROPERTIES &src, EAXSOURCEALLSENDPROPERTIES &dst) noexcept { dst.mSend = src.mSend; dst.mOcclusion = src.mOcclusion; dst.mExclusion = src.mExclusion; } static void eax_copy_send_for_set(const EAXSOURCESENDPROPERTIES &src, EAXSOURCEALLSENDPROPERTIES &dst) noexcept { dst.mSend = src.mSend; } static void eax_copy_send_for_set(const EAXSOURCEOCCLUSIONSENDPROPERTIES &src, EAXSOURCEALLSENDPROPERTIES &dst) noexcept { dst.mOcclusion = src.mOcclusion; } static void eax_copy_send_for_set(const EAXSOURCEEXCLUSIONSENDPROPERTIES &src, EAXSOURCEALLSENDPROPERTIES &dst) noexcept { dst.mExclusion = src.mExclusion; } template TIndexGetter, typename TSrcSend> static void eax_defer_sends(const EaxCall &call, EaxSends &dst_sends, std::invocable auto&& validator) { const auto src_sends = call.as_span(EAX_MAX_FXSLOTS); std::ranges::for_each(src_sends, std::forward(validator)); std::ranges::for_each(src_sends, [&dst_sends](const TSrcSend &src_send) { const auto dst_index = std::invoke(TIndexGetter{}, src_send.guidReceivingFXSlotID); eax_copy_send_for_set(src_send, dst_sends[dst_index]); }); } template static void eax4_defer_sends(const EaxCall &call, EaxSends &dst_sends, std::invocable auto validator) { eax_defer_sends(call, dst_sends, std::move(validator)); } template static void eax5_defer_sends(const EaxCall &call, EaxSends &dst_sends, std::invocable auto validator) { eax_defer_sends(call, dst_sends, std::move(validator)); } template TValidator> static void eax_defer_active_fx_slot_id(const EaxCall &call, const std::span dst_ids) { const auto src_ids = call.as_span(dst_ids.size()); std::ranges::for_each(src_ids, TValidator{}); std::ranges::uninitialized_copy(src_ids, dst_ids); } static void eax4_defer_active_fx_slot_id(const EaxCall &call, const std::span dst_ids) { eax_defer_active_fx_slot_id(call, dst_ids); } static void eax5_defer_active_fx_slot_id(const EaxCall &call, const std::span dst_ids) { eax_defer_active_fx_slot_id(call, dst_ids); } template static void eax_defer(const EaxCall &call, TProperty &property, std::invocable auto&& validator) { const auto& value = call.load(); std::forward(validator)(value); property = value; } 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, EAXBUFFER_REVERBPROPERTIES& props); static void eax2_set(const EaxCall& call, EAX20BUFFERPROPERTIES& props); static void eax3_set(const EaxCall& call, EAX30SOURCEPROPERTIES& props); static 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); static 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(intrusive_ptr slot, usize sendidx, EaxAlLowPassParam const &filter); void eax_commit_active_fx_slots(); #endif // ALSOFT_EAX }; } /* namespace al */ void UpdateAllSourceProps(gsl::not_null context); struct SourceSubList { u64 mFreeMask{~0_u64}; gsl::owner*> mSources{nullptr}; SourceSubList() noexcept = default; SourceSubList(const SourceSubList&) = delete; SourceSubList(SourceSubList&& rhs) noexcept : mFreeMask{rhs.mFreeMask}, mSources{rhs.mSources} { rhs.mFreeMask = ~0_u64; rhs.mSources = nullptr; } ~SourceSubList(); SourceSubList& operator=(const SourceSubList&) = delete; SourceSubList& operator=(SourceSubList&& rhs) & noexcept { std::swap(mFreeMask, rhs.mFreeMask); std::swap(mSources, rhs.mSources); return *this; } }; #endif kcat-openal-soft-75c0059/al/state.cpp000066400000000000000000000554141512220627100173660ustar00rootroot00000000000000/** * 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 "alformat.hpp" #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 "gsl/gsl" #include "intrusive_ptr.h" #include "opthelpers.h" #include "strutils.hpp" #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"; } }; template<> struct ResamplerName { static constexpr const ALchar *Get() noexcept { return "47th order Sinc (fast)"; } }; template<> struct ResamplerName { static constexpr const ALchar *Get() noexcept { return "47th order Sinc"; } }; constexpr auto GetResamplerName(const Resampler rtype) -> const ALchar* { #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); HANDLE_RESAMPLER(Resampler::FastBSinc48); HANDLE_RESAMPLER(Resampler::BSinc48); } #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{al::format("Unexpected distance model {:#x}", al::to_underlying(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 [[nodiscard]] constexpr auto operator()(U&& value) const noexcept -> T { return gsl::narrow_cast(std::forward(value)); } }; /* Special-case ALboolean to be an actual bool instead of a char type. */ template<> struct PropertyCastType { template [[nodiscard]] constexpr auto operator()(U&& value) const noexcept -> ALboolean { return gsl::narrow_cast(std::forward(value)) ? AL_TRUE : AL_FALSE; } }; template void GetValue(gsl::not_null context, ALenum pname, T *values) noexcept { static constexpr auto cast_value = PropertyCastType{}; if(!values) [[unlikely]] return context->setError(AL_INVALID_VALUE, "NULL pointer"); switch(PropertyValue{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: { auto debuglock = std::lock_guard{context->mDebugCbLock}; *values = cast_value(context->mDebugLog.size()); return; } case AL_DEBUG_NEXT_LOGGED_MESSAGE_LENGTH_EXT: { auto debuglock = std::lock_guard{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 const device = al::get_not_null(context->mALDevice); auto devlock = std::lock_guard{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)); } template<> void GetValue(gsl::not_null 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: /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) */ *values = reinterpret_cast(context->mEventCb); return; case AL_EVENT_CALLBACK_USER_PARAM_SOFT: *values = context->mEventParam; return; case AL_DEBUG_CALLBACK_FUNCTION_EXT: /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) */ *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)); } inline void UpdateProps(al::Context *context) { if(!context->mDeferUpdates) UpdateContextProps(context); else context->mPropsDirty = true; } void alEnable(gsl::not_null context, ALenum capability) noexcept { switch(capability) { case AL_SOURCE_DISTANCE_MODEL: { auto proplock = std::lock_guard{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)); } void alDisable(gsl::not_null context, ALenum capability) noexcept { switch(capability) { case AL_SOURCE_DISTANCE_MODEL: { auto proplock = std::lock_guard{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)); } auto alIsEnabled(gsl::not_null context, ALenum capability) noexcept -> ALboolean { auto proplock = std::lock_guard{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; } auto alGetString(gsl::not_null context, ALenum pname) noexcept -> gsl::czstring { switch(pname) { case AL_VENDOR: if(auto &device = context->mALDevice; !device->mVendorOverride.empty()) return device->mVendorOverride.c_str(); return GetVendorString(); case AL_VERSION: if(auto &device = context->mALDevice; !device->mVersionOverride.empty()) return device->mVersionOverride.c_str(); return GetVersionString(); case AL_RENDERER: if(auto &device = context->mALDevice; !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; } void alDopplerFactor(gsl::not_null context, ALfloat value) noexcept { if(!(value >= 0.0f && std::isfinite(value))) context->setError(AL_INVALID_VALUE, "Doppler factor {} out of range", value); else { auto proplock = std::lock_guard{context->mPropLock}; context->mDopplerFactor = value; UpdateProps(context); } } void alSpeedOfSound(gsl::not_null context, ALfloat value) noexcept { if(!(value > 0.0f && std::isfinite(value))) context->setError(AL_INVALID_VALUE, "Speed of sound {} out of range", value); else { auto proplock = std::lock_guard{context->mPropLock}; context->mSpeedOfSound = value; UpdateProps(context); } } void alDistanceModel(gsl::not_null context, ALenum value) noexcept { if(auto model = DistanceModelFromALenum(value)) { auto proplock = std::lock_guard{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)); } auto alGetStringiSOFT(gsl::not_null context, ALenum pname, ALsizei index) noexcept -> gsl::czstring { switch(pname) { case AL_RESAMPLER_NAME_SOFT: if(index >= 0 && index <= al::to_underlying(Resampler::Max)) return GetResamplerName(gsl::narrow_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; } void alDeferUpdatesSOFT(gsl::not_null context) noexcept { auto proplock = std::lock_guard{context->mPropLock}; context->deferUpdates(); } void alProcessUpdatesSOFT(gsl::not_null context) noexcept { auto proplock = std::lock_guard{context->mPropLock}; context->processUpdates(); } } // 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) AL_API DECL_FUNC1(void, alDisable, ALenum,capability) AL_API DECL_FUNC1(ALboolean, alIsEnabled, ALenum,capability) #define DECL_GETFUNC(DECL, R, Name, Ext) \ DECL auto AL_APIENTRY Name##Ext(ALenum pname) noexcept -> R \ { \ auto value = R{}; \ auto context = GetContextRef(); \ if(context) [[likely]] \ GetValue(gsl::make_not_null(context.get()), pname, &value); \ return value; \ } \ FORCE_ALIGN auto AL_APIENTRY Name##Direct##Ext(ALCcontext *context, \ ALenum pname) noexcept -> R \ { \ auto value = R{}; \ GetValue(al::verify_context(context), pname, &value); \ return value; \ } \ DECL auto AL_APIENTRY Name##v##Ext(ALenum pname, R *values) noexcept -> void \ { \ auto context = GetContextRef(); \ if(context) [[likely]] \ GetValue(gsl::make_not_null(context.get()), pname, values); \ } \ FORCE_ALIGN auto AL_APIENTRY Name##v##Direct##Ext(ALCcontext *context, \ ALenum pname, R *values) noexcept -> void \ { \ GetValue(al::verify_context(context), pname, values); \ } DECL_GETFUNC(AL_API, ALboolean, alGetBoolean,) DECL_GETFUNC(AL_API, ALdouble, alGetDouble,) DECL_GETFUNC(AL_API, ALfloat, alGetFloat,) DECL_GETFUNC(AL_API, ALint, alGetInteger,) DECL_GETFUNC(FORCE_ALIGN, ALvoidptr, alGetPointer,EXT) DECL_GETFUNC(AL_API, ALint64SOFT, alGetInteger64,SOFT) DECL_GETFUNC(AL_API, ALvoidptr, alGetPointer,SOFT) #undef DECL_GETFUNC AL_API DECL_FUNC1(const ALchar*, alGetString, ALenum,pname) AL_API DECL_FUNC1(void, alDopplerFactor, ALfloat,value) AL_API DECL_FUNC1(void, alSpeedOfSound, ALfloat,value) AL_API DECL_FUNC1(void, alDistanceModel, ALenum,value) AL_API DECL_FUNCEXT(void, alDeferUpdates,SOFT) AL_API DECL_FUNCEXT(void, alProcessUpdates,SOFT) AL_API DECL_FUNCEXT2(const ALchar*, alGetStringi,SOFT, ALenum,pname, ALsizei,index) AL_API void AL_APIENTRY alDopplerVelocity(ALfloat value) noexcept { auto 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 {} out of range", value); else { auto proplock = std::lock_guard{context->mPropLock}; context->mDopplerVelocity = value; UpdateProps(context.get()); } } void UpdateContextProps(al::Context *context) { /* Get an unused property container, or allocate a new one as needed. */ auto *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.mPosition; props->Velocity = listener.mVelocity; props->OrientAt = listener.mOrientAt; props->OrientUp = listener.mOrientUp; props->Gain = listener.mGain; 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); } } kcat-openal-soft-75c0059/alc/000077500000000000000000000000001512220627100156745ustar00rootroot00000000000000kcat-openal-soft-75c0059/alc/alc.cpp000066400000000000000000003750111512220627100171460ustar00rootroot00000000000000/** * 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 #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 #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 "alconfig.h" #include "alformat.hpp" #include "alnumeric.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/fpu_ctrl.h" #include "core/front_stablizer.h" #include "core/helpers.h" #include "core/hrtf.h" #include "core/logging.h" #include "core/mastering.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 "fmt/ranges.h" #include "gsl/gsl" #include "inprogext.h" #include "intrusive_ptr.h" #include "opthelpers.h" #include "strutils.hpp" #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_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) /* NOLINTNEXTLINE(misc-use-internal-linkage) Needs external linkage for Windows. */ auto APIENTRY DllMain(HINSTANCE module, DWORD reason, LPVOID /*reserved*/) -> BOOL { 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); /* NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) */ break; } return TRUE; } #endif namespace { using namespace std::string_view_literals; using std::chrono::seconds; using std::chrono::nanoseconds; using voidp = void*; auto gProcessRunning = true; struct ProcessWatcher { ProcessWatcher() = default; ProcessWatcher(const ProcessWatcher&) = delete; ProcessWatcher& operator=(const ProcessWatcher&) = delete; ~ProcessWatcher() { gProcessRunning = false; } }; ProcessWatcher gProcessWatcher; /************************************************ * Backends ************************************************/ struct BackendInfo { std::string_view name; BackendFactory& (*getFactory)(); }; std::array BackendList{ #if HAVE_PIPEWIRE BackendInfo{"pipewire"sv, PipeWireBackendFactory::getFactory}, #endif #if HAVE_PULSEAUDIO BackendInfo{"pulse"sv, PulseBackendFactory::getFactory}, #endif #if HAVE_WASAPI BackendInfo{"wasapi"sv, WasapiBackendFactory::getFactory}, #endif #if HAVE_COREAUDIO BackendInfo{"core"sv, CoreAudioBackendFactory::getFactory}, #endif #if HAVE_OBOE BackendInfo{"oboe"sv, OboeBackendFactory::getFactory}, #endif #if HAVE_OPENSL BackendInfo{"opensl"sv, OSLBackendFactory::getFactory}, #endif #if HAVE_ALSA BackendInfo{"alsa"sv, AlsaBackendFactory::getFactory}, #endif #if HAVE_SOLARIS BackendInfo{"solaris"sv, SolarisBackendFactory::getFactory}, #endif #if HAVE_SNDIO BackendInfo{"sndio"sv, SndIOBackendFactory::getFactory}, #endif #if HAVE_OSS BackendInfo{"oss"sv, OSSBackendFactory::getFactory}, #endif #if HAVE_DSOUND BackendInfo{"dsound"sv, DSoundBackendFactory::getFactory}, #endif #if HAVE_WINMM BackendInfo{"winmm"sv, WinMMBackendFactory::getFactory}, #endif #if HAVE_PORTAUDIO BackendInfo{"port"sv, PortBackendFactory::getFactory}, #endif #if HAVE_SDL3 BackendInfo{"sdl3"sv, SDL3BackendFactory::getFactory}, #endif #if HAVE_SDL2 BackendInfo{"sdl2"sv, SDL2BackendFactory::getFactory}, #endif #if HAVE_JACK BackendInfo{"jack"sv, JackBackendFactory::getFactory}, #endif BackendInfo{"null"sv, NullBackendFactory::getFactory}, #if HAVE_WAVE BackendInfo{"wave"sv, 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 */ auto alcAllDevicesArray = std::vector{}; auto alcCaptureDeviceArray = std::vector{}; auto alcAllDevicesList = std::string{}; auto alcCaptureDeviceList = std::string{}; /* Default is always the first in the list */ auto alcDefaultAllDevicesSpecifier = std::string{}; auto alcCaptureDefaultDeviceSpecifier = std::string{}; /* Flag to specify if alcSuspendContext/alcProcessContext should defer/process * updates. */ auto SuspendDefers = true; /* Initial seed for dithering. */ constexpr auto DitherRNGSeed = 22222_u32; /************************************************ * ALC information ************************************************/ [[nodiscard]] constexpr auto GetNoDeviceExtString() noexcept -> gsl::czstring { 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 GetExtensionString() noexcept -> gsl::czstring { 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"; } /* Returns the above NoDeviceExt string as an array of string_views. */ [[nodiscard]] consteval auto GetNoDeviceExtArray() noexcept { constexpr auto extlist = std::string_view{GetNoDeviceExtString()}; auto ret = std::array{}; std::ranges::transform(extlist | std::views::split(' '), ret.begin(), [](auto&& namerange) { return std::string_view{namerange.begin(), namerange.end()}; }); return ret; } /* Returns the above Extension string as an array of string_views. */ [[nodiscard]] consteval auto GetExtensionArray() noexcept { constexpr auto extlist = std::string_view{GetExtensionString()}; auto ret = std::array{}; std::ranges::transform(extlist | std::views::split(' '), ret.begin(), [](auto&& namerange) { return std::string_view{namerange.begin(), namerange.end()}; }); return ret; } constexpr auto alcMajorVersion = 1_i32; constexpr auto alcMinorVersion = 1_i32; constexpr auto alcEFXMajorVersion = 1_i32; constexpr auto alcEFXMinorVersion = 0_i32; using DeviceRef = al::intrusive_ptr; /************************************************ * Device lists ************************************************/ auto DeviceList = std::vector>{}; auto ContextList = std::vector>{}; auto ListLock = std::recursive_mutex{}; /* NOLINT(cert-err58-cpp) May throw on construction? */ void alc_initconfig() { if(const auto loglevel = al::getenv("ALSOFT_LOGLEVEL")) { const auto lvl = strtol(loglevel->c_str(), nullptr, 0); if(lvl >= al::to_underlying(LogLevel::Trace)) gLogLevel = LogLevel::Trace; else if(lvl <= al::to_underlying(LogLevel::Disable)) gLogLevel = LogLevel::Disable; else gLogLevel = gsl::narrow_cast(lvl); } #ifdef _WIN32 if(const auto logfile = al::getenv(L"ALSOFT_LOGFILE")) al_open_logfile(*logfile); #else if(const auto logfile = al::getenv("ALSOFT_LOGFILE")) al_open_logfile(al::char_as_u8(*logfile)); #endif TRACE("Initializing library v{}-{} {}", ALSOFT_VERSION, std::string_view{ALSOFT_GIT_COMMIT_HASH}.empty() ? "unknown" : ALSOFT_GIT_COMMIT_HASH, std::string_view{ALSOFT_GIT_BRANCH}.empty() ? "unknown" : ALSOFT_GIT_BRANCH); { auto names = std::array{}; std::ranges::transform(BackendList, names.begin(), &BackendInfo::name); TRACE("{}", fmt::format("Supported backends: {}", fmt::join(names, ", "))); } ReadALConfig(); if(auto const 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); } auto capfilter = 0_i32; #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)) { if(auto const cpulist = std::string_view{*cpuopt}; al::case_compare(cpulist, "all"sv) == 0) capfilter = 0; else { std::ranges::for_each(cpulist | std::views::split(','), [&capfilter](auto&& namerange) { auto entry = std::string_view{namerange.begin(), namerange.end()}; constexpr auto wspace_chars = " \t\n\f\r\v"sv; entry.remove_prefix(std::min(entry.find_first_not_of(wspace_chars), entry.size())); entry.remove_suffix(entry.size() - (entry.find_last_not_of(wspace_chars)+1)); if(entry.empty()) return; 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 const cpuopt = GetCPUInfo()) { if(!cpuopt->mVendor.empty() || !cpuopt->mName.empty()) { TRACE("Vendor ID: \"{}\"", cpuopt->mVendor); TRACE("Name: \"{}\"", cpuopt->mName); } auto const 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 = ConfigValueI32({}, {}, "rt-prio"sv)) RTPrioLevel = *priopt; if(auto limopt = ConfigValueBool({}, {}, "rt-time-limit"sv)) AllowRTTimeLimit = *limopt; { auto compatflags = CompatFlagBitset{}; static constexpr auto checkflag = [](gsl::czstring const envname, std::string_view const optname) -> bool { if(auto const 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, ConfigValueF32({}, "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; al::Device::sTrapALCError = 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) al::Device::sTrapALCError = al::case_compare(*traperr, "true"sv) == 0 || strtol(traperr->c_str(), nullptr, 0) == 1; else al::Device::sTrapALCError = GetConfigValueBool({}, {}, "trap-alc-error"sv, false); } if(auto boostopt = ConfigValueF32({}, "reverb"sv, "boost"sv)) { const auto valf = std::isfinite(*boostopt) ? std::clamp(*boostopt, -24.0f, 24.0f) : 0.0f; ReverbBoost *= std::pow(10.0f, valf / 20.0f); } auto backends = std::span{BackendList}.subspan(0); auto drvopt = al::getenv("ALSOFT_DRIVERS"); if(!drvopt) drvopt = ConfigValueStr({}, {}, "drivers"sv); if(drvopt) { auto BackendListEnd = backends.end(); auto backendlist_cur = backends.begin(); auto endlist = true; std::ranges::for_each(*drvopt | std::views::split(','), [backends,&BackendListEnd,&backendlist_cur,&endlist](auto&& namerange) { auto entry = std::string_view{namerange.begin(), namerange.end()}; constexpr auto whitespace_chars = " \t\n\f\r\v"sv; entry.remove_prefix(std::min(entry.find_first_not_of(whitespace_chars), entry.size())); entry.remove_suffix(entry.size() - (entry.find_last_not_of(whitespace_chars)+1)); if(entry.empty()) { endlist = false; return; } endlist = true; const auto delitem = (!entry.empty() && entry.front() == '-'); if(delitem) entry.remove_prefix(1); #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 const auto this_backend = std::ranges::find(backends.begin(), BackendListEnd, entry, &BackendInfo::name); if(this_backend == BackendListEnd) return; 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; backends = std::span{backends.begin(), BackendListEnd}; } 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. */ const auto reversenamerange = backends | std::views::reverse; const auto iter = std::ranges::find(reversenamerange, "null"sv, &BackendInfo::name); backends = std::span{backends.begin(), iter.base()}; } std::ignore = std::ranges::find_if(backends, [](const BackendInfo &backend) { auto &factory = backend.getFactory(); if(!factory.init()) { WARN("Failed to initialize backend \"{}\"", backend.name); return false; } 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); } return (PlaybackFactory && CaptureFactory); }); LoopbackBackendFactory::getFactory().init(); if(!PlaybackFactory) WARN("No playback backend available!"); if(!CaptureFactory) WARN("No capture backend available!"); if(auto exclopt = ConfigValueStr({}, {}, "excludefx"sv)) { std::ranges::for_each(*exclopt | std::views::split(','), [](auto&& namerange) noexcept { const auto entry = std::string_view{namerange.begin(), namerange.end()}; std::ranges::for_each(gEffectList, [entry](const EffectList &effectitem) noexcept { if(entry == effectitem.name) DisabledEffects.set(effectitem.type); }); }); } InitEffect(&al::Context::sDefaultEffect); auto defrevopt = al::getenv("ALSOFT_DEFAULT_REVERB"); if(!defrevopt) defrevopt = ConfigValueStr({}, {}, "default-reverb"sv); if(defrevopt) LoadReverbPreset(*defrevopt, &al::Context::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); } if(eax_g_is_enabled) { if(auto optval = al::getenv("ALSOFT_EAX_TRACE_COMMITS")) { EaxTraceCommits = al::case_compare(*optval, "true"sv) == 0 || strtol(optval->c_str(), nullptr, 0) == 1; } else EaxTraceCommits = GetConfigValueBool({}, "eax"sv, "trace-commits"sv, false); } #endif // ALSOFT_EAX } void InitConfig() { static constinit auto init_once = std::once_flag{}; std::call_once(init_once, [] { alc_initconfig(); }); } /************************************************ * Device enumeration ************************************************/ void ProbeAllDevicesList() { InitConfig(); auto listlock = std::lock_guard{ListLock}; if(!PlaybackFactory) { decltype(alcAllDevicesArray){}.swap(alcAllDevicesArray); decltype(alcAllDevicesList){}.swap(alcAllDevicesList); } else { alcAllDevicesArray = PlaybackFactory->enumerate(BackendType::Playback); if constexpr(constexpr auto prefix = GetDevicePrefix(); !prefix.empty()) std::ranges::for_each(alcAllDevicesArray, [prefix](std::string &name) { name.insert(0, prefix); }); decltype(alcAllDevicesList){}.swap(alcAllDevicesList); if(alcAllDevicesArray.empty()) alcAllDevicesList += '\0'; else { std::ranges::for_each(alcAllDevicesArray, [](const std::string_view devname) { alcAllDevicesList.append(devname) += '\0'; }); } } } void ProbeCaptureDeviceList() { InitConfig(); auto listlock = std::lock_guard{ListLock}; if(!CaptureFactory) { decltype(alcCaptureDeviceArray){}.swap(alcCaptureDeviceArray); decltype(alcCaptureDeviceList){}.swap(alcCaptureDeviceList); } else { alcCaptureDeviceArray = CaptureFactory->enumerate(BackendType::Capture); if constexpr(constexpr auto prefix = GetDevicePrefix(); !prefix.empty()) std::ranges::for_each(alcCaptureDeviceArray, [prefix](std::string &name) { name.insert(0, prefix); }); decltype(alcCaptureDeviceList){}.swap(alcCaptureDeviceList); if(alcCaptureDeviceArray.empty()) alcCaptureDeviceList += '\0'; else { std::ranges::for_each(alcCaptureDeviceArray, [](const std::string_view devname) { alcCaptureDeviceList.append(devname) += '\0'; }); } } } struct AttributePair { ALCint attribute; ALCint value; }; static_assert(sizeof(AttributePair) == sizeof(int)*2); auto SpanFromAttributeList(ALCint const *const attribs LIFETIMEBOUND) noexcept -> std::span { auto attrSpan = std::span{}; if(attribs && *attribs != 0) { /* NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic) */ auto *attrEnd = attribs+2; while(*attrEnd != 0) attrEnd += 2; /* NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic) */ /* NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast) */ attrSpan = {std::launder(reinterpret_cast(attribs)), reinterpret_cast(attrEnd)}; /* NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast) */ } return attrSpan; } struct DevFmtPair { DevFmtChannels chans; DevFmtType type; }; auto DecomposeDevFormat(ALenum const format) -> std::optional { struct FormatType { ALenum alformat; DevFmtPair formatpair; }; static constexpr auto list = std::array{ 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}}, }; if(const auto item = std::ranges::find(list, format, &FormatType::alformat); item != list.end()) return item->formatpair; return std::nullopt; } auto DevFmtTypeFromEnum(ALCenum const type) -> std::optional { 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; } auto EnumFromDevFmt(DevFmtType const type) -> ALCenum { 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{al::format("Invalid DevFmtType: {}", int{al::to_underlying(type)})}; } auto DevFmtChannelsFromEnum(ALCenum const channels) -> std::optional { 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; } auto EnumFromDevFmt(DevFmtChannels const channels) -> ALCenum { 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{al::format("Invalid DevFmtChannels: {}", int{al::to_underlying(channels)})}; } auto DevAmbiLayoutFromEnum(ALCenum const layout) -> std::optional { 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; } auto EnumFromDevAmbi(DevAmbiLayout const layout) -> ALCenum { switch(layout) { case DevAmbiLayout::FuMa: return ALC_FUMA_SOFT; case DevAmbiLayout::ACN: return ALC_ACN_SOFT; } throw std::runtime_error{al::format("Invalid DevAmbiLayout: {}", int{al::to_underlying(layout)})}; } auto DevAmbiScalingFromEnum(ALCenum const scaling) -> std::optional { 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; } auto EnumFromDevAmbi(DevAmbiScaling const scaling) -> ALCenum { 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{al::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 auto inv_sqrt2f = gsl::narrow_cast(1.0 / std::numbers::sqrt2); constexpr auto FrontStereo3dB = std::array{ InputRemixMap::TargetMix{FrontLeft, inv_sqrt2f}, InputRemixMap::TargetMix{FrontRight, inv_sqrt2f} }; constexpr auto FrontStereo6dB = std::array{ InputRemixMap::TargetMix{FrontLeft, 0.5f}, InputRemixMap::TargetMix{FrontRight, 0.5f} }; constexpr auto SideStereo3dB = std::array{ InputRemixMap::TargetMix{SideLeft, inv_sqrt2f}, InputRemixMap::TargetMix{SideRight, inv_sqrt2f} }; constexpr auto BackStereo3dB = std::array{ InputRemixMap::TargetMix{BackLeft, inv_sqrt2f}, InputRemixMap::TargetMix{BackRight, inv_sqrt2f} }; constexpr auto FrontLeft3dB = std::array{InputRemixMap::TargetMix{FrontLeft, inv_sqrt2f}}; constexpr auto FrontRight3dB = std::array{InputRemixMap::TargetMix{FrontRight, inv_sqrt2f}}; constexpr auto SideLeft0dB = std::array{InputRemixMap::TargetMix{SideLeft, 1.0f}}; constexpr auto SideRight0dB = std::array{InputRemixMap::TargetMix{SideRight, 1.0f}}; constexpr auto BackLeft0dB = std::array{InputRemixMap::TargetMix{BackLeft, 1.0f}}; constexpr auto BackRight0dB = std::array{InputRemixMap::TargetMix{BackRight, 1.0f}}; constexpr auto BackCenter3dB = std::array{InputRemixMap::TargetMix{BackCenter, inv_sqrt2f}}; constexpr auto StereoDownmix = std::array{ InputRemixMap{FrontCenter, FrontStereo3dB}, InputRemixMap{SideLeft, FrontLeft3dB}, InputRemixMap{SideRight, FrontRight3dB}, InputRemixMap{BackLeft, FrontLeft3dB}, InputRemixMap{BackRight, FrontRight3dB}, InputRemixMap{BackCenter, FrontStereo6dB}, }; constexpr auto QuadDownmix = std::array{ InputRemixMap{FrontCenter, FrontStereo3dB}, InputRemixMap{SideLeft, BackLeft0dB}, InputRemixMap{SideRight, BackRight0dB}, InputRemixMap{BackCenter, BackStereo3dB}, }; constexpr auto X51Downmix = std::array{ InputRemixMap{BackLeft, SideLeft0dB}, InputRemixMap{BackRight, SideRight0dB}, InputRemixMap{BackCenter, SideStereo3dB}, }; constexpr auto X61Downmix = std::array{ InputRemixMap{BackLeft, BackCenter3dB}, InputRemixMap{BackRight, BackCenter3dB}, }; constexpr auto X71Downmix = std::array{ InputRemixMap{BackCenter, BackStereo3dB}, }; auto CreateDeviceLimiter(gsl::not_null const device, f32 const threshold) -> std::unique_ptr { static constexpr auto LookAheadTime = 0.001f; static constexpr auto HoldTime = 0.002f; static constexpr auto PreGainDb = 0.0f; static constexpr auto PostGainDb = 0.0f; static constexpr auto Ratio = std::numeric_limits::infinity(); static constexpr auto KneeDb = 0.0f; static constexpr auto AttackTime = 0.02f; static constexpr auto ReleaseTime = 0.2f; auto const 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(), gsl::narrow_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. */ void UpdateClockBase(gsl::not_null const device) { using std::chrono::duration_cast; auto const 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(gsl::not_null device, std::span const attrList) -> ALCenum { if(attrList.empty() && device->Type == DeviceType::Loopback) { WARN("Missing attributes for loopback device"); return ALC_INVALID_VALUE; } auto numMono = device->NumMonoSources; auto numStereo = device->NumStereoSources; auto numSends = device->NumAuxSends; auto stereomode = std::optional{}; auto optlimit = std::optional{}; auto optsrate = std::optional{}; auto optchans = std::optional{}; auto opttype = std::optional{}; auto optlayout = std::optional{}; auto optscale = std::optional{}; auto period_size = u32{DefaultUpdateSize}; auto buffer_size = u32{DefaultUpdateSize * DefaultNumUpdates}; auto hrtf_id = -1_i32; auto aorder = 0_u32; 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{gsl::narrow_cast(*optsrate) / f64{DefaultOutputRate}}; period_size = gsl::narrow_cast(std::lround(period_size * scale)); } if(auto persizeopt = device->configValue({}, "period_size")) period_size = std::clamp(*persizeopt, 64_u32, 8192_u32); if(auto numperopt = device->configValue({}, "periods")) buffer_size = std::clamp(*numperopt, 2_u32, 16_u32) * 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 }, }; const auto iter = std::ranges::find_if(typelist, [svfmt=std::string_view{*typeopt}](TypeMap const &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 const chanopt = device->configValue({}, "channels"); chanopt && al::case_compare(*chanopt, "surround3d71"sv) != 0) { struct ChannelMap { std::string_view name; DevFmtChannels chans; u8 order; }; constexpr auto chanlist = std::array{ 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{"3d71"sv, DevFmtX3D71, 0}, ChannelMap{"surround714"sv, DevFmtX714, 0}, ChannelMap{"surround7144"sv, DevFmtX7144, 0}, ChannelMap{"ambi1"sv, DevFmtAmbi3D, 1}, ChannelMap{"ambi2"sv, DevFmtAmbi3D, 2}, ChannelMap{"ambi3"sv, DevFmtAmbi3D, 3}, ChannelMap{"ambi4"sv, DevFmtAmbi3D, 4}, ChannelMap{"surround51rear"sv, DevFmtX51, 0}, }; const auto iter = std::ranges::find_if(chanlist, [svfmt=std::string_view{*chanopt}](ChannelMap const &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; } } else if(chanopt) { ERR("Channels setting {} is deprecated;", *chanopt); ERR(" If you mean 7.1 surround sound, please use \"surround71\""); ERR(" Otherwise, if 3D7.1 specifically is set up correctly, please use \"3d71\""); } 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(aorder > 3 && (optlayout == DevAmbiLayout::FuMa || optscale == DevAmbiScaling::FuMa)) { ERR("FuMa unsupported with {}{} order ambisonics", aorder, GetCounterSuffix(aorder)); optlayout = DevAmbiLayout::Default; optscale = DevAmbiScaling::Default; } 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()) { auto outmode = ALenum{ALC_ANY_SOFT}; auto opthrtf = std::optional{}; auto freqAttr = i32{}; for(const auto attrparam : attrList) { #define ATTRIBUTE(a) a: TRACE("{} = {}", #a, attrparam.value); #define ATTRIBUTE_HEX(a) a: TRACE("{} = {:#x}", #a, as_unsigned(attrparam.value)); switch(attrparam.attribute) { case ATTRIBUTE_HEX(ALC_FORMAT_CHANNELS_SOFT) if(device->Type == DeviceType::Loopback) optchans = DevFmtChannelsFromEnum(attrparam.value); break; case ATTRIBUTE_HEX(ALC_FORMAT_TYPE_SOFT) if(device->Type == DeviceType::Loopback) opttype = DevFmtTypeFromEnum(attrparam.value); break; case ATTRIBUTE(ALC_FREQUENCY) freqAttr = attrparam.value; break; case ATTRIBUTE_HEX(ALC_AMBISONIC_LAYOUT_SOFT) if(device->Type == DeviceType::Loopback) optlayout = DevAmbiLayoutFromEnum(attrparam.value); break; case ATTRIBUTE_HEX(ALC_AMBISONIC_SCALING_SOFT) if(device->Type == DeviceType::Loopback) optscale = DevAmbiScalingFromEnum(attrparam.value); break; case ATTRIBUTE(ALC_AMBISONIC_ORDER_SOFT) if(device->Type == DeviceType::Loopback) aorder = gsl::narrow_cast(attrparam.value); break; case ATTRIBUTE(ALC_MONO_SOURCES) if(const auto val = attrparam.value; val >= 0) numMono = gsl::narrow_cast(val); else numMono = 0; break; case ATTRIBUTE(ALC_STEREO_SOURCES) if(const auto val = attrparam.value; val >= 0) numStereo = gsl::narrow_cast(val); else numStereo = 0; break; case ATTRIBUTE(ALC_MAX_AUXILIARY_SENDS) if(const auto val = attrparam.value; val >= 0) numSends = std::min(gsl::narrow_cast(val), uint{MaxSendCount}); else numSends = 0; break; case ATTRIBUTE(ALC_HRTF_SOFT) if(attrparam.value == ALC_FALSE) opthrtf = false; else if(attrparam.value == ALC_TRUE) opthrtf = true; else if(attrparam.value == ALC_DONT_CARE_SOFT) opthrtf = std::nullopt; break; case ATTRIBUTE(ALC_HRTF_ID_SOFT) hrtf_id = attrparam.value; break; case ATTRIBUTE(ALC_OUTPUT_LIMITER_SOFT) if(attrparam.value == ALC_FALSE) optlimit = false; else if(attrparam.value == ALC_TRUE) optlimit = true; else if(attrparam.value == ALC_DONT_CARE_SOFT) optlimit = std::nullopt; break; case ATTRIBUTE_HEX(ALC_OUTPUT_MODE_SOFT) outmode = attrparam.value; break; case ATTRIBUTE_HEX(ALC_CONTEXT_FLAGS_EXT) /* Handled in alcCreateContext */ break; case ATTRIBUTE(ALC_REFRESH) /* Ignored attribute */ break; case ATTRIBUTE(ALC_SYNC) /* Ignored attribute */ break; default: TRACE("{:#04x} = {} ({:#x})", as_unsigned(attrparam.attribute), attrparam.value, as_unsigned(attrparam.value)); 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 = gsl::narrow_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 > 0) { auto oldrate = optsrate.value_or(DefaultOutputRate); freqAttr = std::clamp(freqAttr, MinOutputRate, MaxOutputRate); const auto scale = gsl::narrow_cast(freqAttr) / oldrate; period_size = gsl::narrow_cast(std::lround(period_size * scale)); buffer_size = gsl::narrow_cast(std::lround(buffer_size * scale)); optsrate = gsl::narrow_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->mPostProcess.emplace(); device->Limiter = nullptr; device->ChannelDelays = nullptr; device->HrtfAccumData.fill(f32x2{}); device->Dry.AmbiMap.fill(BFChannelConfig{}); device->Dry.Buffer = {}; device->NumChannelsPerOrder.fill(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 auto oldFreq = device->mSampleRate; const auto oldChans = device->FmtChans; const auto oldType = device->FmtType; try { if(auto *const backend = device->Backend.get(); !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, 16_u32); } else { numMono = std::min(numMono, std::numeric_limits::max()-numStereo); numMono = std::max(numMono+numStereo, 256_u32); } 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, u32{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(!std::holds_alternative(device->mPostProcess)) 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; } auto sample_delay = 0_uz; if(auto *uhjenc = std::get_if(&device->mPostProcess)) sample_delay += uhjenc->mUhjEncoder->getDelay(); if(device->getConfigValueBool({}, "dither"sv, true)) { auto 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 = gsl::narrow_cast(1 << (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 { auto 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 auto 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()); auto mixer_mode = FPUCtl{}; std::ranges::for_each(*device->mContexts.load(), [device](ContextBase *ctxbase) { /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-static-cast-downcast) */ auto const context = gsl::make_not_null(static_cast(ctxbase)); auto proplock = std::unique_lock{context->mPropLock}; auto slotlock = std::unique_lock{context->mEffectSlotLock}; /* Remove unused effect slot clusters. */ auto slotcluster_rem = std::ranges::remove_if(context->mEffectSlotClusters, [](ContextBase::EffectSlotCluster &clusterptr) -> bool { return std::ranges::none_of(*clusterptr, &EffectSlotBase::InUse); }); context->mEffectSlotClusters.erase(slotcluster_rem.begin(), slotcluster_rem.end()); /* Free all wet buffers. Any in use will be reallocated with an updated * configuration in aluInitEffectPanning. */ std::ranges::for_each(context->mEffectSlotClusters | std::views::transform(&ContextBase::EffectSlotCluster::operator*) | std::views::join, [](EffectSlotBase &slot) { slot.mWetBuffer.clear(); slot.mWetBuffer.shrink_to_fit(); slot.Wet.Buffer = {}; }); if(auto *slot = context->mDefaultSlot.get()) { const auto slotbase = slot->mSlot; aluInitEffectPanning(slotbase, context); if(auto *props = slotbase->Update.exchange(nullptr, std::memory_order_relaxed)) AtomicReplaceHead(context->mFreeEffectSlotProps, props); auto *state = slot->mEffect.State.get(); state->mOutTarget = device->Dry.Buffer; state->deviceUpdate(device, slot->mBuffer.get()); slot->mPropsDirty = true; } if(auto *curarray = context->mActiveAuxSlots.load(std::memory_order_relaxed)) std::ranges::fill(*curarray | std::views::drop(curarray->size()>>1), nullptr); std::ranges::for_each(context->mEffectSlotList,[device,context](EffectSlotSubList &sublist) { auto usemask = ~sublist.mFreeMask; while(usemask) { const auto idx = gsl::narrow_cast(std::countr_zero(usemask)); auto &slot = (*sublist.mEffectSlots)[idx]; usemask &= ~(1_u64 << idx); const auto slotbase = slot.mSlot; aluInitEffectPanning(slotbase, context); if(auto *props = slotbase->Update.exchange(nullptr, std::memory_order_relaxed)) AtomicReplaceHead(context->mFreeEffectSlotProps, props); auto &state = *slot.mEffect.State; state.mOutTarget = device->Dry.Buffer; state.deviceUpdate(device, slot.mBuffer.get()); slot.mPropsDirty = true; } }); /* Clear all effect slot props to let them get allocated again. */ context->mEffectSlotPropClusters.clear(); context->mFreeEffectSlotProps.store(nullptr, std::memory_order_relaxed); slotlock.unlock(); auto srclock = std::unique_lock{context->mSourceLock}; const auto num_sends = device->NumAuxSends; std::ranges::for_each(context->mSourceList, [num_sends](SourceSubList &sublist) { auto usemask = ~sublist.mFreeMask; while(usemask) { const auto idx = gsl::narrow_cast(std::countr_zero(usemask)); auto &source = (*sublist.mSources)[idx]; usemask &= ~(1_u64 << idx); const auto sendrange = source.mSend | std::views::drop(num_sends); std::ranges::fill(sendrange, al::Source::SendData{.mSlot={}, .mGain=1.0f, .mGainHF=1.0f, .mHFReference=LowPassFreqRef, .mGainLF=1.0f, .mLFReference=HighPassFreqRef}); source.mPropsDirty = true; } }); std::ranges::for_each(context->getVoicesSpan(), [device,num_sends,context](Voice *voice) { /* Clear extraneous property set sends. */ std::ranges::fill(voice->mProps.Send | std::views::drop(num_sends), VoiceProps::SendData{}); std::ranges::fill(voice->mSend | std::views::drop(num_sends), Voice::TargetData{}); /* FIXME: Not sure why fill(..., SendParams{}); doesn't work here, * while this does? */ for(auto &chan : voice->mChans) std::ranges::for_each(chan.mWetParams | std::views::drop(num_sends), [](SendParams ¶ms) { params = SendParams{}; }); if(auto *props = voice->mUpdate.exchange(nullptr, std::memory_order_relaxed)) AtomicReplaceHead(context->mFreeVoiceProps, props); /* Force the voice to "Stopped" if it was stopping. */ auto 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); }); /* Clear all voice props to let them get allocated again. */ context->mFreeVoiceProps.store(nullptr, std::memory_order_relaxed); context->mVoicePropClusters.clear(); srclock.unlock(); context->mPropsDirty = false; UpdateContextProps(context); UpdateAllEffectSlotProps(context); UpdateAllSourceProps(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(gsl::not_null device, const std::span attrList) -> bool { /* If the device was disconnected, reset it since we're opened anew. */ if(!device->Connected.load(std::memory_order_relaxed)) { /* Make sure disconnection is finished before continuing on. */ std::ignore = device->waitForMix(); std::ranges::for_each(*device->mContexts.load(std::memory_order_acquire), [](ContextBase *ctxbase) { /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-static-cast-downcast) */ auto const ctx = gsl::make_not_null(static_cast(ctxbase)); if(!ctx->mStopVoicesOnDisconnect.load(std::memory_order_acquire)) return; /* Clear any pending voice changes and reallocate voices to get a * clean restart. */ auto srclock = std::lock_guard{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); } auto err = UpdateDeviceParams(device, attrList); if(err == ALC_NO_ERROR) [[likely]] return ALC_TRUE; device->setError(err); return ALC_FALSE; } /** * Checks if the given device is a valid handle, returning a new reference to * it or setting the global error state and throwing an exception. */ auto VerifyDevice(ALCdevice *device) -> gsl::not_null { auto listlock = std::lock_guard{ListLock}; const auto iter = std::ranges::lower_bound(DeviceList, device); if(iter != DeviceList.end() && *iter == device) { (*iter)->inc_ref(); return gsl::make_not_null(DeviceRef{*iter}); } al::Device::SetGlobalError(ALC_INVALID_DEVICE); throw al::base_exception{al::format("Invalid device handle {}", voidp{device})}; } /** * Checks if the given context is a valid handle, returning a new reference to * it or setting the global error state and throwing an exception. */ auto VerifyContext(ALCcontext *context) -> gsl::not_null { auto listlock = std::lock_guard{ListLock}; const auto iter = std::ranges::lower_bound(ContextList, context); if(iter != ContextList.end() && *iter == context) { (*iter)->inc_ref(); return gsl::make_not_null(ContextRef{*iter}); } al::Device::SetGlobalError(ALC_INVALID_CONTEXT); throw al::base_exception{al::format("Invalid context handle {}", voidp{context})}; } } // 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 { auto *context = al::Context::getThreadContext(); if(context) context->inc_ref(); else { while(al::Context::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 = al::Context::sGlobalContext.load(std::memory_order_acquire); if(context) [[likely]] context->inc_ref(); al::Context::sGlobalContextLock.store(false, std::memory_order_release); } return ContextRef{context}; } /************************************************ * Standard ALC functions ************************************************/ ALC_API ALCenum ALC_APIENTRY alcGetError(ALCdevice *device) noexcept { if(!gProcessRunning) return ALC_INVALID_DEVICE; if(!device) return al::Device::sLastGlobalError.exchange(ALC_NO_ERROR); try { return VerifyDevice(device)->mLastError.exchange(ALC_NO_ERROR); } catch(al::base_exception&) { /* Will return ALC_INVALID_DEVICE. */ return al::Device::sLastGlobalError.exchange(ALC_NO_ERROR); } } ALC_API void ALC_APIENTRY alcSuspendContext(ALCcontext *context) noexcept try { auto ctx = VerifyContext(context); 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) { auto proplock = std::lock_guard{ctx->mPropLock}; ctx->deferUpdates(); } } catch(al::base_exception&) { } ALC_API void ALC_APIENTRY alcProcessContext(ALCcontext *context) noexcept try { auto ctx = VerifyContext(context); 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) { auto proplock = std::lock_guard{ctx->mPropLock}; ctx->processUpdates(); } } catch(al::base_exception&) { } ALC_API auto ALC_APIENTRY alcGetString(ALCdevice *Device, ALCenum param) noexcept -> const ALCchar* try { 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(!Device) { ProbeAllDevicesList(); return alcAllDevicesList.c_str(); } { auto dev = VerifyDevice(Device); if(dev->Type == DeviceType::Capture) { dev->setError(ALC_INVALID_ENUM); return nullptr; } if(dev->Type == DeviceType::Loopback) return GetDefaultName(); auto statelock = std::lock_guard{dev->StateLock}; return dev->mDeviceName.c_str(); } case ALC_CAPTURE_DEVICE_SPECIFIER: if(!Device) { ProbeCaptureDeviceList(); return alcCaptureDeviceList.c_str(); } { auto dev = VerifyDevice(Device); if(dev->Type != DeviceType::Capture) { dev->setError(ALC_INVALID_ENUM); return nullptr; } auto statelock = std::lock_guard{dev->StateLock}; return dev->mDeviceName.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()) alcDefaultAllDevicesSpecifier = alcAllDevicesArray.front(); else alcDefaultAllDevicesSpecifier.clear(); return alcDefaultAllDevicesSpecifier.c_str(); case ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER: if(alcCaptureDeviceList.empty()) ProbeCaptureDeviceList(); /* Copy first entry as default. */ if(!alcCaptureDeviceArray.empty()) alcCaptureDefaultDeviceSpecifier = alcCaptureDeviceArray.front(); else alcCaptureDefaultDeviceSpecifier.clear(); return alcCaptureDefaultDeviceSpecifier.c_str(); case ALC_EXTENSIONS: if(!Device) return GetNoDeviceExtString(); return GetExtensionString(); case ALC_HRTF_SPECIFIER_SOFT: { auto dev = VerifyDevice(Device); auto statelock = std::lock_guard{dev->StateLock}; return dev->mHrtf ? dev->mHrtfName.c_str() : ""; } default: if(!Device) al::Device::SetGlobalError(ALC_INVALID_ENUM); else VerifyDevice(Device)->setError(ALC_INVALID_ENUM); } return nullptr; } catch(al::base_exception&) { return nullptr; } namespace { auto GetIntegerv(al::Device *device, ALCenum param, const std::span values) -> size_t { Expects(!values.empty()); 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: al::Device::SetGlobalError(ALC_INVALID_DEVICE); return 0; default: al::Device::SetGlobalError(ALC_INVALID_ENUM); } return 0; } auto statelock = std::lock_guard{device->StateLock}; if(device->Type == DeviceType::Capture) { static constexpr auto MaxCaptureAttributes = 9; switch(param) { case ALC_ATTRIBUTES_SIZE: values[0] = MaxCaptureAttributes; return 1; case ALC_ALL_ATTRIBUTES: if(values.size() >= MaxCaptureAttributes) { auto i = 0_uz; values[i++] = ALC_MAJOR_VERSION; values[i++] = alcMajorVersion; values[i++] = ALC_MINOR_VERSION; values[i++] = alcMinorVersion; values[i++] = ALC_CAPTURE_SAMPLES; values[i++] = gsl::narrow_cast(device->Backend->availableSamples()); values[i++] = ALC_CONNECTED; values[i++] = device->Connected.load(std::memory_order_relaxed); values[i++] = 0; Ensures(i == MaxCaptureAttributes); return i; } device->setError(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] = gsl::narrow_cast(device->Backend->availableSamples()); return 1; case ALC_CONNECTED: values[0] = device->Connected.load(std::memory_order_acquire); return 1; default: device->setError(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()) { auto i = 0_uz; 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++] = gsl::narrow_cast(device->mSampleRate); if(device->Type != DeviceType::Loopback) { values[i++] = ALC_REFRESH; values[i++] = gsl::narrow_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++] = gsl::narrow_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++] = gsl::narrow_cast(device->NumMonoSources); values[i++] = ALC_STEREO_SOURCES; values[i++] = gsl::narrow_cast(device->NumStereoSources); values[i++] = ALC_MAX_AUXILIARY_SENDS; values[i++] = gsl::narrow_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++] = al::to_underlying(device->getOutputMode1()); values[i++] = 0; Ensures(i == NumAttrsForDevice()); return i; } device->setError(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] = gsl::narrow_cast(device->mSampleRate); return 1; case ALC_REFRESH: if(device->Type == DeviceType::Loopback) { device->setError(ALC_INVALID_DEVICE); return 0; } values[0] = gsl::narrow_cast(device->mSampleRate / device->mUpdateSize); return 1; case ALC_SYNC: if(device->Type == DeviceType::Loopback) { device->setError(ALC_INVALID_DEVICE); return 0; } values[0] = ALC_FALSE; return 1; case ALC_FORMAT_CHANNELS_SOFT: if(device->Type != DeviceType::Loopback) { device->setError(ALC_INVALID_DEVICE); return 0; } values[0] = EnumFromDevFmt(device->FmtChans); return 1; case ALC_FORMAT_TYPE_SOFT: if(device->Type != DeviceType::Loopback) { device->setError(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) { device->setError(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) { device->setError(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) { device->setError(ALC_INVALID_DEVICE); return 0; } values[0] = gsl::narrow_cast(device->mAmbiOrder); return 1; case ALC_MONO_SOURCES: values[0] = gsl::narrow_cast(device->NumMonoSources); return 1; case ALC_STEREO_SOURCES: values[0] = gsl::narrow_cast(device->NumStereoSources); return 1; case ALC_MAX_AUXILIARY_SENDS: values[0] = gsl::narrow_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] = al::saturate_cast(device->mHrtfList.size()); 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] = al::to_underlying(device->getOutputMode1()); return 1; default: device->setError(ALC_INVALID_ENUM); } return 0; } } // namespace ALC_API void ALC_APIENTRY alcGetIntegerv(ALCdevice *device, ALCenum param, ALCsizei size, ALCint *values) noexcept try { auto dev = device ? VerifyDevice(device).get() : DeviceRef{}; if(size <= 0 || values == nullptr) { if(dev) dev->setError(ALC_INVALID_VALUE); else al::Device::SetGlobalError(ALC_INVALID_VALUE); } else GetIntegerv(std::to_address(dev), param, std::views::counted(values, size)); } catch(al::base_exception&) { } ALC_API void ALC_APIENTRY alcGetInteger64vSOFT(ALCdevice *device, ALCenum pname, ALCsizei size, ALCint64SOFT *values) noexcept try { auto dev = device ? VerifyDevice(device).get() : DeviceRef{}; if(size <= 0 || values == nullptr) { if(dev) dev->setError(ALC_INVALID_VALUE); else al::Device::SetGlobalError(ALC_INVALID_VALUE); return; } const auto valuespan = std::views::counted(values, size); if(!dev || dev->Type == DeviceType::Capture) { auto ivals = std::vector(valuespan.size()); if(const auto got = GetIntegerv(dev.get(), pname, ivals)) std::ranges::copy(ivals | std::views::take(got), valuespan.begin()); return; } /* render device */ auto NumAttrsForDevice = [&dev]() noexcept -> uint8_t { if(dev->Type == DeviceType::Loopback && dev->FmtChans == DevFmtAmbi3D) return 41; return 35; }; auto statelock = std::lock_guard{dev->StateLock}; switch(pname) { case ALC_ATTRIBUTES_SIZE: valuespan[0] = ALCint64SOFT{NumAttrsForDevice()}; break; case ALC_ALL_ATTRIBUTES: if(valuespan.size() < NumAttrsForDevice()) dev->setError(ALC_INVALID_VALUE); else { auto i = 0_uz; 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; const auto 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; Ensures(i == NumAttrsForDevice()); } break; case ALC_DEVICE_CLOCK_SOFT: { auto samplecount = uint{}; auto refcount = uint{}; auto clocksec = seconds{}; auto clocknsec = nanoseconds{}; 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 + 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) dev->setError(ALC_INVALID_VALUE); else { const auto 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(const auto got = GetIntegerv(dev.get(), pname, ivals)) std::ranges::copy(ivals | std::views::take(got), valuespan.begin()); break; } } catch(al::base_exception&) { } ALC_API auto ALC_APIENTRY alcIsExtensionPresent(ALCdevice *device, const ALCchar *extName) noexcept -> ALCboolean try { auto dev = device ? VerifyDevice(device).get() : DeviceRef{}; if(!extName) { if(dev) dev->setError(ALC_INVALID_VALUE); else al::Device::SetGlobalError(ALC_INVALID_VALUE); return ALC_FALSE; } static constexpr auto extarray = GetExtensionArray(); static constexpr auto nodevextarray = GetNoDeviceExtArray(); const auto extlist = dev ? std::span{extarray}.subspan(0) : std::span{nodevextarray}; const auto matchext = [tofind = std::string_view{extName}](const std::string_view entry) { return tofind.size() == entry.size() && al::case_compare(tofind, entry) == 0; }; return std::ranges::any_of(extlist, matchext) ? ALC_TRUE : ALC_FALSE; } catch(al::base_exception&) { return ALC_FALSE; } auto ALC_APIENTRY alcGetProcAddress2(ALCdevice *device, const ALCchar *funcName) noexcept -> ALCvoid* { return alcGetProcAddress(device, funcName); } ALC_API auto ALC_APIENTRY alcGetProcAddress(ALCdevice *device, const ALCchar *funcName) noexcept -> ALCvoid* try { if(!funcName) { if(auto dev = device ? VerifyDevice(device).get() : DeviceRef{}) dev->setError(ALC_INVALID_VALUE); else al::Device::SetGlobalError(ALC_INVALID_VALUE); return nullptr; } #if ALSOFT_EAX if(eax_g_is_enabled) { const auto iter = std::ranges::find(eaxFunctions, std::string_view{funcName}, &FuncExport::funcName); if(iter != eaxFunctions.end()) return iter->address; } #endif const auto iter = std::ranges::find(alcFunctions, std::string_view{funcName}, &FuncExport::funcName); return (iter != std::end(alcFunctions)) ? iter->address : nullptr; } catch(al::base_exception&) { return nullptr; } ALC_API auto ALC_APIENTRY alcGetEnumValue(ALCdevice *device, const ALCchar *enumName) noexcept -> ALCenum try { if(!enumName) { if(auto dev = device ? VerifyDevice(device).get() : DeviceRef{}) dev->setError(ALC_INVALID_VALUE); else al::Device::SetGlobalError(ALC_INVALID_VALUE); return 0; } #if ALSOFT_EAX if(eax_g_is_enabled) { const auto iter = std::ranges::find(eaxEnumerations, std::string_view{enumName}, &EnumExport::enumName); if(iter != eaxEnumerations.end()) return iter->value; } #endif const auto iter = std::ranges::find(alcEnumerations, std::string_view{enumName}, &EnumExport::enumName); return (iter != std::end(alcEnumerations)) ? iter->value : 0; } catch(al::base_exception&) { return 0; } ALC_API auto ALC_APIENTRY alcCreateContext(ALCdevice *device, const ALCint *attrList) noexcept -> ALCcontext* try { /* 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. */ auto listlock = std::unique_lock{ListLock}; auto dev = VerifyDevice(device); if(dev->Type == DeviceType::Capture || !dev->Connected.load(std::memory_order_relaxed)) { dev->setError(ALC_INVALID_DEVICE); return nullptr; } auto statelock = std::unique_lock{dev->StateLock}; listlock.unlock(); dev->mLastError.store(ALC_NO_ERROR); const auto attrSpan = SpanFromAttributeList(attrList); if(const auto err = UpdateDeviceParams(al::get_not_null(dev), attrSpan); err != ALC_NO_ERROR) { dev->setError(err); return nullptr; } auto ctxflags = ContextFlagBitset{0}; for(const auto attrparam : attrSpan) { if(attrparam.attribute == ALC_CONTEXT_FLAGS_EXT) { ctxflags = as_unsigned(attrparam.value); break; } } auto context = al::Context::Create(dev, ctxflags); if(auto const volopt = dev->configValue({}, "volume-adjust")) { if(auto const valf = *volopt; !std::isfinite(valf)) ERR("volume-adjust must be finite: {:f}", valf); else { const auto 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::ranges::copy(*oldarray, newarray->begin()).out; *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::ranges::lower_bound(ContextList, context.get(), std::less{}, [](gsl::not_null &ctx) { return std::to_address(ctx); }); ContextList.emplace(iter, context.get()); listlock.unlock(); } if(auto *slot = context->mDefaultSlot.get()) { try { slot->initEffect(0, al::Context::sDefaultEffect.mType, al::Context::sDefaultEffect.mProps, gsl::make_not_null(context.get())); } catch(std::exception& e) { ERR("Exception initializing the default effect: {}", e.what()); } } TRACE("Created context {}", voidp{context.get()}); return context.release(); } catch(std::bad_alloc&) { WARN("Caught bad_alloc exception while creating context"); VerifyDevice(device)->setError(ALC_OUT_OF_MEMORY); return nullptr; } catch(al::base_exception&) { return nullptr; } catch(std::exception &e) { ERR("Caught exception in {}: {}", std::source_location::current().function_name(), e.what()); return nullptr; } ALC_API void ALC_APIENTRY alcDestroyContext(ALCcontext *context) noexcept { if(!gProcessRunning) return; auto listlock = std::unique_lock{ListLock}; auto iter = std::ranges::lower_bound(ContextList, context); if(iter == ContextList.end() || *iter != context) { listlock.unlock(); al::Device::SetGlobalError(ALC_INVALID_CONTEXT); return; } /* Hold a reference to this context so it remains valid until the ListLock * is released. */ auto ctx = ContextRef{*iter}; ContextList.erase(iter); auto const device = al::get_not_null(ctx->mALDevice); auto statelock = std::lock_guard{device->StateLock}; const auto stopPlayback = device->removeContext(ctx.get()) == 0; ctx->deinit(); if(stopPlayback && device->mDeviceState == DeviceState::Playing) { device->Backend->stop(); device->mDeviceState = DeviceState::Configured; } } ALC_API auto ALC_APIENTRY alcGetCurrentContext() noexcept -> ALCcontext* { auto *Context = al::Context::getThreadContext(); if(!Context) Context = al::Context::sGlobalContext.load(); return Context; } /** Returns the currently active thread-local context. */ ALC_API auto ALC_APIENTRY alcGetThreadContext() noexcept -> ALCcontext* { return al::Context::getThreadContext(); } ALC_API auto ALC_APIENTRY alcMakeContextCurrent(ALCcontext *context) noexcept -> ALCboolean try { /* context must be valid or nullptr */ auto ctx = context ? VerifyContext(context).get() : ContextRef{}; /* 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(al::Context::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{al::Context::sGlobalContext.exchange(ctx.release())}; al::Context::sGlobalContextLock.store(false, std::memory_order_release); /* Take ownership of the thread-local context reference (if any), clearing * the storage to null. */ ctx = ContextRef{al::Context::getThreadContext()}; if(ctx) al::Context::setThreadContext(nullptr); /* Reset (decrement) the previous thread-local reference. */ return ALC_TRUE; } catch(al::base_exception&) { return ALC_FALSE; } /** Makes the given context the active context for the current thread. */ ALC_API auto ALC_APIENTRY alcSetThreadContext(ALCcontext *context) noexcept -> ALCboolean try { /* context must be valid or nullptr */ auto ctx = context ? VerifyContext(context).get() : ContextRef{}; /* context's reference count is already incremented */ auto old = ContextRef{al::Context::getThreadContext()}; al::Context::setThreadContext(ctx.release()); return ALC_TRUE; } catch(al::base_exception&) { return ALC_FALSE; } ALC_API auto ALC_APIENTRY alcGetContextsDevice(ALCcontext *Context) noexcept -> ALCdevice* try { return std::to_address(VerifyContext(Context)->mALDevice); } catch(al::base_exception&) { return nullptr; } ALC_API auto ALC_APIENTRY alcOpenDevice(const ALCchar *deviceName) noexcept -> ALCdevice* try { InitConfig(); if(!PlaybackFactory) { al::Device::SetGlobalError(ALC_INVALID_VALUE); return nullptr; } auto devname = std::string_view{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. */ || devname.starts_with("'("sv) || al::case_compare(devname, "openal-soft"sv) == 0) devname = {}; else { constexpr auto prefix = GetDevicePrefix(); if(!prefix.empty() && devname.size() > prefix.size() && devname.starts_with(prefix)) devname = devname.substr(prefix.size()); } } else TRACE("Opening default playback device"); const auto DefaultSends = #if ALSOFT_EAX eax_g_is_enabled ? uint{EAX_MAX_FXSLOTS} : #endif // ALSOFT_EAX uint{DefaultSendCount}; auto device = al::Device::Create(DeviceType::Playback); /* 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(gsl::make_not_null(device.get()), BackendType::Playback); auto listlock = std::lock_guard{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()); al::Device::SetGlobalError((e.errorCode() == al::backend_error::OutOfMemory) ? ALC_OUT_OF_MEMORY : ALC_INVALID_VALUE); return nullptr; } auto checkopt = [&device](const gsl::czstring 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); } { auto listlock = std::lock_guard{ListLock}; auto iter = std::ranges::lower_bound(DeviceList, device.get(), std::less{}, [](gsl::not_null &dev) { return std::to_address(dev); }); DeviceList.emplace(iter, device.get()); } TRACE("Created device {}, \"{}\"", voidp{device.get()}, device->mDeviceName); return device.release(); } catch(std::bad_alloc&) { WARN("Caught bad_alloc exception while creating playback device"); al::Device::SetGlobalError(ALC_OUT_OF_MEMORY); return nullptr; } catch(std::exception &e) { ERR("Caught exception in {}: {}", std::source_location::current().function_name(), e.what()); return nullptr; } ALC_API auto ALC_APIENTRY alcCloseDevice(ALCdevice *device) noexcept -> ALCboolean { if(!gProcessRunning) return ALC_FALSE; auto listlock = std::unique_lock{ListLock}; auto iter = std::ranges::lower_bound(DeviceList, device); if(iter == DeviceList.end() || *iter != device) { al::Device::SetGlobalError(ALC_INVALID_DEVICE); return ALC_FALSE; } if((*iter)->Type == DeviceType::Capture) { (*iter)->setError(ALC_INVALID_DEVICE); return ALC_FALSE; } /* Erase the device, and any remaining contexts left on it, from their * respective lists. */ auto dev = DeviceRef{*iter}; DeviceList.erase(iter); auto statelock = std::unique_lock{dev->StateLock}; if(dev->mDeviceState == DeviceState::Playing) { dev->Backend->stop(); dev->mDeviceState = DeviceState::Configured; } auto prevarray = dev->mContexts.exchange(al::Device::ContextArray::Create(0)); std::ignore = dev->waitForMix(); auto orphanctxs = std::vector{}; std::ranges::for_each(*prevarray, [&orphanctxs](ContextBase *ctx) { const auto ctxiter = std::ranges::lower_bound(ContextList, ctx); if(ctxiter != ContextList.end() && *ctxiter == ctx) { orphanctxs.emplace_back(*ctxiter); ContextList.erase(ctxiter); } }); listlock.unlock(); prevarray.reset(); std::ranges::for_each(orphanctxs, [](al::Context *context) { WARN("Releasing orphaned context {}", voidp{context}); context->deinit(); }, &ContextRef::get); return ALC_TRUE; } /************************************************ * ALC capture functions ************************************************/ ALC_API auto ALC_APIENTRY alcCaptureOpenDevice(const ALCchar *deviceName, ALCuint frequency, ALCenum format, ALCsizei samples) noexcept -> ALCdevice* try { InitConfig(); if(!CaptureFactory) { al::Device::SetGlobalError(ALC_INVALID_VALUE); return nullptr; } if(samples <= 0) { al::Device::SetGlobalError(ALC_INVALID_VALUE); return nullptr; } auto devname = deviceName ? std::string_view{deviceName} : std::string_view{}; 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 { constexpr auto prefix = GetDevicePrefix(); if(!prefix.empty() && devname.size() > prefix.size() && devname.starts_with(prefix)) devname = devname.substr(prefix.size()); } } else TRACE("Opening default capture device"); auto device = al::Device::Create(DeviceType::Capture); auto decompfmt = DecomposeDevFormat(format); if(!decompfmt) { al::Device::SetGlobalError(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 = gsl::narrow_cast(samples); device->mBufferSize = gsl::narrow_cast(samples); TRACE("Capture format: {}, {}, {}hz, {} / {} buffer", DevFmtChannelsString(device->FmtChans), DevFmtTypeString(device->FmtType), device->mSampleRate, device->mUpdateSize, device->mBufferSize); try { auto backend = CaptureFactory->createBackend(gsl::make_not_null(device.get()), BackendType::Capture); auto listlock = std::lock_guard{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()); al::Device::SetGlobalError((e.errorCode() == al::backend_error::OutOfMemory) ? ALC_OUT_OF_MEMORY : ALC_INVALID_VALUE); return nullptr; } { auto listlock = std::lock_guard{ListLock}; const auto iter = std::ranges::lower_bound(DeviceList, device.get(), std::less{}, [](gsl::not_null &dev) { return std::to_address(dev); }); DeviceList.emplace(iter, device.get()); } device->mDeviceState = DeviceState::Configured; TRACE("Created capture device {}, \"{}\"", voidp{device.get()}, device->mDeviceName); return device.release(); } catch(std::bad_alloc&) { WARN("Caught bad_alloc exception while creating capture device"); al::Device::SetGlobalError(ALC_OUT_OF_MEMORY); return nullptr; } catch(std::exception &e) { ERR("Caught exception in {}: {}", std::source_location::current().function_name(), e.what()); return nullptr; } ALC_API auto ALC_APIENTRY alcCaptureCloseDevice(ALCdevice *device) noexcept -> ALCboolean { if(!gProcessRunning) return ALC_FALSE; auto listlock = std::unique_lock{ListLock}; auto iter = std::ranges::lower_bound(DeviceList, device); if(iter == DeviceList.end() || *iter != device) { al::Device::SetGlobalError(ALC_INVALID_DEVICE); return ALC_FALSE; } if((*iter)->Type != DeviceType::Capture) { (*iter)->setError(ALC_INVALID_DEVICE); return ALC_FALSE; } auto dev = DeviceRef{*iter}; DeviceList.erase(iter); listlock.unlock(); auto statelock = std::lock_guard{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 try { auto dev = VerifyDevice(device); if(dev->Type != DeviceType::Capture) { dev->setError(ALC_INVALID_DEVICE); return; } auto statelock = std::lock_guard{dev->StateLock}; if(!dev->Connected.load(std::memory_order_acquire) || dev->mDeviceState < DeviceState::Configured) dev->setError(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()); dev->setError(ALC_INVALID_DEVICE); } } } catch(al::base_exception&) { } ALC_API void ALC_APIENTRY alcCaptureStop(ALCdevice *device) noexcept try { auto dev = VerifyDevice(device); if(dev->Type != DeviceType::Capture) dev->setError(ALC_INVALID_DEVICE); else { auto statelock = std::lock_guard{dev->StateLock}; if(dev->mDeviceState == DeviceState::Playing) { dev->Backend->stop(); dev->mDeviceState = DeviceState::Configured; } } } catch(al::base_exception&) { } ALC_API void ALC_APIENTRY alcCaptureSamples(ALCdevice *device, ALCvoid *buffer, ALCsizei samples) noexcept try { auto dev = VerifyDevice(device); if(dev->Type != DeviceType::Capture) { dev->setError(ALC_INVALID_DEVICE); return; } if(samples < 0 || (samples > 0 && buffer == nullptr)) { dev->setError(ALC_INVALID_VALUE); return; } if(samples < 1) return; auto statelock = std::lock_guard{dev->StateLock}; auto *backend = dev->Backend.get(); const auto usamples = gsl::narrow_cast(samples); if(usamples > backend->availableSamples()) { dev->setError(ALC_INVALID_VALUE); return; } backend->captureSamples(std::span{static_cast(buffer), size_t{usamples}*dev->frameSizeFromFmt()}); } catch(al::base_exception&) { } /************************************************ * ALC loopback functions ************************************************/ /** Open a loopback device, for manual rendering. */ ALC_API auto ALC_APIENTRY alcLoopbackOpenDeviceSOFT(const ALCchar *deviceName) noexcept -> ALCdevice* try { InitConfig(); /* Make sure the device name, if specified, is us. */ if(deviceName && strcmp(deviceName, GetDefaultName()) != 0) { al::Device::SetGlobalError(ALC_INVALID_VALUE); return nullptr; } const auto DefaultSends = #if ALSOFT_EAX eax_g_is_enabled ? uint{EAX_MAX_FXSLOTS} : #endif // ALSOFT_EAX uint{DefaultSendCount}; auto device = al::Device::Create(DeviceType::Loopback); 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( gsl::make_not_null(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()); al::Device::SetGlobalError((e.errorCode() == al::backend_error::OutOfMemory) ? ALC_OUT_OF_MEMORY : ALC_INVALID_VALUE); return nullptr; } { auto listlock = std::lock_guard{ListLock}; const auto iter = std::ranges::lower_bound(DeviceList, device.get(), std::less{}, [](gsl::not_null &dev) { return std::to_address(dev); }); DeviceList.emplace(iter, device.get()); } TRACE("Created loopback device {}", voidp{device.get()}); return device.release(); } catch(std::bad_alloc&) { WARN("Caught bad_alloc exception while creating loopback device"); al::Device::SetGlobalError(ALC_OUT_OF_MEMORY); return nullptr; } catch(std::exception &e) { ERR("Caught exception in {}: {}", std::source_location::current().function_name(), e.what()); return nullptr; } /** * Determines if the loopback device supports the given format for rendering. */ ALC_API auto ALC_APIENTRY alcIsRenderFormatSupportedSOFT(ALCdevice *device, ALCsizei freq, ALCenum channels, ALCenum type) noexcept -> ALCboolean try { auto dev = VerifyDevice(device); if(dev->Type != DeviceType::Loopback) dev->setError(ALC_INVALID_DEVICE); else if(freq <= 0) dev->setError(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; } catch(al::base_exception&) { 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 { if(!device) [[unlikely]] return al::Device::SetGlobalError(ALC_INVALID_DEVICE); /* NOLINTBEGIN(cppcoreguidelines-pro-type-static-cast-downcast) * Needs to be real-time safe, so can't do full validation. */ auto const dev = gsl::make_not_null(static_cast(device)); /* NOLINTEND(cppcoreguidelines-pro-type-static-cast-downcast) */ if(dev->Type != DeviceType::Loopback) [[unlikely]] return dev->setError(ALC_INVALID_DEVICE); if(samples < 0 || (samples > 0 && buffer == nullptr)) [[unlikely]] return dev->setError(ALC_INVALID_VALUE); dev->renderSamples(buffer, gsl::narrow_cast(samples), dev->channelsFromFmt()); } /************************************************ * ALC DSP pause/resume functions ************************************************/ /** Pause the DSP to stop audio processing. */ ALC_API void ALC_APIENTRY alcDevicePauseSOFT(ALCdevice *device) noexcept try { auto dev = VerifyDevice(device); if(dev->Type != DeviceType::Playback) dev->setError(ALC_INVALID_DEVICE); else { auto statelock = std::lock_guard{dev->StateLock}; if(dev->mDeviceState == DeviceState::Playing) { dev->Backend->stop(); dev->mDeviceState = DeviceState::Configured; } dev->Flags.set(DevicePaused); } } catch(al::base_exception&) { } /** Resume the DSP to restart audio processing. */ ALC_API void ALC_APIENTRY alcDeviceResumeSOFT(ALCdevice *device) noexcept try { auto dev = VerifyDevice(device); if(dev->Type != DeviceType::Playback) { dev->setError(ALC_INVALID_DEVICE); return; } auto statelock = std::lock_guard{dev->StateLock}; if(!dev->Flags.test(DevicePaused)) return; if(dev->mDeviceState < DeviceState::Configured) { WARN("Cannot resume unconfigured device"); dev->setError(ALC_INVALID_DEVICE); return; } if(!dev->Connected.load()) { WARN("Cannot resume a disconnected device"); dev->setError(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()); dev->setError(ALC_INVALID_DEVICE); return; } TRACE("Post-resume: {}, {}, {}hz, {} / {} buffer", DevFmtChannelsString(dev->FmtChans), DevFmtTypeString(dev->FmtType), dev->mSampleRate, dev->mUpdateSize, dev->mBufferSize); } catch(al::base_exception&) { } /************************************************ * ALC HRTF functions ************************************************/ /** Gets a string parameter at the given index. */ ALC_API auto ALC_APIENTRY alcGetStringiSOFT(ALCdevice *device, ALCenum paramName, ALCsizei index) noexcept -> const ALCchar* try { auto dev = VerifyDevice(device); if(dev->Type == DeviceType::Capture) dev->setError(ALC_INVALID_ENUM); else switch(paramName) { case ALC_HRTF_SPECIFIER_SOFT: if(index >= 0 && std::cmp_less(index, dev->mHrtfList.size())) return dev->mHrtfList[gsl::narrow_cast(index)].c_str(); dev->setError(ALC_INVALID_VALUE); break; default: dev->setError(ALC_INVALID_ENUM); break; } return nullptr; } catch(al::base_exception&) { return nullptr; } /** Resets the given device output, using the specified attribute list. */ ALC_API auto ALC_APIENTRY alcResetDeviceSOFT(ALCdevice *device, const ALCint *attribs) noexcept -> ALCboolean try { auto listlock = std::unique_lock{ListLock}; auto dev = VerifyDevice(device); if(dev->Type == DeviceType::Capture) { dev->setError(ALC_INVALID_DEVICE); return ALC_FALSE; } auto statelock = std::lock_guard{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(al::get_not_null(dev), SpanFromAttributeList(attribs)) ? ALC_TRUE : ALC_FALSE; } catch(al::base_exception&) { return ALC_FALSE; } /************************************************ * ALC device reopen functions ************************************************/ /** Reopens the given device output, using the specified name and attribute list. */ FORCE_ALIGN auto ALC_APIENTRY alcReopenDeviceSOFT(ALCdevice *device, const ALCchar *deviceName, const ALCint *attribs) noexcept -> ALCboolean try { auto listlock = std::unique_lock{ListLock}; auto dev = VerifyDevice(device); if(dev->Type != DeviceType::Playback) { dev->setError(ALC_INVALID_DEVICE); return ALC_FALSE; } auto statelock = std::lock_guard{dev->StateLock}; auto devname = std::string_view{deviceName ? deviceName : ""}; if(!devname.empty()) { if(al::case_compare(devname, GetDefaultName()) == 0) devname = {}; else { constexpr auto prefix = GetDevicePrefix(); if(!prefix.empty() && devname.size() > prefix.size() && devname.starts_with(prefix)) devname = devname.substr(prefix.size()); } } /* Force the backend device to stop first since we're opening another one. */ const auto wasPlaying = dev->mDeviceState == DeviceState::Playing; if(wasPlaying) { dev->Backend->stop(); dev->mDeviceState = DeviceState::Configured; } auto newbackend = BackendPtr{}; try { newbackend = PlaybackFactory->createBackend(al::get_not_null(dev), BackendType::Playback); newbackend->open(devname); } catch(al::backend_exception &e) { listlock.unlock(); newbackend = nullptr; WARN("Failed to reopen playback device: {}", e.what()); dev->setError((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{std::to_address(dev)}, dev->mDeviceName); std::string{}.swap(dev->mVendorOverride); std::string{}.swap(dev->mVersionOverride); std::string{}.swap(dev->mRendererOverride); auto checkopt = [&dev](const gsl::czstring 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(al::get_not_null(dev), SpanFromAttributeList(attribs)); return ALC_TRUE; } catch(std::bad_alloc&) { WARN("Caught bad_alloc exception while reopening device"); al::Device::SetGlobalError(ALC_OUT_OF_MEMORY); return ALC_FALSE; } catch(al::base_exception&) { return ALC_FALSE; } catch(std::exception &e) { ERR("Caught exception in {}: {}", std::source_location::current().function_name(), e.what()); return ALC_FALSE; } /************************************************ * ALC event query functions ************************************************/ FORCE_ALIGN auto ALC_APIENTRY alcEventIsSupportedSOFT(ALCenum eventType, ALCenum deviceType) noexcept -> ALCenum { auto etype = alc::GetEventType(eventType); if(!etype) { WARN("Invalid event type: {:#04x}", as_unsigned(eventType)); al::Device::SetGlobalError(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)); al::Device::SetGlobalError(ALC_INVALID_ENUM); return ALC_FALSE; } kcat-openal-soft-75c0059/alc/alconfig.cpp000066400000000000000000000471031512220627100201670ustar00rootroot00000000000000/** * 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 #include #include "almalloc.h" #include "alnumeric.h" #include "alstring.h" #include "core/helpers.h" #include "core/logging.h" #include "filesystem.h" #include "fmt/ranges.h" #include "gsl/gsl" #include "strutils.hpp" #if ALSOFT_UWP #include // !!This is important!! #include #include #include using namespace winrt; #endif namespace { using namespace std::string_view_literals; const auto EmptyString = std::string{}; struct ConfigEntry { std::string key; std::string value; ConfigEntry(auto&& key_, auto&& value_) : key{std::forward(key_)}, value{std::forward(value_)} { } }; std::vector ConfOpts; /* True UTF-8 validation is way beyond me. However, this should weed out any * obviously non-UTF-8 text. * * The general form of the byte stream is relatively simple. The first byte of * a codepoint either has a 0 bit for the msb, indicating a single-byte ASCII- * compatible codepoint, or the number of bytes that make up the codepoint * (including itself) indicated by the number of successive 1 bits, and each * successive byte of the codepoint has '10' for the top bits. That is: * * 0xxxxxxx - single-byte ASCII-compatible codepoint * 110xxxxx 10xxxxxx - two-byte codepoint * 1110xxxx 10xxxxxx 10xxxxxx - three-byte codepoint * 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx - four-byte codepoint * ... etc ... * * Where the 'x' bits are concatenated together to form a 32-bit Unicode * codepoint. This doesn't check whether the codepoints themselves are valid, * it just validates the correct number of bytes for multi-byte sequences. */ auto validate_utf8(const std::string_view str) -> bool { auto const end = str.end(); /* Look for the first multi-byte/non-ASCII codepoint. */ auto current = std::ranges::find_if(str.begin(), end, [](const char ch) -> bool { return (ch&0x80) != 0; }); while(const auto remaining = std::distance(current, end)) { /* Get the number of bytes that make up this codepoint (must be at * least 2). This includes the current byte. */ const auto tocheck = std::countl_one(as_unsigned(*current)); if(tocheck < 2 || tocheck > remaining) return false; const auto next = std::next(current, tocheck); /* Check that the following bytes are a proper continuation. */ const auto valid = std::ranges::all_of(std::next(current), next, [](const char ch) -> bool { return (ch&0xc0) == 0x80; }); if(not valid) return false; /* Seems okay. Look for the next multi-byte/non-ASCII codepoint. */ current = std::ranges::find_if(next, end, [](const char ch) -> bool { return ch&0x80; }); } return true; } auto lstrip(std::string &line) -> std::string& { auto iter = std::ranges::find_if_not(line, [](const char c) { return std::isspace(c); }); line.erase(line.begin(), iter); return line; } auto expdup(std::string_view str) -> std::string { auto output = std::string{}; 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 auto hasbraces = bool{str.front() == '{'}; if(hasbraces) str.remove_prefix(1); const auto envenditer = std::ranges::find_if_not(str, [](const char c) { return c == '_' || std::isalnum(c); }); if(hasbraces && (envenditer == str.end() || *envenditer != '}')) continue; const auto envend = gsl::narrow(std::distance(str.begin(), envenditer)); const auto envname = std::string{str.substr(0, envend)}; str.remove_prefix(envend + hasbraces); if(const auto envval = al::getenv(envname.c_str())) output += *envval; } return output; } void LoadConfigFromFile(std::istream &f) { constexpr auto whitespace_chars = " \t\n\f\r\v"sv; auto curSection = std::string{}; auto buffer = std::string{}; auto linenum = 0_uz; while(std::getline(f, buffer)) { ++linenum; if(lstrip(buffer).empty()) continue; auto cmtpos = std::min(buffer.find('#'), buffer.size()); if(cmtpos != 0) cmtpos = buffer.find_last_not_of(whitespace_chars, cmtpos-1)+1; if(cmtpos == 0) continue; buffer.erase(cmtpos); if(not validate_utf8(buffer)) { ERR(" config parse error: non-UTF-8 characters on line {}", linenum); ERR("{}", fmt::format(" {::#04x}", buffer|std::views::transform([](auto c) { return as_unsigned(c); }))); continue; } if(buffer[0] == '[') { const auto endpos = buffer.find(']', 1); if(endpos == 1 || endpos == std::string::npos) { ERR(" config parse error on line {}: bad section \"{}\"", linenum, buffer); continue; } if(const auto last = buffer.find_first_not_of(whitespace_chars, endpos+1); last < buffer.size() && buffer[last] != '#') { ERR(" config parse error on line {}: extraneous characters after section \"{}\"", linenum, buffer); continue; } auto section = std::string_view{buffer}.substr(1, endpos-1); curSection.clear(); if(al::case_compare(section, "general"sv) == 0) continue; while(!section.empty()) { const 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'))) { auto 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 += gsl::narrow_cast(b); section.remove_prefix(3); } else if(section.size() > 1 && section[1] == '%') { curSection += '%'; section.remove_prefix(2); } else { curSection += '%'; section.remove_prefix(1); } } continue; } auto sep = buffer.find('='); if(sep == std::string::npos) { ERR(" config parse error on line {}: malformed option \"{}\"", linenum, buffer); continue; } auto keypart = std::string_view{buffer}.substr(0, sep++); keypart.remove_suffix(keypart.size() - (keypart.find_last_not_of(whitespace_chars)+1)); if(keypart.empty()) { ERR(" config parse error on line {}: malformed option \"{}\"", linenum, buffer); continue; } auto valpart = std::string_view{buffer}.substr(sep); valpart.remove_prefix(std::min(valpart.find_first_not_of(whitespace_chars), valpart.size())); auto fullKey = curSection; if(!fullKey.empty()) fullKey += '/'; fullKey += keypart; if(valpart.size() > size_t{std::numeric_limits::max()}) { ERR(" config parse error on line {}: value too long \"{}\"", linenum, 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 */ const auto ent = std::ranges::find(ConfOpts, fullKey, &ConfigEntry::key); if(ent != ConfOpts.end()) { if(!valpart.empty()) ent->value = expdup(valpart); else ConfOpts.erase(ent); } else if(!valpart.empty()) ConfOpts.emplace_back(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& { if(keyName.empty()) return EmptyString; auto key = std::string{}; if(!blockName.empty() && al::case_compare(blockName, "general"sv) != 0) { key = blockName; key += '/'; } if(!devName.empty()) { key += devName; key += '/'; } key += keyName; const auto iter = std::ranges::find(ConfOpts, key, &ConfigEntry::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() { auto path = fs::path{}; #if !defined(_GAMING_XBOX) { #if !ALSOFT_UWP auto bufstore = std::unique_ptr{}; const auto hr = SHGetKnownFolderPath(FOLDERID_RoamingAppData, KF_FLAG_DONT_UNEXPAND, nullptr, al::out_ptr(bufstore)); if(SUCCEEDED(hr)) { const auto buffer = std::wstring_view{bufstore.get()}; #else auto localSettings = winrt::Windows::Storage::ApplicationDataContainer{ winrt::Windows::Storage::ApplicationData::Current().LocalSettings()}; auto bufstore = Windows::Storage::ApplicationData::Current().RoamingFolder().Path(); auto buffer = std::wstring_view{bufstore}; { #endif path = fs::path{buffer}; path /= L"alsoft.ini"; TRACE("Loading config {}...", al::u8_as_char(path.u8string())); if(auto f = fs::ifstream{path}; f.is_open()) LoadConfigFromFile(f); } } #endif path = fs::path(al::char_as_u8(GetProcBinary().path)); if(!path.empty()) { path /= L"alsoft.ini"; TRACE("Loading config {}...", al::u8_as_char(path.u8string())); if(auto f = fs::ifstream{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(auto f = fs::ifstream{path}; f.is_open()) LoadConfigFromFile(f); } } #else void ReadALConfig() { auto path = fs::path{"/etc/openal/alsoft.conf"}; TRACE("Loading config {}...", al::u8_as_char(path.u8string())); if(auto f = fs::ifstream{path}; f.is_open()) LoadConfigFromFile(f); auto 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(auto f = fs::ifstream{path}; f.is_open()) LoadConfigFromFile(f); } } #ifdef __APPLE__ auto mainBundle = CFBundleRef{CFBundleGetMainBundle()}; if(mainBundle) { auto configURL = CFURLRef{CFBundleCopyResourceURL(mainBundle, CFSTR(".alsoftrc"), CFSTR(""), nullptr)}; auto fileName = std::array{}; if(configURL && CFURLGetFileSystemRepresentation(configURL, true, fileName.data(), fileName.size())) { if(auto f = std::ifstream{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(auto f = std::ifstream{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(auto f = std::ifstream{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(auto f = std::ifstream{path}; f.is_open()) LoadConfigFromFile(f); } if(auto confname = al::getenv("ALSOFT_CONF")) { TRACE("Loading config {}...", *confname); if(auto f = std::ifstream{*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 ConfigValueI32(std::string_view const devName, std::string_view const blockName, std::string_view const keyName) -> std::optional { if(auto&& val = GetConfigValue(devName, blockName, keyName); !val.empty()) try { return std::stoi(val, nullptr, 0); } catch(std::out_of_range&) { WARN("Option is out of range of i32: {} = {}", keyName, val); } catch(std::exception&) { WARN("Option is not an i32: {} = {}", keyName, val); } return std::nullopt; } auto ConfigValueU32(std::string_view const devName, std::string_view const blockName, std::string_view const keyName) -> std::optional { if(auto&& val = GetConfigValue(devName, blockName, keyName); !val.empty()) try { return gsl::narrow(std::stoul(val, nullptr, 0)); } catch(std::out_of_range&) { WARN("Option is out of range of u32: {} = {}", keyName, val); } catch(gsl::narrowing_error&) { WARN("Option is out of range of u32: {} = {}", keyName, val); } catch(std::exception&) { WARN("Option is not an u32: {} = {}", keyName, val); } return std::nullopt; } auto ConfigValueF32(std::string_view const devName, std::string_view const blockName, std::string_view const 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(std::string_view const devName, std::string_view const blockName, std::string_view const 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; } kcat-openal-soft-75c0059/alc/alconfig.h000066400000000000000000000016611512220627100176330ustar00rootroot00000000000000#ifndef ALCONFIG_H #define ALCONFIG_H #include #include #include #include "alnumeric.h" void ReadALConfig(); auto GetConfigValueBool(std::string_view devName, std::string_view blockName, std::string_view keyName, bool def) -> bool; auto ConfigValueStr(std::string_view devName, std::string_view blockName, std::string_view keyName) -> std::optional; auto ConfigValueI32(std::string_view devName, std::string_view blockName, std::string_view keyName) -> std::optional; auto ConfigValueU32(std::string_view devName, std::string_view blockName, std::string_view keyName) -> std::optional; auto ConfigValueF32(std::string_view devName, std::string_view blockName, std::string_view keyName) -> std::optional; auto ConfigValueBool(std::string_view devName, std::string_view blockName, std::string_view keyName) -> std::optional; #endif /* ALCONFIG_H */ kcat-openal-soft-75c0059/alc/alu.cpp000066400000000000000000003002251512220627100171630ustar00rootroot00000000000000/** * 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 #include #include "alnumeric.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 "core/front_stablizer.h" #include "gsl/gsl" #include "intrusive_ptr.h" #include "opthelpers.h" #include "ringbuffer.h" #include "strutils.hpp" #include "vecmat.h" static_assert((MaxResamplerPadding&1) == 0, "MaxResamplerPadding is not a multiple of two"); namespace { using namespace std::chrono; using namespace std::string_view_literals; [[nodiscard]] auto InitConeScale() noexcept -> f32 { auto ret = 1.0f; if(auto const 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 */ auto const ConeScale = InitConeScale(); /* Localized scalars for mono sources (initialized in aluInit, after * configuration is loaded). */ auto XScale = 1.0f; auto YScale = 1.0f; auto ZScale = 1.0f; /* Source distance scale for NFC filters. */ auto NfcScale = 1.0f; using HrtfDirectMixerFunc = void(*)(FloatBufferSpan LeftOut, FloatBufferSpan RightOut, std::span InSamples, std::span AccumSamples, std::span TempBuf, std::span ChanState, usize IrSize, usize SamplesToDo); constinit auto MixDirectHrtf = HrtfDirectMixerFunc{MixDirectHrtf_C}; [[nodiscard]] auto SelectHrtfMixer() -> HrtfDirectMixerFunc { #if HAVE_NEON if((CPUCapFlags&CPU_CAP_NEON)) return MixDirectHrtf_NEON; #endif #if HAVE_SSE if((CPUCapFlags&CPU_CAP_SSE)) return MixDirectHrtf_SSE; #endif return MixDirectHrtf_C; } void BsincPrepare(u32 const increment, BsincState *const state, BSincTable const *const table) { auto si = usize{BSincScaleCount - 1}; auto sf = 0.0f; if(increment > MixerFracOne) { sf = MixerFracOne/gsl::narrow_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 -= gsl::narrow_cast(si); sf = 1.0f - std::sqrt(1.0f - sf*sf); } state->sf = sf; state->m = table->m[si]; state->l = (state->m/2) - 1; state->filter = table->Tab.subspan(table->filterOffset[si]); } [[nodiscard]] auto SelectResampler(Resampler const resampler, u32 const increment) -> ResamplerFunc { switch(resampler) { case Resampler::Point: return Resample_Point_C; case Resampler::Linear: #if HAVE_NEON if((CPUCapFlags&CPU_CAP_NEON)) return Resample_Linear_NEON; #endif #if HAVE_SSE4_1 if((CPUCapFlags&CPU_CAP_SSE4_1)) return Resample_Linear_SSE4; #endif #if HAVE_SSE2 if((CPUCapFlags&CPU_CAP_SSE2)) return Resample_Linear_SSE2; #endif return Resample_Linear_C; case Resampler::Spline: case Resampler::Gaussian: #if HAVE_NEON if((CPUCapFlags&CPU_CAP_NEON)) return Resample_Cubic_NEON; #endif #if HAVE_SSE4_1 if((CPUCapFlags&CPU_CAP_SSE4_1)) return Resample_Cubic_SSE4; #endif #if HAVE_SSE2 if((CPUCapFlags&CPU_CAP_SSE2)) return Resample_Cubic_SSE2; #endif #if HAVE_SSE if((CPUCapFlags&CPU_CAP_SSE)) return Resample_Cubic_SSE; #endif return Resample_Cubic_C; case Resampler::BSinc12: case Resampler::BSinc24: case Resampler::BSinc48: if(increment > MixerFracOne) { #if HAVE_NEON if((CPUCapFlags&CPU_CAP_NEON)) return Resample_BSinc_NEON; #endif #if HAVE_SSE if((CPUCapFlags&CPU_CAP_SSE)) return Resample_BSinc_SSE; #endif return Resample_BSinc_C; } [[fallthrough]]; case Resampler::FastBSinc12: case Resampler::FastBSinc24: case Resampler::FastBSinc48: #if HAVE_NEON if((CPUCapFlags&CPU_CAP_NEON)) return Resample_FastBSinc_NEON; #endif #if HAVE_SSE if((CPUCapFlags&CPU_CAP_SSE)) return Resample_FastBSinc_SSE; #endif return Resample_FastBSinc_C; } return Resample_Point_C; } } // namespace void aluInit(CompatFlagBitset const flags, f32 const 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); } auto PrepareResampler(Resampler const resampler, u32 const increment, InterpState *const state) -> ResamplerFunc { switch(resampler) { case Resampler::Point: case Resampler::Linear: break; case Resampler::Spline: state->emplace(std::span{gSplineFilter.mTable}); break; case Resampler::Gaussian: state->emplace(std::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; case Resampler::FastBSinc48: case Resampler::BSinc48: BsincPrepare(increment, &state->emplace(), &gBSinc48); break; } return SelectResampler(resampler, increment); } void DeviceBase::Process(AmbiDecPostProcess const &proc, usize const SamplesToDo) const { proc.mAmbiDecoder->process(RealOut.Buffer, Dry.Buffer, SamplesToDo); } void DeviceBase::Process(HrtfPostProcess const &proc, usize const SamplesToDo) { /* HRTF is stereo output only. */ auto const lidx = usize{RealOut.ChannelIndex[FrontLeft]}; auto const ridx = usize{RealOut.ChannelIndex[FrontRight]}; MixDirectHrtf(RealOut.Buffer[lidx], RealOut.Buffer[ridx], Dry.Buffer, HrtfAccumData, proc.mHrtfState->mTemp, proc.mHrtfState->mChannels, proc.mHrtfState->mIrSize, SamplesToDo); } void DeviceBase::Process(UhjPostProcess const &proc, usize const SamplesToDo) { /* UHJ is stereo output only. */ auto const lidx = usize{RealOut.ChannelIndex[FrontLeft]}; auto const ridx = usize{RealOut.ChannelIndex[FrontRight]}; /* Encode to stereo-compatible 2-channel UHJ output. */ proc.mUhjEncoder->encode(std::span{RealOut.Buffer[lidx]}.first(SamplesToDo), std::span{RealOut.Buffer[ridx]}.first(SamplesToDo), {{std::span{Dry.Buffer[0]}.first(SamplesToDo), std::span{Dry.Buffer[1]}.first(SamplesToDo), std::span{Dry.Buffer[2]}.first(SamplesToDo)}}); } void DeviceBase::Process(StablizerPostProcess const &proc, usize const SamplesToDo) { /* Decode with front image stabilization. */ auto const lidx = usize{RealOut.ChannelIndex[FrontLeft]}; auto const ridx = usize{RealOut.ChannelIndex[FrontRight]}; auto const cidx = usize{RealOut.ChannelIndex[FrontCenter]}; /* Move the existing direct L/R signal out so it doesn't get processed by * the stabilizer. */ auto const leftout = std::span{RealOut.Buffer[lidx]}.first(SamplesToDo); auto const rightout = std::span{RealOut.Buffer[ridx]}.first(SamplesToDo); auto const mid = std::span{proc.mStablizer->MidDirect}.first(SamplesToDo); auto const side = std::span{proc.mStablizer->Side}.first(SamplesToDo); std::ranges::transform(leftout, rightout, mid.begin(), std::plus{}); std::ranges::transform(leftout, rightout, side.begin(), std::minus{}); std::ranges::fill(leftout, 0.0f); std::ranges::fill(rightout, 0.0f); /* Decode the B-Format mix to OutBuffer. */ proc.mAmbiDecoder->process(RealOut.Buffer, Dry.Buffer, SamplesToDo); /* Include the decoded side signal with the direct side signal. */ for(auto i = 0_uz;i < SamplesToDo;++i) side[i] += leftout[i] - rightout[i]; /* Get the decoded mid signal and band-split it. */ auto const tmpsamples = std::span{proc.mStablizer->Temp}.first(SamplesToDo); std::ranges::transform(leftout, rightout, tmpsamples.begin(), std::plus{}); proc.mStablizer->MidFilter.process(tmpsamples, proc.mStablizer->MidHF, proc.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. */ for(const auto i : std::views::iota(0_uz, RealOut.Buffer.size())) { /* 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) proc.mStablizer->ChannelFilters[i].processAllPass(mid); else if(i == ridx) proc.mStablizer->ChannelFilters[i].processAllPass(side); else proc.mStablizer->ChannelFilters[i].processAllPass( std::span{RealOut.Buffer[i]}.first(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. */ auto const mid_lf = std::cos(1.0f/3.0f * (std::numbers::pi_v*0.5f)); auto const mid_hf = std::cos(1.0f/4.0f * (std::numbers::pi_v*0.5f)); auto const center_lf = std::sin(1.0f/3.0f * (std::numbers::pi_v*0.5f)); auto const center_hf = std::sin(1.0f/4.0f * (std::numbers::pi_v*0.5f)); auto const centerout = std::span{RealOut.Buffer[cidx]}.first(SamplesToDo); for(auto i = 0_uz;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. */ auto const m = proc.mStablizer->MidLF[i]*mid_lf+proc.mStablizer->MidHF[i]*mid_hf + mid[i]; auto const c = proc.mStablizer->MidLF[i]*center_lf + proc.mStablizer->MidHF[i]*center_hf; auto const 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; } } void DeviceBase::Process(Bs2bPostProcess const &proc, usize const SamplesToDo) { /* BS2B is stereo output only. */ auto const lidx = usize{RealOut.ChannelIndex[FrontLeft]}; auto const ridx = usize{RealOut.ChannelIndex[FrontRight]}; /* First, copy out the existing direct stereo signal so it doesn't get * processed by the BS2B filter. */ auto const leftout = std::span{RealOut.Buffer[lidx]}.first(SamplesToDo); auto const rightout = std::span{RealOut.Buffer[ridx]}.first(SamplesToDo); auto const ldirect = std::span{proc.mBs2b->mStorage[0]}.first(SamplesToDo); auto const rdirect = std::span{proc.mBs2b->mStorage[1]}.first(SamplesToDo); std::ranges::copy(leftout, ldirect.begin()); std::ranges::copy(rightout, rdirect.begin()); std::ranges::fill(leftout, 0.0f); std::ranges::fill(rightout, 0.0f); /* Now, decode the ambisonic mix to the "real" output, and apply the BS2B * binaural/crossfeed filter. */ proc.mAmbiDecoder->process(RealOut.Buffer, Dry.Buffer, SamplesToDo); proc.mBs2b->cross_feed(leftout, rightout); /* Finally, copy the direct signal back to the filtered output. */ std::ranges::transform(leftout, ldirect, leftout.begin(), std::plus{}); std::ranges::transform(rightout, rdirect, rightout.begin(), std::plus{}); } 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. */ [[nodiscard]] auto dither_rng(u32 *const seed) noexcept -> u32 { *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( std::span,MaxAmbiChannels> const output, std::span const> const upsampler, std::span const,MaxAmbiChannels> const rotator, usize const ambi_order) { auto const num_chans = AmbiChannelsFromOrder(ambi_order); std::ranges::fill(output | std::views::take(upsampler.size()) | std::views::join, 0.0f); for(auto const i : std::views::iota(0_uz, upsampler.size())) { for(auto const k : std::views::iota(0_uz, num_chans)) { auto const 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::ranges::transform(rotator[k], output[i], output[i].begin(), [a](f32 const rot, f32 const dst) noexcept { return rot*a + dst; }); } } } [[nodiscard]] constexpr auto GetAmbiScales(AmbiScaling const scaletype) noexcept { switch(scaletype) { case AmbiScaling::FuMa: return std::span{AmbiScale::FromFuMa}; case AmbiScaling::SN3D: return std::span{AmbiScale::FromSN3D}; case AmbiScaling::UHJ: return std::span{AmbiScale::FromUHJ}; case AmbiScaling::N3D: break; } return std::span{AmbiScale::FromN3D}; } [[nodiscard]] constexpr auto GetAmbiLayout(AmbiLayout const layouttype) noexcept { if(layouttype == AmbiLayout::FuMa) return std::span{AmbiIndex::FromFuMa}; return std::span{AmbiIndex::FromACN}; } [[nodiscard]] constexpr auto GetAmbi2DLayout(AmbiLayout const layouttype) noexcept { if(layouttype == AmbiLayout::FuMa) return std::span{AmbiIndex::FromFuMa2D}; return std::span{AmbiIndex::FromACN2D}; } [[nodiscard]] auto CalcContextParams(ContextBase *const ctx) -> bool { auto *const props = ctx->mParams.ContextUpdate.exchange(nullptr, std::memory_order_acq_rel); if(!props) return false; auto const pos = al::Vector{props->Position[0], props->Position[1], props->Position[2], 1.0f}; ctx->mParams.Position = pos; /* AT then UP */ auto N = al::Vector{props->OrientAt[0], props->OrientAt[1], props->OrientAt[2], 0.0f}; N.normalize(); auto V = al::Vector{props->OrientUp[0], props->OrientUp[1], props->OrientUp[2], 0.0f}; V.normalize(); /* Build and normalize right-vector */ auto U = al::Vector{N.cross_product(V)}; U.normalize(); auto const rot = al::Matrix{ U[0], V[0], -N[0], 0.0f, U[1], V[1], -N[1], 0.0f, U[2], V[2], -N[2], 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}; auto const vel = al::Vector{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; } [[nodiscard]] auto CalcEffectSlotParams(EffectSlotBase *const slot, EffectSlotBase **const sorted_slots, ContextBase *const context) ->bool { auto *const 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 *const 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; } } auto *const state = props->State.release(); auto *const 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. */ auto *const ring = context->mAsyncEvents.get(); if(auto const evt_vec = ring->getWriteVector(); !evt_vec[0].empty()) [[likely]] { auto &evt = InitAsyncEvent(evt_vec[0].front()); 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); auto const output = std::invoke([slot,context]() -> EffectTarget { if(auto *const target = slot->Target) return EffectTarget{&target->Wet, nullptr}; auto const device = al::get_not_null(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. */ [[nodiscard]] auto ScaleAzimuthFront3(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. */ auto const len2d = std::sqrt(pos[0]*pos[0] + pos[2]*pos[2]); /* Z > cos(pi/6) = -30 < azimuth < 30 degrees. */ if(auto z = -pos[2] / len2d; z > 0.866025403785f) { auto x = pos[0] / len2d; /* 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. */ [[nodiscard]] auto ScaleAzimuthFront3_2(std::array pos) -> std::array { if(pos[2] < 0.0f) { const auto len2d = std::sqrt(pos[0]*pos[0] + pos[2]*pos[2]); /* Z > cos(pi/3) = -60 < azimuth < 60 degrees. */ if(auto z = -pos[2] / len2d; z > 0.5f) { auto x = pos[0] / len2d; /* 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. */ [[nodiscard]] constexpr auto CalcRotatorSize(usize const l) noexcept -> usize { if(l >= 2) return (l*2 + 1)*(l*2 + 1) + CalcRotatorSize(l-1); return 0; } struct RotatorCoeffs { struct CoeffValues { f32 u, v, w; }; std::array mCoeffs{}; RotatorCoeffs() noexcept { auto coeffs = mCoeffs.begin(); for(auto const l : std::views::iota(2, MaxAmbiOrder+1)) { for(auto const n : std::views::iota(-l, l+1)) { for(auto const m : std::views::iota(-l, l+1)) { /* 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; */ auto const denom = gsl::narrow_cast((std::abs(n) == l) ? (2*l) * (2*l - 1) : (l*l - n*n)); if(m == 0) { coeffs->u = gsl::narrow_cast(std::sqrt(l * l / denom)); coeffs->v = gsl::narrow_cast(std::sqrt((l-1) * l / denom) * -1.0); coeffs->w = 0.0f; } else { const auto abs_m = std::abs(m); coeffs->u = gsl::narrow_cast(std::sqrt((l*l - m*m) / denom)); coeffs->v = gsl::narrow_cast(std::sqrt((l+abs_m-1) * (l+abs_m) / denom) * 0.5); coeffs->w = gsl::narrow_cast(std::sqrt((l-abs_m-1) * (l-abs_m) / denom) * -0.5); } ++coeffs; } } } } }; const auto RotatorCoeffArray = RotatorCoeffs{}; /** * 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, i32 const order) { /* Don't do anything for < 2nd order. */ if(order < 2) return; static constexpr auto P = [](isize const i, isize const l, isize const a, isize const n, usize const last_base, AmbiRotateMatrix const &R) { auto const ri1 = R[ 1+2][gsl::narrow_cast(i+2_z)]; auto const rim1 = R[-1+2][gsl::narrow_cast(i+2_z)]; auto const ri0 = R[ 0+2][gsl::narrow_cast(i+2_z)]; auto const x = last_base + gsl::narrow_cast(a+l-1); if(n == -l) return ri1*R[last_base][x] + rim1*R[last_base + gsl::narrow_cast(l-1_z)*2][x]; if(n == l) return ri1*R[last_base + gsl::narrow_cast(l-1_z)*2][x] - rim1*R[last_base][x]; return ri0*R[last_base + gsl::narrow_cast(l-1_z+n)][x]; }; static constexpr auto U = [](isize const l, isize const m, isize const n, usize const last_base, AmbiRotateMatrix const &R) { return P(0, l, m, n, last_base, R); }; static constexpr auto V = [](isize const l, isize const m, isize const n, usize const last_base, AmbiRotateMatrix const &R) { using namespace std::numbers; if(m > 0) { auto const d = (m == 1); auto const p0 = P( 1, l, m-1, n, last_base, R); auto const p1 = P(-1, l, -m+1, n, last_base, R); return d ? p0*sqrt2_v : (p0 - p1); } auto const d = (m == -1); auto const p0 = P( 1, l, m+1, n, last_base, R); auto const p1 = P(-1, l, -m-1, n, last_base, R); return d ? p1*sqrt2_v : (p0 + p1); }; static constexpr auto W = [](isize const l, isize const m, isize const n, usize const last_base, AmbiRotateMatrix const &R) { Expects(m != 0); if(m > 0) { auto const p0 = P( 1, l, m+1, n, last_base, R); auto const p1 = P(-1, l, -m-1, n, last_base, R); return p0 + p1; } auto const p0 = P( 1, l, m-1, n, last_base, R); auto const p1 = P(-1, l, -m+1, n, last_base, R); return p0 - p1; }; // compute rotation matrix of each subsequent band recursively auto coeffs = RotatorCoeffArray.mCoeffs.cbegin(); auto base_idx = 4_uz; auto last_base = 1_uz; for(auto const l : std::views::iota(2_i32, order+1)) { auto y = base_idx; for(auto const n : std::views::iota(-l, l+1)) { auto x = base_idx; for(const auto m : std::views::iota(-l, l+1)) { auto r = 0.0f; // computes Eq.8.1 if(const auto u = coeffs->u; u != 0.0f) r += u * U(l, m, n, last_base, matrix); if(const auto v = coeffs->v; v != 0.0f) r += v * V(l, m, n, last_base, matrix); if(const auto w = coeffs->w; w != 0.0f) r += w * W(l, m, n, last_base, matrix); matrix[y][x] = r; ++coeffs; ++x; } ++y; } last_base = base_idx; base_idx += gsl::narrow_cast(l)*2_uz + 1; } } /* End ambisonic rotation helpers. */ constexpr auto sin30 = 0.5f; constexpr auto cos30 = 0.866025403785f; constexpr auto sin45 = std::numbers::sqrt2_v*0.5f; constexpr auto cos45 = std::numbers::sqrt2_v*0.5f; constexpr auto sin110 = 0.939692620786f; constexpr auto cos110 = -0.342020143326f; struct ChanPosMap { Channel channel; std::array pos; }; struct GainTriplet { f32 Base, HF, LF; }; /** * Calculates panning gains for a voice playing an ambisonic buffer (B-Format, * UHJ, etc.). */ void CalcAmbisonicPanning(Voice *const voice, f32 const xpos, f32 const ypos, f32 const zpos, f32 const distance, f32 const spread, GainTriplet const &drygain, std::span const wetgain, std::span const sendslots, ContextParams const &ctxparams, DeviceBase *const device) { auto const samplerate = gsl::narrow_cast(device->mSampleRate); if(device->AvgSpeakerDist > 0.0f && voice->mFmtChannels != FmtUHJ2 && voice->mFmtChannels != FmtSuperStereo) { if(!(distance > std::numeric_limits::epsilon())) { /* NOTE: The NFCtrlFilters should use a w0 of 0 for FOA input. */ for(auto &chanparams : voice->mChans) chanparams.mDryParams.NFCtrlFilter.adjust(0.0f); } else { /* Clamp the distance for really close sources, to prevent * excessive bass. */ auto const mdist = std::max(distance*NfcScale, device->AvgSpeakerDist/4.0f); auto const w0 = SpeedOfSoundMetersPerSec / (mdist * samplerate); /* 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. */ auto const coverage = !(distance > std::numeric_limits::epsilon()) ? 1.0f : (std::numbers::inv_pi_v*0.5f * spread); auto const scales = GetAmbiScales(voice->mAmbiScaling); auto coeffs = std::invoke([xpos,ypos,zpos,device] { if(device->mRenderMode != 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); }); if(!(coverage > 0.0f)) { ComputePanGains(&device->Dry, coeffs, drygain.Base*scales[0], std::span{voice->mChans[0].mDryParams.Gains.Target}.first()); for(auto const i : std::views::iota(0_uz, device->NumAuxSends)) { if(auto const *const slot = sendslots[i]) ComputePanGains(&slot->Wet, coeffs, wetgain[i].Base*scales[0], voice->mChans[0].mWetParams[i].Gains.Target); } return; } auto const &props = voice->mProps; /* Local B-Format sources have their XYZ channels rotated according to the * orientation. */ auto N = al::Vector{props.OrientAt[0], props.OrientAt[1], props.OrientAt[2], 0.0f}; N.normalize(); auto V = al::Vector{props.OrientUp[0], props.OrientUp[1], props.OrientUp[2], 0.0f}; V.normalize(); if(!props.HeadRelative) { N = ctxparams.Matrix * N; V = ctxparams.Matrix * V; } /* Build and normalize right-vector */ auto U = al::Vector{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. */ auto &shrot = device->mAmbiRotateMatrix; std::ranges::fill(shrot | std::views::join, 0.0f); 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, gsl::narrow_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 format 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). */ auto &mixmatrix = device->mAmbiRotateMatrix2; if(device->mAmbiOrder > voice->mAmbiOrder || (device->mAmbiOrder >= 2 && !device->m2DMixing && Is2DAmbisonic(voice->mFmtChannels))) { if(voice->mAmbiOrder == 1) { auto const upsampler = Is2DAmbisonic(voice->mFmtChannels) ? std::span{AmbiScale::FirstOrder2DUp} : std::span{AmbiScale::FirstOrderUp}; UpsampleBFormatTransform(mixmatrix, upsampler, shrot, device->mAmbiOrder); } else if(voice->mAmbiOrder == 2) { auto const upsampler = Is2DAmbisonic(voice->mFmtChannels) ? std::span{AmbiScale::SecondOrder2DUp} : std::span{AmbiScale::SecondOrderUp}; UpsampleBFormatTransform(mixmatrix, upsampler, shrot, device->mAmbiOrder); } else if(voice->mAmbiOrder == 3) { auto const upsampler = Is2DAmbisonic(voice->mFmtChannels) ? std::span{AmbiScale::ThirdOrder2DUp} : std::span{AmbiScale::ThirdOrderUp}; UpsampleBFormatTransform(mixmatrix, upsampler, shrot, device->mAmbiOrder); } else if(voice->mAmbiOrder == 4) { auto const upsampler = std::span{AmbiScale::FourthOrder2DUp}; UpsampleBFormatTransform(mixmatrix, upsampler, shrot, device->mAmbiOrder); } } else mixmatrix = shrot; /* Convert the rotation matrix for input ordering and scaling, and whether * input is 2D or 3D. */ auto const index_map = Is2DAmbisonic(voice->mFmtChannels) ? GetAmbi2DLayout(voice->mAmbiLayout).first(voice->mChans.size()) : GetAmbiLayout(voice->mAmbiLayout).first(voice->mChans.size()); /* Scale the panned W signal inversely to coverage (full coverage means no * panned signal), and according to the channel scaling. */ std::ranges::transform(coeffs, coeffs.begin(), [scale=(1.0f-coverage)*scales[0]](f32 const coeff) { return coeff * scale; }); for(const auto c : std::views::iota(0_uz, index_map.size())) { auto const acn = usize{index_map[c]}; auto const 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::ranges::transform(mixmatrix[acn], coeffs, coeffs.begin(), [scale](f32 const in, f32 const coeff) noexcept { return in*scale + coeff; }); ComputePanGains(&device->Dry, coeffs, drygain.Base, std::span{voice->mChans[c].mDryParams.Gains.Target}.first()); for(auto const i : std::views::iota(0_uz, device->NumAuxSends)) { if(auto const *const slot = sendslots[i]) ComputePanGains(&slot->Wet, coeffs, wetgain[i].Base, voice->mChans[c].mWetParams[i].Gains.Target); } coeffs.fill(0.0f); } } [[nodiscard]] auto GetPanGainSelector(VoiceProps const &props) { auto const lgain = std::min(1.0f - props.Panning, 1.0f); auto const rgain = std::min(1.0f + props.Panning, 1.0f); auto const mingain = std::min(lgain, rgain); return [lgain,rgain,mingain](Channel const chan) noexcept -> f32 { 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; }; } /* With non-HRTF mixing, we can cheat for mono-as-stereo by adding the left and * right output gains and mix only one channel to output. */ void MergePannedMono(Voice *const voice, std::span const sendslots, DeviceBase *const device) { auto const drytarget0 = std::span{voice->mChans[0].mDryParams.Gains.Target}; auto const drytarget1 = std::span{voice->mChans[1].mDryParams.Gains.Target}; std::ranges::transform(drytarget0, drytarget1, drytarget0.begin(), std::plus{}); for(auto const i : std::views::iota(0_uz, device->NumAuxSends)) { if(!sendslots[i]) continue; auto const wettarget0 = std::span{voice->mChans[0].mWetParams[i].Gains.Target}; auto const wettarget1 = std::span{voice->mChans[1].mWetParams[i].Gains.Target}; std::ranges::transform(wettarget0, wettarget1, wettarget0.begin(), std::plus{}); } } /** * Calculates panning gains for a voice playing directly to the main output * buffer (bypassing the B-Format dry buffer). */ void CalcDirectPanning(Voice *const voice, DirectMode const directmode, std::span const chans, GainTriplet const &drygain, std::span const wetgain, std::span const sendslots, DeviceBase *const device) { auto const &props = voice->mProps; auto ChannelPanGain = GetPanGainSelector(props); for(auto const c : std::views::iota(0_uz, chans.size())) { auto const pangain = ChannelPanGain(chans[c].channel); if(auto idx = device->RealOut.ChannelIndex[chans[c].channel]; idx != InvalidChannelIndex) voice->mChans[c].mDryParams.Gains.Target[idx] = drygain.Base * pangain; else if(directmode == DirectMode::RemixMismatch) { auto const remap = std::ranges::find(device->RealOut.RemixMap, chans[c].channel, &InputRemixMap::channel); if(remap == device->RealOut.RemixMap.end()) continue; for(auto const &target : remap->targets) { idx = device->RealOut.ChannelIndex[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(auto const c : std::views::iota(0_uz, chans.size())) { /* Skip LFE */ if(chans[c].channel == LFE) continue; auto const pangain = ChannelPanGain(chans[c].channel); auto const coeffs = CalcDirectionCoeffs(chans[c].pos, 0.0f); for(auto const i : std::views::iota(0_uz, device->NumAuxSends)) { if(auto const *const slot = sendslots[i]) ComputePanGains(&slot->Wet, coeffs, wetgain[i].Base * pangain, voice->mChans[c].mWetParams[i].Gains.Target); } } if(voice->mFmtChannels == FmtMono && props.mPanningEnabled) MergePannedMono(voice, sendslots, device); } /** Calculates panning filters for a voice mixing with HRTF. */ void CalcHrtfPanning(Voice *const voice, f32 const xpos, f32 const ypos, f32 const zpos, f32 const distance, f32 const spread, std::span const chans, GainTriplet const &drygain, std::span const wetgain, std::span const sendslots, DeviceBase *const device) { auto const &props = voice->mProps; auto ChannelPanGain = GetPanGainSelector(props); if(distance > std::numeric_limits::epsilon()) { if(voice->mFmtChannels == FmtMono && !props.mPanningEnabled) { auto const src_ev = std::asin(std::clamp(ypos, -1.0f, 1.0f)); auto const 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; auto const coeffs = CalcDirectionCoeffs(std::array{xpos, ypos, zpos}, spread); for(auto const i : std::views::iota(0_uz, device->NumAuxSends)) { if(auto const *const slot = sendslots[i]) ComputePanGains(&slot->Wet, coeffs, wetgain[i].Base, voice->mChans[0].mWetParams[i].Gains.Target); } return; } for(auto const c : std::views::iota(0_uz, chans.size())) { /* Skip LFE */ if(chans[c].channel == LFE) continue; auto const pangain = ChannelPanGain(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. */ auto const a = 1.0f - (std::numbers::inv_pi_v*0.5f)*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)}; if(auto const len = std::sqrt(pos[0]*pos[0] + pos[1]*pos[1] + pos[2]*pos[2]); len < 1.0f) { pos[0] /= len; pos[1] /= len; pos[2] /= len; } auto const ev = std::asin(std::clamp(pos[1], -1.0f, 1.0f)); auto const 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; auto const coeffs = CalcDirectionCoeffs(pos, 0.0f); for(auto const i : std::views::iota(0_uz, device->NumAuxSends)) { if(auto const *slot = sendslots[i]) ComputePanGains(&slot->Wet, coeffs, wetgain[i].Base * pangain, voice->mChans[c].mWetParams[i].Gains.Target); } } return; } /* With no distance, spread is only meaningful for 3D mono sources where it * can be 0 or 1 (non-mono sources are always treated as full spread here). */ auto const spreadmult = gsl::narrow_cast(voice->mFmtChannels == FmtMono && !props.mPanningEnabled) * spread; /* Local sources on HRTF play with each channel panned to its relative * location around the listener, providing "virtual speaker" responses. */ for(auto const c : std::views::iota(0_uz, chans.size())) { /* Skip LFE */ if(chans[c].channel == LFE) continue; auto const pangain = ChannelPanGain(chans[c].channel); /* Get the HRIR coefficients and delays for this channel position. */ auto const ev = std::asin(chans[c].pos[1]); auto const az = std::atan2(chans[c].pos[0], -chans[c].pos[2]); /* With no distance, spread is only meaningful for mono sources where * it can be 0 or 1 (non-mono sources are always treated as full spread * here). */ device->mHrtf->getCoeffs(ev, az, std::numeric_limits::infinity(), spreadmult, 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. */ auto const coeffs = CalcDirectionCoeffs(chans[c].pos, spread); for(auto const i : std::views::iota(0_uz, device->NumAuxSends)) { if(auto const *const slot = sendslots[i]) ComputePanGains(&slot->Wet, coeffs, wetgain[i].Base * pangain, voice->mChans[c].mWetParams[i].Gains.Target); } } } /** Calculates panning gains for a voice playing normally. */ void CalcNormalPanning(Voice *const voice, f32 const xpos, f32 const ypos, f32 const zpos, f32 const distance, f32 const spread, std::span const chans, GainTriplet const &drygain, std::span const wetgain, std::span const sendslots, DeviceBase *const device) { auto const &props = voice->mProps; auto ChannelPanGain = GetPanGainSelector(props); auto const samplerate = gsl::narrow_cast(device->mSampleRate); 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. */ auto const mdist = std::max(distance*NfcScale, device->AvgSpeakerDist/4.0f); auto const w0 = SpeedOfSoundMetersPerSec / (mdist * samplerate); /* Adjust NFC filters. */ for(auto &chanparams : voice->mChans | std::views::take(chans.size())) chanparams.mDryParams.NFCtrlFilter.adjust(w0); voice->mFlags.set(VoiceHasNfc); } if(voice->mFmtChannels == FmtMono && !props.mPanningEnabled) { auto const coeffs = std::invoke([xpos,ypos,zpos,spread,device] { if(device->mRenderMode != RenderMode::Pairwise) return CalcDirectionCoeffs(std::array{xpos, ypos, zpos}, spread); auto const pos = ScaleAzimuthFront3_2(std::array{xpos, ypos, zpos}); return CalcDirectionCoeffs(pos, spread); }); ComputePanGains(&device->Dry, coeffs, drygain.Base, std::span{voice->mChans[0].mDryParams.Gains.Target}.first()); for(auto const i : std::views::iota(0_uz, device->NumAuxSends)) { if(auto const *const slot = sendslots[i]) ComputePanGains(&slot->Wet, coeffs, wetgain[i].Base, voice->mChans[0].mWetParams[i].Gains.Target); } return; } for(auto const c : std::views::iota(0_uz, chans.size())) { auto const pangain = ChannelPanGain(chans[c].channel); /* Special-case LFE */ if(chans[c].channel == LFE) { if(device->Dry.Buffer.data() == device->RealOut.Buffer.data()) { if(auto const idx = u32{device->RealOut.ChannelIndex[chans[c].channel]}; 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. */ auto const a = 1.0f - (std::numbers::inv_pi_v*0.5f)*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)}; if(auto const len = std::sqrt(pos[0]*pos[0] + pos[1]*pos[1] + pos[2]*pos[2]); len < 1.0f) { pos[0] /= len; pos[1] /= len; pos[2] /= len; } if(device->mRenderMode == RenderMode::Pairwise) pos = ScaleAzimuthFront3(pos); auto const coeffs = CalcDirectionCoeffs(pos, 0.0f); ComputePanGains(&device->Dry, coeffs, drygain.Base * pangain, std::span{voice->mChans[c].mDryParams.Gains.Target}.first()); for(auto const i : std::views::iota(0_uz, device->NumAuxSends)) { if(auto const *const 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, use an "identity" filter so it * aligns to the average speaker distance. This avoids excessive * high-pass effects on a sound that is at nominal volume, though * it does mean it will simulate the sound being at that distance * with ambisonic output when decoded with near-field compensation. */ auto const w0 = SpeedOfSoundMetersPerSec / (device->AvgSpeakerDist * samplerate); for(auto &chanparams : voice->mChans | std::views::take(chans.size())) chanparams.mDryParams.NFCtrlFilter.adjust(w0); voice->mFlags.set(VoiceHasNfc); } /* With no distance, spread is only meaningful for 3D mono sources * where it can be 0 or full (non-mono sources are always full spread * here). */ auto const spreadmult = gsl::narrow_cast(voice->mFmtChannels == FmtMono && !props.mPanningEnabled) * spread; for(auto const c : std::views::iota(0_uz, chans.size())) { auto const pangain = ChannelPanGain(chans[c].channel); /* Special-case LFE */ if(chans[c].channel == LFE) { if(device->Dry.Buffer.data() == device->RealOut.Buffer.data()) { if(auto const idx = usize{device->RealOut.ChannelIndex[chans[c].channel]}; idx != InvalidChannelIndex) voice->mChans[c].mDryParams.Gains.Target[idx] = drygain.Base * pangain; } continue; } auto const coeffs = CalcDirectionCoeffs((device->mRenderMode==RenderMode::Pairwise) ? ScaleAzimuthFront3(chans[c].pos) : chans[c].pos, spreadmult); ComputePanGains(&device->Dry, coeffs, drygain.Base * pangain, std::span{voice->mChans[c].mDryParams.Gains.Target}.first()); for(auto const i : std::views::iota(0_uz, device->NumAuxSends)) { if(auto const *const slot = sendslots[i]) ComputePanGains(&slot->Wet, coeffs, wetgain[i].Base * pangain, voice->mChans[c].mWetParams[i].Gains.Target); } } } if(voice->mFmtChannels == FmtMono && props.mPanningEnabled) MergePannedMono(voice, sendslots, device); } void CalcPanningAndFilters(Voice *const voice, f32 const xpos, f32 const ypos, f32 const zpos, f32 const distance, f32 const spread, GainTriplet const &drygain, std::span const wetgain, std::span const sendslots, ContextParams const &ctxparams, DeviceBase *const device) { static constexpr auto MonoMap = std::array{ ChanPosMap{FrontCenter, std::array{0.0f, 0.0f, -1.0f}} }; static constexpr auto RearMap = std::array{ ChanPosMap{BackLeft, std::array{-sin30, 0.0f, cos30}}, ChanPosMap{BackRight, std::array{ sin30, 0.0f, cos30}}, }; static constexpr auto QuadMap = std::array{ 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 auto X51Map = std::array{ 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 auto X61Map = std::array{ 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 auto X71Map = std::array{ 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}}, }; auto StereoMap = std::array{ ChanPosMap{FrontLeft, std::array{-sin30, 0.0f, -cos30}}, ChanPosMap{FrontRight, std::array{ sin30, 0.0f, -cos30}}, }; auto const numsends = device->NumAuxSends; auto const &props = voice->mProps; std::ranges::for_each(voice->mChans, [numsends](Voice::ChannelData &chandata) { chandata.mDryParams.Hrtf.Target = HrtfFilter{}; chandata.mDryParams.Gains.Target.fill(0.0f); std::ranges::for_each(chandata.mWetParams | std::views::take(numsends), [](SendParams ¶ms) -> void { params.Gains.Target.fill(0.0f); }); }); auto const [directmode, chans] = std::invoke([voice,&props,&StereoMap]() -> std::pair> { switch(voice->mFmtChannels) { case FmtMono: if(!props.mPanningEnabled) { /* 3D mono buffers are never played direct. */ return {DirectMode::Off, std::span{MonoMap}}; } /* Mono buffers with panning enabled are basically treated as * stereo, each channel being a copy of the buffer samples, using * the stereo channel positions and the left/right panning * affecting each channel appropriately. */ [[fallthrough]]; case FmtStereo: if(props.DirectChannels == DirectMode::Off) { auto chanpos = StereoMap | std::views::transform(&ChanPosMap::pos); std::ranges::transform(props.StereoPan, chanpos | std::views::elements<1>, chanpos.begin(), [](f32 const a, f32 const y) { /* StereoPan is counter-clockwise in radians. */ return std::array{-std::sin(a), y, -std::cos(a)}; }); } return {props.DirectChannels, std::span{StereoMap}}; case FmtRear: return {props.DirectChannels, std::span{RearMap}}; case FmtQuad: return {props.DirectChannels, std::span{QuadMap}}; case FmtX51: return {props.DirectChannels, std::span{X51Map}}; case FmtX61: return {props.DirectChannels, std::span{X61Map}}; case FmtX71: return {props.DirectChannels, std::span{X71Map}}; case FmtBFormat2D: case FmtBFormat3D: case FmtUHJ2: case FmtUHJ3: case FmtUHJ4: case FmtSuperStereo: return {DirectMode::Off, {}}; } return {props.DirectChannels, {}}; }); voice->mFlags.reset(VoiceHasHrtf).reset(VoiceHasNfc); if(auto *const decoder = voice->mDecoder.get()) decoder->mWidthControl = std::min(props.EnhWidth, 0.7f); if(IsAmbisonic(voice->mFmtChannels)) { /* Special handling for B-Format and UHJ sources. */ CalcAmbisonicPanning(voice, xpos, ypos, zpos, distance, spread, drygain, wetgain, sendslots, ctxparams, device); } else if(directmode != 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; CalcDirectPanning(voice, directmode, chans, drygain, wetgain, sendslots, device); } else if(device->mRenderMode == RenderMode::Hrtf) { /* Full HRTF rendering. Skip the virtual channels and render to the * real outputs with HRTF filters. */ voice->mDirect.Buffer = device->RealOut.Buffer; CalcHrtfPanning(voice, xpos, ypos, zpos, distance, spread, chans, drygain, wetgain, sendslots, device); voice->mDuplicateMono = voice->mFmtChannels == FmtMono && props.mPanningEnabled; voice->mFlags.set(VoiceHasHrtf); } else { /* Non-HRTF rendering. Use normal panning to the normal output. */ CalcNormalPanning(voice, xpos, ypos, zpos, distance, spread, chans, drygain, wetgain, sendslots, device); } const auto inv_samplerate = 1.0f / gsl::narrow_cast(device->mSampleRate); { auto const hfNorm = props.Direct.HFReference * inv_samplerate; auto const lfNorm = props.Direct.LFReference * inv_samplerate; voice->mDirect.FilterActive = false; if(drygain.HF != 1.0f || drygain.LF != 1.0f) voice->mDirect.FilterActive = true; 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(Voice::ChannelData &chandata : voice->mChans | std::views::drop(1)) { chandata.mDryParams.LowPass.copyParamsFrom(lowpass); chandata.mDryParams.HighPass.copyParamsFrom(highpass); } } for(auto const i : std::views::iota(0_uz, numsends)) { auto const hfNorm = props.Send[i].HFReference * inv_samplerate; auto const lfNorm = props.Send[i].LFReference * inv_samplerate; voice->mSend[i].FilterActive = false; if(wetgain[i].HF != 1.0f || wetgain[i].LF != 1.0f) voice->mSend[i].FilterActive = true; 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(Voice::ChannelData &chandata : voice->mChans | std::views::drop(1)) { chandata.mWetParams[i].LowPass.copyParamsFrom(lowpass); chandata.mWetParams[i].HighPass.copyParamsFrom(highpass); } } } void CalcNonAttnVoiceParams(Voice *const voice, ContextBase const *const context) { auto const &props = voice->mProps; auto const device = al::get_not_null(context->mDevice); auto sendslots = std::array{}; voice->mDirect.Buffer = device->Dry.Buffer; for(auto const i : std::views::iota(0_uz, device->NumAuxSends)) { 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 */ auto const pitch = gsl::narrow_cast(voice->mFrequency) / gsl::narrow_cast(device->mSampleRate) * props.Pitch; if(pitch > f32{MaxPitch}) voice->mStep = MaxPitch<mStep = std::max(fastf2u(pitch * MixerFracOne), 1u); voice->mResampler = PrepareResampler(props.mResampler, voice->mStep, &voice->mResampleState); /* Calculate gains */ auto const mingain = std::min(props.MinGain, props.MaxGain); auto const srcgain = std::clamp(props.Gain, mingain, props.MaxGain); auto const drygain = GainTriplet{ .Base = std::min(GainMixMax, srcgain * props.Direct.Gain * context->mParams.Gain), .HF = props.Direct.GainHF, .LF = props.Direct.GainLF }; auto wetgain = std::array{}; std::ranges::transform(props.Send | std::views::take(device->NumAuxSends), wetgain.begin(), [context,srcgain](const VoiceProps::SendData &send) noexcept { return GainTriplet{ .Base = std::min(GainMixMax, srcgain * send.Gain * context->mParams.Gain), .HF = send.GainHF, .LF = send.GainLF }; }); CalcPanningAndFilters(voice, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, drygain, wetgain, sendslots, context->mParams, device); } void CalcAttnVoiceParams(Voice *const voice, ContextBase const *const context) { auto const &props = voice->mProps; auto const device = al::get_not_null(context->mDevice); auto const numsends = device->NumAuxSends; /* Set mixing buffers and get send parameters. */ voice->mDirect.Buffer = device->Dry.Buffer; auto sendslots = std::array{}; auto roomrolloff = std::array{}; for(auto const i : std::views::iota(0_uz, numsends)) { 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) */ auto position = al::Vector{props.Position[0], props.Position[1], props.Position[2], 1.0f}; auto velocity = al::Vector{props.Velocity[0], props.Velocity[1], props.Velocity[2], 0.0f}; auto direction = al::Vector{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; } auto tosource = al::Vector{position[0], position[1], position[2], 0.0f}; auto const distance = tosource.normalize(); auto const directional = bool{direction.normalize() > 0.0f}; /* Calculate distance attenuation */ auto const distancemodel = context->mParams.SourceDistanceModel ? props.mDistanceModel : context->mParams.mDistanceModel; auto const attenDistance = std::invoke([distance,distancemodel,&props] { switch(distancemodel) { case DistanceModel::InverseClamped: case DistanceModel::LinearClamped: case DistanceModel::ExponentClamped: if(!(props.RefDistance <= props.MaxDistance)) return props.RefDistance; return std::clamp(distance, props.RefDistance, props.MaxDistance); case DistanceModel::Inverse: case DistanceModel::Linear: case DistanceModel::Exponent: case DistanceModel::Disable: break; } return distance; }); auto drygain = GainTriplet{ .Base = props.Gain, .HF = 1.0f, .LF = 1.0f }; auto wetgain = std::array{}; wetgain.fill(drygain); auto dryAttnBase = 1.0f; switch(distancemodel) { case DistanceModel::Inverse: case DistanceModel::InverseClamped: if(props.RefDistance > 0.0f) { if(auto const dist = lerpf(props.RefDistance, attenDistance, props.RolloffFactor); dist > 0.0f) { dryAttnBase = props.RefDistance / dist; drygain.Base *= dryAttnBase; } auto const wetbase = wetgain | std::views::transform(&GainTriplet::Base); std::ranges::transform(wetbase | std::views::take(numsends), roomrolloff, wetbase.begin(), [&props,attenDistance](f32 const gain, f32 const rolloff) { if(auto const dist = lerpf(props.RefDistance, attenDistance, rolloff); dist > 0.0f) return gain * (props.RefDistance / dist); return gain; }); } break; case DistanceModel::Linear: case DistanceModel::LinearClamped: if(props.MaxDistance != props.RefDistance) { auto const scale = (attenDistance-props.RefDistance) / (props.MaxDistance-props.RefDistance); dryAttnBase = std::max(1.0f - scale*props.RolloffFactor, 0.0f); drygain.Base *= dryAttnBase; auto const wetbase = wetgain | std::views::transform(&GainTriplet::Base); std::ranges::transform(wetbase | std::views::take(numsends), roomrolloff, wetbase.begin(), [scale](f32 const gain, f32 const rolloff) { return gain * std::max(1.0f - scale*rolloff, 0.0f); }); } break; case DistanceModel::Exponent: case DistanceModel::ExponentClamped: if(attenDistance > 0.0f && props.RefDistance > 0.0f) { auto const dist_ratio = attenDistance / props.RefDistance; dryAttnBase = std::pow(dist_ratio, -props.RolloffFactor); drygain.Base *= dryAttnBase; auto const wetbase = wetgain | std::views::transform(&GainTriplet::Base); std::ranges::transform(wetbase | std::views::take(numsends), roomrolloff, wetbase.begin(), [dist_ratio](f32 const gain, f32 const rolloff) { return gain * std::pow(dist_ratio, -rolloff); }); } break; case DistanceModel::Disable: break; } /* Calculate directional sound cones */ auto wetcone = 1.0f; auto wetconehf = 1.0f; if(directional && props.InnerAngle < 360.0f) { static constexpr auto Rad2Deg = gsl::narrow_cast(180.0 / std::numbers::pi); auto const angle = Rad2Deg*2.0f * std::acos(-direction.dot_product(tosource)) * ConeScale; auto conegain = 1.0f; auto conehf = 1.0f; if(angle >= props.OuterAngle) { conegain = props.OuterGain; conehf = props.OuterGainHF; } else if(angle >= props.InnerAngle) { const auto scale = (angle-props.InnerAngle) / (props.OuterAngle-props.InnerAngle); conegain = lerpf(1.0f, props.OuterGain, scale); conehf = lerpf(1.0f, props.OuterGainHF, scale); } drygain.Base *= conegain; if(props.DryGainHFAuto) drygain.HF *= conehf; if(props.WetGainAuto) wetcone = conegain; if(props.WetGainHFAuto) wetconehf = conehf; } /* Apply gain and frequency filters */ auto const mingain = std::min(props.MinGain, props.MaxGain); auto const maxgain = props.MaxGain; drygain.Base = std::clamp(drygain.Base, mingain, maxgain) * props.Direct.Gain; drygain.Base = std::min(GainMixMax, drygain.Base * context->mParams.Gain); drygain.HF = drygain.HF * props.Direct.GainHF; drygain.LF = props.Direct.GainLF; std::ranges::transform(props.Send | std::views::take(numsends), wetgain, wetgain.begin(), [context,wetcone,wetconehf,mingain,maxgain](VoiceProps::SendData const &send, f32 const wetbase) { auto const gain = std::clamp(wetbase*wetcone, mingain, maxgain) * send.Gain; return GainTriplet{ .Base = std::min(GainMixMax, gain * context->mParams.Gain), .HF = send.GainHF * wetconehf, .LF = send.GainLF }; }, std::identity{}, &GainTriplet::Base); /* 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. */ auto const distance_units = (distance-props.RefDistance) * props.RolloffFactor; auto const distance_meters = distance_units * context->mParams.MetersPerUnit; auto const absorb = 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(auto const i : std::views::iota(props.WetGainAuto ? 0_uz : numsends, numsends)) { 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); auto const 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. */ auto const fact = distance_meters / DecayDistance; auto const gain = std::pow(ReverbDecayGain, fact)*(1.0f-dryAttnBase) + dryAttnBase; wetgain[i].Base *= gain; } } /* Initial source pitch */ auto pitch = props.Pitch; /* Calculate velocity-based doppler effect */ if(auto const DopplerFactor = props.DopplerFactor * context->mParams.DopplerFactor; DopplerFactor > 0.0f) { auto const &lvelocity = context->mParams.Velocity; auto const vss = velocity.dot_product(tosource) * -DopplerFactor; auto const vls = lvelocity.dot_product(tosource) * -DopplerFactor; if(auto const SpeedOfSound = context->mParams.SpeedOfSound; !(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 *= gsl::narrow_cast(voice->mFrequency) / gsl::narrow_cast(device->mSampleRate); if(pitch > f32{MaxPitch}) voice->mStep = MaxPitch<mStep = std::max(fastf2u(pitch * MixerFracOne), 1u); voice->mResampler = PrepareResampler(props.mResampler, voice->mStep, &voice->mResampleState); auto spread = 0.0f; if(props.Radius > distance) spread = std::numbers::pi_v*2.0f - distance/props.Radius*std::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, context->mParams, device); } void CalcVoiceParams(Voice *const voice, ContextBase *const context, bool const force) { if(auto *const props = voice->mUpdate.exchange(nullptr, std::memory_order_acq_rel)) { voice->mProps = static_cast(*props); AtomicReplaceHead(context->mFreeVoiceProps, props); } else if(!force) return; auto const &props = voice->mProps; if(auto const ismono3d = voice->mFmtChannels == FmtMono && !voice->mProps.mPanningEnabled; (props.DirectChannels != DirectMode::Off && !ismono3d && !IsAmbisonic(voice->mFmtChannels)) || props.mSpatializeMode == SpatializeMode::Off || (props.mSpatializeMode == SpatializeMode::Auto && !ismono3d)) CalcNonAttnVoiceParams(voice, context); else CalcAttnVoiceParams(voice, context); } void SendSourceStateEvent(ContextBase const *const context, u32 const id, VChangeState const state) { auto *const ring = context->mAsyncEvents.get(); auto const evt_vec = ring->getWriteVector(); if(evt_vec[0].empty()) return; auto &evt = InitAsyncEvent(evt_vec[0].front()); 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: break; } ring->writeAdvance(1); } void ProcessVoiceChanges(ContextBase *const ctx) { auto *cur = ctx->mCurrentVoiceChange.load(std::memory_order_acquire); auto *next = cur->mNext.load(std::memory_order_acquire); if(!next) return; const auto enabledevt = ctx->mEnabledEvts.load(std::memory_order_acquire); while(next) { cur = next; auto sendevt = false; if(cur->mState == VChangeState::Reset || cur->mState == VChangeState::Stop) { if(auto *const 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; auto 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) { auto *const voice = cur->mVoice; auto 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 on whether 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(auto *const 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); auto 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; auto *const 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. */ auto *const 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. */ auto oldvstate = Voice::Playing; oldvoice->mPlayState.compare_exchange_strong(oldvstate, Voice::Stopping, std::memory_order_relaxed, std::memory_order_acquire); auto *const 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); } ctx->mCurrentVoiceChange.store(cur, std::memory_order_release); } void ProcessParamUpdates(ContextBase *const ctx, std::span const slots, std::span const sorted_slots, std::span const voices) { ProcessVoiceChanges(ctx); IncrementRef(ctx->mUpdateCount); if(!ctx->mHoldUpdates.load(std::memory_order_acquire)) [[likely]] { auto force = CalcContextParams(ctx); auto const sorted_slot_base = std::to_address(sorted_slots.begin()); for(auto *slot : slots) force |= CalcEffectSlotParams(slot, sorted_slot_base, ctx); for(auto *const voice : voices) { /* Only update voices that have a source. */ if(voice->mSourceID.load(std::memory_order_relaxed) != 0) CalcVoiceParams(voice, ctx, force); } } IncrementRef(ctx->mUpdateCount); } void ProcessContexts(DeviceBase const *const device, u32 const SamplesToDo) { ASSUME(SamplesToDo > 0); auto const curtime = device->getClockTime(); auto const contexts = std::span{*device->mContexts.load(std::memory_order_acquire)}; std::ranges::for_each(contexts, [SamplesToDo,curtime](ContextBase *ctx) { auto const auxslotspan = std::span{*ctx->mActiveAuxSlots.load(std::memory_order_acquire)}; auto const auxslots = auxslotspan.first(auxslotspan.size()>>1); auto const sorted_slots = auxslotspan.last(auxslotspan.size()>>1); auto const voices = ctx->getVoicesSpanAcquired(); /* Process pending property updates for objects on the context. */ ProcessParamUpdates(ctx, auxslots, sorted_slots, voices); /* Clear auxiliary effect slot mixing buffers. */ std::ranges::fill(auxslots | std::views::transform(&EffectSlotBase::Wet) | std::views::transform(&MixParams::Buffer) | std::views::join | std::views::join, 0.0f); /* Process voices that have a playing source. */ std::ranges::for_each(voices, [ctx,curtime,SamplesToDo](Voice *voice) { if(auto const vstate = voice->mPlayState.load(std::memory_order_acquire); vstate != Voice::Stopped && vstate != Voice::Pending) voice->mix(vstate, ctx, curtime, SamplesToDo); }); /* 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. */ static constexpr auto has_target = [](EffectSlotBase const *const slot) noexcept { 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. */ Ensures(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 const not_next = [next_target](EffectSlotBase const *const slot) noexcept -> bool { return slot->Target != *next_target; }; split_point = std::partition(sorted_slots.begin(), split_point, not_next); } } std::ranges::for_each(sorted_slots, [SamplesToDo](EffectSlotBase const *const slot) { auto *const state = slot->mEffectState.get(); state->process(SamplesToDo, slot->Wet.Buffer, state->mOutTarget); }); } /* Signal the event handler if there are any events to read. */ if(auto const *const ring = ctx->mAsyncEvents.get(); ring->readSpace() > 0) { ctx->mEventsPending.store(1_u32, std::memory_order_release); al::atomic_notify_all(ctx->mEventsPending); } }); } void ApplyDistanceComp(std::span const Samples, usize const SamplesToDo, std::span const chandata) { ASSUME(SamplesToDo > 0); std::ignore = std::ranges::mismatch(chandata, Samples, [SamplesToDo](const DistanceComp::ChanData &distcomp, FloatBufferSpan chanbuffer) { auto const gain = distcomp.Gain; auto const distbuf = distcomp.Buffer; auto const base = distbuf.size(); if(base < 1) return true; auto const inout = chanbuffer.first(SamplesToDo); if(SamplesToDo >= base) [[likely]] { auto const inout_start = std::prev(inout.end(), gsl::narrow_cast(base)); auto const delay_end = std::ranges::rotate(inout, inout_start).begin(); std::ranges::swap_ranges(std::span{inout.begin(), delay_end}, distbuf); } else { auto const delay_start = std::ranges::swap_ranges(inout, distbuf).in2; std::ranges::rotate(distbuf, delay_start); } std::ranges::transform(inout, inout.begin(), [gain](f32 const s) noexcept -> f32 { return s*gain; }); return true; }); } void ApplyDither(std::span const Samples, u32 *const dither_seed, f32 const quant_scale, usize const SamplesToDo) { static constexpr auto 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. */ auto const invscale = 1.0f / quant_scale; auto seed = *dither_seed; auto dither_sample = [&seed,invscale,quant_scale](f32 const sample) noexcept -> f32 { auto val = sample * quant_scale; auto const rng0 = dither_rng(&seed); auto const rng1 = dither_rng(&seed); val += gsl::narrow_cast(rng0*invRNGRange - rng1*invRNGRange); return fast_roundf(val) * invscale; }; for(FloatBufferSpan const inout : Samples) std::ranges::transform(inout.first(SamplesToDo), inout.begin(), dither_sample); *dither_seed = seed; } template [[nodiscard]] auto SampleConv(f32) noexcept -> T = delete; template<> [[nodiscard]] auto SampleConv(f32 const val) noexcept -> f32 { return val; } template<> [[nodiscard]] auto SampleConv(f32 const val) noexcept -> i32 { /* 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<> [[nodiscard]] auto SampleConv(f32 const val) noexcept -> i16 { return gsl::narrow_cast(fastf2i(std::clamp(val*32768.0f, -32768.0f, 32767.0f))); } template<> [[nodiscard]] auto SampleConv(f32 const val) noexcept -> i8 { return gsl::narrow_cast(fastf2i(std::clamp(val*128.0f, -128.0f, 127.0f))); } /* Define unsigned output variations. */ template<> [[nodiscard]] auto SampleConv(f32 const val) noexcept -> u32 { return as_unsigned(SampleConv(val)) + 2147483648u; } template<> [[nodiscard]] auto SampleConv(f32 const val) noexcept -> u16 { return gsl::narrow_cast(SampleConv(val) + 32768); } template<> [[nodiscard]] auto SampleConv(f32 const val) noexcept -> u8 { return gsl::narrow_cast(SampleConv(val) + 128); } template void Write(std::span const InBuffer, void *const OutBuffer, usize const Offset, usize const SamplesToDo, usize const FrameStep) { ASSUME(FrameStep > 0); ASSUME(SamplesToDo > 0); auto const output = std::span{static_cast(OutBuffer), (Offset+SamplesToDo)*FrameStep} .subspan(Offset*FrameStep); /* If there's extra channels in the interleaved output buffer to skip, * clear the whole output buffer. This is simpler to ensure the extra * channels are silent than trying to clear just the extra channels. */ if(FrameStep > InBuffer.size()) std::ranges::fill(output, SampleConv(0.0f)); auto outbase = output.begin(); for(auto const &srcbuf : InBuffer) { auto out = outbase++; *out = SampleConv(srcbuf.front()); std::ranges::for_each(srcbuf | std::views::take(SamplesToDo) | std::views::drop(1), [FrameStep,&out](f32 const s) noexcept { std::advance(out, FrameStep); *out = SampleConv(s); }); } } template void Write(std::span const InBuffer, std::span const OutBuffers, usize const Offset, usize const SamplesToDo) { ASSUME(SamplesToDo > 0); std::ignore = std::ranges::mismatch(OutBuffers, InBuffer, [Offset,SamplesToDo](void *const dstbuf, FloatConstBufferSpan const srcbuf) { auto const dst = std::span{static_cast(dstbuf), Offset+SamplesToDo}.subspan(Offset); std::ranges::transform(srcbuf | std::views::take(SamplesToDo), dst.begin(), SampleConv); return true; }); } } // namespace auto DeviceBase::renderSamples(u32 const numSamples) -> u32 { auto const samplesToDo = std::min(numSamples, u32{BufferLineSize}); /* Clear main mixing buffers. */ std::ranges::fill(MixBuffer | std::views::join, 0.0f); { auto const 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 const samplesDone = mSamplesDone.load(std::memory_order_relaxed) + samplesToDo; auto const 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.). */ std::visit([this,samplesToDo](auto &arg) { this->Process(arg, samplesToDo); }, mPostProcess); /* 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(std::span const outBuffers, u32 const numSamples) { auto mixer_mode = FPUCtl{}; auto total = 0_u32; while(auto const todo = numSamples - total) { auto const 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 *const outBuffer, u32 const numSamples, usize const frameStep) { auto mixer_mode = FPUCtl{}; auto total = 0_u32; while(auto const todo = numSamples - total) { auto const 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)) { auto evt = std::array{AsyncEvent{std::in_place_type}}; auto &disconnect = std::get(evt.front()); disconnect.msg = std::move(msg); for(auto *ctx : *mContexts.load()) { if(auto *const ring = ctx->mAsyncEvents.get(); ring->write(evt) > 0) { ctx->mEventsPending.store(1_u32, std::memory_order_release); al::atomic_notify_all(ctx->mEventsPending); } if(!ctx->mStopVoicesOnDisconnect.load()) { ProcessVoiceChanges(ctx); continue; } std::ranges::for_each(ctx->getVoicesSpanAcquired(), [](Voice *const voice) { 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); }); } } } kcat-openal-soft-75c0059/alc/alu.h000066400000000000000000000014061512220627100166270ustar00rootroot00000000000000#ifndef ALU_H #define ALU_H #include #include #include struct EffectSlotBase; enum class StereoEncoding : std::uint8_t; namespace al { struct Context; 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(EffectSlotBase *slot, al::Context *context); #endif kcat-openal-soft-75c0059/alc/backends/000077500000000000000000000000001512220627100174465ustar00rootroot00000000000000kcat-openal-soft-75c0059/alc/backends/alsa.cpp000066400000000000000000001345611512220627100211040ustar00rootroot00000000000000/** * 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 "alformat.hpp" #include "alnumeric.h" #include "althrd_setname.h" #include "core/device.h" #include "core/helpers.h" #include "core/logging.h" #include "dynload.h" #include "gsl/gsl" #include "ringbuffer.h" #include namespace { using namespace std::string_literals; 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 using HwParamsPtr = std::unique_ptr; auto CreateHwParams() -> HwParamsPtr { auto ret = HwParamsPtr{}; snd_pcm_hw_params_malloc(al::out_ptr(ret)); return ret; } using SwParamsPtr = std::unique_ptr; auto CreateSwParams() -> SwParamsPtr { auto ret = SwParamsPtr{}; snd_pcm_sw_params_malloc(al::out_ptr(ret)); return ret; } using CtlCardInfoPtr = std::unique_ptr; auto CreateCtlCardInfo() -> CtlCardInfoPtr { auto ret = CtlCardInfoPtr{}; snd_ctl_card_info_malloc(al::out_ptr(ret)); return ret; } using PcmInfoPtr = std::unique_ptr; auto CreatePcmInfo() -> PcmInfoPtr { auto ret = PcmInfoPtr{}; snd_pcm_info_malloc(al::out_ptr(ret)); return ret; } using SndCtlPtr = std::unique_ptr; struct DevMap { std::string name; std::string device_name; }; std::vector PlaybackDevices; std::vector CaptureDevices; auto prefix_name(snd_pcm_stream_t stream) noexcept -> std::string_view { if(stream == SND_PCM_STREAM_PLAYBACK) return "device-prefix"sv; return "capture-prefix"sv; } auto probe_devices(snd_pcm_stream_t stream) -> std::vector { auto devlist = std::vector{}; const auto info = CreateCtlCardInfo(); const auto pcminfo = CreatePcmInfo(); auto defname = ConfigValueStr({}, "alsa"sv, (stream == SND_PCM_STREAM_PLAYBACK) ? "device"sv : "capture"sv); devlist.emplace_back(std::string{GetDefaultName()}, defname ? *defname : "default"s); if(auto customdevs = ConfigValueStr({}, "alsa"sv, (stream == SND_PCM_STREAM_PLAYBACK) ? "custom-devices"sv : "custom-captures"sv)) { auto curpos = customdevs->find_first_not_of(';'); while(curpos < customdevs->length()) { auto nextpos = customdevs->find(';', curpos+1); const auto 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 auto &entry = devlist.emplace_back(customdevs->substr(curpos, seppos-curpos), customdevs->substr(seppos+1, nextpos-seppos-1)); TRACE(R"(Got device "{}", "{}")", entry.name, entry.device_name); } if(nextpos < customdevs->length()) nextpos = customdevs->find_first_not_of(';', nextpos+1); curpos = nextpos; } } const auto main_prefix = std::string{ConfigValueStr({}, "alsa"sv, prefix_name(stream)) .value_or("plughw:")}; auto card = -1; auto err = snd_card_next(&card); for(;err >= 0 && card >= 0;err = snd_card_next(&card)) { auto handle = SndCtlPtr{}; err = snd_ctl_open(al::out_ptr(handle), al::format("hw:{}", card).c_str(), 0); if(err < 0) { ERR("control open (hw:{}): {}", card, snd_strerror(err)); continue; } err = snd_ctl_card_info(handle.get(), info.get()); if(err < 0) { ERR("control hardware info (hw:{}): {}", card, snd_strerror(err)); continue; } const auto *cardname = snd_ctl_card_info_get_name(info.get()); const auto *cardid = snd_ctl_card_info_get_id(info.get()); auto name = al::format("{}-{}", prefix_name(stream), cardid); const auto card_prefix = std::string{ConfigValueStr({}, "alsa"sv, name) .value_or(main_prefix)}; auto dev = -1; while(true) { if(snd_ctl_pcm_next_device(handle.get(), &dev) < 0) ERR("snd_ctl_pcm_next_device failed"); if(dev < 0) break; snd_pcm_info_set_device(pcminfo.get(), gsl::narrow_cast(dev)); snd_pcm_info_set_subdevice(pcminfo.get(), 0); snd_pcm_info_set_stream(pcminfo.get(), stream); err = snd_ctl_pcm_info(handle.get(), pcminfo.get()); if(err < 0) { if(err != -ENOENT) ERR("control digital audio info (hw:{}): {}", card, snd_strerror(err)); continue; } /* "prefix-cardid-dev" */ name = al::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 = al::format("{}, {} (CARD={},DEV={})", cardname, snd_pcm_info_get_name(pcminfo.get()), cardid, dev); /* "devprefixCARD=cardid,DEV=dev" */ auto device = al::format("{}CARD={},DEV={}", device_prefix, cardid, dev); const auto &entry = devlist.emplace_back(std::move(name), std::move(device)); TRACE(R"(Got device "{}", "{}")", entry.name, entry.device_name); } } if(err < 0) ERR("snd_card_next failed: {}", snd_strerror(err)); return devlist; } auto verify_state(snd_pcm_t *handle) -> int { const auto 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(const auto err = snd_pcm_recover(handle, -EPIPE, 1); err < 0) return err; break; case SND_PCM_STATE_SUSPENDED: if(const auto 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*/: break; } return state; } struct AlsaPlayback final : BackendBase { explicit AlsaPlayback(gsl::not_null const device) noexcept : BackendBase{device} { } ~AlsaPlayback() override; void mixerProc(); void mixerNoMMapProc(); void open(std::string_view name) override; auto reset() -> bool override; void start() override; void stop() override; auto getClockLatency() -> ClockLatency override; snd_pcm_t *mPcmHandle{nullptr}; std::mutex mMutex; unsigned mFrameStep{}; std::vector mBuffer; std::atomic mKillNow{true}; std::thread mThread; }; AlsaPlayback::~AlsaPlayback() { if(mPcmHandle) snd_pcm_close(mPcmHandle); mPcmHandle = nullptr; } void AlsaPlayback::mixerProc() { SetRTPriority(); althrd_setname(GetMixerThreadName()); const auto update_size = snd_pcm_uframes_t{mDevice->mUpdateSize}; const auto buffer_size = snd_pcm_uframes_t{mDevice->mBufferSize}; while(!mKillNow.load(std::memory_order_acquire)) { const auto state = verify_state(mPcmHandle); if(state < 0) { ERR("Invalid state detected: {}", snd_strerror(state)); mDevice->handleDisconnect("Bad state: {}", snd_strerror(state)); break; } const auto avails = snd_pcm_avail_update(mPcmHandle); if(avails < 0) { ERR("available update failed: {}", snd_strerror(gsl::narrow_cast(avails))); continue; } auto avail = gsl::narrow_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) { if(const auto err = snd_pcm_start(mPcmHandle); 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 auto dlock = std::lock_guard{mMutex}; while(auto frames = avail) { const snd_pcm_channel_area_t *areas{}; auto offset = snd_pcm_uframes_t{}; if(const auto err = snd_pcm_mmap_begin(mPcmHandle, &areas, &offset, &frames); err < 0) { ERR("mmap begin error: {}", snd_strerror(err)); break; } /* NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) */ auto *WritePtr = static_cast(areas->addr) + (offset * areas->step / 8); mDevice->renderSamples(WritePtr, gsl::narrow_cast(frames), mFrameStep); if(const auto commitres = snd_pcm_mmap_commit(mPcmHandle, offset, frames); std::cmp_not_equal(commitres, frames)) { ERR("mmap commit error: {}", snd_strerror(commitres >= 0 ? -EPIPE : gsl::narrow_cast(commitres))); break; } avail -= frames; } } } void AlsaPlayback::mixerNoMMapProc() { SetRTPriority(); althrd_setname(GetMixerThreadName()); const auto update_size = snd_pcm_uframes_t{mDevice->mUpdateSize}; const auto buffer_size = snd_pcm_uframes_t{mDevice->mBufferSize}; while(!mKillNow.load(std::memory_order_acquire)) { const auto state = verify_state(mPcmHandle); if(state < 0) { ERR("Invalid state detected: {}", snd_strerror(state)); mDevice->handleDisconnect("Bad state: {}", snd_strerror(state)); break; } auto avail = snd_pcm_avail_update(mPcmHandle); if(avail < 0) { ERR("available update failed: {}", snd_strerror(gsl::narrow_cast(avail))); continue; } if(std::cmp_greater(avail, buffer_size)) { WARN("available samples exceeds the buffer size"); snd_pcm_reset(mPcmHandle); continue; } if(std::cmp_less(avail, update_size)) { if(state != SND_PCM_STATE_RUNNING) { if(const auto err = snd_pcm_start(mPcmHandle); 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, std::ssize(mBuffer)); const auto dlock = std::lock_guard{mMutex}; mDevice->renderSamples(std::to_address(WritePtr), gsl::narrow_cast(avail), mFrameStep); while(avail > 0) { auto ret = snd_pcm_writei(mPcmHandle, std::to_address(WritePtr), gsl::narrow_cast(avail)); switch(ret) { case -EAGAIN: continue; #if ESTRPIPE != EPIPE case -ESTRPIPE: #endif case -EPIPE: case -EINTR: ret = snd_pcm_recover(mPcmHandle, gsl::narrow_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; } } } } void AlsaPlayback::open(std::string_view name) { auto driver = "default"s; if(!name.empty()) { if(PlaybackDevices.empty()) PlaybackDevices = probe_devices(SND_PCM_STREAM_PLAYBACK); const auto iter = std::ranges::find(PlaybackDevices, name, &DevMap::name); if(iter == PlaybackDevices.end()) 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{}; if(const auto err = snd_pcm_open(&pcmHandle, driver.c_str(), SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); 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; } auto AlsaPlayback::reset() -> bool { auto 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; } auto allowmmap = GetConfigValueBool(mDevice->mDeviceName, "alsa"sv, "mmap"sv, true); auto periodLen = gsl::narrow_cast(mDevice->mUpdateSize * 1000000_u64 / mDevice->mSampleRate); auto bufferLen = gsl::narrow_cast(mDevice->mBufferSize * 1000000_u64 / mDevice->mSampleRate); auto rate = mDevice->mSampleRate; auto hp = CreateHwParams(); #define CHECK(x) do { \ if(const auto 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 auto formatlist = std::array{ 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) { auto 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(const auto 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(const auto 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 */ auto periodSizeInFrames = snd_pcm_uframes_t{}; auto bufferSizeInFrames = snd_pcm_uframes_t{}; auto access = snd_pcm_access_t{}; 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; auto 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 = gsl::narrow_cast(bufferSizeInFrames); mDevice->mUpdateSize = gsl::narrow_cast(periodSizeInFrames); mDevice->mSampleRate = rate; setDefaultChannelOrder(); return true; } void AlsaPlayback::start() { auto access = snd_pcm_access_t{}; auto hp = CreateHwParams(); #define CHECK(x) do { \ if(const auto 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; void (AlsaPlayback::*thread_func)(){}; if(access == SND_PCM_ACCESS_RW_INTERLEAVED) { auto const datalen = snd_pcm_frames_to_bytes(mPcmHandle, mDevice->mUpdateSize); mBuffer.resize(gsl::narrow(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(); if(const auto err = snd_pcm_drop(mPcmHandle); err < 0) ERR("snd_pcm_drop failed: {}", snd_strerror(err)); } auto AlsaPlayback::getClockLatency() -> ClockLatency { const auto dlock = std::lock_guard{mMutex}; auto ret = ClockLatency{}; ret.ClockTime = mDevice->getClockTime(); auto delay = snd_pcm_sframes_t{}; if(const auto err = snd_pcm_delay(mPcmHandle, &delay); 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(gsl::not_null device) noexcept : BackendBase{device} { } ~AlsaCapture() override; void open(std::string_view name) override; void start() override; void stop() override; void captureSamples(std::span outbuffer) override; auto availableSamples() -> usize override; auto getClockLatency() -> ClockLatency override; snd_pcm_t *mPcmHandle{nullptr}; std::vector mBuffer; bool mDoCapture{false}; RingBufferPtr mRing; snd_pcm_sframes_t mLastAvail{0}; }; AlsaCapture::~AlsaCapture() { if(mPcmHandle) snd_pcm_close(mPcmHandle); mPcmHandle = nullptr; } void AlsaCapture::open(std::string_view name) { auto driver = "default"s; if(!name.empty()) { if(CaptureDevices.empty()) CaptureDevices = probe_devices(SND_PCM_STREAM_CAPTURE); const auto iter = std::ranges::find(CaptureDevices, name, &DevMap::name); if(iter == CaptureDevices.end()) 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(const auto 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(); auto 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; } auto bufferSizeInFrames = snd_pcm_uframes_t{std::max(mDevice->mBufferSize, 100u*mDevice->mSampleRate/1000u)}; auto periodSizeInFrames = snd_pcm_uframes_t{std::min(mDevice->mBufferSize, 25u*mDevice->mSampleRate/1000u)}; auto needring = false; auto hp = CreateHwParams(); #define CHECK(x) do { \ if(const auto 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(const auto err = snd_pcm_prepare(mPcmHandle); err < 0) throw al::backend_exception{al::backend_error::DeviceError, "snd_pcm_prepare failed: {}", snd_strerror(err)}; if(const auto 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. */ const auto 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 const savail = al::saturate_cast(avail); auto const numbytes = snd_pcm_frames_to_bytes(mPcmHandle, savail); auto temp = std::vector(al::saturate_cast(numbytes)); captureSamples(temp); mBuffer = std::move(temp); } if(const auto err = snd_pcm_drop(mPcmHandle); err < 0) ERR("snd_pcm_drop failed: {}", snd_strerror(err)); mDoCapture = false; } void AlsaCapture::captureSamples(std::span outbuffer) { if(mRing) { std::ignore = mRing->read(outbuffer); return; } const auto bpf = snd_pcm_frames_to_bytes(mPcmHandle, 1); mLastAvail -= std::ssize(outbuffer) / bpf; while(mDevice->Connected.load(std::memory_order_acquire) && !outbuffer.empty()) { if(!mBuffer.empty()) { /* First get any data stored from the last stop */ std::ranges::copy(mBuffer | std::views::take(outbuffer.size()), outbuffer.begin()); const auto amt = std::min(std::ssize(outbuffer), std::ssize(mBuffer)); mBuffer.erase(mBuffer.begin(), mBuffer.begin()+amt); outbuffer = outbuffer.subspan(as_unsigned(amt)); continue; } auto amt = snd_pcm_sframes_t{0}; if(mDoCapture) { amt = std::ssize(outbuffer) / bpf; amt = snd_pcm_readi(mPcmHandle, outbuffer.data(), as_unsigned(amt)); } if(amt < 0) { ERR("read error: {}", snd_strerror(gsl::narrow_cast(amt))); if(amt == -EAGAIN) continue; amt = snd_pcm_recover(mPcmHandle, gsl::narrow_cast(amt), 1); if(amt >= 0) { amt = snd_pcm_start(mPcmHandle); if(amt >= 0) amt = snd_pcm_avail_update(mPcmHandle); } if(amt < 0) { auto *err = snd_strerror(gsl::narrow_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(amt*bpf < std::ssize(outbuffer)) break; continue; } outbuffer = outbuffer.subspan(as_unsigned(amt*bpf)); } if(!outbuffer.empty()) std::ranges::fill(outbuffer, (mDevice->FmtType==DevFmtUByte)?std::byte{0x80}:std::byte{0}); } auto AlsaCapture::availableSamples() -> usize { auto avail = snd_pcm_sframes_t{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(gsl::narrow_cast(avail))); avail = snd_pcm_recover(mPcmHandle, gsl::narrow_cast(avail), 1); if(avail >= 0) { avail = snd_pcm_start(mPcmHandle); if(avail >= 0) avail = snd_pcm_avail_update(mPcmHandle); } if(avail < 0) { auto *err = snd_strerror(gsl::narrow_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, std::ssize(mBuffer)); mLastAvail = std::max(mLastAvail, avail); return gsl::narrow_cast(mLastAvail); } while(avail > 0) { auto vec = mRing->getWriteVector(); if(vec[0].empty()) break; auto amt = snd_pcm_bytes_to_frames(mPcmHandle, std::ssize(vec[0])); amt = std::min(amt, avail); amt = snd_pcm_readi(mPcmHandle, vec[0].data(), gsl::narrow_cast(amt)); if(amt < 0) { ERR("read error: {}", snd_strerror(gsl::narrow_cast(amt))); if(amt == -EAGAIN) continue; amt = snd_pcm_recover(mPcmHandle, gsl::narrow_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) { auto *err = snd_strerror(gsl::narrow_cast(amt)); ERR("restore error: {}", err); mDevice->handleDisconnect("Capture recovery failure: {}", err); break; } avail = amt; continue; } mRing->writeAdvance(gsl::narrow_cast(amt)); avail -= amt; } return mRing->readSpace(); } auto AlsaCapture::getClockLatency() -> ClockLatency { auto ret = ClockLatency{}; ret.ClockTime = mDevice->getClockTime(); auto delay = snd_pcm_sframes_t{}; if(const auto err = snd_pcm_delay(mPcmHandle, &delay); 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; } #define ALSA_LIB "libasound.so.2" #if HAVE_DYNLOAD OAL_ELF_NOTE_DLOPEN( "backend-alsa", "Support for the ALSA backend", OAL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, ALSA_LIB ); #endif } // namespace auto AlsaBackendFactory::init() -> bool { #if HAVE_DYNLOAD if(!alsa_handle) { auto *const alsa_lib = gsl::czstring{ALSA_LIB}; if(auto const libresult = LoadLib(alsa_lib)) alsa_handle = libresult.value(); else { WARN("Failed to load {}: {}", alsa_lib, libresult.error()); return false; } static constexpr auto load_func = [](auto *&func, gsl::czstring const name) -> bool { using func_t = std::remove_reference_t; auto const funcresult = GetSymbol(alsa_handle, name); if(!funcresult) { WARN("Failed to load function {}: {}", name, funcresult.error()); return false; } /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) */ func = reinterpret_cast(funcresult.value()); return true; }; auto ok = true; #define LOAD_FUNC(f) ok &= load_func(p##f, #f) ALSA_FUNCS(LOAD_FUNC); #undef LOAD_FUNC if(!ok) { 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 { auto outnames = std::vector{}; 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::ranges::for_each(PlaybackDevices, add_device); break; case BackendType::Capture: CaptureDevices = probe_devices(SND_PCM_STREAM_CAPTURE); outnames.reserve(CaptureDevices.size()); std::ranges::for_each(CaptureDevices, add_device); break; } return outnames; } auto AlsaBackendFactory::createBackend(gsl::not_null device, BackendType type) -> BackendPtr { 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; } kcat-openal-soft-75c0059/alc/backends/alsa.h000066400000000000000000000007141512220627100205410ustar00rootroot00000000000000#ifndef BACKENDS_ALSA_H #define BACKENDS_ALSA_H #include "base.h" struct AlsaBackendFactory final : BackendFactory { auto init() -> bool final; auto querySupport(BackendType type) -> bool final; auto enumerate(BackendType type) -> std::vector final; auto createBackend(gsl::not_null device, BackendType type) -> BackendPtr final; static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_ALSA_H */ kcat-openal-soft-75c0059/alc/backends/base.cpp000066400000000000000000000214331512220627100210670ustar00rootroot00000000000000 #include "config.h" #include "base.h" #include #include #include #include "alformat.hpp" #include "core/devformat.h" namespace al { auto backend_exception::make_string(al::string_view const fmt, al::format_args args) -> std::string { return al::vformat(fmt, std::move(args)); } } // namespace al auto BackendBase::reset() -> bool { throw al::backend_exception{al::backend_error::DeviceError, "Invalid BackendBase call"}; } void BackendBase::captureSamples(std::span outbuffer [[maybe_unused]]) { } auto BackendBase::availableSamples() -> usize { return 0_uz; } auto BackendBase::getClockLatency() -> ClockLatency { auto ret = ClockLatency{}; auto refcount = u32{}; 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; } } kcat-openal-soft-75c0059/alc/backends/base.h000066400000000000000000000070551512220627100205400ustar00rootroot00000000000000#ifndef ALC_BACKENDS_BASE_H #define ALC_BACKENDS_BASE_H #include #include #include #include #include #include #include "alc/events.h" #include "alformat.hpp" #include "altypes.hpp" #include "core/device.h" #include "core/except.h" #include "gsl/gsl" #include "opthelpers.h" struct ClockLatency { std::chrono::nanoseconds ClockTime; std::chrono::nanoseconds Latency; }; struct BackendBase { virtual void open(std::string_view name) = 0; virtual auto reset() -> bool; virtual void start() = 0; virtual void stop() = 0; virtual void captureSamples(std::span outbuffer); virtual auto availableSamples() -> usize; virtual auto getClockLatency() -> ClockLatency; gsl::not_null const mDevice; std::string mDeviceName; BackendBase() = delete; BackendBase(const BackendBase&) = delete; BackendBase(BackendBase&&) = delete; explicit BackendBase(gsl::not_null const 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 auto GetClockLatency(DeviceBase const *const device, BackendBase *const backend) -> ClockLatency { auto 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(gsl::not_null device, BackendType type) -> BackendPtr = 0; }; namespace al { enum class backend_error { NoDevice, DeviceError, OutOfMemory }; /* NOLINTNEXTLINE(clazy-copyable-polymorphic) Exceptions must be copyable. */ class backend_exception final : public base_exception { backend_error mErrorCode; static auto make_string(al::string_view fmt, al::format_args args) -> std::string; public: template backend_exception(backend_error const code, al::format_string fmt, Args&& ...args) : base_exception{make_string(fmt.get(), al::make_format_args(args...))}, mErrorCode{code} { } backend_exception(const backend_exception&) = default; backend_exception(backend_exception&&) = default; NOINLINE ~backend_exception() override = default; 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 */ kcat-openal-soft-75c0059/alc/backends/coreaudio.cpp000066400000000000000000001164211512220627100221310ustar00rootroot00000000000000/** * 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 #include "alformat.hpp" #include "alnumeric.h" #include "alstring.h" #include "core/converter.h" #include "core/device.h" #include "core/logging.h" #include "gsl/gsl" #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(const auto i : std::views::iota(0_uz, sizeof(UInt32))) { const auto ch = gsl::narrow_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{gsl::narrow_cast(code)} { } constexpr auto c_str() const noexcept -> gsl::czstring { 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(gsl::narrow_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); /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) */ 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(usize 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 = std::ranges::find(newdevs, devId, &DeviceEntry::mId); if(match != newdevs.end()) 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) { const auto subrange = std::span{newdevs.begin(), curitem}; auto check_match = [curitem](const DeviceEntry &entry) -> bool { return entry.mName == curitem->mName; }; if(std::ranges::find(subrange, curitem->mName, &DeviceEntry::mName) != subrange.end()) { auto name = std::string{}; auto count = 1_uz; do { name = al::format("{} #{}", curitem->mName, ++count); } while(std::ranges::find(subrange, name, &DeviceEntry::mName) != subrange.end()); 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(gsl::not_null 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{}; u32 mFrameSize{0_u32}; 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(usize 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 devmatch = std::ranges::find(PlaybackList, name, &DeviceEntry::mName); if(devmatch == PlaybackList.end()) 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 = gsl::narrow_cast(mDevice->mBufferSize*streamFormat.mSampleRate /mDevice->mSampleRate + 0.5); mDevice->mSampleRate = gsl::narrow_cast(streamFormat.mSampleRate); } struct ChannelMap { DevFmtChannels fmt; std::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); /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) */ auto *layout = reinterpret_cast(layout_data.get()); err = AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_AudioChannelLayout, kAudioUnitScope_Output, OutputElement, layout, &propSize); if(err == noErr) { auto descs = std::span{std::data(layout->mChannelDescriptions), layout->mNumberChannelDescriptions}; auto labels = std::vector(descs.size()); std::ranges::transform(descs, labels.begin(), &AudioChannelDescription::mChannelLabel); std::ranges::sort(labels); auto check_labels = [&labels](const ChannelMap &chanmap) -> bool { return std::ranges::includes(labels, chanmap.map); }; auto chaniter = std::ranges::find_if(chanmaps, check_labels); if(chaniter != chanmaps.end()) 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; [[fallthrough]]; case DevFmtByte: streamFormat.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger; streamFormat.mBitsPerChannel = 8; break; case DevFmtUShort: mDevice->FmtType = DevFmtShort; [[fallthrough]]; case DevFmtShort: streamFormat.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger; streamFormat.mBitsPerChannel = 16; break; case DevFmtUInt: mDevice->FmtType = DevFmtInt; [[fallthrough]]; 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(gsl::not_null 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::span outbuffer) override; auto availableSamples() -> usize override; AudioUnit mAudioUnit{0}; u32 mFrameSize{0_u32}; AudioStreamBasicDescription mFormat{}; // This is the OpenAL format as a CoreAudio ASBD SampleConverterPtr mConverter; std::vector mCaptureData; RingBufferPtr mRing; }; 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 = gsl::narrow_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(std::span{mCaptureData}.first(inNumberFrames*usize{mFrameSize})); 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 devmatch = std::ranges::find(CaptureList, name, &DeviceEntry::mName); if(devmatch == CaptureList.end()) 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( gsl::narrow_cast(std::ceil(mDevice->mBufferSize*srateScale)), gsl::narrow_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 = gsl::narrow_cast(std::max(u64{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, gsl::narrow_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::span outbuffer) { if(!mConverter) { std::ignore = mRing->read(outbuffer); return; } auto rec_vec = mRing->getReadVector(); const void *src0 = rec_vec[0].data(); auto src0len = gsl::narrow_cast(rec_vec[0].size() / mFrameSize); auto got = mConverter->convert(&src0, &src0len, outbuffer.data(), gsl::narrow_cast(outbuffer.size()/mFrameSize)); auto total_read = rec_vec[0].size()/mFrameSize - src0len; if(got < outbuffer.size()/mFrameSize && !src0len && !rec_vec[1].empty()) { outbuffer = outbuffer.subspan(got*mFrameSize); const void *src1 = rec_vec[1].data(); auto src1len = gsl::narrow_cast(rec_vec[1].size()/mFrameSize); std::ignore = mConverter->convert(&src1, &src1len, outbuffer.data(), gsl::narrow_cast(outbuffer.size()/mFrameSize)); total_read += rec_vec[1].size()/mFrameSize - src1len; } mRing->readAdvance(total_read); } auto CoreAudioCapture::availableSamples() -> usize { if(!mConverter) return mRing->readSpace(); return mConverter->availableOut(gsl::narrow_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; } auto CoreAudioBackendFactory::createBackend(gsl::not_null device, BackendType type) -> BackendPtr { 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; } kcat-openal-soft-75c0059/alc/backends/coreaudio.h000066400000000000000000000011041512220627100215650ustar00rootroot00000000000000#ifndef BACKENDS_COREAUDIO_H #define BACKENDS_COREAUDIO_H #include "base.h" struct CoreAudioBackendFactory final : BackendFactory { 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(gsl::not_null device, BackendType type) -> BackendPtr final; static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_COREAUDIO_H */ kcat-openal-soft-75c0059/alc/backends/dsound.cpp000066400000000000000000000674151512220627100214630ustar00rootroot00000000000000/** * 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" #include #include #include #ifndef _WAVEFORMATEXTENSIBLE_ #include #include #endif #include #include #include #include #include #include #include #include #include #include "alformat.hpp" #include "alnumeric.h" #include "althrd_setname.h" #include "comptr.h" #include "core/device.h" #include "core/helpers.h" #include "core/logging.h" #include "dynload.h" #include "gsl/gsl" #include "ringbuffer.h" #include "strutils.hpp" /* MinGW-w64 needs this for some unknown reason now. */ using LPCWAVEFORMATEX = const WAVEFORMATEX*; #include /* NOLINT(readability-duplicate-include) Not the same */ #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; }; auto PlaybackDevices = std::vector{}; auto CaptureDevices = std::vector{}; auto checkName(const std::span list, const std::string_view name) -> bool { return std::ranges::find(list, name, &DevMap::name) != list.end(); } auto CALLBACK DSoundEnumDevices(GUID *guid, const WCHAR *desc, const WCHAR*, void *data) noexcept -> BOOL { 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 = al::format("{} #{}", basename, ++count); const DevMap &newentry = devices.emplace_back(std::move(newname), *guid); auto *guidstr = LPOLESTR{}; if(const auto hr = StringFromCLSID(*guid, &guidstr); SUCCEEDED(hr)) { TRACE(R"(Got device "{}", GUID "{}")", newentry.name, wstr_to_utf8(guidstr)); CoTaskMemFree(guidstr); } return TRUE; } struct DSoundPlayback final : public BackendBase { explicit DSoundPlayback(gsl::not_null device) noexcept : BackendBase{device} { } ~DSoundPlayback() override; void mixerProc() const; void open(std::string_view name) override; auto reset() -> bool 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 void DSoundPlayback::mixerProc() const { SetRTPriority(); althrd_setname(GetMixerThreadName()); auto DSBCaps = DSBCAPS{}; DSBCaps.dwSize = sizeof(DSBCaps); auto 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; } auto const FrameStep = usize{mDevice->channelsFromFmt()}; auto const FrameSize = DWORD{mDevice->frameSizeFromFmt()}; auto const FragSize = DWORD{mDevice->mUpdateSize} * FrameSize; auto Playing = false; auto LastCursor = DWORD{0}; std::ignore = mBuffer->GetCurrentPosition(&LastCursor, nullptr); while(!mKillNow.load(std::memory_order_acquire) && mDevice->Connected.load(std::memory_order_acquire)) { // Get current play cursor auto PlayCursor = DWORD{}; std::ignore = mBuffer->GetCurrentPosition(&PlayCursor, nullptr); auto 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; } Playing = true; } avail = WaitForSingleObjectEx(mNotifyEvent, 2000, FALSE); if(avail != WAIT_OBJECT_0) ERR("WaitForSingleObjectEx error: {:#x}", avail); continue; } avail -= avail%FragSize; // Lock output buffer auto *WritePtr1 = LPVOID{}; auto *WritePtr2 = LPVOID{}; auto WriteCnt1 = DWORD{}; auto WriteCnt2 = DWORD{}; 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; } mDevice->renderSamples(WritePtr1, WriteCnt1/FrameSize, FrameStep); if(WriteCnt2 > 0) mDevice->renderSamples(WritePtr2, WriteCnt2/FrameSize, FrameStep); std::ignore = mBuffer->Unlock(WritePtr1, WriteCnt1, WritePtr2, WriteCnt2); // Update old write cursor location LastCursor += WriteCnt1+WriteCnt2; LastCursor %= DSBCaps.dwBufferBytes; } } void DSoundPlayback::open(std::string_view name) { auto hr = HRESULT{}; if(PlaybackDevices.empty()) { /* Initialize COM to prevent name truncation */ auto const com = ComWrapper{}; hr = DirectSoundEnumerateW(DSoundEnumDevices, &PlaybackDevices); if(FAILED(hr)) ERR("Error enumerating DirectSound devices: {:#x}", as_unsigned(hr)); } auto *guid = LPCGUID{nullptr}; if(name.empty() && !PlaybackDevices.empty()) { name = PlaybackDevices[0].name; guid = &PlaybackDevices[0].guid; } else { auto iter = std::ranges::find(PlaybackDevices, name, &DevMap::name); if(iter == PlaybackDevices.end()) { auto id = GUID{}; hr = CLSIDFromString(utf8_to_wstr(name).c_str(), &id); if(SUCCEEDED(hr)) iter = std::ranges::find(PlaybackDevices, id, &DevMap::guid); if(iter == PlaybackDevices.end()) 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 auto ds = ComPtr{}; 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; } auto DSoundPlayback::reset() -> bool { mNotifies = nullptr; mBuffer = nullptr; mPrimaryBuffer = nullptr; switch(mDevice->FmtType) { case DevFmtByte: mDevice->FmtType = DevFmtUByte; break; case DevFmtFloat: if(mDevice->Flags.test(SampleTypeRequest)) break; [[fallthrough]]; case DevFmtUShort: mDevice->FmtType = DevFmtShort; break; case DevFmtUInt: mDevice->FmtType = DevFmtInt; break; case DevFmtUByte: case DevFmtShort: case DevFmtInt: break; } auto OutputType = WAVEFORMATEXTENSIBLE{}; auto speakers = DWORD{}; auto 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)); auto const isRear51 = speakers == DSSPEAKER_5POINT1_BACK; switch(mDevice->FmtChans) { case DevFmtMono: OutputType.dwChannelMask = MONO; break; case DevFmtAmbi3D: mDevice->FmtChans = DevFmtStereo; [[fallthrough]]; 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; [[fallthrough]]; 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 = gsl::narrow_cast(mDevice->channelsFromFmt()); OutputType.Format.wBitsPerSample = gsl::narrow_cast(mDevice->bytesFromFmt() * 8); OutputType.Format.nBlockAlign = gsl::narrow_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) { auto DSBDescription = DSBUFFERDESC{}; 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; auto num_updates = mDevice->mBufferSize / mDevice->mUpdateSize; if(num_updates > MAX_UPDATES) num_updates = MAX_UPDATES; mDevice->mBufferSize = mDevice->mUpdateSize * num_updates; auto DSBDescription = DSBUFFERDESC{}; 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)) { auto const num_updates = std::min(mDevice->mBufferSize / mDevice->mUpdateSize, u32{MAX_UPDATES}); auto nots = std::array{}; for(auto i = 0_u32;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(); if(auto const hr = mBuffer->Stop(); FAILED(hr)) ERR("Failed to stop DirectSound buffer playback: {:#x}", as_unsigned(hr)); } struct DSoundCapture final : BackendBase { explicit DSoundCapture(gsl::not_null const device) noexcept : BackendBase{device} { } ~DSoundCapture() override; void open(std::string_view name) override; void start() override; void stop() override; void captureSamples(std::span outbuffer) override; auto availableSamples() -> usize override; ComPtr mDSC; ComPtr mDSCbuffer; DWORD mBufferBytes{0u}; DWORD mCursor{0u}; RingBufferPtr mRing; }; DSoundCapture::~DSoundCapture() { if(mDSCbuffer) { std::ignore = mDSCbuffer->Stop(); mDSCbuffer = nullptr; } mDSC = nullptr; } void DSoundCapture::open(std::string_view name) { auto hr = HRESULT{}; if(CaptureDevices.empty()) { /* Initialize COM to prevent name truncation */ auto com = ComWrapper{}; 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::ranges::find(CaptureDevices, name, &DevMap::name); if(iter == CaptureDevices.end()) { auto id = GUID{}; hr = CLSIDFromString(utf8_to_wstr(name).c_str(), &id); if(SUCCEEDED(hr)) iter = std::ranges::find(CaptureDevices, id, &DevMap::guid); if(iter == CaptureDevices.end()) 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; } auto InputType = WAVEFORMATEXTENSIBLE{}; 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 = gsl::narrow_cast(mDevice->channelsFromFmt()); InputType.Format.wBitsPerSample = gsl::narrow_cast(mDevice->bytesFromFmt() * 8); InputType.Format.nBlockAlign = gsl::narrow_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 auto samples = std::max(mDevice->mBufferSize, mDevice->mSampleRate/10u); auto DSCBDescription = DSCBUFFERDESC{}; 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)) 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() { if(const auto hr = mDSCbuffer->Start(DSCBSTART_LOOPING); FAILED(hr)) throw al::backend_exception{al::backend_error::DeviceError, "Failure starting capture: {:#x}", as_unsigned(hr)}; } void DSoundCapture::stop() { if(const auto hr = mDSCbuffer->Stop(); FAILED(hr)) { ERR("stop failed: {:#x}", as_unsigned(hr)); mDevice->handleDisconnect("Failure stopping capture: {:#x}", as_unsigned(hr)); } } void DSoundCapture::captureSamples(std::span outbuffer) { std::ignore = mRing->read(outbuffer); } auto DSoundCapture::availableSamples() -> usize { if(mDevice->Connected.load(std::memory_order_acquire)) { const auto BufferBytes = mBufferBytes; const auto LastCursor = mCursor; auto ReadCursor = DWORD{}; auto *ReadPtr1 = LPVOID{}; auto *ReadPtr2 = LPVOID{}; auto ReadCnt1 = DWORD{}; auto ReadCnt2 = DWORD{}; auto hr = mDSCbuffer->GetCurrentPosition(nullptr, &ReadCursor); if(SUCCEEDED(hr)) { const auto NumBytes = (BufferBytes+ReadCursor-LastCursor) % BufferBytes; if(!NumBytes) return mRing->readSpace(); hr = mDSCbuffer->Lock(LastCursor, NumBytes, &ReadPtr1, &ReadCnt1, &ReadPtr2, &ReadCnt2, 0); } if(SUCCEEDED(hr)) { std::ignore = mRing->write(std::span{static_cast(ReadPtr1), ReadCnt1}); if(ReadPtr2 != nullptr && ReadCnt2 > 0) std::ignore = mRing->write(std::span{static_cast(ReadPtr2), ReadCnt2}); 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 mRing->readSpace(); } } // namespace BackendFactory &DSoundBackendFactory::getFactory() { static DSoundBackendFactory factory{}; return factory; } auto DSoundBackendFactory::init() -> bool { #if HAVE_DYNLOAD if(!ds_handle) { if(auto libresult = LoadLib("dsound.dll")) ds_handle = libresult.value(); else { WARN("Failed to load dsound.dll: {}", libresult.error()); return false; } static constexpr auto load_func = [](auto *&func, const char *name) -> bool { using func_t = std::remove_reference_t; auto funcresult = GetSymbol(ds_handle, name); if(!funcresult) { WARN("Failed to load function {}: {}", name, funcresult.error()); return false; } /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) */ func = reinterpret_cast(funcresult.value()); return true; }; auto ok = true; #define LOAD_FUNC(f) ok &= load_func(p##f, #f) LOAD_FUNC(DirectSoundCreate); LOAD_FUNC(DirectSoundEnumerateW); LOAD_FUNC(DirectSoundCaptureCreate); LOAD_FUNC(DirectSoundCaptureEnumerateW); #undef LOAD_FUNC if(!ok) { CloseLib(ds_handle); ds_handle = nullptr; return false; } } #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 */ const auto com = ComWrapper{}; switch(type) { case BackendType::Playback: PlaybackDevices.clear(); if(const auto hr = DirectSoundEnumerateW(DSoundEnumDevices, &PlaybackDevices); FAILED(hr)) ERR("Error enumerating DirectSound playback devices: {:#x}", as_unsigned(hr)); outnames.reserve(PlaybackDevices.size()); std::ranges::for_each(PlaybackDevices, add_device); break; case BackendType::Capture: CaptureDevices.clear(); if(const auto hr = DirectSoundCaptureEnumerateW(DSoundEnumDevices, &CaptureDevices); FAILED(hr)) ERR("Error enumerating DirectSound capture devices: {:#x}", as_unsigned(hr)); outnames.reserve(CaptureDevices.size()); std::ranges::for_each(CaptureDevices, add_device); break; } return outnames; } auto DSoundBackendFactory::createBackend(gsl::not_null device, BackendType type) -> BackendPtr { if(type == BackendType::Playback) return BackendPtr{new DSoundPlayback{device}}; if(type == BackendType::Capture) return BackendPtr{new DSoundCapture{device}}; return nullptr; } kcat-openal-soft-75c0059/alc/backends/dsound.h000066400000000000000000000007241512220627100211160ustar00rootroot00000000000000#ifndef BACKENDS_DSOUND_H #define BACKENDS_DSOUND_H #include "base.h" struct DSoundBackendFactory final : BackendFactory { auto init() -> bool final; auto querySupport(BackendType type) -> bool final; auto enumerate(BackendType type) -> std::vector final; auto createBackend(gsl::not_null device, BackendType type) -> BackendPtr final; static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_DSOUND_H */ kcat-openal-soft-75c0059/alc/backends/jack.cpp000066400000000000000000000567331512220627100211000ustar00rootroot00000000000000/** * 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 #include #include #include "alc/alconfig.h" #include "alformat.hpp" #include "alnumeric.h" #include "althrd_setname.h" #include "core/device.h" #include "core/helpers.h" #include "core/logging.h" #include "dynload.h" #include "gsl/gsl" #include "opthelpers.h" #include "ringbuffer.h" #include #include namespace { using namespace std::string_literals; 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; #if defined(_WIN64) #define JACK_LIB "libjack64.dll" #elif defined(_WIN32) #define JACK_LIB "libjack.dll" #else #define JACK_LIB "libjack.so.0" #endif #if HAVE_DYNLOAD OAL_ELF_NOTE_DLOPEN( "backend-jack", "Support for the JACK backend", OAL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, JACK_LIB ); #endif auto jack_load() -> bool { #if HAVE_DYNLOAD if(!jack_handle) { const char *jack_lib = JACK_LIB; if(auto libresult = LoadLib(jack_lib)) jack_handle = libresult.value(); else { WARN("Failed to load {}: {}", jack_lib, libresult.error()); return false; } static constexpr auto load_func = [](auto *&func, const char *name) -> bool { using func_t = std::remove_reference_t; auto funcresult = GetSymbol(jack_handle, name); if(!funcresult) { WARN("Failed to load function {}: {}", name, funcresult.error()); return false; } /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) */ func = reinterpret_cast(funcresult.value()); return true; }; auto ok = true; #define LOAD_FUNC(f) ok &= load_func(p##f, #f) JACK_FUNCS(LOAD_FUNC) #undef LOAD_FUNC if(!ok) { CloseLib(jack_handle); jack_handle = nullptr; return false; } /* Optional symbols. These don't exist in all versions of JACK. */ #define LOAD_SYM(f) std::ignore = load_func(p##f, #f) LOAD_SYM(jack_error_callback); #undef LOAD_SYM } #endif return true; } /* NOLINTNEXTLINE(*-avoid-c-arrays) */ using JackPortsPtr = std::unique_ptr(ptr)); })>; struct DeviceEntry { std::string mName; std::string mPattern; NOINLINE ~DeviceEntry() = default; }; std::vector PlaybackList; void EnumerateDevices(jack_client_t *client, std::vector &list) { std::remove_reference_t{}.swap(list); if(const auto ports = JackPortsPtr{jack_get_ports(client, nullptr, JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput)}) { for(auto i = 0_uz;ports[i];++i) { const auto portname = std::string_view{ports[i]}; const auto seppos = portname.find(':'); if(seppos == 0 || seppos >= portname.size()) continue; const auto portdev = portname.substr(0, seppos); if(std::ranges::find(list, portdev, &DeviceEntry::mName) != list.end()) continue; const auto &entry = list.emplace_back(std::string{portdev}, al::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"s, ""s); } } if(auto listopt = ConfigValueStr({}, "jack", "custom-devices")) { for(auto strpos = 0_uz;strpos < listopt->size();) { auto nextpos = listopt->find(';', strpos); const auto 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 itemmatch = std::ranges::find(list, pattern, &DeviceEntry::mPattern); 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(std::string{name}, std::string{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) { const auto subrange = std::span{list.begin(), curitem}; if(std::ranges::find(subrange, curitem->mName, &DeviceEntry::mName) != subrange.end()) { auto name = std::string{}; auto count = 1_uz; do { name = al::format("{} #{}", curitem->mName, ++count); } while(std::ranges::find(subrange, name, &DeviceEntry::mName) != subrange.end()); curitem->mName = std::move(name); } } } } struct JackPlayback final : public BackendBase { explicit JackPlayback(gsl::not_null 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::vector mPort; std::mutex mMutex; std::atomic mPlaying{false}; bool mRTMixing{false}; RingBufferPtr mRing; std::atomic mSignal; std::atomic mKillNow{true}; std::thread mThread; }; JackPlayback::~JackPlayback() { if(!mClient) return; std::ranges::for_each(mPort, [this](jack_port_t *port) -> void { jack_port_unregister(mClient, port); }); jack_client_close(mClient); mClient = nullptr; } int JackPlayback::processRt(jack_nframes_t numframes) noexcept { auto outptrs = std::array{}; std::ranges::transform(mPort, outptrs.begin(), [numframes](jack_port_t *port) { return jack_port_get_buffer(port, numframes); }); const auto dst = std::span{outptrs}.first(mPort.size()); if(mPlaying.load(std::memory_order_acquire)) [[likely]] mDevice->renderSamples(dst, gsl::narrow_cast(numframes)); else { std::ranges::for_each(dst, [numframes](void *outbuf) -> void { std::ranges::fill(std::views::counted(static_cast(outbuf), numframes), 0.0f); }); } return 0; } int JackPlayback::process(jack_nframes_t numframes) noexcept { auto out = std::array,MaxOutputChannels>{}; std::ranges::transform(mPort, out.begin(), [numframes](jack_port_t *port) { auto *ptr = static_cast(jack_port_get_buffer(port, numframes)); return std::span{ptr, numframes}; }); const auto numchans = mPort.size(); if(mPlaying.load(std::memory_order_acquire)) [[likely]] { auto const data = mRing->getReadVector(); const auto outlen = usize{numframes / mDevice->mUpdateSize}; const auto updates1 = std::min(data[0].size() / mRing->getElemSize(), outlen); const auto updates2 = std::min(data[1].size() / mRing->getElemSize(), outlen - updates1); auto src = data[0]; for(auto i = 0_uz;i < updates1;++i) { for(auto c = 0_uz;c < numchans;++c) { std::ranges::copy(src.first(mDevice->mUpdateSize), out[c].begin()); out[c] = out[c].subspan(mDevice->mUpdateSize); src = src.subspan(mDevice->mUpdateSize); } } src = data[1]; for(auto i = 0_uz;i < updates2;++i) { for(auto c = 0_uz;c < numchans;++c) { std::ranges::copy(src.first(mDevice->mUpdateSize), out[c].begin()); out[c] = out[c].subspan(mDevice->mUpdateSize); src = src.subspan(mDevice->mUpdateSize); } } mRing->readAdvance(updates1 + updates2); mSignal.store(true, std::memory_order_release); mSignal.notify_all(); } std::ranges::fill(out | std::views::take(numchans) | std::views::join, 0.0f); return 0; } int JackPlayback::mixerProc() { SetRTPriority(); althrd_setname(GetMixerThreadName()); const auto update_size = mDevice->mUpdateSize; auto outptrs = std::vector(mPort.size()); while(!mKillNow.load(std::memory_order_acquire) && mDevice->Connected.load(std::memory_order_acquire)) { if(mRing->writeSpace() == 0) { mSignal.wait(false, std::memory_order_acquire); mSignal.store(false, std::memory_order_release); continue; } auto dlock = std::lock_guard{mMutex}; auto writevec = mRing->getWriteVector(); std::ranges::for_each(writevec, [this,update_size,&outptrs](const std::span samples) { auto bufiter = samples.begin(); const auto updates = samples.size() / mRing->getElemSize(); for(auto i = 0_uz;i < updates;++i) { std::ranges::generate(outptrs, [&bufiter,update_size] { auto ret = std::to_address(bufiter); std::advance(bufiter, update_size); return ret; }); mDevice->renderSamples(outptrs, update_size); } mRing->writeAdvance(updates); }); } return 0; } void JackPlayback::open(std::string_view name) { if(!mClient) { auto&& binname = GetProcBinary(); auto *client_name = binname.fname.empty() ? "alsoft" : binname.fname.c_str(); auto status = jack_status_t{}; 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 iter = std::ranges::find(PlaybackList, name, &DeviceEntry::mName); if(iter == PlaybackList.end()) throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found", name}; mPortPattern = iter->mPattern; } mDeviceName = name; } bool JackPlayback::reset() { std::ranges::for_each(mPort, [this](jack_port_t *port) -> void { jack_port_unregister(mClient, port); }); decltype(mPort){}.swap(mPort); 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 = ConfigValueU32(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; try { const auto numchans = usize{mDevice->channelsFromFmt()}; std::ranges::for_each(std::views::iota(0_uz, numchans), [this](usize const idx) { auto const name = al::format("channel_{}", idx); auto &newport = mPort.emplace_back(); newport = jack_port_register(mClient, name.c_str(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput | JackPortIsTerminal, 0); if(!newport) { mPort.pop_back(); throw std::runtime_error{al::format( "Failed to register enough JACK ports for {} output", DevFmtChannelsString(mDevice->FmtChans))}; } }); } catch(std::exception& e) { ERR("Exception: {}", e.what()); if(mPort.size() >= 2) { std::ranges::for_each(mPort | std::views::drop(2), [this](jack_port_t *port) { jack_port_unregister(mClient, port); }); mPort.resize(2_uz); mPort.shrink_to_fit(); mDevice->FmtChans = DevFmtStereo; } else if(mPort.size() == 1) mDevice->FmtChans = DevFmtMono; else throw; } 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)) { auto pnamesptr = JackPortsPtr{jack_get_ports(mClient, mPortPattern.c_str(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput)}; if(!pnamesptr) { jack_deactivate(mClient); throw al::backend_exception{al::backend_error::DeviceError, "No playback ports found"}; } auto *pnames_end = pnamesptr.get(); /* NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) */ while(*pnames_end) ++pnames_end; const auto pnames = std::span{pnamesptr.get(), pnames_end}; std::ignore = std::ranges::mismatch(mPort, pnames, [this](jack_port_t *const port, gsl::czstring const portname) { if(!portname) { ERR("No playback port for \"{}\"", jack_port_name(port)); return false; } if(jack_connect(mClient, jack_port_name(port), portname)) ERR(R"(Failed to connect output port "{}" to "{}")", jack_port_name(port), portname); return true; }); } /* 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 { auto bufsize = ConfigValueU32(devname, "jack", "buffer-size") .value_or(mDevice->mUpdateSize); bufsize = std::max(NextPowerOf2(bufsize), mDevice->mUpdateSize) / mDevice->mUpdateSize; mDevice->mBufferSize = (bufsize+1) * mDevice->mUpdateSize; mRing = RingBuffer::Create(bufsize, usize{mDevice->mUpdateSize} * mDevice->channelsFromFmt(), 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()) { mSignal.store(true, std::memory_order_release); mSignal.notify_all(); mThread.join(); } jack_deactivate(mClient); mPlaying.store(false, std::memory_order_release); } } ClockLatency JackPlayback::getClockLatency() { auto dlock = std::lock_guard{mMutex}; auto ret = ClockLatency{}; ret.ClockTime = mDevice->getClockTime(); ret.Latency = std::chrono::seconds{mRing ? mRing->readSpace() : 1_uz} * 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 = gsl::narrow_cast(ClientOptions | JackNoStartServer); auto&& binname = GetProcBinary(); auto *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); auto status = jack_status_t{}; auto *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 { auto outnames = std::vector{}; auto&& binname = GetProcBinary(); auto *client_name = binname.fname.empty() ? "alsoft" : binname.fname.c_str(); auto status = jack_status_t{}; switch(type) { case BackendType::Playback: if(auto *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::ranges::transform(PlaybackList, std::back_inserter(outnames), &DeviceEntry::mName); break; case BackendType::Capture: break; } return outnames; } auto JackBackendFactory::createBackend(gsl::not_null device, BackendType type) -> BackendPtr { if(type == BackendType::Playback) return BackendPtr{new JackPlayback{device}}; return nullptr; } BackendFactory &JackBackendFactory::getFactory() { static JackBackendFactory factory{}; return factory; } kcat-openal-soft-75c0059/alc/backends/jack.h000066400000000000000000000007141512220627100205310ustar00rootroot00000000000000#ifndef BACKENDS_JACK_H #define BACKENDS_JACK_H #include "base.h" struct JackBackendFactory final : BackendFactory { auto init() -> bool final; auto querySupport(BackendType type) -> bool final; auto enumerate(BackendType type) -> std::vector final; auto createBackend(gsl::not_null device, BackendType type) -> BackendPtr final; static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_JACK_H */ kcat-openal-soft-75c0059/alc/backends/loopback.cpp000066400000000000000000000040371512220627100217500ustar00rootroot00000000000000/** * 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 : BackendBase { explicit LoopbackBackend(gsl::not_null const device) noexcept : BackendBase{device} { } void open(std::string_view name) override; auto reset() -> bool override; void start() override; void stop() override; }; void LoopbackBackend::open(std::string_view const name) { mDeviceName = name; } auto LoopbackBackend::reset() -> bool { setDefaultWFXChannelOrder(); return true; } void LoopbackBackend::start() { } void LoopbackBackend::stop() { } } // namespace auto LoopbackBackendFactory::init() -> bool { return true; } auto LoopbackBackendFactory::querySupport(BackendType) -> bool { return true; } auto LoopbackBackendFactory::enumerate(BackendType) -> std::vector { return {}; } auto LoopbackBackendFactory::createBackend(gsl::not_null const device, BackendType) -> BackendPtr { return BackendPtr{new LoopbackBackend{device}}; } auto LoopbackBackendFactory::getFactory() -> BackendFactory& { static LoopbackBackendFactory factory{}; return factory; } kcat-openal-soft-75c0059/alc/backends/loopback.h000066400000000000000000000007341512220627100214150ustar00rootroot00000000000000#ifndef BACKENDS_LOOPBACK_H #define BACKENDS_LOOPBACK_H #include "base.h" struct LoopbackBackendFactory final : BackendFactory { auto init() -> bool final; auto querySupport(BackendType type) -> bool final; auto enumerate(BackendType type) -> std::vector final; auto createBackend(gsl::not_null device, BackendType type) -> BackendPtr final; static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_LOOPBACK_H */ kcat-openal-soft-75c0059/alc/backends/null.cpp000066400000000000000000000114121512220627100211230ustar00rootroot00000000000000/** * 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 "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 : BackendBase { explicit NullBackend(gsl::not_null const device) noexcept : BackendBase{device} { } void mixerProc() const; void open(std::string_view name) override; auto reset() -> bool override; void start() override; void stop() override; std::atomic mKillNow{true}; std::thread mThread; }; void NullBackend::mixerProc() const { auto const restTime = milliseconds{mDevice->mUpdateSize*1000/mDevice->mSampleRate / 2}; SetRTPriority(); althrd_setname(GetMixerThreadName()); auto done = 0_i64; 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 = i64{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) { const auto s = seconds{done/mDevice->mSampleRate}; start += s; done -= mDevice->mSampleRate*s.count(); } } } 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; } auto NullBackend::reset() -> bool { 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 auto NullBackendFactory::init() -> bool { return true; } auto NullBackendFactory::querySupport(BackendType const type) -> bool { return (type == BackendType::Playback); } auto NullBackendFactory::enumerate(BackendType const type) -> std::vector { switch(type) { case BackendType::Playback: /* Include null char. */ return std::vector{std::string{GetDeviceName()}}; case BackendType::Capture: break; } return {}; } auto NullBackendFactory::createBackend(gsl::not_null const device, BackendType const type) -> BackendPtr { if(type == BackendType::Playback) return BackendPtr{new NullBackend{device}}; return nullptr; } auto NullBackendFactory::getFactory() -> BackendFactory& { static NullBackendFactory factory{}; return factory; } kcat-openal-soft-75c0059/alc/backends/null.h000066400000000000000000000007141512220627100205730ustar00rootroot00000000000000#ifndef BACKENDS_NULL_H #define BACKENDS_NULL_H #include "base.h" struct NullBackendFactory final : BackendFactory { auto init() -> bool final; auto querySupport(BackendType type) -> bool final; auto enumerate(BackendType type) -> std::vector final; auto createBackend(gsl::not_null device, BackendType type) -> BackendPtr final; static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_NULL_H */ kcat-openal-soft-75c0059/alc/backends/oboe.cpp000066400000000000000000000307621512220627100211060ustar00rootroot00000000000000 #include "config.h" #include "oboe.h" #include #include "alnumeric.h" #include "alstring.h" #include "core/device.h" #include "core/logging.h" #include "gsl/gsl" #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 : BackendBase, oboe::AudioStreamCallback { explicit OboePlayback(gsl::not_null const device) : BackendBase{device} { } std::shared_ptr mStream; auto onAudioReady(oboe::AudioStream *oboeStream, void *audioData, int32_t numFrames) -> oboe::DataCallbackResult override; void onErrorAfterClose(oboe::AudioStream* /* audioStream */, oboe::Result /* error */) override; void open(std::string_view name) override; auto reset() -> bool override; void start() override; void stop() override; }; auto OboePlayback::onAudioReady(oboe::AudioStream *const oboeStream, void *const audioData, int32_t const numFrames) -> oboe::DataCallbackResult { mDevice->renderSamples(audioData, gsl::narrow_cast(numFrames), gsl::narrow_cast(oboeStream->getChannelCount())); return oboe::DataCallbackResult::Continue; } void OboePlayback::onErrorAfterClose(oboe::AudioStream*, oboe::Result const 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. */ auto stream = std::shared_ptr{}; const auto result = oboe::AudioStreamBuilder{}.setDirection(oboe::Direction::Output) ->setPerformanceMode(oboe::PerformanceMode::LowLatency) ->openStream(stream); if(result != oboe::Result::OK) throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: {}", oboe::convertToText(result)}; mDeviceName = name; } auto OboePlayback::reset() -> bool { auto builder = oboe::AudioStreamBuilder{}; 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(gsl::narrow_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); } auto result = builder.openStream(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.openStream(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(gsl::narrow_cast(mDevice->mBufferSize), mStream->getBufferCapacityInFrames())); TRACE("Got stream with properties:\n{}", oboe::convertToText(mStream.get())); if(std::cmp_not_equal(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 = gsl::narrow_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, gsl::narrow_cast(mStream->getFramesPerBurst())); mDevice->mBufferSize = std::max(mDevice->mUpdateSize*2u, gsl::narrow_cast(mStream->getBufferSizeInFrames())); return true; } void OboePlayback::start() { if(const auto result = mStream->start(); result != oboe::Result::OK) throw al::backend_exception{al::backend_error::DeviceError, "Failed to start stream: {}", oboe::convertToText(result)}; } void OboePlayback::stop() { if(const auto result = mStream->stop(); result != oboe::Result::OK) ERR("Failed to stop stream: {}", oboe::convertToText(result)); } struct OboeCapture final : BackendBase, oboe::AudioStreamCallback { explicit OboeCapture(gsl::not_null const device) : BackendBase{device} { } std::shared_ptr mStream; RingBufferPtr mRing; auto onAudioReady(oboe::AudioStream *oboeStream, void *audioData, int32_t numFrames) -> oboe::DataCallbackResult override; void open(std::string_view name) override; void start() override; void stop() override; void captureSamples(std::span outbuffer) override; auto availableSamples() -> usize override; }; auto OboeCapture::onAudioReady(oboe::AudioStream*, void *const audioData, int32_t const numFrames) -> oboe::DataCallbackResult { std::ignore = mRing->write(std::span{static_cast(audioData), gsl::narrow_cast(numFrames)*mRing->getElemSize()}); 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}; auto builder = oboe::AudioStreamBuilder{}; builder.setDirection(oboe::Direction::Input) ->setPerformanceMode(oboe::PerformanceMode::LowLatency) ->setSampleRateConversionQuality(oboe::SampleRateConversionQuality::High) ->setChannelConversionAllowed(true) ->setFormatConversionAllowed(true) ->setSampleRate(gsl::narrow_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)}; } if(const auto result = builder.openStream(mStream); 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), gsl::narrow_cast(mStream->getBytesPerFrame()), false); mDeviceName = name; } void OboeCapture::start() { if(const auto result = mStream->start(); result != oboe::Result::OK) throw al::backend_exception{al::backend_error::DeviceError, "Failed to start stream: {}", oboe::convertToText(result)}; } void OboeCapture::stop() { if(const auto result = mStream->stop(); result != oboe::Result::OK) ERR("Failed to stop stream: {}", oboe::convertToText(result)); } auto OboeCapture::availableSamples() -> usize { return mRing->readSpace(); } void OboeCapture::captureSamples(std::span const outbuffer) { std::ignore = mRing->read(outbuffer); } } // namespace auto OboeBackendFactory::init() -> bool { return true; } auto OboeBackendFactory::querySupport(BackendType const type) -> bool { return type == BackendType::Playback || type == BackendType::Capture; } auto OboeBackendFactory::enumerate(BackendType const type) -> std::vector { switch(type) { case BackendType::Playback: case BackendType::Capture: return std::vector{std::string{GetDeviceName()}}; } return {}; } auto OboeBackendFactory::createBackend(gsl::not_null const device, BackendType const type) -> BackendPtr { if(type == BackendType::Playback) return BackendPtr{new OboePlayback{device}}; if(type == BackendType::Capture) return BackendPtr{new OboeCapture{device}}; return BackendPtr{}; } auto OboeBackendFactory::getFactory() -> BackendFactory& { static OboeBackendFactory factory{}; return factory; } kcat-openal-soft-75c0059/alc/backends/oboe.h000066400000000000000000000007141512220627100205450ustar00rootroot00000000000000#ifndef BACKENDS_OBOE_H #define BACKENDS_OBOE_H #include "base.h" struct OboeBackendFactory final : BackendFactory { auto init() -> bool final; auto querySupport(BackendType type) -> bool final; auto enumerate(BackendType type) -> std::vector final; auto createBackend(gsl::not_null device, BackendType type) -> BackendPtr final; static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_OBOE_H */ kcat-openal-soft-75c0059/alc/backends/opensl.cpp000066400000000000000000001021011512220627100214450ustar00rootroot00000000000000/* * 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 #include #include #include #include "alnumeric.h" #include "alstring.h" #include "althrd_setname.h" #include "core/device.h" #include "core/helpers.h" #include "core/logging.h" #include "dynload.h" #include "gsl/gsl" #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 auto GetChannelMask(DevFmtChannels chans) noexcept -> SLuint32 { 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 auto GetTypeRepresentation(DevFmtType type) noexcept -> SLuint32 { 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 auto GetByteOrderEndianness() noexcept -> SLuint32 { if constexpr(std::endian::native == std::endian::little) return SL_BYTEORDER_LITTLEENDIAN; return SL_BYTEORDER_BIGENDIAN; } constexpr auto res_str(SLresult result) noexcept -> const char* { 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(gsl::not_null device) noexcept : BackendBase{device} { } ~OpenSLPlayback() override; void process(SLAndroidSimpleBufferQueueItf bq) noexcept; void mixerProc(); void open(std::string_view name) override; auto reset() -> bool override; void start() override; void stop() override; auto getClockLatency() -> ClockLatency override; /* engine interfaces */ SLObjectItf mEngineObj{nullptr}; SLEngineItf mEngine{nullptr}; /* output mix interfaces */ SLObjectItf mOutputMix{nullptr}; /* buffer queue player interfaces */ SLObjectItf mBufferQueueObj{nullptr}; RingBufferPtr mRing; std::atomic mSignal; std::mutex mMutex; u32 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); mSignal.store(true, std::memory_order_release); mSignal.notify_all(); } void OpenSLPlayback::mixerProc() { SetRTPriority(); althrd_setname(GetMixerThreadName()); auto player = SLPlayItf{}; auto bufferQueue = SLAndroidSimpleBufferQueueItf{}; auto result = VCALL(mBufferQueueObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE, static_cast(&bufferQueue)); PrintErr(result, "bufferQueue->GetInterface SL_IID_ANDROIDSIMPLEBUFFERQUEUE"); if(SL_RESULT_SUCCESS == result) { result = VCALL(mBufferQueueObj,GetInterface)(SL_IID_PLAY, static_cast(&player)); PrintErr(result, "bufferQueue->GetInterface SL_IID_PLAY"); } const auto frame_step = usize{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) { auto state = SLuint32{0u}; 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) { mSignal.wait(false, std::memory_order_acquire); mSignal.store(false, std::memory_order_release); continue; } } auto dlock = std::unique_lock{mMutex}; auto data = mRing->getWriteVector(); mDevice->renderSamples(data[0].data(), gsl::narrow_cast(data[0].size()/mFrameSize), frame_step); if(!data[1].empty()) mDevice->renderSamples(data[1].data(), gsl::narrow_cast(data[1].size()/mFrameSize), frame_step); const auto updatebytes = mRing->getElemSize(); const auto todo = usize{data[0].size() + data[1].size()} / updatebytes; mRing->writeAdvance(todo); dlock.unlock(); for(usize i{0};i < todo;++i) { if(data[0].empty()) { data[0] = data[1]; data[1] = {}; } result = VCALL(bufferQueue,Enqueue)(data[0].data(), updatebytes); PrintErr(result, "bufferQueue->Enqueue"); if(SL_RESULT_SUCCESS != result) { mDevice->handleDisconnect("Failed to queue audio: {:#08x}", result); break; } data[0] = data[0].subspan(updatebytes); } } } 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 auto 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, static_cast(&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() { auto result = SLresult{}; if(mBufferQueueObj) VCALL0(mBufferQueueObj,Destroy)(); mBufferQueueObj = nullptr; mRing = nullptr; mDevice->FmtChans = DevFmtStereo; mDevice->FmtType = DevFmtShort; setDefaultWFXChannelOrder(); mFrameSize = mDevice->frameSizeFromFmt(); const auto ids = std::array{SL_IID_ANDROIDSIMPLEBUFFERQUEUE, SL_IID_ANDROIDCONFIGURATION}; const auto reqs = std::array{SL_BOOLEAN_TRUE, SL_BOOLEAN_FALSE}; auto loc_outmix = SLDataLocator_OutputMix{}; loc_outmix.locatorType = SL_DATALOCATOR_OUTPUTMIX; loc_outmix.outputMix = mOutputMix; auto audioSnk = SLDataSink{}; audioSnk.pLocator = &loc_outmix; audioSnk.pFormat = nullptr; auto loc_bufq = SLDataLocator_AndroidSimpleBufferQueue{}; loc_bufq.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE; loc_bufq.numBuffers = mDevice->mBufferSize / mDevice->mUpdateSize; auto audioSrc = SLDataSource{}; #ifdef SL_ANDROID_DATAFORMAT_PCM_EX auto format_pcm_ex = SLAndroidDataFormat_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; } auto format_pcm = SLDataFormat_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. */ auto config = SLAndroidConfigurationItf{}; result = VCALL(mBufferQueueObj,GetInterface)(SL_IID_ANDROIDCONFIGURATION, static_cast(&config)); PrintErr(result, "bufferQueue->GetInterface SL_IID_ANDROIDCONFIGURATION"); if(SL_RESULT_SUCCESS == result) { auto streamType = SLint32{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 auto 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(); auto bufferQueue = SLAndroidSimpleBufferQueueItf{}; auto result = VCALL(mBufferQueueObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE, static_cast(&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; mSignal.store(true, std::memory_order_release); mSignal.notify_all(); mThread.join(); auto player = SLPlayItf{}; auto result = VCALL(mBufferQueueObj,GetInterface)(SL_IID_PLAY, static_cast(&player)); PrintErr(result, "bufferQueue->GetInterface"); if(SL_RESULT_SUCCESS == result) { result = VCALL(player,SetPlayState)(SL_PLAYSTATE_STOPPED); PrintErr(result, "player->SetPlayState"); } auto bufferQueue = SLAndroidSimpleBufferQueueItf{}; result = VCALL(mBufferQueueObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE, static_cast(&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) { auto state = SLAndroidSimpleBufferQueueState{}; 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() { auto ret = ClockLatency{}; auto dlock = std::lock_guard{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(gsl::not_null device) noexcept : BackendBase{device} { } ~OpenSLCapture() override; void process(SLAndroidSimpleBufferQueueItf bq) const noexcept; void open(std::string_view name) override; void start() override; void stop() override; void captureSamples(std::span outbuffer) override; auto availableSamples() -> usize override; /* engine interfaces */ SLObjectItf mEngineObj{nullptr}; SLEngineItf mEngine{nullptr}; /* recording interfaces */ SLObjectItf mRecordObj{nullptr}; RingBufferPtr mRing; u32 mByteOffset{0u}; u32 mFrameSize{0u}; }; OpenSLCapture::~OpenSLCapture() { if(mRecordObj) VCALL0(mRecordObj,Destroy)(); mRecordObj = nullptr; if(mEngineObj) VCALL0(mEngineObj,Destroy)(); mEngineObj = nullptr; mEngine = nullptr; } void OpenSLCapture::process(SLAndroidSimpleBufferQueueItf) const 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}; auto 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, static_cast(&mEngine)); PrintErr(result, "engine->GetInterface"); } if(SL_RESULT_SUCCESS == result) { mFrameSize = mDevice->frameSizeFromFmt(); /* Ensure the total length is at least 100ms */ auto length = std::max(mDevice->mBufferSize, mDevice->mSampleRate/10u); /* Ensure the per-chunk length is at least 10ms, and no more than 50ms. */ auto update_len = std::clamp(mDevice->mBufferSize/3u, mDevice->mSampleRate/100u, mDevice->mSampleRate/100u*5u); auto num_updates = (length+update_len-1) / update_len; mRing = RingBuffer::Create(num_updates, update_len*mFrameSize, false); mDevice->mUpdateSize = update_len; mDevice->mBufferSize = gsl::narrow_cast(mRing->writeSpace() * update_len); } if(SL_RESULT_SUCCESS == result) { const auto ids = std::array{SL_IID_ANDROIDSIMPLEBUFFERQUEUE, SL_IID_ANDROIDCONFIGURATION}; const auto reqs = std::array{SL_BOOLEAN_TRUE, SL_BOOLEAN_FALSE}; auto loc_dev = SLDataLocator_IODevice{}; loc_dev.locatorType = SL_DATALOCATOR_IODEVICE; loc_dev.deviceType = SL_IODEVICE_AUDIOINPUT; loc_dev.deviceID = SL_DEFAULTDEVICEID_AUDIOINPUT; loc_dev.device = nullptr; auto audioSrc = SLDataSource{}; audioSrc.pLocator = &loc_dev; audioSrc.pFormat = nullptr; auto loc_bq = SLDataLocator_AndroidSimpleBufferQueue{}; loc_bq.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE; loc_bq.numBuffers = mDevice->mBufferSize / mDevice->mUpdateSize; auto audioSnk = SLDataSink{}; #ifdef SL_ANDROID_DATAFORMAT_PCM_EX auto format_pcm_ex = SLAndroidDataFormat_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) { auto format_pcm = SLDataFormat_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. */ auto config = SLAndroidConfigurationItf{}; result = VCALL(mRecordObj,GetInterface)(SL_IID_ANDROIDCONFIGURATION, static_cast(&config)); PrintErr(result, "recordObj->GetInterface SL_IID_ANDROIDCONFIGURATION"); if(SL_RESULT_SUCCESS == result) { auto preset = SLuint32{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"); } auto bufferQueue = SLAndroidSimpleBufferQueueItf{}; if(SL_RESULT_SUCCESS == result) { result = VCALL(mRecordObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE, static_cast(&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 auto chunk_size = mDevice->mUpdateSize * mFrameSize; const auto silence = (mDevice->FmtType == DevFmtUByte) ? std::byte{0x80} : std::byte{0}; auto data = mRing->getWriteVector(); std::ranges::fill(data[0], silence); std::ranges::fill(data[1], silence); for(usize i{0u};i < data[0].size() && SL_RESULT_SUCCESS == result;i+=chunk_size) { result = VCALL(bufferQueue,Enqueue)(data[0].data() + i, chunk_size); PrintErr(result, "bufferQueue->Enqueue"); } for(usize i{0u};i < data[1].size() && SL_RESULT_SUCCESS == result;i+=chunk_size) { result = VCALL(bufferQueue,Enqueue)(data[1].data() + 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() { auto record = SLRecordItf{}; auto result = VCALL(mRecordObj,GetInterface)(SL_IID_RECORD, static_cast(&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() { auto record = SLRecordItf{}; auto result = VCALL(mRecordObj,GetInterface)(SL_IID_RECORD, static_cast(&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::span outbuffer) { const auto update_size = usize{mDevice->mUpdateSize}; const auto chunk_size = update_size * mFrameSize; auto bufferQueue = SLAndroidSimpleBufferQueueItf{}; if(mDevice->Connected.load(std::memory_order_acquire)) [[likely]] { auto const result = VCALL(mRecordObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE, static_cast(&bufferQueue)); PrintErr(result, "recordObj->GetInterface"); if(SL_RESULT_SUCCESS != result) [[unlikely]] { mDevice->handleDisconnect("Failed to get capture buffer queue: {:#08x}", result); bufferQueue = nullptr; } } /* Read the desired samples from the ring buffer then advance its read * pointer. */ auto rdata = mRing->getReadVector(); while(!outbuffer.empty()) { auto const rem = std::min(outbuffer.size(), usize{chunk_size}-mByteOffset); auto const oiter = std::ranges::copy(rdata[0].subspan(mByteOffset, rem), outbuffer.begin()).out; outbuffer = {oiter, outbuffer.end()}; mByteOffset += rem; if(mByteOffset == chunk_size) { /* Finished a chunk, reset the offset and advance the read pointer. */ mByteOffset = 0; mRing->readAdvance(1); if(bufferQueue) { auto const result = VCALL(bufferQueue,Enqueue)(rdata[0].data(), chunk_size); PrintErr(result, "bufferQueue->Enqueue"); if(SL_RESULT_SUCCESS != result) [[unlikely]] { bufferQueue = nullptr; mDevice->handleDisconnect("Failed to queue capture buffer: {:#08x}", result); } } rdata[0] = rdata[0].subspan(chunk_size); if(rdata[0].empty()) rdata[0] = rdata[1]; } } } auto OpenSLCapture::availableSamples() -> usize { return mRing->readSpace()*mDevice->mUpdateSize - mByteOffset/mFrameSize; } #define SLES_LIB "libOpenSLES.so" #if HAVE_DYNLOAD OAL_ELF_NOTE_DLOPEN( "backend-opensl", "Support for the OpenSL backend", OAL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, SLES_LIB ); #endif } // namespace auto OSLBackendFactory::init() -> bool { #if HAVE_DYNLOAD if(!sles_handle) { auto *const sles_lib = gsl::czstring{SLES_LIB}; if(auto const libresult = LoadLib(sles_lib)) sles_handle = libresult.value(); else { WARN("Failed to load {}: {}", sles_lib, libresult.error()); return false; } static constexpr auto load_func = [](auto *&func, gsl::czstring const name) -> bool { using func_t = std::remove_reference_t; auto const funcresult = GetSymbol(sles_handle, name); if(!funcresult) { WARN("Failed to load function {}: {}", name, funcresult.error()); return false; } /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) */ func = reinterpret_cast(funcresult.value()); return true; }; auto ok = true; #define LOAD_FUNC(f) ok &= load_func(p##f, #f) SLES_SYMBOLS(LOAD_FUNC) #undef LOAD_FUNC if(!ok) { CloseLib(sles_handle); sles_handle = nullptr; return false; } } #endif return true; } auto OSLBackendFactory::querySupport(BackendType const type) -> bool { return (type == BackendType::Playback || type == BackendType::Capture); } auto OSLBackendFactory::enumerate(BackendType const type) -> std::vector { switch(type) { case BackendType::Playback: case BackendType::Capture: return std::vector{std::string{GetDeviceName()}}; } return {}; } auto OSLBackendFactory::createBackend(gsl::not_null const device, BackendType const type) -> BackendPtr { if(type == BackendType::Playback) return BackendPtr{new OpenSLPlayback{device}}; if(type == BackendType::Capture) return BackendPtr{new OpenSLCapture{device}}; return nullptr; } auto OSLBackendFactory::getFactory() -> BackendFactory& { static OSLBackendFactory factory{}; return factory; } kcat-openal-soft-75c0059/alc/backends/opensl.h000066400000000000000000000007101512220627100211150ustar00rootroot00000000000000#ifndef BACKENDS_OSL_H #define BACKENDS_OSL_H #include "base.h" struct OSLBackendFactory final : BackendFactory { auto init() -> bool final; auto querySupport(BackendType type) -> bool final; auto enumerate(BackendType type) -> std::vector final; auto createBackend(gsl::not_null device, BackendType type) -> BackendPtr final; static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_OSL_H */ kcat-openal-soft-75c0059/alc/backends/oss.cpp000066400000000000000000000505161512220627100207650ustar00rootroot00000000000000/** * 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 #include "alc/alconfig.h" #include "alformat.hpp" #include "alnumeric.h" #include "althrd_setname.h" #include "core/device.h" #include "core/helpers.h" #include "core/logging.h" #include "gsl/gsl" #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; } auto DefaultPlayback = "/dev/dsp"s; /* NOLINT(cert-err58-cpp) */ auto DefaultCapture = "/dev/dsp"s; /* NOLINT(cert-err58-cpp) */ struct DevMap { std::string name; std::string device_name; }; 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(std::string{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(usize i{0};i < path.size();++i) { if(path[i] == '.' && handle.size() >= path.size() - i) { auto const 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; if(std::ranges::find(list, path, &DevMap::device_name) != list.end()) return; auto count = 1; auto newname = std::string{handle}; while(std::ranges::find(list, newname, &DevMap::name) != list.end()) newname = al::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) { auto si = oss_sysinfo{}; auto file = FileHandle{}; 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::ranges::find(devlist, defdev, &DevMap::device_name); if(iter == devlist.end()) devlist.insert(devlist.begin(), DevMap{GetDefaultName(), defdev}); else { auto entry = DevMap{std::move(*iter)}; devlist.erase(iter); devlist.insert(devlist.begin(), std::move(entry)); } devlist.shrink_to_fit(); } #endif constexpr auto log2i(u32 x) -> u32 { auto y = 0_u32; while(x > 1) { x >>= 1; y++; } return y; } struct OSSPlayback final : BackendBase { explicit OSSPlayback(gsl::not_null const device) noexcept : BackendBase{device} { } ~OSSPlayback() override; void mixerProc(); void open(std::string_view name) override; auto reset() -> bool 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; } void OSSPlayback::mixerProc() { SetRTPriority(); althrd_setname(GetMixerThreadName()); const auto frame_step = usize{mDevice->channelsFromFmt()}; const auto frame_size = usize{mDevice->frameSizeFromFmt()}; while(!mKillNow.load(std::memory_order_acquire) && mDevice->Connected.load(std::memory_order_acquire)) { auto pollitem = pollfd{}; pollitem.fd = mFd; pollitem.events = POLLOUT; if(const auto 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; } auto write_buf = std::span{mMixData}; mDevice->renderSamples(write_buf.data(), gsl::narrow_cast(write_buf.size()/frame_size), frame_step); while(!write_buf.empty() && !mKillNow.load(std::memory_order_acquire)) { const auto 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(gsl::narrow_cast(wrote)); } } } void OSSPlayback::open(std::string_view name) { const auto *devname = DefaultPlayback.c_str(); if(name.empty()) name = GetDefaultName(); else { if(PlaybackDevices.empty()) ALCossListPopulate(PlaybackDevices, DSP_CAP_OUTPUT); const auto iter = std::ranges::find(PlaybackDevices, name, &DevMap::name); if(iter == PlaybackDevices.end()) 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; } auto OSSPlayback::reset() -> bool { auto ossFormat = int{}; 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; [[fallthrough]]; case DevFmtShort: ossFormat = AFMT_S16_NE; break; } auto numChannels = mDevice->channelsFromFmt(); auto ossSpeed = mDevice->mSampleRate; auto frameSize = numChannels * mDevice->bytesFromFmt(); /* Number of periods in the upper 16 bits. */ auto numFragmentsLogSize = ((mDevice->mBufferSize + mDevice->mUpdateSize/2) / mDevice->mUpdateSize) << 16u; /* According to the OSS spec, 16 bytes is the minimum period size. */ numFragmentsLogSize |= std::max(log2i(mDevice->mUpdateSize * frameSize), 4u); auto info = audio_buf_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) */ /* Don't fail if SETFRAGMENT fails. We can handle just about anything * that's reported back via GETOSPACE */ 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 = gsl::narrow_cast(info.fragsize) / frameSize; mDevice->mBufferSize = gsl::narrow_cast(info.fragments) * mDevice->mUpdateSize; setDefaultChannelOrder(); mMixData.resize(usize{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(gsl::not_null device) noexcept : BackendBase{device} { } ~OSScapture() override; void recordProc() const; void open(std::string_view name) override; void start() override; void stop() override; void captureSamples(std::span outbuffer) override; auto availableSamples() -> usize override; int mFd{-1}; RingBufferPtr mRing; std::atomic mKillNow{true}; std::thread mThread; }; OSScapture::~OSScapture() { if(mFd != -1) close(mFd); mFd = -1; } void OSScapture::recordProc() const { SetRTPriority(); althrd_setname(GetRecordThreadName()); auto const frame_size = usize{mDevice->frameSizeFromFmt()}; while(!mKillNow.load(std::memory_order_acquire)) { auto pollitem = pollfd{}; pollitem.fd = mFd; pollitem.events = POLLIN; if(auto const pret = poll(&pollitem, 1, 1000); pret < 0) { if(errno == EINTR || errno == EAGAIN) continue; auto const 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].empty()) { auto amt = read(mFd, vec[0].data(), vec[0].size()); if(amt < 0) { auto const errstr = std::generic_category().message(errno); ERR("read failed: {}", errstr); mDevice->handleDisconnect("Failed reading capture samples: {}", errstr); break; } mRing->writeAdvance(gsl::narrow_cast(amt) / frame_size); } } } void OSScapture::open(std::string_view name) { auto *devname = DefaultCapture.c_str(); if(name.empty()) name = GetDefaultName(); else { if(CaptureDevices.empty()) ALCossListPopulate(CaptureDevices, DSP_CAP_INPUT); auto iter = std::ranges::find(CaptureDevices, name, &DevMap::name); if(iter == CaptureDevices.end()) 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)}; auto ossFormat = int{}; 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)}; } auto numChannels = mDevice->channelsFromFmt(); auto frameSize = numChannels * mDevice->bytesFromFmt(); auto ossSpeed = mDevice->mSampleRate; /* according to the OSS spec, 16 bytes are the minimum */ constexpr auto periods = 4u; const auto log2FragmentSize = std::max(log2i(mDevice->mBufferSize * frameSize / periods), 4u); auto numFragmentsLogSize = (periods << 16) | log2FragmentSize; auto info = audio_buf_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::span outbuffer) { std::ignore = mRing->read(outbuffer); } auto OSScapture::availableSamples() -> usize { return mRing->readSpace(); } } // namespace auto OSSBackendFactory::init() -> bool { if(auto devopt = ConfigValueStr({}, "oss", "device")) DefaultPlayback = std::move(*devopt); if(auto capopt = ConfigValueStr({}, "oss", "capture")) DefaultCapture = std::move(*capopt); return true; } auto OSSBackendFactory::querySupport(BackendType const type) -> bool { return (type == BackendType::Playback || type == BackendType::Capture); } auto OSSBackendFactory::enumerate(BackendType const type) -> std::vector { auto outnames = std::vector{}; 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::ranges::for_each(PlaybackDevices, add_device); break; case BackendType::Capture: CaptureDevices.clear(); ALCossListPopulate(CaptureDevices, DSP_CAP_INPUT); outnames.reserve(CaptureDevices.size()); std::ranges::for_each(CaptureDevices, add_device); break; } return outnames; } auto OSSBackendFactory::createBackend(gsl::not_null const device, BackendType const type) -> BackendPtr { if(type == BackendType::Playback) return BackendPtr{new OSSPlayback{device}}; if(type == BackendType::Capture) return BackendPtr{new OSScapture{device}}; return nullptr; } auto OSSBackendFactory::getFactory() -> BackendFactory& { static OSSBackendFactory factory{}; return factory; } kcat-openal-soft-75c0059/alc/backends/oss.h000066400000000000000000000007101512220627100204210ustar00rootroot00000000000000#ifndef BACKENDS_OSS_H #define BACKENDS_OSS_H #include "base.h" struct OSSBackendFactory final : BackendFactory { auto init() -> bool final; auto querySupport(BackendType type) -> bool final; auto enumerate(BackendType type) -> std::vector final; auto createBackend(gsl::not_null device, BackendType type) -> BackendPtr final; static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_OSS_H */ kcat-openal-soft-75c0059/alc/backends/pipewire.cpp000066400000000000000000002451551512220627100220120ustar00rootroot00000000000000/** * 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 #include "alc/alconfig.h" #include "alc/backends/base.h" #include "alformat.hpp" #include "alstring.h" #include "core/devformat.h" #include "core/device.h" #include "core/helpers.h" #include "core/logging.h" #include "dynload.h" #include "fmt/core.h" #include "fmt/ranges.h" #include "gsl/gsl" #include "opthelpers.h" #include "pragmadefs.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). */ DIAGNOSTIC_PUSH std_pragma("GCC diagnostic ignored \"-Wpedantic\"") std_pragma("GCC diagnostic ignored \"-Wconversion\"") std_pragma("GCC diagnostic ignored \"-Warith-conversion\"") std_pragma("GCC diagnostic ignored \"-Wfloat-conversion\"") std_pragma("GCC diagnostic ignored \"-Wmissing-field-initializers\"") std_pragma("GCC diagnostic ignored \"-Wunused-parameter\"") std_pragma("GCC diagnostic ignored \"-Wold-style-cast\"") std_pragma("GCC diagnostic ignored \"-Wsign-compare\"") std_pragma("GCC diagnostic ignored \"-Winline\"") std_pragma("GCC diagnostic ignored \"-Wpragmas\"") std_pragma("GCC diagnostic ignored \"-Wvla\"") std_pragma("GCC diagnostic ignored \"-Winvalid-constexpr\"") std_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(spa_pod const *const pod, usize const count) noexcept { return std::span{static_cast(SPA_POD_BODY(pod)), count}; } template constexpr auto get_pod_body(spa_pod const *const pod) noexcept { return std::span{static_cast(SPA_POD_BODY(pod)), N}; } constexpr auto get_array_value_type(spa_pod const *const pod) noexcept { return SPA_POD_ARRAY_VALUE_TYPE(pod); } constexpr auto make_pod_builder(void *const data, u32 const size) noexcept { return SPA_POD_BUILDER_INIT(data, size); } constexpr auto PwIdAny = PW_ID_ANY; } // namespace /* NOLINTEND */ DIAGNOSTIC_POP namespace { template [[nodiscard]] constexpr auto as_const_ptr(T *ptr) noexcept -> std::add_const_t* { return ptr; } struct SpaHook : spa_hook { SpaHook() : spa_hook{} { } ~SpaHook() { spa_hook_remove(this); } void remove() { spa_hook_remove(this); static_cast(*this) = {}; } SpaHook(const SpaHook&) = delete; SpaHook(SpaHook&&) = delete; auto operator=(const SpaHook&) -> SpaHook& = delete; auto operator=(SpaHook&&) -> SpaHook& = delete; }; struct PodDynamicBuilder { private: std::vector mStorage; spa_pod_builder mPod{}; auto overflow(u32 const size) noexcept -> i32 { 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(u32 const initSize=1024) : mStorage(initSize), mPod{make_pod_builder(mStorage.data(), initSize)} { static constexpr auto callbacks = spa_pod_builder_callbacks{ .version = SPA_VERSION_POD_BUILDER_CALLBACKS, .overflow = [](void *data, u32 const size) noexcept { return static_cast(data)->overflow(size); } }; spa_pod_builder_set_callbacks(&mPod, &callbacks, this); } auto get() noexcept -> spa_pod_builder* { 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; auto *gConfigFileName = gsl::czstring{}; auto check_version(gsl::czstring const version) -> bool { /* 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. */ auto major = int{}; auto minor = int{}; auto revision = int{}; /* NOLINTNEXTLINE(cert-err34-c,cppcoreguidelines-pro-type-vararg) */ if(auto const ret = sscanf(version, "%d.%d.%d", &major, &minor, &revision); ret != 3) return false; /* client-rt.conf is deprecated since PipeWire 1.3.81, and we should just * use the default. */ if(!(major > 1 || (major == 1 && minor > 3) || (major == 1 && minor == 3 && revision >= 81))) gConfigFileName = "client-rt.conf"; return 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 #define PWIRE_LIB "libpipewire-0.3.so.0" OAL_ELF_NOTE_DLOPEN( "backend-pipewire", "Support for the PipeWire backend", OAL_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, PWIRE_LIB ); auto pwire_load() -> bool { if(pwire_handle) return true; auto *const pwire_lib = gsl::czstring{PWIRE_LIB}; if(auto const libresult = LoadLib(pwire_lib)) pwire_handle = libresult.value(); else { WARN("Failed to load {}: {}", pwire_lib, libresult.error()); return false; } static constexpr auto load_func = [](auto *&func, gsl::czstring const name) -> bool { using func_t = std::remove_reference_t; auto const funcresult = GetSymbol(pwire_handle, name); if(!funcresult) { WARN("Failed to load function {}: {}", name, funcresult.error()); return false; } /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) */ func = reinterpret_cast(funcresult.value()); return true; }; auto ok = true; #define LOAD_FUNC(f) ok &= load_func(p##f, #f); PWIRE_FUNCS(LOAD_FUNC) PWIRE_FUNCS2(LOAD_FUNC) #undef LOAD_FUNC if(!ok) { 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, usize /*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 = i32; static auto get_value(const spa_pod *pod, i32 *val) { return spa_pod_get_int(pod, val); } }; template<> struct PodInfo { using Type = u32; static auto get_value(const spa_pod *pod, u32 *val) { return spa_pod_get_id(pod, val); } }; template using Pod_t = PodInfo::Type; template auto get_array_span(const spa_pod *pod) -> std::span> { auto nvals = u32{}; if(auto *v = spa_pod_get_array(pod, &nvals)) { if(get_array_value_type(pod) == T) return {static_cast*>(v), nvals}; } return {}; } template auto get_value(const spa_pod *value) -> std::optional> { auto val = Pod_t{}; if(PodInfo::get_value(value, &val) == 0) return val; return std::nullopt; } /* NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast) * 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 auto as(From) noexcept -> To = delete; /* pw_proxy * - pw_registry * - pw_node * - pw_metadata */ template<> [[nodiscard]] auto as(pw_registry *reg) noexcept -> pw_proxy* { return reinterpret_cast(reg); } template<> [[nodiscard]] auto as(pw_node *node) noexcept -> pw_proxy* { return reinterpret_cast(node); } template<> [[nodiscard]] auto as(pw_metadata *mdata) noexcept -> pw_proxy* { return reinterpret_cast(mdata); } /* NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast) */ using PwContextPtr = std::unique_ptr; using PwCorePtr = std::unique_ptr; using PwRegistryPtr = std::unique_ptr(reg)); })>; using PwNodePtr = std::unique_ptr(node)); })>; using PwMetadataPtr = std::unique_ptr(mdata)); })>; using PwStreamPtr = std::unique_ptr; /* NOLINTBEGIN(*EnumCastOutOfRange) Enums for bitflags... again... *sigh* */ [[nodiscard]] constexpr auto operator|(pw_stream_flags const lhs, pw_stream_flags const rhs) noexcept -> pw_stream_flags { return static_cast(lhs | al::to_underlying(rhs)); } constexpr auto operator|=(pw_stream_flags &lhs, pw_stream_flags rhs) noexcept -> pw_stream_flags& { lhs = lhs | rhs; return lhs; } /* NOLINTEND(*EnumCastOutOfRange) */ 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); } auto operator=(const ThreadMainloop&) -> ThreadMainloop& = delete; auto operator=(ThreadMainloop&& rhs) noexcept -> ThreadMainloop& { std::swap(mLoop, rhs.mLoop); return *this; } auto operator=(std::nullptr_t) noexcept -> ThreadMainloop& { 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 const wait) const { return pw_thread_loop_signal(mLoop, wait); } auto newContext(pw_properties *const props=nullptr, usize const user_data_size=0) const { return PwContextPtr{pw_context_new(getLoop(), props, user_data_size)}; } static auto Create(gsl::czstring const name, spa_dict *const props=nullptr) { return ThreadMainloop{pw_thread_loop_new(name, props)}; } friend struct MainloopUniqueLock; }; struct MainloopUniqueLock : 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. */ enum class NodeType : unsigned char { Sink, Source, Duplex }; auto AsString(NodeType const type) noexcept -> std::string_view { switch(type) { case NodeType::Sink: return "sink"sv; case NodeType::Source: return "source"sv; case NodeType::Duplex: return "duplex"sv; } return ""sv; } /* Enumerated devices. This is updated asynchronously as the app runs, and the * gEventHandler thread loop must be locked when accessing the list. */ constexpr auto InvalidChannelConfig = gsl::narrow(255); struct DeviceNode { u32 mId{}; u64 mSerial{}; std::string mName; std::string mDevName; NodeType mType{}; bool mIsHeadphones{}; bool mIs51Rear{}; u32 mSampleRate{}; DevFmtChannels mChannels{InvalidChannelConfig}; 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 const type, std::string_view const 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); } }; auto DefaultSinkDevice = std::string{}; auto DefaultSourceDevice = std::string{}; /* A generic PipeWire node proxy object used to track changes to sink and * source nodes. */ struct NodeProxy { u32 mId{}; PwNodePtr mNode; SpaHook mListener; NodeProxy(u32 const id, PwNodePtr&& node) : mId{id}, mNode{std::move(node)} { static constexpr auto nodeEvents = std::invoke([]() -> pw_node_events { auto ret = pw_node_events{}; ret.version = PW_VERSION_NODE_EVENTS; ret.info = infoCallback; ret.param = [](void *const object_, int const seq_, uint32_t const id_, uint32_t const index_, uint32_t const next_, spa_pod const *const param_) noexcept -> void { static_cast(object_)->paramCallback(seq_, id_, index_, next_, param_); }; return ret; }); 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). */ auto fmtids = std::to_array({SPA_PARAM_EnumFormat, SPA_PARAM_Format}); ppw_node_subscribe_params(mNode.get(), fmtids.data(), fmtids.size()); } static void infoCallback(void *object, const pw_node_info *info) noexcept; void paramCallback(int seq, u32 id, u32 index, u32 next, spa_pod const *param) const noexcept; }; /* A metadata proxy object used to query the default sink and source. */ struct MetadataProxy { u32 mId{}; PwMetadataPtr mMetadata; SpaHook mListener; MetadataProxy(u32 const id, PwMetadataPtr&& mdata) : mId{id}, mMetadata{std::move(mdata)} { static constexpr auto metadataEvents = std::invoke([]() -> pw_metadata_events { auto ret = pw_metadata_events{}; ret.version = PW_VERSION_METADATA_EVENTS; ret.property = propertyCallback; return ret; }); ppw_metadata_add_listener(mMetadata.get(), &mListener, &metadataEvents, this); } static auto propertyCallback(void *object, u32 id, gsl::czstring key, gsl::czstring type, gsl::czstring 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; SpaHook mRegistryListener; SpaHook 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{}; static auto AddDevice(u32 id) -> DeviceNode&; static auto FindDevice(u32 id) -> DeviceNode*; static auto FindDevice(std::string_view devname) -> DeviceNode*; static void RemoveDevice(u32 id); static auto GetDeviceList() noexcept { return std::span{sList}; } ~EventManager() { if(mLoop) mLoop.stop(); } auto init() -> bool; void kill(); auto lock() const { return mLoop.lock(); } auto unlock() const { return mLoop.unlock(); } [[nodiscard]] auto initIsDone(std::memory_order const 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]] { auto const plock = MainloopUniqueLock{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. */ auto waitForAudio() -> bool { auto const plock = MainloopUniqueLock{mLoop}; auto has_audio = false; 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(u32 id, u32 permissions, gsl::czstring type, u32 version, spa_dict const *props) noexcept; void removeCallback(u32 id) noexcept; void coreCallback(u32 id, int seq) noexcept; private: static inline auto sList = std::vector{}; }; using EventWatcherLockGuard = std::lock_guard; auto gEventHandler = EventManager{}; /* NOLINT(cert-err58-cpp) */ auto EventManager::AddDevice(u32 const id) -> DeviceNode& { /* If the node is already in the list, return the existing entry. */ const auto match = std::ranges::lower_bound(sList, id, std::less{}, &DeviceNode::mId); if(match != sList.end() && match->mId == id) return *match; auto &n = *sList.emplace(match); n.mId = id; return n; } auto EventManager::FindDevice(u32 const id) -> DeviceNode* { if(auto const match = std::ranges::find(sList, id, &DeviceNode::mId); match != sList.end()) return std::to_address(match); return nullptr; } auto EventManager::FindDevice(std::string_view const devname) -> DeviceNode* { if(auto const match = std::ranges::find(sList, devname, &DeviceNode::mDevName); match != sList.end()) return std::to_address(match); return nullptr; } void EventManager::RemoveDevice(u32 const id) { const auto end = std::ranges::remove_if(sList, [id](DeviceNode &n) noexcept -> bool { if(n.mId != id) return false; TRACE("Removing device \"{}\"", n.mDevName); if(gEventHandler.initIsDone(std::memory_order_relaxed)) n.callEvent(alc::EventType::DeviceRemoved, al::format("Device removed: {}", n.mName)); return true; }); sList.erase(end.begin(), end.end()); } constexpr auto MonoMap = std::array{ SPA_AUDIO_CHANNEL_MONO }; constexpr auto StereoMap = std::array{ SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR }; constexpr auto QuadMap = std::array{ SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR }; constexpr auto X51Map = std::array{ 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 auto X51RearMap = std::array{ 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 auto X61Map = std::array{ 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 auto X71Map = std::array{ 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 auto X714Map = std::array{ 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). */ auto MatchChannelMap(std::span const map0, std::span const map1) -> bool { if(map0.size() < map1.size()) return false; return std::ranges::all_of(map1, [map0](spa_audio_channel const chid) -> bool { return std::ranges::find(map0, chid) != map0.end(); }); } void DeviceNode::parseSampleRate(spa_pod const *value, bool const force_update) noexcept { /* TODO: Can this be anything else? Long, Float, Double? */ auto nvals = u32{}; auto choiceType = u32{}; value = spa_pod_get_values(value, &nvals, &choiceType); if(const auto podType = get_pod_type(value); 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: {} ({} -> {})", srates[0], srates[1], srates[2]); if(!mSampleRate || force_update) mSampleRate = gsl::narrow_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. */ TRACE("{}", fmt::format(" sample rate: {} {}", srates[0], srates.subspan(1))); /* Pick the first rate listed that's within the allowed range (default * rate if possible). */ for(const auto &rate : srates) { if(rate >= i32{MinOutputRate} && rate <= i32{MaxOutputRate}) { if(!mSampleRate || force_update) mSampleRate = gsl::narrow_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 = gsl::narrow_cast(std::clamp(srates[0], MinOutputRate, MaxOutputRate)); return; } WARN(" Unhandled sample rate choice type: {}", choiceType); } void DeviceNode::parsePositions(spa_pod const *value, bool const force_update) noexcept { auto choiceCount = u32{}; auto choiceType = u32{}; 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(spa_pod const *value, bool const force_update) noexcept { /* As a fallback with just a channel count, just assume mono or stereo. */ auto choiceCount = u32{}; auto choiceType = u32{}; 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? */ auto *media_class = spa_dict_lookup(info->props, PW_KEY_MEDIA_CLASS); if(!media_class) [[unlikely]] return; const auto className = std::string_view{media_class}; auto ntype = NodeType{}; 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); EventManager::RemoveDevice(info->id); return; } auto *devName = spa_dict_lookup(info->props, PW_KEY_NODE_NAME); auto *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; auto serial_id = u64{info->id}; #ifdef PW_KEY_OBJECT_SERIAL if(auto *serial_str = spa_dict_lookup(info->props, PW_KEY_OBJECT_SERIAL)) { errno = 0; auto *serial_end = gsl::zstring{}; 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 auto name = std::invoke([nodeName,info]() -> std::string { if(nodeName && *nodeName) return std::string{nodeName}; return al::format("PipeWire node #{}", info->id); }); auto *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); auto &node = EventManager::AddDevice(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. */ auto notifyAdd = false; if(node.mName != name) { if(gEventHandler.initIsDone(std::memory_order_relaxed)) { if(!node.mName.empty()) { const auto msg = al::format("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 auto msg = al::format("Device added: {}", node.mName); node.callEvent(alc::EventType::DeviceAdded, msg); } } } void NodeProxy::paramCallback(int, u32 const id, u32, u32, spa_pod const *const param) const noexcept { if(id == SPA_PARAM_EnumFormat || id == SPA_PARAM_Format) { auto *node = EventManager::FindDevice(mId); if(!node) [[unlikely]] return; TRACE("Device ID {} {} format{}:", node->mSerial, (id == SPA_PARAM_EnumFormat) ? "available" : "current", (id == SPA_PARAM_EnumFormat) ? "s" : ""); const auto force_update = id == SPA_PARAM_Format; if(const auto *prop = spa_pod_find_prop(param, nullptr, SPA_FORMAT_AUDIO_rate)) node->parseSampleRate(&prop->value, force_update); if(const auto *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*, u32 id, gsl::czstring key, gsl::czstring type, gsl::czstring value) noexcept -> int { if(id != PW_ID_CORE) return 0; auto isCapture = bool{}; 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; } auto it = std::array{}; spa_json_init(it.data(), value, strlen(value)); if(spa_json_enter_object(&std::get<0>(it), &std::get<1>(it)) <= 0) return 0; static constexpr auto get_json_string = [](spa_json *const iter) { auto str = std::optional{}; const char *val{}; const auto len = spa_json_next(iter, &val); if(len <= 0) return str; str.emplace(gsl::narrow_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 = EventManager::FindDevice(*propValue); const auto message = al::format("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 = EventManager::FindDevice(*propValue); const auto message = al::format("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; } auto EventManager::init() -> bool { 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; } static constexpr auto coreEvents = std::invoke([]() -> pw_core_events { auto ret = pw_core_events{}; ret.version = PW_VERSION_CORE_EVENTS; ret.done = [](void *const object, u32 const id, int const seq) noexcept -> void { static_cast(object)->coreCallback(id, seq); }; return ret; }); ppw_core_add_listener(mCore.get(), &mCoreListener, &coreEvents, this); 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 auto registryEvents = std::invoke([]() -> pw_registry_events { auto ret = pw_registry_events{}; ret.version = PW_VERSION_REGISTRY_EVENTS; ret.global = [](void *const object, u32 const id, u32 const permissions, gsl::czstring const type, u32 const version, spa_dict const *const props) noexcept -> void { static_cast(object)->addCallback(id, permissions, type, version, props); }; ret.global_remove = [](void *const object, uint32_t const id) noexcept -> void { static_cast(object)->removeCallback(id); }; return ret; }); 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(const auto res = mLoop.start()) { ERR("Failed to start PipeWire event thread loop (res: {})", res); return false; } return true; } void EventManager::kill() { if(!mLoop) return; mLoop.stop(); mDefaultMetadata.reset(); mNodeList.clear(); mRegistryListener.remove(); mRegistry = nullptr; mCoreListener.remove(); mCore = nullptr; mContext = nullptr; mLoop = nullptr; } void EventManager::addCallback(u32 const id, u32, gsl::czstring const type, u32 const version, spa_dict const *const props) noexcept { /* We're only interested in interface nodes. */ if(std::strcmp(type, PW_TYPE_INTERFACE_Node) == 0) { auto *media_class = spa_dict_lookup(props, PW_KEY_MEDIA_CLASS); if(!media_class) return; const auto className = std::string_view{media_class}; /* Specifically, audio sinks and sources (and duplexes). */ const auto 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) && !className.starts_with("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) { auto *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(u32 const id) noexcept { RemoveDevice(id); auto node_end = std::ranges::remove_if(mNodeList, [id](NodeProxy const &node) noexcept { return node.mId == id; }, &std::unique_ptr::operator*); mNodeList.erase(node_end.begin(), node_end.end()); if(mDefaultMetadata && mDefaultMetadata->mId == id) mDefaultMetadata.reset(); } void EventManager::coreCallback(u32 const id, int const seq) noexcept { if(id == PW_ID_CORE && seq == mInitSeq) { /* Initialization done. Remove this callback and signal anyone that may * be waiting. */ mCoreListener.remove(); mInitDone.store(true); mLoop.signal(false); } } enum use_f32p_e : bool { UseDevType=false, ForceF32Planar=true }; auto make_spa_info(DeviceBase *const device, bool const is51rear, use_f32p_e const use_f32p) -> spa_audio_info_raw { auto info = spa_audio_info_raw{}; 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; auto map = std::span{}; 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 = gsl::narrow_cast(map.size()); std::ranges::copy(map, std::begin(info.position)); } return info; } class PipeWirePlayback final : public BackendBase { void stateChangedCallback(pw_stream_state old, pw_stream_state state, gsl::czstring error) const noexcept; void ioChangedCallback(u32 id, void *area, u32 size) noexcept; void outputCallback() noexcept; void open(std::string_view name) override; auto reset() -> bool override; void start() override; void stop() override; auto getClockLatency() -> ClockLatency override; uint64_t mTargetId{PwIdAny}; nanoseconds mTimeBase{0}; ThreadMainloop mLoop; PwContextPtr mContext; PwCorePtr mCore; PwStreamPtr mStream; SpaHook mStreamListener; spa_io_rate_match *mRateMatch{}; std::vector mChannelPtrs; public: explicit PipeWirePlayback(gsl::not_null const 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, gsl::czstring) const noexcept { mLoop.signal(false); } void PipeWirePlayback::ioChangedCallback(u32 const id, void *const area, u32 const 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 { auto *const pw_buf = pw_stream_dequeue_buffer(mStream.get()); if(!pw_buf) [[unlikely]] return; auto const datas = std::span{pw_buf->buffer->datas, std::min(mChannelPtrs.size(), usize{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. */ auto length = al::saturate_cast(pw_buf->requested); #else /* In 0.3.48 and earlier, spa_io_rate_match::size apparently has the number * of samples per update. */ auto length = mRateMatch ? u32{mRateMatch->size} : 0_u32; #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/u32{sizeof(float)}); *chanptr_end = data.data; ++chanptr_end; data.chunk->offset = 0; data.chunk->stride = sizeof(float); data.chunk->size = length * u32{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 auto OpenCount = std::atomic{0u}; auto targetid = u64{PwIdAny}; auto devname = std::string{}; gEventHandler.waitForInit(); if(name.empty()) { const auto evtlock = EventWatcherLockGuard{gEventHandler}; auto&& devlist = EventManager::GetDeviceList(); auto match = devlist.end(); if(!DefaultSinkDevice.empty()) match = std::ranges::find(devlist, DefaultSinkDevice, &DeviceNode::mDevName); if(match == devlist.end()) { match = std::ranges::find_if(devlist, [](const DeviceNode &n) noexcept -> bool { return n.mType != NodeType::Source; }); } if(match == devlist.end()) throw al::backend_exception{al::backend_error::NoDevice, "No PipeWire playback device found"}; targetid = match->mSerial; devname = match->mName; } else { const auto evtlock = EventWatcherLockGuard{gEventHandler}; auto&& devlist = EventManager::GetDeviceList(); auto match = std::ranges::find_if(devlist, [name](const DeviceNode &n) -> bool { return n.mType != NodeType::Source && (n.mName == name || n.mDevName == name); }); if(match == devlist.end()) throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found", name}; targetid = match->mSerial; devname = match->mName; } if(!mLoop) { const auto count = OpenCount.fetch_add(1u, std::memory_order_relaxed); const auto thread_name = al::format("ALSoftP{}", 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(const auto res = mLoop.start()) throw al::backend_exception{al::backend_error::DeviceError, "Failed to start PipeWire mainloop (res: {})", res}; } auto mlock = MainloopUniqueLock{mLoop}; mContext = mLoop.newContext(!gConfigFileName ? nullptr : pw_properties_new(PW_KEY_CONFIG_NAME, gConfigFileName, nullptr)); if(!mContext) throw al::backend_exception{al::backend_error::DeviceError, "Failed to create PipeWire event context (errno: {})\n", errno}; 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; } auto PipeWirePlayback::reset() -> bool { if(mStream) { auto looplock = MainloopLockGuard{mLoop}; mStreamListener.remove(); mStream = nullptr; } mRateMatch = nullptr; mTimeBase = mDevice->getClockTime(); /* If connecting to a specific device, update various device parameters to * match its format. */ auto is51rear = false; mDevice->Flags.reset(DirectEar); if(mTargetId != PwIdAny) { const auto evtlock = EventWatcherLockGuard{gEventHandler}; auto&& devlist = EventManager::GetDeviceList(); const auto match = std::ranges::find(devlist, mTargetId, &DeviceNode::mSerial); if(match != devlist.end()) { if(!mDevice->Flags.test(FrequencyRequest) && match->mSampleRate > 0) { /* Scale the update size if the sample rate changes. */ const auto scale = gsl::narrow_cast(match->mSampleRate) / mDevice->mSampleRate; /* Don't scale down power-of-two sizes unless it would be more * than halfway to the next lower power-of-two. PipeWire uses * powers of two updates at the graph sample rate, but seems to * always round down streams' non-power-of-two update sizes. So * for instance, with the default 48khz playback rate and 512 * update size, if the device is 44.1khz the update size would * be scaled to 470 samples, which gets rounded down to 256 * when 512 would be closer to the requested size. */ if(scale < 0.75 && std::popcount(mDevice->mUpdateSize) == 1) { const auto updatesize = std::round(mDevice->mUpdateSize * scale); const auto buffersize = std::round(mDevice->mBufferSize * scale); mDevice->mUpdateSize = gsl::narrow_cast(std::clamp(updatesize, 64.0, 8192.0)); mDevice->mBufferSize = gsl::narrow_cast(std::max(buffersize, 128.0)); } mDevice->mSampleRate = match->mSampleRate; } 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(); auto *const appname = !binary.fname.empty() ? binary.fname.c_str() : "OpenAL Soft"; auto *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_set(props, PW_KEY_TARGET_OBJECT, std::to_string(mTargetId).c_str()); #else pw_properties_set(props, PW_KEY_NODE_TARGET, std::to_string(mTargetId).c_str()); #endif auto plock = MainloopUniqueLock{mLoop}; /* The stream takes ownership 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 auto streamEvents = std::invoke([]() -> pw_stream_events { auto ret = pw_stream_events{}; ret.version = PW_VERSION_STREAM_EVENTS; ret.state_changed = [](void *const data, pw_stream_state const old, pw_stream_state const state, gsl::czstring const error) noexcept -> void { static_cast(data)->stateChangedCallback(old, state, error); }; ret.io_changed = [](void *const data, u32 const id, void *const area, u32 const size) noexcept -> void { static_cast(data)->ioChangedCallback(id, area, size); }; ret.process = [](void *const data) noexcept -> void { static_cast(data)->outputCallback(); }; return ret; }); pw_stream_add_listener(mStream.get(), &mStreamListener, &streamEvents, this); auto 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(const auto 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()] { auto *error = gsl::czstring{}; auto const 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() { auto plock = MainloopUniqueLock{mLoop}; if(const auto 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()] { auto *error = gsl::czstring{}; auto const 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). */ auto wait_count = 100; do { auto ptime = pw_time{}; if(const auto 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 auto updatesize = mRateMatch ? mRateMatch->size : 0_u32; #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 auto totalbuffers = ptime.avail_buffers + ptime.queued_buffers; /* Ensure the delay is in sample frames. */ const auto delay = gsl::narrow_cast(ptime.delay) * mDevice->mSampleRate * ptime.rate.num / ptime.rate.denom; mDevice->mUpdateSize = updatesize; mDevice->mBufferSize = gsl::narrow_cast(ptime.buffered + delay + u64{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 auto delay = gsl::narrow_cast(ptime.delay) * mDevice->mSampleRate * ptime.rate.num / ptime.rate.denom; mDevice->mUpdateSize = updatesize; mDevice->mBufferSize = gsl::narrow_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() { auto plock = MainloopUniqueLock{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; }); } auto PipeWirePlayback::getClockLatency() -> ClockLatency { /* 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). */ auto ptime = pw_time{}; if(mStream) { auto looplock = MainloopLockGuard{mLoop}; if(const auto 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'). */ auto mixtime = nanoseconds{}; auto tspec = timespec{}; auto refcount = u32{}; 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. */ auto const monoclock = nanoseconds{seconds{tspec.tv_sec} + nanoseconds{tspec.tv_nsec}}; auto curtic = nanoseconds{}; auto delay = nanoseconds{}; 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, gsl::czstring error) const noexcept; void inputCallback() const noexcept; void open(std::string_view name) override; void start() override; void stop() override; void captureSamples(std::span outbuffer) override; auto availableSamples() -> usize override; u64 mTargetId{PwIdAny}; ThreadMainloop mLoop; PwContextPtr mContext; PwCorePtr mCore; PwStreamPtr mStream; SpaHook mStreamListener; RingBufferPtr mRing; public: explicit PipeWireCapture(gsl::not_null const device) noexcept : BackendBase{device} { } ~PipeWireCapture() final { if(mLoop) mLoop.stop(); } }; void PipeWireCapture::stateChangedCallback(pw_stream_state, pw_stream_state, gsl::czstring) const noexcept { mLoop.signal(false); } void PipeWireCapture::inputCallback() const noexcept { auto *const pw_buf = pw_stream_dequeue_buffer(mStream.get()); if(!pw_buf) [[unlikely]] return; auto const *const bufdata = pw_buf->buffer->datas; auto const offset = bufdata->chunk->offset % bufdata->maxsize; auto const input = std::span{static_cast(bufdata->data), bufdata->maxsize} | std::views::drop(offset) | std::views::take(bufdata->chunk->size); std::ignore = mRing->write(input); pw_stream_queue_buffer(mStream.get(), pw_buf); } void PipeWireCapture::open(std::string_view name) { static auto OpenCount = std::atomic{0u}; auto targetid = u64{PwIdAny}; auto devname = std::string{}; gEventHandler.waitForInit(); if(name.empty()) { const auto evtlock = EventWatcherLockGuard{gEventHandler}; auto&& devlist = EventManager::GetDeviceList(); auto match = devlist.end(); if(!DefaultSourceDevice.empty()) match = std::ranges::find(devlist, DefaultSourceDevice, &DeviceNode::mDevName); if(match == devlist.end()) { match = std::ranges::find_if(devlist, [](const DeviceNode &n) noexcept -> bool { return n.mType != NodeType::Sink; }); } if(match == devlist.end()) { match = devlist.begin(); if(match == devlist.end()) 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 { const auto evtlock = EventWatcherLockGuard{gEventHandler}; auto&& devlist = EventManager::GetDeviceList(); constexpr auto prefix = GetMonitorPrefix(); constexpr auto suffix = GetMonitorSuffix(); auto match = std::ranges::find_if(devlist, [name](const DeviceNode &n) -> bool { return n.mType != NodeType::Sink && n.mName == name; }); if(match == devlist.end() && name.starts_with(prefix)) { const auto sinkname = name.substr(prefix.length()); match = std::ranges::find_if(devlist, [sinkname](const DeviceNode &n) -> bool { return n.mType == NodeType::Sink && n.mName == sinkname; }); } else if(match == devlist.end() && name.ends_with(suffix)) { const auto sinkname = name.substr(0, name.size()-suffix.size()); match = std::ranges::find_if(devlist, [sinkname](const DeviceNode &n) -> bool { return n.mType == NodeType::Sink && n.mDevName == sinkname; }); } if(match == devlist.end()) 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 auto count = OpenCount.fetch_add(1u, std::memory_order_relaxed); const auto thread_name = al::format("ALSoftC{}", 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}; } auto mlock = MainloopUniqueLock{mLoop}; mContext = mLoop.newContext(!gConfigFileName ? nullptr : pw_properties_new(PW_KEY_CONFIG_NAME, gConfigFileName, nullptr)); if(!mContext) throw al::backend_exception{al::backend_error::DeviceError, "Failed to create PipeWire event context (errno: {})\n", errno}; 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; auto is51rear = false; if(mTargetId != PwIdAny) { const auto evtlock = EventWatcherLockGuard{gEventHandler}; auto&& devlist = EventManager::GetDeviceList(); auto match = std::ranges::find(devlist, mTargetId, &DeviceNode::mSerial); if(match != devlist.end()) 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(); auto *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_set(props, PW_KEY_TARGET_OBJECT, std::to_string(mTargetId).c_str()); #else pw_properties_set(props, PW_KEY_NODE_TARGET, std::to_string(mTargetId).c_str()); #endif auto plock = MainloopUniqueLock{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 auto streamEvents = std::invoke([]() -> pw_stream_events { auto ret = pw_stream_events{}; ret.version = PW_VERSION_STREAM_EVENTS; ret.state_changed = [](void *const data, pw_stream_state const old, pw_stream_state const state, gsl::czstring const error) noexcept -> void { static_cast(data)->stateChangedCallback(old, state, error); }; ret.process = [](void *const data) noexcept -> void { static_cast(data)->inputCallback(); }; return ret; }); pw_stream_add_listener(mStream.get(), &mStreamListener, &streamEvents, this); static constexpr auto Flags = PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_INACTIVE | PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS; if(const auto 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()] { auto *error = gsl::czstring{}; auto const 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() { const auto plock = MainloopUniqueLock{mLoop}; if(const auto 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()] { auto *error = gsl::czstring{}; auto const 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() { const auto plock = MainloopUniqueLock{mLoop}; if(const auto 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; }); } auto PipeWireCapture::availableSamples() -> usize { return mRing->readSpace(); } void PipeWireCapture::captureSamples(std::span const outbuffer) { std::ignore = mRing->read(outbuffer); } } // namespace bool PipeWireBackendFactory::init() { if(!pwire_load()) return false; auto const 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; } auto PipeWireBackendFactory::querySupport(BackendType const type) -> bool { return type == BackendType::Playback || type == BackendType::Capture; } auto PipeWireBackendFactory::enumerate(BackendType const type) -> std::vector { auto outnames = std::vector{}; gEventHandler.waitForInit(); auto const evtlock = EventWatcherLockGuard{gEventHandler}; auto&& devlist = EventManager::GetDeviceList(); auto defmatch = devlist.begin(); switch(type) { case BackendType::Playback: defmatch = std::ranges::find(devlist, DefaultSinkDevice, &DeviceNode::mDevName); if(defmatch != devlist.end()) outnames.emplace_back(defmatch->mName); for(auto iter = devlist.begin();iter != devlist.end();++iter) { if(iter != defmatch && iter->mType != NodeType::Source) outnames.emplace_back(iter->mName); } break; case BackendType::Capture: outnames.reserve(devlist.size()); defmatch = std::ranges::find(devlist, DefaultSourceDevice, &DeviceNode::mDevName); if(defmatch != devlist.end()) { if(defmatch->mType == NodeType::Sink) outnames.emplace_back(std::string{GetMonitorPrefix()}+defmatch->mName); else outnames.emplace_back(defmatch->mName); } for(auto iter = devlist.begin();iter != devlist.end();++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; } auto PipeWireBackendFactory::createBackend(gsl::not_null const device, BackendType const type) -> BackendPtr { if(type == BackendType::Playback) return BackendPtr{new PipeWirePlayback{device}}; if(type == BackendType::Capture) return BackendPtr{new PipeWireCapture{device}}; return nullptr; } auto PipeWireBackendFactory::getFactory() -> BackendFactory& { static auto factory = PipeWireBackendFactory{}; return factory; } auto PipeWireBackendFactory::queryEventSupport(alc::EventType const eventType, BackendType) -> alc::EventSupport { 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; } kcat-openal-soft-75c0059/alc/backends/pipewire.h000066400000000000000000000012211512220627100214370ustar00rootroot00000000000000#ifndef BACKENDS_PIPEWIRE_H #define BACKENDS_PIPEWIRE_H #include #include #include "alc/events.h" #include "base.h" struct DeviceBase; struct PipeWireBackendFactory final : BackendFactory { 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(gsl::not_null device, BackendType type) -> BackendPtr final; static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_PIPEWIRE_H */ kcat-openal-soft-75c0059/alc/backends/portaudio.cpp000066400000000000000000000453631512220627100221730ustar00rootroot00000000000000/** * 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 #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; unsigned mPlaybackChannels{}; unsigned 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(gsl::narrow_cast(devcount)).swap(DeviceNames); auto idx = PaDeviceIndex{0}; for(auto &entry : DeviceNames) { if(auto const info = Pa_GetDeviceInfo(idx); info && info->name) { entry.mName = info->name; entry.mPlaybackChannels = gsl::narrow_cast(std::max(info->maxOutputChannels, 0)); entry.mCaptureChannels = gsl::narrow_cast(std::max(info->maxInputChannels, 0)); TRACE("Device {} \"{}\": {} playback, {} capture channels", idx, entry.mName, info->maxOutputChannels, info->maxInputChannels); } ++idx; } } struct StreamParamsExt : PaStreamParameters { u32 updateSize; }; struct PortPlayback final : BackendBase { explicit PortPlayback(gsl::not_null const device) noexcept : BackendBase{device} { } ~PortPlayback() override; auto writeCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo, const PaStreamCallbackFlags statusFlags) noexcept -> int; void createStream(PaDeviceIndex deviceid); void open(std::string_view name) override; auto reset() -> bool override; void start() override; void stop() override; PaStream *mStream{nullptr}; StreamParamsExt mParams{}; PaDeviceIndex mDeviceIdx{-1}; }; PortPlayback::~PortPlayback() { if(const auto err = mStream ? Pa_CloseStream(mStream) : paNoError; err != paNoError) ERR("Error closing stream: {}", Pa_GetErrorText(err)); mStream = nullptr; } auto PortPlayback::writeCallback(const void*, void *const outputBuffer, unsigned long const framesPerBuffer, PaStreamCallbackTimeInfo const*, PaStreamCallbackFlags) noexcept -> int { mDevice->renderSamples(outputBuffer, gsl::narrow_cast(framesPerBuffer), gsl::narrow_cast(mParams.channelCount)); return 0; } void PortPlayback::createStream(PaDeviceIndex const deviceid) { auto const &devinfo = gsl::at(DeviceNames, deviceid); auto params = StreamParamsExt{}; params.device = deviceid; params.suggestedLatency = mDevice->mBufferSize / gsl::narrow_cast(mDevice->mSampleRate); params.hostApiSpecificStreamInfo = nullptr; params.channelCount = gsl::narrow_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 = mDevice->mSampleRate; static constexpr auto writeCallback = [](void const *const inputBuffer, void *const outputBuffer, unsigned long const framesPerBuffer, PaStreamCallbackTimeInfo const *const timeInfo, PaStreamCallbackFlags const statusFlags, void *const userData) noexcept -> int { return static_cast(userData)->writeCallback(inputBuffer, outputBuffer, framesPerBuffer, timeInfo, statusFlags); }; while(const auto err = Pa_OpenStream(&mStream, nullptr, ¶ms, srate, params.updateSize, paNoFlag, writeCallback, this)) { if(params.updateSize != DefaultUpdateSize) params.updateSize = DefaultUpdateSize; else if(srate != 48000_u32) srate = (srate != 44100_u32) ? 44100_u32 : 48000_u32; 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(); auto deviceid = PaDeviceIndex{-1}; if(name.empty()) { if(const auto devidopt = ConfigValueI32({}, "port", "device")) deviceid = *devidopt; if(deviceid < 0 || std::cmp_greater_equal(deviceid, DeviceNames.size())) deviceid = Pa_GetDefaultOutputDevice(); name = gsl::at(DeviceNames, deviceid).mName; } else { const auto iter = std::ranges::find_if(DeviceNames, [name](const DeviceEntry &entry) { return entry.mPlaybackChannels > 0 && name == entry.mName; }); if(iter == DeviceNames.end()) throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found", name}; deviceid = gsl::narrow_cast(std::distance(DeviceNames.begin(), iter)); } createStream(deviceid); mDeviceIdx = deviceid; mDeviceName = name; } bool PortPlayback::reset() { if(mStream) { if(const auto err = Pa_CloseStream(mStream); 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 != gsl::narrow_cast(mDevice->channelsFromFmt())) { if(mParams.channelCount >= 2) mDevice->FmtChans = DevFmtStereo; else if(mParams.channelCount == 1) mDevice->FmtChans = DevFmtMono; mDevice->mAmbiOrder = 0; } const auto *streamInfo = Pa_GetStreamInfo(mStream); mDevice->mSampleRate = gsl::narrow_cast(std::lround(streamInfo->sampleRate)); mDevice->mUpdateSize = mParams.updateSize; mDevice->mBufferSize = mDevice->mUpdateSize * 2u; if(streamInfo->outputLatency > 0.0f) { const auto sampleLatency = streamInfo->outputLatency * streamInfo->sampleRate; TRACE("Reported stream latency: {:f} sec ({:f} samples)", streamInfo->outputLatency, sampleLatency); mDevice->mBufferSize = gsl::narrow_cast(std::clamp(sampleLatency, gsl::narrow_cast(mDevice->mBufferSize), f64{std::numeric_limits::max()})); } setDefaultChannelOrder(); return true; } void PortPlayback::start() { if(const auto 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(const auto err = Pa_StopStream(mStream); err != paNoError) ERR("Error stopping stream: {}", Pa_GetErrorText(err)); } struct PortCapture final : public BackendBase { explicit PortCapture(gsl::not_null device) noexcept : BackendBase{device} { } ~PortCapture() override; auto readCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo, const PaStreamCallbackFlags statusFlags) const noexcept -> int; void open(std::string_view name) override; void start() override; void stop() override; void captureSamples(std::span outbuffer) override; auto availableSamples() -> usize override; PaStream *mStream{nullptr}; PaStreamParameters mParams{}; RingBufferPtr mRing; }; PortCapture::~PortCapture() { if(const auto err = mStream ? Pa_CloseStream(mStream) : paNoError; err != paNoError) ERR("Error closing stream: {}", Pa_GetErrorText(err)); mStream = nullptr; } auto PortCapture::readCallback(void const *const inputBuffer, void*, unsigned long const framesPerBuffer, PaStreamCallbackTimeInfo const*, PaStreamCallbackFlags) const noexcept -> int { std::ignore = mRing->write(std::span{static_cast(inputBuffer), framesPerBuffer*mRing->getElemSize()}); return 0; } void PortCapture::open(std::string_view name) { if(DeviceNames.empty()) EnumerateDevices(); auto deviceid = PaDeviceIndex{}; if(name.empty()) { if(auto const devidopt = ConfigValueI32({}, "port", "capture")) deviceid = *devidopt; if(deviceid < 0 || std::cmp_greater_equal(deviceid, DeviceNames.size())) deviceid = Pa_GetDefaultInputDevice(); name = gsl::at(DeviceNames, deviceid).mName; } else { auto const iter = std::ranges::find_if(DeviceNames, [name](DeviceEntry const &entry) { return entry.mCaptureChannels > 0 && name == entry.mName; }); if(iter == DeviceNames.end()) throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found", name}; deviceid = gsl::narrow_cast(std::distance(DeviceNames.begin(), iter)); } const auto samples = std::max(mDevice->mBufferSize, mDevice->mSampleRate/10u); const auto 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 = gsl::narrow_cast(mDevice->channelsFromFmt()); static constexpr auto readCallback = [](const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo, const PaStreamCallbackFlags statusFlags, void *userData) noexcept -> int { return static_cast(userData)->readCallback(inputBuffer, outputBuffer, framesPerBuffer, timeInfo, statusFlags); }; const auto 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 auto 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(const auto err = Pa_StopStream(mStream); err != paNoError) ERR("Error stopping stream: {}", Pa_GetErrorText(err)); } auto PortCapture::availableSamples() -> usize { return mRing->readSpace(); } void PortCapture::captureSamples(std::span const outbuffer) { std::ignore = mRing->read(outbuffer); } #ifdef _WIN32 # define PA_LIB "portaudio.dll" #elif defined(__APPLE__) && defined(__MACH__) # define PA_LIB "libportaudio.2.dylib" #elif defined(__OpenBSD__) # define PA_LIB "libportaudio.so" #else # define PA_LIB "libportaudio.so.2" #endif #if HAVE_DYNLOAD OAL_ELF_NOTE_DLOPEN( "backend-portaudio", "Support for the PortAudio backend", OAL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, PA_LIB ); #endif } // namespace auto PortBackendFactory::init() -> bool { #if HAVE_DYNLOAD if(!pa_handle) { auto *const pa_lib = gsl::czstring{PA_LIB}; if(auto const libresult = LoadLib(pa_lib)) pa_handle = libresult.value(); else { WARN("Failed to load {}: {}", pa_lib, libresult.error()); return false; } static constexpr auto load_func = [](auto *&func, gsl::czstring const name) -> bool { using func_t = std::remove_reference_t; auto const funcresult = GetSymbol(pa_handle, name); if(!funcresult) { WARN("Failed to load function {}: {}", name, funcresult.error()); return false; } /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) */ func = reinterpret_cast(funcresult.value()); return true; }; auto ok = true; #define LOAD_FUNC(f) ok &= load_func(p##f, #f) 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 if(!ok) { CloseLib(pa_handle); pa_handle = nullptr; return false; } if(const auto err = Pa_Initialize(); err != paNoError) { ERR("Pa_Initialize() returned an error: {}", Pa_GetErrorText(err)); CloseLib(pa_handle); pa_handle = nullptr; return false; } } #else if(const auto err = Pa_Initialize(); err != paNoError) { ERR("Pa_Initialize() returned an error: {}", Pa_GetErrorText(err)); return false; } #endif return true; } auto PortBackendFactory::querySupport(BackendType const type) -> bool { return (type == BackendType::Playback || type == BackendType::Capture); } auto PortBackendFactory::enumerate(BackendType const type) -> std::vector { std::vector devices; EnumerateDevices(); auto defaultid = PaDeviceIndex{-1}; switch(type) { case BackendType::Playback: defaultid = Pa_GetDefaultOutputDevice(); if(auto const devidopt = ConfigValueI32({}, "port", "device"); devidopt && *devidopt >= 0 && std::cmp_less(*devidopt, DeviceNames.size())) defaultid = *devidopt; for(auto const i : std::views::iota(0_uz, DeviceNames.size())) { if(DeviceNames[i].mPlaybackChannels > 0) { if(std::cmp_equal(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 const devidopt = ConfigValueI32({}, "port", "capture"); devidopt && *devidopt >= 0 && std::cmp_less(*devidopt, DeviceNames.size())) defaultid = *devidopt; for(auto const i : std::views::iota(0_uz, DeviceNames.size())) { if(DeviceNames[i].mCaptureChannels > 0) { if(std::cmp_equal(defaultid, i)) devices.emplace(devices.cbegin(), DeviceNames[i].mName); else devices.emplace_back(DeviceNames[i].mName); } } break; } return devices; } auto PortBackendFactory::createBackend(gsl::not_null const device, BackendType const type) -> BackendPtr { if(type == BackendType::Playback) return BackendPtr{new PortPlayback{device}}; if(type == BackendType::Capture) return BackendPtr{new PortCapture{device}}; return nullptr; } auto PortBackendFactory::getFactory() -> BackendFactory& { static PortBackendFactory factory{}; return factory; } kcat-openal-soft-75c0059/alc/backends/portaudio.hpp000066400000000000000000000007411512220627100221670ustar00rootroot00000000000000#ifndef BACKENDS_PORTAUDIO_HPP #define BACKENDS_PORTAUDIO_HPP #include "base.h" struct PortBackendFactory final : BackendFactory { auto init() -> bool final; auto querySupport(BackendType type) -> bool final; auto enumerate(BackendType type) -> std::vector final; auto createBackend(gsl::not_null device, BackendType type) -> BackendPtr final; static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_PORTAUDIO_HPP */ kcat-openal-soft-75c0059/alc/backends/pulseaudio.cpp000066400000000000000000001652211512220627100223330ustar00rootroot00000000000000/** * 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 "alformat.hpp" #include "alnumeric.h" #include "base.h" #include "core/devformat.h" #include "core/device.h" #include "core/logging.h" #include "dynload.h" #include "gsl/gsl" #include "opthelpers.h" #include "strutils.hpp" #include namespace { using namespace std::string_view_literals; using voidp = void*; using cvoidp = const void*; #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 auto MonoChanMap = pa_channel_map{1, {PA_CHANNEL_POSITION_MONO}}; constexpr auto StereoChanMap = pa_channel_map{2, { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT } }; constexpr auto QuadChanMap = pa_channel_map{4, { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT } }; constexpr auto X51ChanMap = pa_channel_map{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 } }; constexpr auto X51RearChanMap = pa_channel_map{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 } }; constexpr auto X61ChanMap = pa_channel_map{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 } }; constexpr auto X71ChanMap = pa_channel_map{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 } }; constexpr auto X714ChanMap = pa_channel_map{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. */ [[nodiscard]] constexpr auto operator|(pa_stream_flags_t lhs, pa_stream_flags_t rhs) -> pa_stream_flags_t { return gsl::narrow_cast(lhs | al::to_underlying(rhs)); } constexpr auto operator|=(pa_stream_flags_t &lhs, pa_stream_flags_t rhs) -> pa_stream_flags_t& { lhs = lhs | rhs; return lhs; } [[nodiscard]] constexpr auto operator~(pa_stream_flags_t flag) -> pa_stream_flags_t { return gsl::narrow_cast(~al::to_underlying(flag)); } constexpr auto operator&=(pa_stream_flags_t &lhs, pa_stream_flags_t rhs) -> pa_stream_flags_t& { lhs = gsl::narrow_cast(al::to_underlying(lhs) & rhs); return lhs; } [[nodiscard]] constexpr auto operator|(pa_context_flags_t lhs, pa_context_flags_t rhs) -> pa_context_flags_t { return gsl::narrow_cast(lhs | al::to_underlying(rhs)); } constexpr auto operator|=(pa_context_flags_t &lhs, pa_context_flags_t rhs) -> pa_context_flags_t& { lhs = lhs | rhs; return lhs; } [[nodiscard]] constexpr auto operator|(pa_subscription_mask_t lhs, pa_subscription_mask_t rhs) -> pa_subscription_mask_t { return gsl::narrow_cast(lhs | al::to_underlying(rhs)); } /* NOLINTEND(*EnumCastOutOfRange) */ struct DevMap { std::string name; std::string device_name; u32 index{}; }; auto checkName(std::span const list, std::string_view const name) -> bool { return std::ranges::find(list, name, &DevMap::name) != list.end(); } auto PlaybackDevices = std::vector{}; auto CaptureDevices = std::vector{}; auto DefaultPlaybackDevName = std::string{}; auto DefaultCaptureDevName = std::string{}; /* Global flags and properties */ auto pulse_ctx_flags = pa_context_flags_t{}; 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(); auto operator=(const PulseMainloop&) -> PulseMainloop& = delete; auto operator=(PulseMainloop&& rhs) noexcept -> PulseMainloop& { std::swap(mLoop, rhs.mLoop); return *this; } auto operator=(std::nullptr_t) noexcept -> PulseMainloop& { 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 const 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 const success, void *const pdata) noexcept { static_cast(pdata)->streamSuccessCallback(stream, success); } void close(pa_stream *stream=nullptr); void updateDefaultDevice(pa_context*, pa_server_info const *const 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 = al::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 = al::format("Default capture device changed: {}", default_src); alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Capture, msg); } signal(); } void deviceSinkCallback(pa_context*, pa_sink_info const *const info, int const eol) const noexcept { if(eol) { signal(); return; } /* Skip this device is if it's already in the list. */ auto const match = std::ranges::find(PlaybackDevices, info->name, &DevMap::device_name); if(match != PlaybackDevices.end()) 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 = al::format("{} #{}", info->description, ++count); const auto &newentry = PlaybackDevices.emplace_back(DevMap{std::move(newname), info->name, info->index}); TRACE(R"(Got device "{}", "{}" ({}))", newentry.name, newentry.device_name, newentry.index); const auto msg = al::format("Device added: {}", newentry.device_name); alc::Event(alc::EventType::DeviceAdded, alc::DeviceType::Playback, msg); } void deviceSourceCallback(pa_context*, pa_source_info const *const info, int const eol) const noexcept { if(eol) { signal(); return; } /* Skip this device is if it's already in the list. */ auto const match = std::ranges::find(CaptureDevices, info->name, &DevMap::device_name); if(match != CaptureDevices.end()) 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 = al::format("{} #{}", info->description, ++count); const auto &newentry = CaptureDevices.emplace_back(DevMap{std::move(newname), info->name, info->index}); TRACE(R"(Got device "{}", "{}" ({}))", newentry.name, newentry.device_name, newentry.index); const auto msg = al::format("Device added: {}", newentry.device_name); alc::Event(alc::EventType::DeviceAdded, alc::DeviceType::Capture, msg); } void eventCallback(pa_context *const context, pa_subscription_event_type_t const t, u32 const 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 *const ctx, pa_server_info const *const info, void *const pdata) noexcept { return static_cast(pdata)->updateDefaultDevice(ctx, info); }; if(auto *const op = pa_context_get_server_info(context, server_cb, this)) 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 *const ctx, pa_sink_info const *const info, int const eol, void *const pdata) noexcept { return static_cast(pdata)->deviceSinkCallback(ctx, info, eol); }; if(auto *op = pa_context_get_sink_info_by_index(context, idx, devcallback, this)) pa_operation_unref(op); } else { static constexpr auto devcallback = [](pa_context *const ctx, pa_source_info const *const info, int const eol, void *const pdata) noexcept { return static_cast(pdata)->deviceSourceCallback(ctx, info, eol); }; if(auto *op = pa_context_get_source_info_by_index(context, idx, devcallback, this)) pa_operation_unref(op); } } else if(eventType == PA_SUBSCRIPTION_EVENT_REMOVE) { auto &devlist = (eventFacility == PA_SUBSCRIPTION_EVENT_SINK) ? PlaybackDevices : CaptureDevices; auto iter = std::ranges::find(devlist, idx, &DevMap::index); if(iter != devlist.end()) { devlist.erase(iter); const auto msg = al::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() const { 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 *const ctx, pa_subscription_event_type_t const t, uint32_t const index, void *const 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. */ static constexpr auto sink_callback = [](pa_context *const ctx, pa_sink_info const *const info, int const eol, void *const pdata) noexcept { return static_cast(pdata)->deviceSinkCallback(ctx, info, eol); }; static constexpr auto src_callback = [](pa_context *const ctx, pa_source_info const *const info, int const eol, void *const pdata) noexcept { return static_cast(pdata)->deviceSourceCallback(ctx, info, eol); }; static constexpr auto server_callback = [](pa_context *const ctx, pa_server_info const *const info, void *const pdata) noexcept { return static_cast(pdata)->updateDefaultDevice(ctx, info); }; auto *const sinkop = pa_context_get_sink_info_list(context, sink_callback, mutex()); auto *const srcop = pa_context_get_source_info_list(context, src_callback, mutex()); auto *const serverop = pa_context_get_server_info(context, server_callback, mutex()); waitForOperation(sinkop); waitForOperation(srcop); waitForOperation(serverop); } void contextStateCallback(pa_context *const context) const noexcept { const auto state = pa_context_get_state(context); if(state == PA_CONTEXT_READY || !PA_CONTEXT_IS_GOOD(state)) mutex()->signal(); } void streamStateCallback(pa_stream *const stream) const noexcept { const auto state = pa_stream_get_state(stream); if(state == PA_STREAM_READY || !PA_STREAM_IS_GOOD(state)) mutex()->signal(); } void connectContext(); auto connectStream(gsl::czstring device_name, pa_stream_flags_t flags, pa_buffer_attr *attr, pa_sample_spec *spec, pa_channel_map *chanmap, BackendType type) -> pa_stream*; auto connectStream(std::string const &device_name, pa_stream_flags_t const flags, pa_buffer_attr *const attr, pa_sample_spec *const spec, pa_channel_map *const chanmap, BackendType const type) -> pa_stream* { return connectStream(device_name.empty() ? nullptr : device_name.c_str(), flags, attr, spec, chanmap, type); } }; PulseMainloop::~PulseMainloop() { if(mContext) { auto looplock = MainloopUniqueLock{*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); auto err = pa_context_connect(mutex()->mContext, nullptr, pulse_ctx_flags, nullptr); if(err >= 0) { wait([&err,this] { auto 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)}; } } auto 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) -> pa_stream* { auto *stream_id = (type==BackendType::Playback) ? "Playback Stream" : "Capture Stream"; auto *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); auto 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] { auto 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; auto looplock = MainloopUniqueLock{*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. */ auto gGlobalMainloop = PulseMainloop{}; struct PulsePlayback final : BackendBase { explicit PulsePlayback(gsl::not_null const device) noexcept : BackendBase{device} { } ~PulsePlayback() override; void bufferAttrCallback(pa_stream *stream) noexcept; void streamStateCallback(pa_stream *stream) const noexcept; void streamWriteCallback(pa_stream *stream, usize nbytes) const 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; auto reset() -> bool override; void start() override; void stop() override; auto getClockLatency() -> ClockLatency override; PulseMainloop mMainloop; std::optional mDeviceId{std::nullopt}; bool mIs51Rear{false}; pa_buffer_attr mAttr{}; pa_sample_spec mSpec{}; pa_stream *mStream{nullptr}; u32 mFrameSize{0u}; }; PulsePlayback::~PulsePlayback() { if(mStream) mMainloop.close(mStream); } void PulsePlayback::bufferAttrCallback(pa_stream *const 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 *const stream) const 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 *const stream, usize nbytes) const noexcept { do { auto free_func = pa_free_cb_t{nullptr}; auto buflen = ~1_uz; auto *buf = voidp{}; 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, gsl::narrow_cast(buflen/mFrameSize), mSpec.channels); if(auto const ret = pa_stream_write(stream, buf, buflen, free_func, 0, PA_SEEK_RELATIVE); ret != PA_OK) [[unlikely]] ERR("Failed to write to stream: {}, {}", ret, pa_strerror(ret)); } while(nbytes > 0); } void PulsePlayback::sinkInfoCallback(pa_context*, pa_sink_info const *const info, int const eol) noexcept { struct ChannelMap { DevFmtChannels fmt; pa_channel_map map; bool is_51rear; }; static constexpr auto chanmaps = std::array{ ChannelMap{DevFmtX714, X714ChanMap, false}, ChannelMap{DevFmtX71, X71ChanMap, false}, ChannelMap{DevFmtX61, X61ChanMap, false}, ChannelMap{DevFmtX51, X51ChanMap, false}, ChannelMap{DevFmtX51, X51RearChanMap, true}, ChannelMap{DevFmtQuad, QuadChanMap, false}, ChannelMap{DevFmtStereo, StereoChanMap, false}, ChannelMap{DevFmtMono, MonoChanMap, false} }; if(eol) { mMainloop.signal(); return; } auto const chaniter = std::ranges::find_if(chanmaps, [info](ChannelMap const &chanmap) -> bool { return pa_channel_map_superset(&info->channel_map, &chanmap.map); }); if(chaniter != chanmaps.end()) { if(!mDevice->Flags.test(ChannelsRequest)) mDevice->FmtChans = chaniter->fmt; mIs51Rear = chaniter->is_51rear; } else { mIs51Rear = false; auto chanmap_str = std::array{}; 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 && info->active_port->name == "analog-output-headphones"sv)); } void PulsePlayback::sinkNameCallback(pa_context*, pa_sink_info const *const info, int const eol) noexcept { if(eol) { mMainloop.signal(); return; } mDeviceName = info->description; } void PulsePlayback::streamMovedCallback(pa_stream *const 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 plock = MainloopUniqueLock{gGlobalMainloop}; auto iter = std::ranges::find(PlaybackDevices, name, &DevMap::name); if(iter == PlaybackDevices.end()) iter = std::ranges::find(PlaybackDevices, name, &DevMap::device_name); if(iter == PlaybackDevices.end()) throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found", name}; pulse_name = iter->device_name; mDeviceName = iter->name; } auto plock = MainloopUniqueLock{mMainloop}; plock.connectContext(); auto 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; auto spec = pa_sample_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 *const stream, void *const pdata) noexcept { return static_cast(pdata)->streamMovedCallback(stream); }; pa_stream_set_moved_callback(mStream, move_callback, this); mFrameSize = gsl::narrow_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 *const context, pa_sink_info const *const info, int const eol, void *const pdata) noexcept { return static_cast(pdata)->sinkNameCallback(context, info, eol); }; auto *op = pa_context_get_sink_info_by_name(mMainloop.getContext(), pa_stream_get_device_name(mStream), name_callback, this); plock.waitForOperation(op); } } auto PulsePlayback::reset() -> bool { auto plock = MainloopUniqueLock{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; } static constexpr auto info_cb = [](pa_context *const context, pa_sink_info const *const info, int const eol, void *const pdata) noexcept { return static_cast(pdata)->sinkInfoCallback(context, info, eol); }; auto *op = pa_context_get_sink_info_by_name(mMainloop.getContext(), deviceName, info_cb, this); plock.waitForOperation(op); auto 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; auto chanmap = pa_channel_map{}; switch(mDevice->FmtChans) { case DevFmtMono: chanmap = MonoChanMap; break; case DevFmtAmbi3D: mDevice->FmtChans = DevFmtStereo; [[fallthrough]]; 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; [[fallthrough]]; case DevFmtX714: chanmap = X714ChanMap; break; } setDefaultWFXChannelOrder(); switch(mDevice->FmtType) { case DevFmtByte: mDevice->FmtType = DevFmtUByte; [[fallthrough]]; case DevFmtUByte: mSpec.format = PA_SAMPLE_U8; break; case DevFmtUShort: mDevice->FmtType = DevFmtShort; [[fallthrough]]; case DevFmtShort: mSpec.format = PA_SAMPLE_S16NE; break; case DevFmtUInt: mDevice->FmtType = DevFmtInt; [[fallthrough]]; case DevFmtInt: mSpec.format = PA_SAMPLE_S32NE; break; case DevFmtFloat: mSpec.format = PA_SAMPLE_FLOAT32NE; break; } mSpec.rate = mDevice->mSampleRate; mSpec.channels = gsl::narrow_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 = gsl::narrow_cast(pa_frame_size(&mSpec)); mAttr.maxlength = ~0_u32; mAttr.tlength = mDevice->mBufferSize * frame_size; mAttr.prebuf = 0_u32; mAttr.minreq = mDevice->mUpdateSize * frame_size; mAttr.fragsize = ~0_u32; mStream = plock.connectStream(deviceName, flags, &mAttr, &mSpec, &chanmap, BackendType::Playback); static constexpr auto state_callback = [](pa_stream *const stream, void *const pdata) noexcept { return static_cast(pdata)->streamStateCallback(stream); }; pa_stream_set_state_callback(mStream, state_callback, this); static constexpr auto move_callback = [](pa_stream *const stream, void *const 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 = gsl::narrow_cast(pa_frame_size(&mSpec)); if(mDevice->mSampleRate != mSpec.rate) { /* Server updated our playback rate, so modify the buffer attribs * accordingly. */ const auto scale = gsl::narrow_cast(mSpec.rate) / mDevice->mSampleRate; const auto perlen = std::clamp(std::round(scale*mDevice->mUpdateSize), 64.0, 8192.0); const auto bufmax = u32{std::numeric_limits::max()} / mFrameSize; const auto buflen = std::clamp(std::round(scale*mDevice->mBufferSize), perlen*2.0, gsl::narrow_cast(bufmax)); mAttr.maxlength = ~0_u32; mAttr.tlength = gsl::narrow_cast(buflen) * mFrameSize; mAttr.prebuf = 0_u32; mAttr.minreq = gsl::narrow_cast(perlen) * mFrameSize; op = pa_stream_set_buffer_attr(mStream, &mAttr, &PulseMainloop::streamSuccessCallbackC, &mMainloop); plock.waitForOperation(op); mDevice->mSampleRate = mSpec.rate; } static 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() { auto plock = MainloopUniqueLock{mMainloop}; /* Write some samples to fill the buffer before we start feeding it newly * mixed samples. */ if(const auto todo = pa_stream_writable_size(mStream)) { auto *const buf = pa_xmalloc(todo); mDevice->renderSamples(buf, gsl::narrow_cast(todo/mFrameSize), mSpec.channels); pa_stream_write(mStream, buf, todo, pa_xfree, 0, PA_SEEK_RELATIVE); } static constexpr auto stream_write = [](pa_stream *const stream, usize const nbytes, void *const pdata) noexcept { return static_cast(pdata)->streamWriteCallback(stream, nbytes); }; pa_stream_set_write_callback(mStream, stream_write, this); auto *const op = pa_stream_cork(mStream, 0, &PulseMainloop::streamSuccessCallbackC, &mMainloop); plock.waitForOperation(op); } void PulsePlayback::stop() { auto const plock = MainloopUniqueLock{mMainloop}; auto *const op = pa_stream_cork(mStream, 1, &PulseMainloop::streamSuccessCallbackC, &mMainloop); plock.waitForOperation(op); pa_stream_set_write_callback(mStream, nullptr, nullptr); } auto PulsePlayback::getClockLatency() -> ClockLatency { auto ret = ClockLatency{}; auto latency = pa_usec_t{}; auto neg = int{}; auto err = int{}; { auto plock = MainloopUniqueLock{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(gsl::not_null device) noexcept : BackendBase{device} { } ~PulseCapture() override; void streamStateCallback(pa_stream *stream) const 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::span outbuffer) override; auto availableSamples() -> usize override; auto getClockLatency() -> ClockLatency override; PulseMainloop mMainloop; std::optional mDeviceId{std::nullopt}; std::span mCapBuffer; usize mHoleLength{0}; usize mPacketLength{0}; usize mLastReadable{0}; 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 *const stream) const 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 plock = MainloopUniqueLock{gGlobalMainloop}; auto iter = std::ranges::find(CaptureDevices, name, &DevMap::name); if(iter == CaptureDevices.end()) iter = std::ranges::find(CaptureDevices, name, &DevMap::device_name); if(iter == CaptureDevices.end()) throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found", name}; pulse_name = iter->device_name; mDeviceName = iter->name; } auto plock = MainloopUniqueLock{mMainloop}; plock.connectContext(); auto chanmap = pa_channel_map{}; 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 = gsl::narrow_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 = gsl::narrow_cast(pa_frame_size(&mSpec)); const auto samples = std::max(mDevice->mBufferSize, mDevice->mSampleRate*100_u32/1000_u32); mAttr.minreq = ~0_u32; mAttr.prebuf = ~0_u32; mAttr.maxlength = samples * frame_size; mAttr.tlength = ~0_u32; mAttr.fragsize = std::min(samples, mDevice->mSampleRate*50_u32/1000_u32) * frame_size; auto 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); static constexpr auto move_callback = [](pa_stream *const stream, void *const pdata) noexcept { return static_cast(pdata)->streamMovedCallback(stream); }; pa_stream_set_moved_callback(mStream, move_callback, this); static constexpr auto state_callback = [](pa_stream *const stream, void *const 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()) { static constexpr auto name_callback = [](pa_context *const context, pa_source_info const *const info, int const eol, void *const pdata) noexcept { return static_cast(pdata)->sourceNameCallback(context, info, eol); }; auto *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() { auto const plock = MainloopUniqueLock{mMainloop}; auto *const op = pa_stream_cork(mStream, 0, &PulseMainloop::streamSuccessCallbackC, &mMainloop); plock.waitForOperation(op); } void PulseCapture::stop() { auto const plock = MainloopUniqueLock{mMainloop}; auto *const op = pa_stream_cork(mStream, 1, &PulseMainloop::streamSuccessCallbackC, &mMainloop); plock.waitForOperation(op); } void PulseCapture::captureSamples(std::span outbuffer) { /* Capture is done in fragment-sized chunks, so we loop until we get all * that's available. */ mLastReadable -= outbuffer.size(); while(!outbuffer.empty()) { if(mHoleLength > 0) [[unlikely]] { const auto rem = std::min(outbuffer.size(), mHoleLength); std::ranges::fill(outbuffer | std::views::take(rem), mSilentVal); outbuffer = outbuffer.subspan(rem); mHoleLength -= rem; continue; } if(!mCapBuffer.empty()) { const auto rem = std::min(outbuffer.size(), mCapBuffer.size()); std::ranges::copy(mCapBuffer | std::views::take(rem), outbuffer.begin()); outbuffer = outbuffer.subspan(rem); mCapBuffer = mCapBuffer.subspan(rem); continue; } if(!mDevice->Connected.load(std::memory_order_acquire)) [[unlikely]] break; auto plock = MainloopUniqueLock{mMainloop}; if(mPacketLength > 0) { pa_stream_drop(mStream); mPacketLength = 0; } if(const auto state = pa_stream_get_state(mStream); !PA_STREAM_IS_GOOD(state)) [[unlikely]] { mDevice->handleDisconnect("Bad capture state: {}", al::to_underlying(state)); break; } auto *capbuf = cvoidp{}; auto caplen = usize{}; 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(!outbuffer.empty()) std::ranges::fill(outbuffer, mSilentVal); } auto PulseCapture::availableSamples() -> usize { auto readable = std::max(mCapBuffer.size(), mHoleLength); if(mDevice->Connected.load(std::memory_order_acquire)) { auto plock = MainloopUniqueLock{mMainloop}; if(auto const got = pa_stream_readable_size(mStream); as_signed(got) < 0) [[unlikely]] { auto *err = pa_strerror(gsl::narrow_cast(as_signed(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 decreasing the readable count. */ mLastReadable = std::max(mLastReadable, readable); return mLastReadable / pa_frame_size(&mSpec); } auto PulseCapture::getClockLatency() -> ClockLatency { auto ret = ClockLatency{}; auto latency = pa_usec_t{}; auto neg = int{}; auto err = int{}; { auto plock = MainloopUniqueLock{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; } #ifdef _WIN32 #define PULSE_LIB "libpulse-0.dll" #elif defined(__APPLE__) && defined(__MACH__) #define PULSE_LIB "libpulse.0.dylib" #else #define PULSE_LIB "libpulse.so.0" #endif #if HAVE_DYNLOAD OAL_ELF_NOTE_DLOPEN( "backend-pulseaudio", "Support for the PulseAudio backend", OAL_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, PULSE_LIB ); #endif } // namespace auto PulseBackendFactory::init() -> bool { #if HAVE_DYNLOAD if(!pulse_handle) { auto *const pulse_lib = gsl::czstring{PULSE_LIB}; if(auto const libresult = LoadLib(pulse_lib)) pulse_handle = libresult.value(); else { WARN("Failed to load {}: {}", pulse_lib, libresult.error()); return false; } static constexpr auto load_func = [](auto *&func, gsl::czstring const name) -> bool { using func_t = std::remove_reference_t; auto const funcresult = GetSymbol(pulse_handle, name); if(!funcresult) { WARN("Failed to load function {}: {}", name, funcresult.error()); return false; } /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) */ func = reinterpret_cast(funcresult.value()); return true; }; auto ok = true; #define LOAD_FUNC(f) ok &= load_func(p##f, #f) PULSE_FUNCS(LOAD_FUNC) #undef LOAD_FUNC if(!ok) { 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; } } auto plock = MainloopUniqueLock{gGlobalMainloop}; plock.connectContext(); plock.setEventHandler(); return true; } catch(...) { return false; } } auto PulseBackendFactory::querySupport(BackendType const type) -> bool { return type == BackendType::Playback || type == BackendType::Capture; } auto PulseBackendFactory::enumerate(BackendType const type) -> std::vector { auto outnames = std::vector{}; auto plock = MainloopUniqueLock{gGlobalMainloop}; switch(type) { case BackendType::Playback: outnames.reserve(PlaybackDevices.size()); std::ranges::for_each(PlaybackDevices, [&outnames](const DevMap &entry) { if(entry.device_name == DefaultPlaybackDevName) outnames.emplace(outnames.cbegin(), entry.name); else outnames.push_back(entry.name); }); break; case BackendType::Capture: outnames.reserve(CaptureDevices.size()); std::ranges::for_each(CaptureDevices, [&outnames](const DevMap &entry) { if(entry.device_name == DefaultCaptureDevName) outnames.emplace(outnames.cbegin(), entry.name); else outnames.push_back(entry.name); }); break; } return outnames; } auto PulseBackendFactory::createBackend(gsl::not_null const device, BackendType const type) -> BackendPtr { if(type == BackendType::Playback) return BackendPtr{new PulsePlayback{device}}; if(type == BackendType::Capture) return BackendPtr{new PulseCapture{device}}; return nullptr; } auto PulseBackendFactory::getFactory() -> BackendFactory& { static PulseBackendFactory factory{}; return factory; } auto PulseBackendFactory::queryEventSupport(alc::EventType const eventType, BackendType) -> alc::EventSupport { 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; } kcat-openal-soft-75c0059/alc/backends/pulseaudio.h000066400000000000000000000012421512220627100217700ustar00rootroot00000000000000#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(gsl::not_null device, BackendType type) -> BackendPtr final; static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_PULSEAUDIO_H */ kcat-openal-soft-75c0059/alc/backends/sdl2.cpp000066400000000000000000000215431512220627100210230ustar00rootroot00000000000000/** * 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 "alnumeric.h" #include "core/device.h" #include "gsl/gsl" #include "pragmadefs.h" DIAGNOSTIC_PUSH std_pragma("GCC diagnostic ignored \"-Wold-style-cast\"") #include "SDL.h" DIAGNOSTIC_POP namespace { using namespace std::string_view_literals; [[nodiscard]] constexpr auto getDefaultDeviceName() noexcept -> std::string_view { return "Default Device"sv; } struct Sdl2Backend final : BackendBase { explicit Sdl2Backend(gsl::not_null const device) noexcept : BackendBase{device} { } ~Sdl2Backend() override; void audioCallback(Uint8 *stream, int len) noexcept; void open(std::string_view name) override; auto reset() -> bool override; void start() override; void stop() override; std::string mSDLName; SDL_AudioDeviceID mDeviceID{0u}; u32 mFrameSize{0}; }; Sdl2Backend::~Sdl2Backend() { if(mDeviceID) SDL_CloseAudioDevice(mDeviceID); mDeviceID = 0; } void Sdl2Backend::audioCallback(Uint8 *const stream, int const len) noexcept { const auto ulen = gsl::narrow_cast(len); mDevice->renderSamples(stream, ulen / mFrameSize, mDevice->channelsFromFmt()); } void Sdl2Backend::open(std::string_view name) { 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 = al::saturate_cast(mDevice->channelsFromFmt()); want.samples = static_cast(std::min(mDevice->mUpdateSize, 8192_u32)); want.callback = [](void *const ptr, Uint8 *const stream, int const 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. */ auto have = SDL_AudioSpec{}; 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()}; auto devtype = DevFmtType{}; 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; } auto Sdl2Backend::reset() -> bool { if(mDeviceID) SDL_CloseAudioDevice(mDeviceID); mDeviceID = 0; auto want = SDL_AudioSpec{}; want.freq = al::saturate_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 = al::saturate_cast(mDevice->channelsFromFmt()); want.samples = al::saturate_cast(std::min(mDevice->mUpdateSize, 8192_u32)); want.callback = [](void *const ptr, Uint8 *const stream, int const 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 auto SDL2BackendFactory::getFactory() -> BackendFactory& { static SDL2BackendFactory factory{}; return factory; } auto SDL2BackendFactory::init() -> bool { return (SDL_InitSubSystem(SDL_INIT_AUDIO) == 0); } auto SDL2BackendFactory::querySupport(BackendType const type) -> bool { return type == BackendType::Playback; } auto SDL2BackendFactory::enumerate(BackendType const type) -> std::vector { auto outnames = std::vector{}; if(type != BackendType::Playback) return outnames; auto num_devices = SDL_GetNumAudioDevices(SDL_FALSE); if(num_devices <= 0) return outnames; outnames.reserve(gsl::narrow_cast(num_devices)+1_uz); outnames.emplace_back(getDefaultDeviceName()); for(int i{0};i < num_devices;++i) { if(auto *const name = SDL_GetAudioDeviceName(i, SDL_FALSE)) outnames.emplace_back(name); else outnames.emplace_back("Unknown Device Name #"+std::to_string(i)); } return outnames; } auto SDL2BackendFactory::createBackend(gsl::not_null const device, BackendType const type) -> BackendPtr { if(type == BackendType::Playback) return BackendPtr{new Sdl2Backend{device}}; return nullptr; } kcat-openal-soft-75c0059/alc/backends/sdl2.h000066400000000000000000000007141512220627100204650ustar00rootroot00000000000000#ifndef BACKENDS_SDL2_H #define BACKENDS_SDL2_H #include "base.h" struct SDL2BackendFactory final : BackendFactory { auto init() -> bool final; auto querySupport(BackendType type) -> bool final; auto enumerate(BackendType type) -> std::vector final; auto createBackend(gsl::not_null device, BackendType type) -> BackendPtr final; static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_SDL2_H */ kcat-openal-soft-75c0059/alc/backends/sdl3.cpp000066400000000000000000000306231512220627100210230ustar00rootroot00000000000000/** * 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 "alnumeric.h" #include "core/device.h" #include "core/logging.h" #include "gsl/gsl" #include "pragmadefs.h" DIAGNOSTIC_PUSH std_pragma("GCC diagnostic ignored \"-Wold-style-cast\"") #include "SDL3/SDL_audio.h" #include "SDL3/SDL_init.h" #include "SDL3/SDL_stdinc.h" namespace { constexpr auto DefaultPlaybackDeviceID = SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK; } /* namespace */ DIAGNOSTIC_POP namespace { using namespace std::string_view_literals; template using unique_sdl_ptr = std::unique_ptr ptr) { SDL_free(ptr); })>; struct DeviceEntry { std::string mName; SDL_AudioDeviceID mPhysDeviceID{}; }; auto gPlaybackDevices = std::vector{}; 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 = std::span{devicelist.get(), gsl::narrow(numdevs)}; auto newlist = std::vector{}; newlist.reserve(devids.size()); std::ranges::transform(devids, std::back_inserter(newlist), [](SDL_AudioDeviceID const 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 : BackendBase { explicit Sdl3Backend(gsl::not_null const 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}; u32 mNumChannels{0}; u32 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 = gsl::narrow_cast(additional_amount); if(ulen > mBuffer.size()) { mBuffer.resize(ulen); std::ranges::fill(mBuffer, (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::ranges::find(gPlaybackDevices, name, &DeviceEntry::mName); if(iter == gPlaybackDevices.end()) 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 = gsl::narrow_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 = gsl::narrow_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 = gsl::narrow_cast(update_size); mDevice->mBufferSize = mDevice->mUpdateSize*2_u32; } 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 = gsl::narrow_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 = al::saturate_cast(mDevice->channelsFromFmt()); 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) || (std::cmp_not_equal(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 = gsl::narrow_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 = gsl::narrow_cast(have.freq); if(update_size >= 64) { mDevice->mUpdateSize = gsl::narrow_cast(update_size); mDevice->mBufferSize = mDevice->mUpdateSize * 2_u32; mBuffer.resize(usize{mDevice->mUpdateSize} * mFrameSize); std::ranges::fill(mBuffer, 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 const type) -> bool { return type == BackendType::Playback; } auto SDL3BackendFactory::enumerate(BackendType const type) -> std::vector { auto outnames = std::vector{}; if(type != BackendType::Playback) return outnames; EnumeratePlaybackDevices(); outnames.reserve(gPlaybackDevices.size()+1); outnames.emplace_back(getDefaultDeviceName()); std::ranges::transform(gPlaybackDevices, std::back_inserter(outnames), &DeviceEntry::mName); return outnames; } auto SDL3BackendFactory::createBackend(gsl::not_null const device, BackendType const type) -> BackendPtr { if(type == BackendType::Playback) return BackendPtr{new Sdl3Backend{device}}; return nullptr; } kcat-openal-soft-75c0059/alc/backends/sdl3.h000066400000000000000000000007141512220627100204660ustar00rootroot00000000000000#ifndef BACKENDS_SDL3_H #define BACKENDS_SDL3_H #include "base.h" struct SDL3BackendFactory final : BackendFactory { auto init() -> bool final; auto querySupport(BackendType type) -> bool final; auto enumerate(BackendType type) -> std::vector final; auto createBackend(gsl::not_null device, BackendType type) -> BackendPtr final; static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_SDL3_H */ kcat-openal-soft-75c0059/alc/backends/sndio.cpp000066400000000000000000000371711512220627100212770ustar00rootroot00000000000000/** * 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 #include "althrd_setname.h" #include "core/device.h" #include "core/helpers.h" #include "core/logging.h" #include "gsl/gsl" #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 : BackendBase { explicit SndioPlayback(gsl::not_null const device) noexcept : BackendBase{device} { } ~SndioPlayback() override; void mixerProc(); void open(std::string_view name) override; auto reset() -> bool override; void start() override; void stop() override; sio_hdl *mSndHandle{nullptr}; u32 mFrameStep{}; std::vector mBuffer; std::atomic mKillNow{true}; std::thread mThread; }; SndioPlayback::~SndioPlayback() { if(mSndHandle) sio_close(mSndHandle); mSndHandle = nullptr; } void SndioPlayback::mixerProc() { auto const frameStep = usize{mFrameStep}; auto const frameSize = frameStep * mDevice->bytesFromFmt(); SetRTPriority(); althrd_setname(GetMixerThreadName()); while(!mKillNow.load(std::memory_order_acquire) && mDevice->Connected.load(std::memory_order_acquire)) { auto buffer = std::span{mBuffer}; mDevice->renderSamples(buffer.data(), gsl::narrow_cast(buffer.size() / frameSize), frameStep); while(!buffer.empty() && !mKillNow.load(std::memory_order_acquire)) { const auto 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); } } } 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}; auto *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; } auto SndioPlayback::reset() -> bool { auto par = SioPar{}; 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(usize{mDevice->mUpdateSize} * par.pchan*par.bps); 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 : BackendBase { explicit SndioCapture(gsl::not_null const device) noexcept : BackendBase{device} { } ~SndioCapture() override; void recordProc(); void open(std::string_view name) override; void start() override; void stop() override; void captureSamples(std::span outbuffer) override; auto availableSamples() -> usize override; sio_hdl *mSndHandle{nullptr}; RingBufferPtr mRing; std::atomic mKillNow{true}; std::thread mThread; }; SndioCapture::~SndioCapture() { if(mSndHandle) sio_close(mSndHandle); mSndHandle = nullptr; } void SndioCapture::recordProc() { SetRTPriority(); althrd_setname(GetRecordThreadName()); auto const frameSize = mDevice->frameSizeFromFmt(); auto const nfds_pre = sio_nfds(mSndHandle); if(nfds_pre <= 0) { mDevice->handleDisconnect("Incorrect return value from sio_nfds(): {}", nfds_pre); return; } auto fds = std::vector(gsl::narrow_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 auto nfds = sio_pollfd(mSndHandle, fds.data(), POLLIN); if(nfds <= 0) { mDevice->handleDisconnect("Failed to get polling fds: {}", nfds); break; } const auto 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 auto revents = sio_revents(mSndHandle, fds.data()); if((revents&POLLHUP)) { mDevice->handleDisconnect("Got POLLHUP from poll events"); break; } if(!(revents&POLLIN)) continue; auto buffer = mRing->getWriteVector()[0]; while(!buffer.empty()) { const auto 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()) buffer = mRing->getWriteVector()[0]; } if(buffer.empty()) { /* Got samples to read, but no place to store it. Drop it. */ static auto junk = std::array{}; sio_read(mSndHandle, junk.data(), junk.size() - (junk.size()%frameSize)); } } } 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, usize{par.bps}*par.rchan, false); mDevice->mBufferSize = gsl::narrow_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::span const outbuffer) { std::ignore = mRing->read(outbuffer); } auto SndioCapture::availableSamples() -> usize { return mRing->readSpace(); } } // namespace auto SndIOBackendFactory::getFactory() -> BackendFactory& { static SndIOBackendFactory factory{}; return factory; } auto SndIOBackendFactory::init() -> bool { return true; } auto SndIOBackendFactory::querySupport(BackendType const type) -> bool { return (type == BackendType::Playback || type == BackendType::Capture); } auto SndIOBackendFactory::enumerate(BackendType const type) -> std::vector { switch(type) { case BackendType::Playback: case BackendType::Capture: return std::vector{std::string{GetDefaultName()}}; } return {}; } auto SndIOBackendFactory::createBackend(gsl::not_null const device, BackendType const type) -> BackendPtr { if(type == BackendType::Playback) return BackendPtr{new SndioPlayback{device}}; if(type == BackendType::Capture) return BackendPtr{new SndioCapture{device}}; return nullptr; } kcat-openal-soft-75c0059/alc/backends/sndio.hpp000066400000000000000000000007261512220627100213000ustar00rootroot00000000000000#ifndef BACKENDS_SNDIO_HPP #define BACKENDS_SNDIO_HPP #include "base.h" struct SndIOBackendFactory final : BackendFactory { auto init() -> bool final; auto querySupport(BackendType type) -> bool final; auto enumerate(BackendType type) -> std::vector final; auto createBackend(gsl::not_null device, BackendType type) -> BackendPtr final; static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_SNDIO_HPP */ kcat-openal-soft-75c0059/alc/backends/solaris.cpp000066400000000000000000000205071512220627100216320ustar00rootroot00000000000000/** * 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 #include "alc/alconfig.h" #include "alstring.h" #include "althrd_setname.h" #include "core/device.h" #include "core/helpers.h" #include "core/logging.h" #include "gsl/gsl" #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 : BackendBase { explicit SolarisBackend(gsl::not_null const 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}; u32 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()); auto const frame_step = usize{mDevice->channelsFromFmt()}; auto const frame_size = usize{mDevice->frameSizeFromFmt()}; while(!mKillNow.load(std::memory_order_acquire) && mDevice->Connected.load(std::memory_order_acquire)) { auto pollitem = pollfd{}; pollitem.fd = mFd; pollitem.events = POLLOUT; if(auto const pret = poll(&pollitem, 1, 1000); 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; } auto buffer = std::span{mBuffer}; mDevice->renderSamples(buffer.data(), gsl::narrow_cast(buffer.size()/frame_size), frame_step); while(!buffer.empty() && !mKillNow.load(std::memory_order_acquire)) { auto const 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(gsl::narrow(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; [[fallthrough]]; 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; } auto const frame_size = u32{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 * usize{frame_size}); std::ranges::fill(mBuffer, 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 auto SolarisBackendFactory::getFactory() -> BackendFactory & { static SolarisBackendFactory factory{}; return factory; } auto SolarisBackendFactory::init() -> bool { if(auto devopt = ConfigValueStr({}, "solaris", "device")) solaris_driver = std::move(*devopt); return true; } auto SolarisBackendFactory::querySupport(BackendType const type) -> bool { return type == BackendType::Playback; } auto SolarisBackendFactory::enumerate(BackendType const 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 {}; } auto SolarisBackendFactory::createBackend(gsl::not_null const device, BackendType const type) -> BackendPtr { if(type == BackendType::Playback) return BackendPtr{new SolarisBackend{device}}; return nullptr; } kcat-openal-soft-75c0059/alc/backends/solaris.h000066400000000000000000000007301512220627100212730ustar00rootroot00000000000000#ifndef BACKENDS_SOLARIS_H #define BACKENDS_SOLARIS_H #include "base.h" struct SolarisBackendFactory final : BackendFactory { auto init() -> bool final; auto querySupport(BackendType type) -> bool final; auto enumerate(BackendType type) -> std::vector final; auto createBackend(gsl::not_null device, BackendType type) -> BackendPtr final; static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_SOLARIS_H */ kcat-openal-soft-75c0059/alc/backends/wasapi.cpp000066400000000000000000003354251512220627100214520ustar00rootroot00000000000000/** * 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" #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 #include #include #include #include #include "alc/alconfig.h" #include "alformat.hpp" #include "alnumeric.h" #include "alstring.h" #include "althrd_setname.h" #include "comptr.h" #include "core/converter.h" #include "core/device.h" #include "core/logging.h" #include "gsl/gsl" #include "opthelpers.h" #include "ringbuffer.h" #include "strutils.hpp" #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 auto MonoMask = MaskFromTopBits(MONO); constexpr auto StereoMask = MaskFromTopBits(STEREO); constexpr auto QuadMask = MaskFromTopBits(QUAD); constexpr auto X51Mask = MaskFromTopBits(X5DOT1); constexpr auto X51RearMask = MaskFromTopBits(X5DOT1REAR); constexpr auto X61Mask = MaskFromTopBits(X6DOT1); constexpr auto X71Mask = MaskFromTopBits(X7DOT1); constexpr auto X714Mask = MaskFromTopBits(X7DOT1DOT4); #ifndef _MSC_VER [[nodiscard]] constexpr auto operator|(AudioObjectType const lhs, AudioObjectType const rhs) noexcept -> AudioObjectType { return static_cast(lhs | al::to_underlying(rhs)); } #endif constexpr auto ChannelMask_Mono = AudioObjectType_FrontCenter; constexpr auto ChannelMask_Stereo = AudioObjectType_FrontLeft | AudioObjectType_FrontRight; constexpr auto ChannelMask_Quad = AudioObjectType_FrontLeft | AudioObjectType_FrontRight | AudioObjectType_BackLeft | AudioObjectType_BackRight; constexpr auto ChannelMask_X51 = AudioObjectType_FrontLeft | AudioObjectType_FrontRight | AudioObjectType_FrontCenter | AudioObjectType_LowFrequency | AudioObjectType_SideLeft | AudioObjectType_SideRight; constexpr auto ChannelMask_X61 = AudioObjectType_FrontLeft | AudioObjectType_FrontRight | AudioObjectType_FrontCenter | AudioObjectType_LowFrequency | AudioObjectType_SideLeft | AudioObjectType_SideRight | AudioObjectType_BackCenter; constexpr auto ChannelMask_X71 = AudioObjectType_FrontLeft | AudioObjectType_FrontRight | AudioObjectType_FrontCenter | AudioObjectType_LowFrequency | AudioObjectType_SideLeft | AudioObjectType_SideRight | AudioObjectType_BackLeft | AudioObjectType_BackRight; constexpr auto 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 auto 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 using unique_coptr = std::unique_ptr; /* Scales the given reftime value, rounding the result. */ constexpr auto RefTime2Samples(ReferenceTime const &val, DWORD const srate) noexcept -> u32 { const auto retval = (val*srate + ReferenceTime{seconds{1}}/2) / seconds{1}; return al::saturate_cast(retval); } class GuidPrinter { std::string mMsg; public: explicit GuidPrinter(const GUID &guid) : mMsg{al::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; } }; class PropVariant { PROPVARIANT mProp{}; public: PropVariant() { PropVariantInit(&mProp); } ~PropVariant() { clear(); } PropVariant(const PropVariant &rhs) = delete; auto operator=(const PropVariant &rhs) -> PropVariant& = delete; void clear() { std::ignore = PropVariantClear(&mProp); } auto get() noexcept -> PROPVARIANT* { 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) { Expects(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) { Expects(mProp.vt == VT_LPWSTR); return mProp.pwszVal; } } void setBlob(const std::span data) { if constexpr(sizeof(usize) > sizeof(ULONG)) Expects(data.size() <= std::numeric_limits::max()); clear(); mProp.vt = VT_BLOB; mProp.blob.cbSize = gsl::narrow_cast(data.size()); /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) */ mProp.blob.pBlobData = reinterpret_cast(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; NOINLINE ~DevMap() = default; }; auto checkName(const std::span list, const std::string_view name) -> bool { return std::ranges::find(list, name, &DevMap::name) != list.end(); } 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; } }; auto gDeviceList = DeviceList{}; auto gInitDone = std::atomic{false}; #ifdef AVRTAPI using AvrtHandlePtr = std::unique_ptr, decltype( [](HANDLE const handle) { AvRevertMmThreadCharacteristics(handle); })>; #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(std::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(std::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, gsl::narrow_cast(devIdStartEnd - devIdStart)}); std::transform(ret.mGuid.begin(), ret.mGuid.end(), ret.mGuid.begin(), [](char ch) { return gsl::narrow_cast(std::toupper(ch)); }); } } #endif if(ret.mName.empty()) ret.mName = UnknownName; if(ret.mGuid.empty()) ret.mGuid = UnknownGuid; return ret; } #if !ALSOFT_UWP auto GetDeviceFormfactor(IMMDevice *device) -> EndpointFormFactor { ComPtr ps; HRESULT hr{device->OpenPropertyStore(STGM_READ, al::out_ptr(ps))}; if(FAILED(hr)) { WARN("OpenPropertyStore failed: {:#x}", as_unsigned(hr)); return UnknownFormFactor; } auto formfactor = UnknownFormFactor; auto pvform = PropVariant{}; 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) std::ignore = 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 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 deviceId, DWORD newState) noexcept override { TRACE("OnDeviceStateChanged({}, {:#x})", deviceId ? wstr_to_utf8(deviceId) : std::string{""}, newState); if(!(newState&DEVICE_STATE_ACTIVE)) return DeviceRemoved(deviceId); return DeviceAdded(deviceId); } STDMETHODIMP OnDeviceAdded(LPCWSTR deviceId) noexcept override { TRACE("OnDeviceAdded({})", deviceId ? wstr_to_utf8(deviceId) : std::string{""}); return DeviceAdded(deviceId); } STDMETHODIMP OnDeviceRemoved(LPCWSTR deviceId) noexcept override { TRACE("OnDeviceRemoved({})", deviceId ? wstr_to_utf8(deviceId) : std::string{""}); return DeviceRemoved(deviceId); } /* 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 defaultDeviceId) noexcept override { TRACE("OnDefaultDeviceChanged({}, {}, {})", al::to_underlying(flow), al::to_underlying(role), defaultDeviceId ? wstr_to_utf8(defaultDeviceId) : std::string{""}); if(role != eMultimedia) return S_OK; const auto devid = defaultDeviceId ? std::wstring_view{defaultDeviceId} : 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 -------------------------- */ auto init() -> HRESULT { #if !ALSOFT_UWP const auto hr = CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER, __uuidof(IMMDeviceEnumerator), al::out_ptr(mEnumerator)); if(SUCCEEDED(hr)) { if(auto const hr2 = mEnumerator->RegisterEndpointNotificationCallback(this); FAILED(hr2)) ERR("Failed to register endpoint notification callback: {:#x}", as_unsigned(hr)); } 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; } auto count = UINT{0}; hr = coll->GetCount(&count); if(SUCCEEDED(hr) && count > 0) list.reserve(count); auto device = ComPtr{}; hr = mEnumerator->GetDefaultAudioEndpoint(flowdir, eMultimedia, al::out_ptr(device)); if(SUCCEEDED(hr)) { auto devid = unique_coptr{}; if(auto const 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 const 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(); // Set up 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 = al::format("{} #{}", name, ++count); const auto &newentry = list.emplace_back(std::move(newname), std::move(guid), devid); TRACE(R"(Got device "{}", "{}", "{}")", newentry.name, newentry.endpoint_guid, wstr_to_utf8(newentry.devid)); return true; } #if !ALSOFT_UWP auto DeviceAdded(LPCWSTR const deviceId) const noexcept -> HRESULT { auto device = ComPtr{}; auto hr = mEnumerator->GetDevice(deviceId, al::out_ptr(device)); if(FAILED(hr)) { ERR("Failed to get device: {:#x}", as_unsigned(hr)); return S_OK; } auto state = DWORD{}; hr = device->GetState(&state); if(FAILED(hr)) { ERR("Failed to get device state: {:#x}", as_unsigned(hr)); return S_OK; } if(!(state&DEVICE_STATE_ACTIVE)) return S_OK; auto endpoint = ComPtr{}; 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; } auto flowdir = EDataFlow{}; hr = endpoint->GetDataFlow(&flowdir); if(FAILED(hr)) { ERR("Failed to get endpoint data flow: {:#x}", as_unsigned(hr)); return S_OK; } auto const devlock = DeviceListLock{gDeviceList}; auto &list = (flowdir == eRender) ? devlock.getPlaybackList() : devlock.getCaptureList(); if(AddDevice(device, deviceId, list)) { const auto devtype = (flowdir == eRender) ? alc::DeviceType::Playback : alc::DeviceType::Capture; const auto msg = "Device added: "+list.back().name; alc::Event(alc::EventType::DeviceAdded, devtype, msg); } return S_OK; } static auto DeviceRemoved(LPCWSTR const deviceId) noexcept -> HRESULT { auto const devlock = DeviceListLock{gDeviceList}; for(auto const flowdir : std::array{eRender, eCapture}) { auto &list = (flowdir==eRender) ? devlock.getPlaybackList() : devlock.getCaptureList(); auto const devtype = (flowdir == eRender) ? alc::DeviceType::Playback : alc::DeviceType::Capture; /* Find the ID in the list to remove. */ auto const iter = std::ranges::find(list, deviceId, &DevMap::devid); if(iter == list.end()) continue; TRACE(R"(Removing device "{}", "{}", "{}")", iter->name, iter->endpoint_guid, wstr_to_utf8(iter->devid)); const auto msg = "Device removed: "+std::move(iter->name); list.erase(iter); alc::Event(alc::EventType::DeviceRemoved, devtype, msg); } return S_OK; } 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"); const auto com = ComWrapper{COINIT_MULTITHREADED}; if(!com) { WARN("Failed to initialize COM: {:#x}", as_unsigned(com.status())); promise->set_value(com.status()); return; } auto helper = std::optional{}; try { auto devlock = DeviceListLock{gDeviceList}; auto hr = helper.emplace().init(); promise->set_value(hr); promise = nullptr; if(FAILED(hr)) return; 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); } catch(std::exception &e) { ERR("Exception probing devices: {}", e.what()); if(promise) promise->set_value(E_FAIL); } TRACE("Watcher thread started"); gInitDone.store(true, std::memory_order_release); gInitDone.notify_all(); 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(std::wstring const &devid, EDataFlow const flow, DeviceHandle &device) const -> 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 const &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 { const auto 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(std::wstring const &devid, EDataFlow const flow, DeviceHandle &device) const -> HRESULT { auto 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 const &device, REFIID iid, void **ppv) -> HRESULT { if(iid == __uuidof(IAudioClient)) { /* Always (try) to activate an IAudioClient3, even if giving back * an IAudioClient iface. This may(?) offer more features even if * not using its new methods. */ auto ac3 = ComPtr{}; const auto hr = device->Activate(__uuidof(IAudioClient3), CLSCTX_INPROC_SERVER, nullptr, al::out_ptr(ac3)); if(SUCCEEDED(hr)) return ac3->QueryInterface(iid, ppv); } 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) { static constexpr auto 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); } template constexpr auto span_to_array(const std::span span, std::index_sequence) { return std::array{{span[I]...}}; } template requires(N != std::dynamic_extent) constexpr auto span_to_array(const std::span span) -> std::array { return span_to_array(span, std::make_index_sequence{}); } /* Duplicates the first sample of each sample frame to the second sample, * halving the volume. Essentially converting mono to stereo. */ template void DuplicateSamples(std::span insamples, usize step) { if constexpr(std::is_floating_point_v || std::is_signed_v) { step *= sizeof(T); while(!insamples.empty()) { /* This may look ugly, but it avoids type-punning with * reinterpret_cast and optimizes equally well. */ const auto sbytes = span_to_array(insamples.first()); const auto s = std::bit_cast(sbytes) / T{2}; const auto result = std::bit_cast>(T(s)); std::ranges::copy(result, std::ranges::copy(result, insamples.begin()).out); insamples = insamples.subspan(step); } } else { using ST = std::make_signed_t; static constexpr auto SignBit = T{1u << (sizeof(T)*8 - 1)}; step *= sizeof(T); while(!insamples.empty()) { const auto sbytes = span_to_array(insamples.first()); const auto s = static_cast(std::bit_cast(sbytes)^SignBit) / ST{2}; const auto result = std::bit_cast>(T(T(s)^SignBit)); std::ranges::copy(result, std::ranges::copy(result, insamples.begin()).out); insamples = insamples.subspan(step); } } } void DuplicateSamples(std::span const insamples, DevFmtType const sampletype, usize const 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 : BackendBase { explicit WasapiPlayback(gsl::not_null const 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 const &audio); void mixerProc(SpatialDevice const &audio); auto openProxy(std::string_view name, DeviceHelper const &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(const std::string &name); void open(std::string_view name) override; auto reset() -> bool 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 : u8 { Initializing, Waiting, Playing, Done }; ThreadState mState{ThreadState::Initializing}; enum class ThreadAction : u8 { Nothing, Configure, Play, Quit }; ThreadAction mAction{ThreadAction::Nothing}; static inline auto sAvIndex = DWORD{}; HANDLE mNotifyEvent{nullptr}; UINT32 mOutBufferSize{}, mOutUpdateSize{}; std::vector mResampleBuffer; u32 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 const &audio) { auto const _ = gsl::finally([oldprio=GetThreadPriority(GetCurrentThread())] { SetThreadPriority(GetCurrentThread(), oldprio); }); if(!SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL)) ERR("Failed to set priority level for thread"); const auto frame_size = u32{mFormat.Format.nChannels} * mFormat.Format.wBitsPerSample / 8_u32; const auto buffer_len = mOutBufferSize; auto *resbufferptr = LPCVOID{}; Expects(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 const 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 = u32{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 = std::span{buffer, usize{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(usize{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(std::span{buffer, usize{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 const 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(auto const res = WaitForSingleObjectEx(mNotifyEvent, 2000, FALSE); res != WAIT_OBJECT_0) ERR("WaitForSingleObjectEx error: {:#x}", res); } mPadding.store(0u, std::memory_order_release); if(auto const hr = audio.mClient->Stop(); FAILED(hr)) ERR("Failed to stop audio client: {:#x}", as_unsigned(hr)); } FORCE_ALIGN void WasapiPlayback::mixerProc(SpatialDevice const &audio) { auto const _ = gsl::finally([oldprio=GetThreadPriority(GetCurrentThread())] { SetThreadPriority(GetCurrentThread(), oldprio); }); if(!SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL)) ERR("Failed to set priority level for thread"); #ifdef AVRTAPI auto taskname = (mOutUpdateSize < mFormat.Format.nSamplesPerSec/100) ? L"Pro Audio" : L"Audio"; auto avhandle = AvrtHandlePtr{AvSetMmThreadCharacteristicsW(taskname, &sAvIndex)}; #endif auto channels = std::vector>{}; auto buffers = std::vector{}; auto resbuffers = std::vector{}; auto tmpbuffers = std::vector{}; /* 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); mBufferFilled = 0; auto firstupdate = true; while(!mKillNow.load(std::memory_order_relaxed)) { auto dynamicCount = UINT32{}; auto framesToDo = UINT32{}; auto 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(std::popcount(flags))); while(flags) { auto id = decltype(flags){1} << std::countr_zero(flags); flags &= ~id; audio.mRender->ActivateSpatialAudioObject(static_cast(id), al::out_ptr(channels.emplace_back())); } buffers.resize(channels.size()); if(mResampler) { tmpbuffers.resize(buffers.size()); resbuffers.resize(buffers.size()); auto bufptr = mResampleBuffer.begin(); for(usize i{0};i < tmpbuffers.size();++i) { resbuffers[i] = std::to_address(bufptr); std::advance(bufptr, mDevice->mUpdateSize*sizeof(float)); } } } /* We have to call to get each channel's buffer individually every * update, unfortunately. */ std::ranges::transform(channels, buffers.begin(), [](ISpatialAudioObject &obj) -> void* { auto *buffer = LPBYTE{}; auto size = UINT32{}; obj.GetBuffer(&buffer, &size); return buffer; }, &ComPtr::operator*); if(!mResampler) mDevice->renderSamples(buffers, framesToDo); else { auto dlock = std::lock_guard{mMutex}; for(auto pos = 0u;pos < framesToDo;) { if(mBufferFilled == 0) { mDevice->renderSamples(resbuffers, mDevice->mUpdateSize); std::ranges::copy(resbuffers, tmpbuffers.begin()); mBufferFilled = mDevice->mUpdateSize; } const auto got = mResampler->convertPlanar(tmpbuffers.data(), &mBufferFilled, buffers.data(), framesToDo-pos); std::ranges::transform(buffers, buffers.begin(), [got](void *buf) -> void* { return static_cast(buf) + got; /* NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) */ }); pos += got; } } hr = audio.mRender->EndUpdatingAudioObjects(); } if(firstupdate) { firstupdate = false; ResetEvent(mNotifyEvent); hr = audio.mRender->Start(); if(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; } } if(const auto res = WaitForSingleObjectEx(mNotifyEvent, 1000, FALSE); res != WAIT_OBJECT_0) { ERR("WaitForSingleObjectEx error: {:#x}", res); hr = audio.mRender->Reset(); if(FAILED(hr)) { ERR("ISpatialAudioObjectRenderStream::Reset failed: {:#x}", as_unsigned(hr)); mDevice->handleDisconnect("Device lost: {:#x}", as_unsigned(hr)); break; } firstupdate = true; } 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(const 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; } gInitDone.wait(false, std::memory_order_acquire); auto helper = DeviceHelper{}; if(const auto 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 const 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{}; 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) { 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(std::string_view const name, DeviceHelper const &helper, DeviceHandle &mmdev) -> HRESULT { auto devname = std::string{}; auto devid = std::wstring{}; if(!name.empty()) { auto const devlock = DeviceListLock{gDeviceList}; auto const list = std::span{devlock.getPlaybackList()}; auto iter = std::ranges::find_if(list, [name](const DevMap &entry) -> bool { return entry.name == name || al::case_compare(entry.endpoint_guid, name) == 0; }); if(iter == list.end()) { const auto wname = utf8_to_wstr(name); iter = std::ranges::find_if(list, [&wname](const DevMap &entry) -> bool { return al::case_compare(entry.devid, wname) == 0; }); } if(iter == list.end()) { WARN("Failed to find device name matching \"{}\"", name); return E_NOTFOUND; } devname = iter->name; devid = iter->devid; } if(const auto hr = helper.openDevice(devid, eRender, mmdev); FAILED(hr)) { WARN("Failed to open device \"{}\": {:#x}", 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 = gsl::narrow_cast(OutputType.Format.nSamplesPerSec); else mDevice->mSampleRate = std::min(mDevice->mSampleRate, gsl::narrow_cast(OutputType.Format.nSamplesPerSec)); const auto chancount = u32{OutputType.Format.nChannels}; const auto chanmask = OutputType.dwChannelMask; /* Don't update the channel format if the requested format fits what's * supported. */ auto 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 { auto 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; } auto fmtenum = ComPtr{}; hr = audio.mClient->GetSupportedAudioObjectFormatEnumerator(al::out_ptr(fmtenum)); if(FAILED(hr)) { ERR("Failed to get format enumerator: {:#x}", as_unsigned(hr)); return false; } auto fmtcount = UINT32{}; hr = fmtenum->GetCount(&fmtcount); if(FAILED(hr) || fmtcount == 0) { ERR("Failed to get format count: {:#08x}", as_unsigned(hr)); return false; } auto *preferredFormat = LPWAVEFORMATEX{}; 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); auto maxFrames = UINT32{}; 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(auto i = 1u;i < fmtcount;++i) { auto *otherFormat = LPWAVEFORMATEX{}; 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); auto otherMaxFrames = UINT32{}; 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); } } auto OutputType = WAVEFORMATEXTENSIBLE{}; 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 = gsl::narrow_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; }; auto streamParams = SpatialAudioObjectRenderStreamActivationParams{}; streamParams.ObjectFormat = &OutputType.Format; streamParams.StaticObjectTypeMask = getTypeMask(mDevice->FmtChans); streamParams.Category = AudioCategory_Media; streamParams.EventHandle = mNotifyEvent; auto paramProp = PropVariant{}; paramProp.setBlob(std::as_writable_bytes(std::span{&streamParams, 1_uz})); 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 = gsl::narrow_cast(OutputType.Format.nSamplesPerSec); else mDevice->mSampleRate = std::min(mDevice->mSampleRate, gsl::narrow_cast(OutputType.Format.nSamplesPerSec)); setDefaultWFXChannelOrder(); /* TODO: ISpatialAudioClient::GetMaxFrameCount returns the maximum number * of frames per processing pass, which is ostensibly the period size. This * should be checked on a real Windows system. * * In either case, this won't get the buffer size of the * ISpatialAudioObjectRenderStream, so we only assume there's two periods. */ mOutUpdateSize = maxFrames; mOutBufferSize = mOutUpdateSize*2; mDevice->mUpdateSize = gsl::narrow_cast((u64{mOutUpdateSize}*mDevice->mSampleRate + (mFormat.Format.nSamplesPerSec-1)) / mFormat.Format.nSamplesPerSec); 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(std::popcount(flags)); mResampler = SampleConverter::Create(mDevice->FmtType, mDevice->FmtType, channelCount, mDevice->mSampleRate, mFormat.Format.nSamplesPerSec, Resampler::FastBSinc24); mResampleBuffer.resize(usize{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; } auto WasapiPlayback::reset() -> bool { 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. */ auto 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 auto chancount = OutputType.Format.nChannels; const auto 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 auto chancount = OutputType.Format.nChannels; const auto 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 = gsl::narrow_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 = gsl::narrow_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 auto 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_val = REFERENCE_TIME{}; hr = audio.mClient->GetDevicePeriod(nullptr, &min_period_val); if(FAILED(hr)) ERR("Failed to get minimum period time: {:#x}", as_unsigned(hr)); else if(const auto min_period = ReferenceTime{min_period_val}; 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{}; auto period_time_val = REFERENCE_TIME{}; hr = audio.mClient->GetDevicePeriod(&period_time_val, nullptr); if(SUCCEEDED(hr)) { period_time = ReferenceTime{period_time_val}; 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 = gsl::narrow_cast(u64{buffer_len} * mDevice->mSampleRate / mFormat.Format.nSamplesPerSec); mDevice->mBufferSize = mDevice->mUpdateSize * 2; } else { mOutUpdateSize = RefTime2Samples(period_time, mFormat.Format.nSamplesPerSec); mDevice->mBufferSize = gsl::narrow_cast(u64{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(usize{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() { auto dlock = std::lock_guard{mMutex}; auto ret = ClockLatency{}; 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 : BackendBase { explicit WasapiCapture(gsl::not_null const device) noexcept : BackendBase{device} { } ~WasapiCapture() override; void recordProc(IAudioClient *client, IAudioCaptureClient *capture) const; void proc_thread(const std::string &name); auto openProxy(std::string_view name, DeviceHelper const &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::span outbuffer) override; auto availableSamples() -> usize override; std::thread mProcThread; std::mutex mProcMutex; std::condition_variable mProcCond; HRESULT mProcResult{E_FAIL}; enum class ThreadState : u8 { Initializing, Waiting, Recording, Done }; ThreadState mState{ThreadState::Initializing}; enum class ThreadAction : u8 { Nothing, Record, Quit }; ThreadAction mAction{ThreadAction::Nothing}; HANDLE mNotifyEvent{nullptr}; ChannelConverter mChannelConv{}; SampleConverterPtr mSampleConv; RingBufferPtr mRing; std::atomic mKillNow{true}; }; 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) const { ResetEvent(mNotifyEvent); if(const auto 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; } auto samples = std::vector{}; 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{}; auto *rdata = LPBYTE{}; 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); /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) */ rdata = reinterpret_cast(samples.data()); } auto data = mRing->getWriteVector(); auto dstframes = usize{}; if(mSampleConv) { static constexpr auto lenlimit = usize{std::numeric_limits::max()}; auto *srcdata = LPCVOID{rdata}; auto srcframes = u32{numsamples}; const auto len1 = data[0].size() / mRing->getElemSize(); dstframes = mSampleConv->convert(&srcdata, &srcframes, data[0].data(), gsl::narrow_cast(std::min(len1, lenlimit))); if(srcframes > 0 && dstframes == len1 && !data[1].empty()) { /* 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. */ const auto len2 = data[1].size() / mRing->getElemSize(); dstframes += mSampleConv->convert(&srcdata, &srcframes, data[1].data(), gsl::narrow_cast(std::min(len2, lenlimit))); } } else { const auto framesize = mDevice->frameSizeFromFmt(); auto dst = std::span{rdata, usize{numsamples}*framesize}; auto len1 = data[0].size() / mRing->getElemSize(); auto len2 = data[1].size() / mRing->getElemSize(); len1 = std::min(len1, usize{numsamples}); len2 = std::min(len2, numsamples-len1); memcpy(data[0].data(), dst.data(), std::min(data[0].size(), dst.size())); if(dst.size() > data[0].size()) { dst = dst.subspan(data[0].size()); memcpy(data[1].data(), dst.data(), std::min(data[1].size(), dst.size())); } 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(const auto res = WaitForSingleObjectEx(mNotifyEvent, 2000, FALSE); res != WAIT_OBJECT_0) ERR("WaitForSingleObjectEx error: {:#x}", res); } client->Stop(); client->Reset(); } void WasapiCapture::proc_thread(const 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; } gInitDone.wait(false, std::memory_order_acquire); auto helper = DeviceHelper{}; if(const auto 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(const 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(const 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(std::string_view const name, DeviceHelper const &helper, DeviceHandle &mmdev) -> HRESULT { auto devname = std::string{}; auto devid = std::wstring{}; if(!name.empty()) { auto const devlock = DeviceListLock{gDeviceList}; auto const devlist = std::span{devlock.getCaptureList()}; auto iter = std::ranges::find_if(devlist, [name](const DevMap &entry) -> bool { return entry.name == name || al::case_compare(entry.endpoint_guid, name) == 0; }); if(iter == devlist.end()) { const auto wname = utf8_to_wstr(name); iter = std::ranges::find_if(devlist, [&wname](const DevMap &entry) -> bool { return al::case_compare(entry.devid, wname) == 0; }); } if(iter == devlist.end()) { WARN("Failed to find device name matching \"{}\"", name); return E_NOTFOUND; } devname = iter->name; devid = iter->devid; } if(const auto hr = helper.openDevice(devid, eCapture, mmdev); FAILED(hr)) { WARN("Failed to open device \"{}\": {:#x}", 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; } 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 auto isRear51 = InputType.Format.nChannels == 6 && (InputType.dwChannelMask&X51RearMask) == X5DOT1REAR; // Make sure buffer is at least 100ms in size auto 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 = gsl::narrow_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; static constexpr auto validate_fmt = [](DeviceBase const *const device, u32 const chancount, u32 const 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; } } auto srcType = DevFmtType{}; 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) { auto 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; } auto buffer_len = UINT32{}; auto min_per = ReferenceTime{}; auto min_per_val = REFERENCE_TIME{}; hr = client->GetDevicePeriod(&min_per_val, nullptr); if(SUCCEEDED(hr)) { min_per = ReferenceTime{min_per_val}; 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::span const outbuffer) { std::ignore = mRing->read(outbuffer); } auto WasapiCapture::availableSamples() -> usize { return mRing->readSpace(); } } // namespace auto WasapiBackendFactory::init() -> bool { static constinit auto 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); } auto WasapiBackendFactory::querySupport(BackendType const type) -> bool { return type == BackendType::Playback || type == BackendType::Capture; } auto WasapiBackendFactory::enumerate(BackendType const type) -> std::vector { auto outnames = std::vector{}; auto const devlock = DeviceListLock{gDeviceList}; switch(type) { case BackendType::Playback: { auto const defaultId = devlock.getPlaybackDefaultId(); auto &devlist = devlock.getPlaybackList(); outnames.reserve(devlist.size()); std::ranges::transform(devlist, std::back_inserter(outnames), &DevMap::name); /* Default device goes first. */ if(auto const defiter = std::ranges::find(devlist, defaultId, &DevMap::devid); defiter != devlist.end()) { auto const defname = outnames.begin() + std::distance(devlist.begin(), defiter); std::rotate(outnames.begin(), defname, defname+1); } } break; case BackendType::Capture: { auto const defaultId = devlock.getCaptureDefaultId(); auto &devlist = devlock.getCaptureList(); outnames.reserve(devlist.size()); std::ranges::transform(devlist, std::back_inserter(outnames), &DevMap::name); /* Default device goes first. */ if(auto const defiter = std::ranges::find(devlist, defaultId, &DevMap::devid); defiter != devlist.end()) { auto const defname = outnames.begin() + std::distance(devlist.begin(), defiter); std::rotate(outnames.begin(), defname, defname+1); } } break; } return outnames; } auto WasapiBackendFactory::createBackend(gsl::not_null const device, BackendType const type) -> BackendPtr { if(type == BackendType::Playback) return BackendPtr{new WasapiPlayback{device}}; if(type == BackendType::Capture) return BackendPtr{new WasapiCapture{device}}; return nullptr; } auto WasapiBackendFactory::getFactory() -> BackendFactory& { static WasapiBackendFactory factory{}; return factory; } auto WasapiBackendFactory::queryEventSupport(alc::EventType const eventType, BackendType) -> alc::EventSupport { 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; } kcat-openal-soft-75c0059/alc/backends/wasapi.h000066400000000000000000000010701512220627100211010ustar00rootroot00000000000000#ifndef BACKENDS_WASAPI_H #define BACKENDS_WASAPI_H #include "base.h" struct WasapiBackendFactory final : BackendFactory { 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(gsl::not_null device, BackendType type) -> BackendPtr final; static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_WASAPI_H */ kcat-openal-soft-75c0059/alc/backends/wave.cpp000066400000000000000000000412011512220627100211120ustar00rootroot00000000000000/** * 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 #include "alc/alconfig.h" #include "alnumeric.h" #include "alstring.h" #include "althrd_setname.h" #include "core/device.h" #include "core/logging.h" #include "filesystem.h" #include "gsl/gsl" #include "strutils.hpp" namespace { using namespace std::string_view_literals; using std::chrono::seconds; using std::chrono::milliseconds; using std::chrono::nanoseconds; [[nodiscard]] constexpr auto GetDeviceName() noexcept { return "Wave File Writer"sv; } constexpr auto SUBTYPE_PCM = std::bit_cast>(std::to_array({ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 })); constexpr auto SUBTYPE_FLOAT = std::bit_cast>(std::to_array({ 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 })); constexpr auto SUBTYPE_BFORMAT_PCM = std::bit_cast>(std::to_array({ 0x01, 0x00, 0x00, 0x00, 0x21, 0x07, 0xd3, 0x11, 0x86, 0x44, 0xc8, 0xc1, 0xca, 0x00, 0x00, 0x00 })); constexpr auto SUBTYPE_BFORMAT_FLOAT = std::bit_cast>(std::to_array({ 0x03, 0x00, 0x00, 0x00, 0x21, 0x07, 0xd3, 0x11, 0x86, 0x44, 0xc8, 0xc1, 0xca, 0x00, 0x00, 0x00 })); constexpr auto MonoChannels = 0x04u; constexpr auto StereoChannels = 0x01u | 0x02u; constexpr auto QuadChannels = 0x01u | 0x02u | 0x10u | 0x20u; constexpr auto X51Channels = 0x01u | 0x02u | 0x04u | 0x08u | 0x200u | 0x400u; constexpr auto X61Channels = 0x01u | 0x02u | 0x04u | 0x08u | 0x100u | 0x200u | 0x400u; constexpr auto X71Channels = 0x01u | 0x02u | 0x04u | 0x08u | 0x010u | 0x020u | 0x200u | 0x400u; constexpr auto X714Channels = 0x01u | 0x02u | 0x04u | 0x08u | 0x010u | 0x020u | 0x200u | 0x400u | 0x1000u | 0x4000u | 0x8000u | 0x20000u; void fwrite16le(u16 const val, std::ostream &f) { auto data = std::bit_cast>(val); if constexpr(std::endian::native != std::endian::little) std::ranges::reverse(data); f.write(data.data(), std::ssize(data)); } void fwrite32le(u32 const val, std::ostream &f) { auto data = std::bit_cast>(val); if constexpr(std::endian::native != std::endian::little) std::ranges::reverse(data); f.write(data.data(), std::ssize(data)); } void fwrite16be(u16 const val, std::ostream &f) { auto data = std::bit_cast>(val); if constexpr(std::endian::native != std::endian::big) std::ranges::reverse(data); f.write(data.data(), std::ssize(data)); } void fwrite32be(u32 const val, std::ostream &f) { auto data = std::bit_cast>(val); if constexpr(std::endian::native != std::endian::big) std::ranges::reverse(data); f.write(data.data(), std::ssize(data)); } void fwrite64be(u64 const val, std::ostream &f) { auto data = std::bit_cast>(val); if constexpr(std::endian::native != std::endian::big) std::ranges::reverse(data); f.write(data.data(), std::ssize(data)); } struct WaveBackend final : public BackendBase { explicit WaveBackend(gsl::not_null const device) noexcept : BackendBase{device} { } ~WaveBackend() override; void mixerProc(); void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; std::ofstream mFile; std::streamoff mDataStart{-1}; std::vector mBuffer; bool mCAFOutput{}; std::atomic mKillNow{true}; std::thread mThread; }; WaveBackend::~WaveBackend() = default; void WaveBackend::mixerProc() { auto const restTime = milliseconds{mDevice->mUpdateSize*1000/mDevice->mSampleRate / 2}; althrd_setname(GetMixerThreadName()); auto const frameStep = usize{mDevice->channelsFromFmt()}; auto const frameSize = usize{mDevice->frameSizeFromFmt()}; auto done = 0_i64; 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. */ auto const avail = i64{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 constexpr(std::endian::native != std::endian::little) { if(!mCAFOutput) { auto const bytesize = mDevice->bytesFromFmt(); if(bytesize == 2) { auto const len = mBuffer.size() & ~1_uz; for(auto i=0_uz;i < len;i+=2) std::swap(mBuffer[i], mBuffer[i+1]); } else if(bytesize == 4) { auto const len = mBuffer.size() & ~3_uz; for(auto i=0_uz;i < len;i+=4) { std::swap(mBuffer[i ], mBuffer[i+3]); std::swap(mBuffer[i+1], mBuffer[i+2]); } } } } auto const buffer = std::span{mBuffer}.first(mDevice->mUpdateSize*frameSize); if(!mFile.write(buffer.data(), std::ssize(buffer))) { 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) { auto const s = seconds{done/mDevice->mSampleRate}; done %= mDevice->mSampleRate; start += s; } } } 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}; mFile.open(fs::path(al::char_as_u8(*fname)), std::ios_base::binary); if(!mFile.is_open()) throw al::backend_exception{al::backend_error::DeviceError, "Could not open file '{}': {}", *fname, std::generic_category().message(errno)}; mDeviceName = name; } auto WaveBackend::reset() -> bool { mCAFOutput = false; if(const auto formatopt = ConfigValueStr({}, "wave"sv, "format"sv)) { if(al::case_compare(*formatopt, "caf"sv) == 0) mCAFOutput = true; else if(al::case_compare(*formatopt, "wav"sv) != 0) WARN("Unsupported wave file format: \"{}\"", *formatopt); } if(GetConfigValueBool({}, "wave", "bformat", false)) { mDevice->FmtChans = DevFmtAmbi3D; mDevice->mAmbiOrder = std::max(mDevice->mAmbiOrder, 1u); } switch(mDevice->FmtType) { case DevFmtByte: case DevFmtUByte: if(!mCAFOutput) mDevice->FmtType = DevFmtUByte; else mDevice->FmtType = DevFmtByte; break; case DevFmtUShort: mDevice->FmtType = DevFmtShort; break; case DevFmtUInt: mDevice->FmtType = DevFmtInt; break; case DevFmtShort: case DevFmtInt: case DevFmtFloat: break; } auto chanmask = 0_u32; auto isbformat = false; switch(mDevice->FmtChans) { case DevFmtMono: chanmask = MonoChannels; break; case DevFmtStereo: chanmask = StereoChannels; break; case DevFmtQuad: chanmask = QuadChannels; break; case DevFmtX51: chanmask = X51Channels; break; case DevFmtX61: chanmask = X61Channels; break; case DevFmtX71: chanmask = X71Channels; break; case DevFmtX7144: mDevice->FmtChans = DevFmtX714; [[fallthrough]]; case DevFmtX714: chanmask = X714Channels; break; /* NOTE: Same as 7.1. */ case DevFmtX3D71: chanmask = X71Channels; break; case DevFmtAmbi3D: if(!mCAFOutput) { /* .amb output requires FuMa */ mDevice->mAmbiOrder = std::min(mDevice->mAmbiOrder, 3_u32); mDevice->mAmbiLayout = DevAmbiLayout::FuMa; mDevice->mAmbiScale = DevAmbiScaling::FuMa; } else { /* .ambix output requires ACN+SN3D */ mDevice->mAmbiOrder = std::min(mDevice->mAmbiOrder, u32{MaxAmbiOrder}); mDevice->mAmbiLayout = DevAmbiLayout::ACN; mDevice->mAmbiScale = DevAmbiScaling::SN3D; } isbformat = true; break; } const auto bytes = mDevice->bytesFromFmt(); const auto channels = mDevice->channelsFromFmt(); mFile.seekp(0); mFile.clear(); if(!mCAFOutput) { mFile.write("RIFF", 4); fwrite32le(0xFFFFFFFF, mFile); // 'RIFF' header len; filled in at stop mFile.write("WAVE", 4); mFile.write("fmt ", 4); fwrite32le(40, mFile); // 'fmt ' header len; 40 bytes for EXTENSIBLE // 16-bit val, format type id (extensible: 0xFFFE) fwrite16le(0xFFFE, mFile); // 16-bit val, channel count fwrite16le(gsl::narrow_cast(channels), mFile); // 32-bit val, frequency fwrite32le(mDevice->mSampleRate, mFile); // 32-bit val, bytes per second fwrite32le(mDevice->mSampleRate * channels * bytes, mFile); // 16-bit val, frame size fwrite16le(gsl::narrow_cast(channels * bytes), mFile); // 16-bit val, bits per sample fwrite16le(gsl::narrow_cast(bytes * 8), mFile); // 16-bit val, extra byte count fwrite16le(22, mFile); // 16-bit val, valid bits per sample fwrite16le(gsl::narrow_cast(bytes * 8), mFile); // 32-bit val, channel mask fwrite32le(chanmask, mFile); // 16 byte GUID, sub-type format mFile.write((mDevice->FmtType == DevFmtFloat) ? (isbformat ? SUBTYPE_BFORMAT_FLOAT.data() : SUBTYPE_FLOAT.data()) : (isbformat ? SUBTYPE_BFORMAT_PCM.data() : SUBTYPE_PCM.data()), 16); mFile.write("data", 4); fwrite32le(~0u, mFile); // 'data' header len; filled in at stop mDataStart = mFile.tellp(); } else { /* 32-bit uint, mFileType */ mFile.write("caff", 4); /* 16-bit uint, mFileVersion */ fwrite16be(1, mFile); /* 16-bit uint, mFileFlags */ fwrite16be(0, mFile); /* Audio Description chunk */ mFile.write("desc", 4); fwrite64be(32, mFile); /* 64-bit double, mSampleRate */ fwrite64be(std::bit_cast(gsl::narrow_cast(mDevice->mSampleRate)), mFile); /* 32-bit uint, mFormatID */ mFile.write("lpcm", 4); const auto flags = std::invoke([this] { switch(mDevice->FmtType) { case DevFmtByte: case DevFmtUByte: break; case DevFmtShort: case DevFmtUShort: case DevFmtInt: case DevFmtUInt: if constexpr(std::endian::native == std::endian::little) return 2_u32; /* kCAFLinearPCMFormatFlagIsLittleEndian */ else break; case DevFmtFloat: if constexpr(std::endian::native == std::endian::little) return 3_u32; /* kCAFLinearPCMFormatFlagIsFloat | kCAFLinearPCMFormatFlagIsLittleEndian */ else return 1_u32; /* kCAFLinearPCMFormatFlagIsFloat */ } return 0_u32; }); /* 32-bit uint, mFormatFlags */ fwrite32be(flags, mFile); /* 32-bit uint, mBytesPerPacket */ fwrite32be(bytes*channels, mFile); /* 32-bit uint, mFramesPerPacket */ fwrite32be(1, mFile); /* 32-bit uint, mChannelsPerFrame */ fwrite32be(channels, mFile); /* 32-bit uint, mBitsPerChannel */ fwrite32be(bytes*8, mFile); if(chanmask != 0) { /* Channel Layout chunk */ mFile.write("chan", 4); fwrite64be(12, mFile); /* 32-bit uint, mChannelLayoutTag */ fwrite32be(0x10000, mFile); /* kCAFChannelLayoutTag_UseChannelBitmap */ /* 32-bit uint, mChannelBitmap */ fwrite32be(chanmask, mFile); /* Same as WFX, thankfully. */ /* 32-bit uint, mNumberChannelDescriptions */ fwrite32be(0, mFile); } /* Audio Data chunk */ mFile.write("data", 4); fwrite64be(~0_u64, mFile); /* filled in at stop */ mDataStart = mFile.tellp(); /* 32-bit uint, mEditCount */ fwrite32be(0, mFile); } if(!mFile) { ERR("Error writing header: {}", std::generic_category().message(errno)); return false; } setDefaultWFXChannelOrder(); mBuffer.resize(usize{mDevice->frameSizeFromFmt()} * mDevice->mUpdateSize); return true; } void WaveBackend::start() { 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) { auto const size = std::streamoff{mFile.tellp()}; if(size > mDataStart) { auto const dataLen = size - mDataStart; if(!mCAFOutput) { if(mFile.seekp(4)) // 'WAVE' header len fwrite32le(gsl::narrow_cast(size-8), mFile); if(mFile.seekp(mDataStart-4)) // 'data' header len fwrite32le(gsl::narrow_cast(dataLen), mFile); } else { if(mFile.seekp(mDataStart-8)) // 'data' header len fwrite64be(gsl::narrow_cast(dataLen), mFile); } mFile.seekp(0, std::ios_base::end); } } } } // namespace auto WaveBackendFactory::init() -> bool { return true; } auto WaveBackendFactory::querySupport(BackendType const type) -> bool { return type == BackendType::Playback; } auto WaveBackendFactory::enumerate(BackendType const type) -> std::vector { switch(type) { case BackendType::Playback: return std::vector{std::string{GetDeviceName()}}; case BackendType::Capture: break; } return {}; } auto WaveBackendFactory::createBackend(gsl::not_null const device, BackendType const type) -> BackendPtr { if(type == BackendType::Playback) return BackendPtr{new WaveBackend{device}}; return nullptr; } auto WaveBackendFactory::getFactory() -> BackendFactory& { static WaveBackendFactory factory{}; return factory; } kcat-openal-soft-75c0059/alc/backends/wave.h000066400000000000000000000007141512220627100205630ustar00rootroot00000000000000#ifndef BACKENDS_WAVE_H #define BACKENDS_WAVE_H #include "base.h" struct WaveBackendFactory final : BackendFactory { auto init() -> bool final; auto querySupport(BackendType type) -> bool final; auto enumerate(BackendType type) -> std::vector final; auto createBackend(gsl::not_null device, BackendType type) -> BackendPtr final; static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_WAVE_H */ kcat-openal-soft-75c0059/alc/backends/winmm.cpp000066400000000000000000000451031512220627100213040ustar00rootroot00000000000000/** * 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 #include "alformat.hpp" #include "alnumeric.h" #include "althrd_setname.h" #include "core/device.h" #include "core/helpers.h" #include "core/logging.h" #include "gsl/gsl" #include "ringbuffer.h" #include "strutils.hpp" #include "vector.h" #ifndef WAVE_FORMAT_IEEE_FLOAT #define WAVE_FORMAT_IEEE_FLOAT 0x0003 #endif namespace { std::vector PlaybackDevices; std::vector CaptureDevices; [[nodiscard]] auto checkName(const std::vector &list, const std::string &name) -> bool { return std::ranges::find(list, name) != list.end(); } void ProbePlaybackDevices() { PlaybackDevices.clear(); const auto numdevs = waveOutGetNumDevs(); PlaybackDevices.reserve(numdevs); for(const auto i : std::views::iota(0u, numdevs)) { auto dname = std::string{}; auto WaveCaps = WAVEOUTCAPSW{}; 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 = al::format("{} #{}", basename, ++count); dname = std::move(newname); TRACE("Got device \"{}\", ID {}", dname, i); } PlaybackDevices.emplace_back(std::move(dname)); } } void ProbeCaptureDevices() { CaptureDevices.clear(); const auto numdevs = waveInGetNumDevs(); CaptureDevices.reserve(numdevs); for(const auto i : std::views::iota(0u, numdevs)) { auto dname = std::string{}; auto WaveCaps = WAVEINCAPSW{}; 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 = al::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(gsl::not_null const 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 const device, UINT const msg, DWORD_PTR const instance, DWORD_PTR const param1, DWORD_PTR const param2) noexcept { std::bit_cast(instance)->waveOutProc(device, msg, param1, param2); } void mixerProc(); void open(std::string_view name) override; auto reset() -> bool override; void start() override; void stop() override; std::atomic mWritable{0_u32}; u32 mIdx{0_u32}; 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 const msg, DWORD_PTR, DWORD_PTR) noexcept { if(msg != WOM_DONE) return; mWritable.fetch_add(1, std::memory_order_acq_rel); mWritable.notify_all(); } FORCE_ALIGN void WinMMPlayback::mixerProc() { SetRTPriority(); althrd_setname(GetMixerThreadName()); while(!mKillNow.load(std::memory_order_acquire) && mDevice->Connected.load(std::memory_order_acquire)) { mWritable.wait(0, std::memory_order_acquire); auto todo = mWritable.load(std::memory_order_acquire); auto widx = usize{mIdx}; while(todo > 0) { auto &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)); --todo; } mIdx = gsl::narrow_cast(widx); } } void WinMMPlayback::open(std::string_view name) { if(PlaybackDevices.empty()) ProbePlaybackDevices(); // Find the Device ID matching the deviceName if valid auto const iter = !name.empty() ? std::ranges::find(PlaybackDevices, name) : PlaybackDevices.begin(); if(iter == PlaybackDevices.end()) throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found", name}; auto const DeviceID = gsl::narrow_cast(std::distance(PlaybackDevices.begin(), iter)); auto fmttype = mDevice->FmtType; auto format = WAVEFORMATEX{}; 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 = gsl::narrow_cast(format.wBitsPerSample * format.nChannels / 8); format.nSamplesPerSec = mDevice->mSampleRate; format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign; format.cbSize = 0; auto const res = waveOutOpen(&mOutHdl, DeviceID, &format, std::bit_cast(&WinMMPlayback::waveOutProcC), std::bit_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]; } auto WinMMPlayback::reset() -> bool { mDevice->mBufferSize = gsl::narrow_cast(u64{mDevice->mBufferSize} * mFormat.nSamplesPerSec / mDevice->mSampleRate); mDevice->mBufferSize = (mDevice->mBufferSize+3) & ~0x3_u32; mDevice->mUpdateSize = mDevice->mBufferSize / 4; mDevice->mSampleRate = mFormat.nSamplesPerSec; auto clearval = char{0}; 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; clearval = char{-0x80}; } 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(); auto const BufferSize = mDevice->mUpdateSize * mFormat.nChannels * mDevice->bytesFromFmt(); decltype(mBuffer)(BufferSize*mWaveBuffer.size(), clearval).swap(mBuffer); auto bufferiter = mBuffer.begin(); mWaveBuffer[0] = WAVEHDR{}; mWaveBuffer[0].lpData = std::to_address(bufferiter); mWaveBuffer[0].dwBufferLength = BufferSize; for(auto i=1_uz;i < mWaveBuffer.size();i++) { bufferiter += mWaveBuffer[i-1].dwBufferLength; mWaveBuffer[i] = WAVEHDR{}; mWaveBuffer[i].lpData = std::to_address(bufferiter); mWaveBuffer[i].dwBufferLength = BufferSize; } mIdx = 0; return true; } void WinMMPlayback::start() { try { for(auto &waveHdr : mWaveBuffer) waveOutPrepareHeader(mOutHdl, &waveHdr, sizeof(WAVEHDR)); mWritable.store(gsl::narrow_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(); auto writable = mWritable.load(std::memory_order_acquire); while(writable < mWaveBuffer.size()) { mWritable.wait(writable, std::memory_order_acquire); writable = mWritable.load(std::memory_order_acquire); } for(auto &waveHdr : mWaveBuffer) waveOutUnprepareHeader(mOutHdl, &waveHdr, sizeof(WAVEHDR)); mWritable.store(0, std::memory_order_release); } struct WinMMCapture final : public BackendBase { explicit WinMMCapture(gsl::not_null 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 const device, UINT const msg, DWORD_PTR const instance, DWORD_PTR const param1, DWORD_PTR const param2) noexcept { std::bit_cast(instance)->waveInProc(device, msg, param1, param2); } void captureProc(); void open(std::string_view name) override; void start() override; void stop() override; void captureSamples(std::span outbuffer) override; auto availableSamples() -> usize override; std::atomic mReadable{0_u32}; u32 mIdx{0_u32}; std::array mWaveBuffer{}; al::vector mBuffer; HWAVEIN mInHdl{nullptr}; RingBufferPtr mRing; 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 const msg, DWORD_PTR, DWORD_PTR) noexcept { if(msg != WIM_DATA) return; mReadable.fetch_add(1, std::memory_order_acq_rel); mReadable.notify_all(); } void WinMMCapture::captureProc() { althrd_setname(GetRecordThreadName()); while(!mKillNow.load(std::memory_order_acquire) && mDevice->Connected.load(std::memory_order_acquire)) { mReadable.wait(0, std::memory_order_acquire); auto todo = mReadable.load(std::memory_order_acquire); auto widx = usize{mIdx}; while(todo > 0) { auto &waveHdr = mWaveBuffer[widx]; widx = (widx+1) % mWaveBuffer.size(); std::ignore = mRing->write(std::as_bytes(std::span{waveHdr.lpData, waveHdr.dwBytesRecorded})); mReadable.fetch_sub(1, std::memory_order_acq_rel); waveInAddBuffer(mInHdl, &waveHdr, sizeof(WAVEHDR)); --todo; } mIdx = gsl::narrow_cast(widx); } } void WinMMCapture::open(std::string_view name) { if(CaptureDevices.empty()) ProbeCaptureDevices(); // Find the Device ID matching the deviceName if valid auto const iter = !name.empty() ? std::ranges::find(CaptureDevices, name) : CaptureDevices.begin(); if(iter == CaptureDevices.end()) throw al::backend_exception{al::backend_error::NoDevice, "Device name \"{}\" not found", name}; auto const DeviceID = gsl::narrow_cast(std::distance(CaptureDevices.begin(), 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)}; } auto clearval = char{0}; switch(mDevice->FmtType) { case DevFmtUByte: clearval = char{-0x80}; [[fallthrough]]; 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 = gsl::narrow_cast(mDevice->channelsFromFmt()); mFormat.wBitsPerSample = gsl::narrow_cast(mDevice->bytesFromFmt() * 8); mFormat.nBlockAlign = gsl::narrow_cast(mFormat.wBitsPerSample * mFormat.nChannels / 8); mFormat.nSamplesPerSec = mDevice->mSampleRate; mFormat.nAvgBytesPerSec = mFormat.nSamplesPerSec * mFormat.nBlockAlign; mFormat.cbSize = 0; auto res = waveInOpen(&mInHdl, DeviceID, &mFormat, std::bit_cast(&WinMMCapture::waveInProcC), std::bit_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 auto BufferSize = DWORD{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 auto const CapturedDataSize = std::max(mDevice->mBufferSize, BufferSize*mWaveBuffer.size()); mRing = RingBuffer::Create(CapturedDataSize, mFormat.nBlockAlign, false); decltype(mBuffer)(BufferSize*mWaveBuffer.size(), clearval).swap(mBuffer); auto bufferiter = mBuffer.begin(); mWaveBuffer[0] = WAVEHDR{}; mWaveBuffer[0].lpData = std::to_address(bufferiter); mWaveBuffer[0].dwBufferLength = BufferSize; for(auto i=1_uz;i < mWaveBuffer.size();++i) { bufferiter += mWaveBuffer[i-1].dwBufferLength; mWaveBuffer[i] = WAVEHDR{}; mWaveBuffer[i].lpData = std::to_address(bufferiter); mWaveBuffer[i].dwBufferLength = mWaveBuffer[i-1].dwBufferLength; } mDeviceName = CaptureDevices[DeviceID]; } void WinMMCapture::start() { try { for(auto &buffer : mWaveBuffer) { waveInPrepareHeader(mInHdl, &buffer, sizeof(WAVEHDR)); waveInAddBuffer(mInHdl, &buffer, 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() { mKillNow.store(true, std::memory_order_release); if(mThread.joinable()) mThread.join(); waveInStop(mInHdl); waveInReset(mInHdl); for(auto &buffer : mWaveBuffer) waveInUnprepareHeader(mInHdl, &buffer, sizeof(WAVEHDR)); mReadable.store(0, std::memory_order_release); mIdx = 0; } void WinMMCapture::captureSamples(std::span outbuffer) { std::ignore = mRing->read(outbuffer); } auto WinMMCapture::availableSamples() -> usize { return mRing->readSpace(); } } // namespace auto WinMMBackendFactory::init() -> bool { return true; } auto WinMMBackendFactory::querySupport(BackendType const type) -> bool { return type == BackendType::Playback || type == BackendType::Capture; } auto WinMMBackendFactory::enumerate(BackendType const type) -> std::vector { auto outnames = std::vector{}; auto add_device = [&outnames](std::string const &dname) -> void { if(!dname.empty()) outnames.emplace_back(dname); }; switch(type) { case BackendType::Playback: ProbePlaybackDevices(); outnames.reserve(PlaybackDevices.size()); std::ranges::for_each(PlaybackDevices, add_device); break; case BackendType::Capture: ProbeCaptureDevices(); outnames.reserve(CaptureDevices.size()); std::ranges::for_each(CaptureDevices, add_device); break; } return outnames; } auto WinMMBackendFactory::createBackend(gsl::not_null const device, BackendType const type) -> BackendPtr { if(type == BackendType::Playback) return BackendPtr{new WinMMPlayback{device}}; if(type == BackendType::Capture) return BackendPtr{new WinMMCapture{device}}; return nullptr; } auto WinMMBackendFactory::getFactory() -> BackendFactory& { static WinMMBackendFactory factory{}; return factory; } kcat-openal-soft-75c0059/alc/backends/winmm.h000066400000000000000000000007201512220627100207450ustar00rootroot00000000000000#ifndef BACKENDS_WINMM_H #define BACKENDS_WINMM_H #include "base.h" struct WinMMBackendFactory final : BackendFactory { auto init() -> bool final; auto querySupport(BackendType type) -> bool final; auto enumerate(BackendType type) -> std::vector final; auto createBackend(gsl::not_null device, BackendType type) -> BackendPtr final; static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_WINMM_H */ kcat-openal-soft-75c0059/alc/context.cpp000066400000000000000000000651321512220627100200730ustar00rootroot00000000000000 #include "config.h" #include "context.h" #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 "alc/alu.h" #include "alc/backends/base.h" #include "alnumeric.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 "fmt/format.h" #include "fmt/ranges.h" #include "gsl/gsl" #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 */ auto getContextExtensions() noexcept -> std::vector { 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 namespace al { std::atomic Context::sGlobalContextLock{false}; std::atomic Context::sGlobalContext{nullptr}; Context::ThreadCtx::~ThreadCtx() { if(auto *ctx = std::exchange(sLocalContext, nullptr)) { const auto result = ctx->releaseIfNoDelete(); ERR("Context {} current for thread being destroyed{}!", voidp{ctx}, result ? "" : ", leak detected"); } } thread_local Context::ThreadCtx Context::sThreadContext; Effect Context::sDefaultEffect; void ContextDeleter::operator()(gsl::owner const context) const noexcept { delete context; } auto Context::Create(const gsl::not_null> &device, ContextFlagBitset const flags) -> intrusive_ptr { auto ret = ContextRef{new Context{device, flags}}; ret->init(); return ret; } Context::Context(gsl::not_null> const &device, ContextFlagBitset const flags) : ContextBase{get_not_null(device)}, mALDevice{device}, mContextFlags{flags} , mDebugEnabled{flags.test(ContextFlags::DebugBit)} , mDebugGroups{{DebugSource::Other, 0, std::string{}}} { /* 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); } Context::~Context() { TRACE("Freeing context {}", voidp{this}); deinit(); auto count = std::accumulate(mSourceList.cbegin(), mSourceList.cend(), 0_uz, [](size_t cur, const SourceSubList &sublist) noexcept -> size_t { return cur + gsl::narrow_cast(std::popcount(~sublist.mFreeMask)); }); 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 + gsl::narrow_cast(std::popcount(~sublist.mFreeMask)); }); if(count > 0) WARN("{} AuxiliaryEffectSlot{} not deleted", count, (count==1)?"":"s"); mEffectSlotList.clear(); mNumEffectSlots = 0; } void Context::init() { if(sDefaultEffect.mType != AL_EFFECT_NULL && mDevice->Type == DeviceType::Playback) { mDefaultSlot = std::make_unique(gsl::make_not_null(this)); aluInitEffectPanning(mDefaultSlot->mSlot, this); } auto auxslots = std::unique_ptr{}; if(!mDefaultSlot) auxslots = EffectSlotBase::CreatePtrArray(0); else { auxslots = EffectSlotBase::CreatePtrArray(2); (*auxslots)[0] = mDefaultSlot->mSlot; (*auxslots)[1] = mDefaultSlot->mSlot; mDefaultSlot->mState = SlotState::Playing; } mActiveAuxSlots.store(std::move(auxslots), std::memory_order_relaxed); allocVoiceChanges(); { auto *cur = mVoiceChangeTail; while(auto *next = cur->mNext.load(std::memory_order_relaxed)) cur = next; mCurrentVoiceChange.store(cur, std::memory_order_relaxed); } mExtensions = getContextExtensions(); if(sBufferSubDataCompat) { auto iter = std::ranges::find(mExtensions, "AL_EXT_SOURCE_RADIUS"sv); if(iter != mExtensions.end()) mExtensions.erase(iter); /* Insert the AL_SOFT_buffer_sub_data extension string between * AL_SOFT_buffer_length_query and AL_SOFT_callback_buffer. */ iter = std::ranges::find(mExtensions, "AL_SOFT_callback_buffer"sv); mExtensions.emplace(iter, "AL_SOFT_buffer_sub_data"sv); } #if ALSOFT_EAX eax_initialize_extensions(); #endif // ALSOFT_EAX mExtensionsString = fmt::format("{}", fmt::join(mExtensions, " ")); #if ALSOFT_EAX eax_set_defaults(); #endif mParams.Position = al::Vector{0.0f, 0.0f, 0.0f, 1.0f}; mParams.Matrix = al::Matrix::Identity(); mParams.Velocity = al::Vector{}; mParams.Gain = mListener.mGain; 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 = FifoBuffer::Create(1024, false); StartEventThrd(this); allocVoices(256); mActiveVoiceCount.store(64, std::memory_order_relaxed); } void Context::deinit() { if(sLocalContext == this) { WARN("{} released while current on thread", voidp{this}); auto _ = ContextRef{sLocalContext}; sThreadContext.set(nullptr); } if(auto *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. */ } } StopEventThrd(this); } void Context::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(gsl::make_not_null(this)); UpdateAllSourceProps(gsl::make_not_null(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); } } // namespace al #if ALSOFT_EAX namespace { void ForEachSource(al::Context *context, std::invocable auto&& func) { std::ranges::for_each(context->mSourceList, [&func](SourceSubList &sublist) { auto usemask = ~sublist.mFreeMask; while(usemask) { const auto idx = as_unsigned(std::countr_zero(usemask)); usemask ^= 1_u64 << idx; std::invoke(func, (*sublist.mSources)[idx]); } }); } } // namespace namespace al { auto Context::eaxIsCapable() const noexcept -> bool { return eax_has_enough_aux_sends(); } void Context::eaxUninitialize() noexcept { if(!mEaxIsInitialized) return; mEaxIsInitialized = false; mEaxIsTried = false; mEaxFxSlots.uninitialize(); } auto Context::eax_eax_set(const GUID *property_set_id, ALuint property_id, ALuint property_source_id, ALvoid *property_value, ALuint property_value_size) -> ALenum { 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; } auto Context::eax_eax_get(const GUID* property_set_id, ALuint property_id, ALuint property_source_id, ALvoid *property_value, ALuint property_value_size) -> ALenum { 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 Context::eaxSetLastError() noexcept { mEaxLastError = EAXERR_INVALID_OPERATION; } [[noreturn]] void Context::eax_fail(const std::string_view message) { throw ContextException{message}; } [[noreturn]] void Context::eax_fail_unknown_property_set_id() { eax_fail("Unknown property ID."); } [[noreturn]] void Context::eax_fail_unknown_primary_fx_slot_id() { eax_fail("Unknown primary FX Slot ID."); } [[noreturn]] void Context::eax_fail_unknown_property_id() { eax_fail("Unknown property ID."); } [[noreturn]] void Context::eax_fail_unknown_version() { eax_fail("Unknown version."); } void Context::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 Context::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_absorption_hf(); eax_update_speaker_configuration(); eax_initialize_fx_slots(); mEaxIsInitialized = true; } auto Context::eax_has_no_default_effect_slot() const noexcept -> bool { return mDefaultSlot == nullptr; } void Context::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."); } auto Context::eax_has_enough_aux_sends() const noexcept -> bool { return mALDevice->NumAuxSends >= EAX_MAX_FXSLOTS; } void Context::eax_ensure_enough_aux_sends() const { if(!eax_has_enough_aux_sends()) eax_fail("Not enough aux sends."); } void Context::eax_ensure_compatibility() const { eax_ensure_enough_aux_sends(); } auto Context::eax_detect_speaker_configuration() const -> eax_ulong { #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(std::holds_alternative(mDevice->mPostProcess)) 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 Context::eax_update_speaker_configuration() { mEaxSpeakerConfig = eax_detect_speaker_configuration(); } void Context::eax_set_last_error_defaults() noexcept { mEaxLastError = EAXCONTEXT_DEFAULTLASTERROR; } void Context::eax_session_set_defaults() noexcept { mEaxSession.ulEAXVersion = EAXCONTEXT_DEFAULTEAXSESSION; mEaxSession.ulMaxActiveSends = EAXCONTEXT_DEFAULTMAXACTIVESENDS; } void Context::eax4_context_set_defaults(Eax4Props& props) noexcept { props.guidPrimaryFXSlotID = EAX40CONTEXT_DEFAULTPRIMARYFXSLOTID; props.flDistanceFactor = EAXCONTEXT_DEFAULTDISTANCEFACTOR; props.flAirAbsorptionHF = EAXCONTEXT_DEFAULTAIRABSORPTIONHF; props.flHFReference = EAXCONTEXT_DEFAULTHFREFERENCE; } void Context::eax4_context_set_defaults(Eax4State& state) noexcept { eax4_context_set_defaults(state.i); state.d = state.i; } void Context::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 Context::eax5_context_set_defaults(Eax5State& state) noexcept { eax5_context_set_defaults(state.i); state.d = state.i; } void Context::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.reset(); } void Context::eax_set_defaults() { eax_set_last_error_defaults(); eax_session_set_defaults(); eax_context_set_defaults(); } void Context::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)) { const auto srclock = std::lock_guard{mSourceLock}; ForEachSource(this, &Source::eaxMarkAsChanged); } } void Context::eax_dispatch_source(const EaxCall& call) { const auto source_id = call.get_property_al_name(); const auto srclock = std::lock_guard{mSourceLock}; const auto source = Source::EaxLookupSource(gsl::make_not_null(this), source_id); if(source == nullptr) eax_fail("Source not found."); source->eaxDispatch(call); } void Context::eax_get_misc(const EaxCall& call) { switch(call.get_property_id()) { case EAXCONTEXT_NONE: break; case EAXCONTEXT_LASTERROR: call.store(std::exchange(mEaxLastError, EAX_OK)); break; case EAXCONTEXT_SPEAKERCONFIG: call.store(mEaxSpeakerConfig); break; case EAXCONTEXT_EAXSESSION: call.store(mEaxSession); break; default: eax_fail_unknown_property_id(); } } void Context::eax4_get(const EaxCall& call, const Eax4Props& props) { switch(call.get_property_id()) { case EAXCONTEXT_ALLPARAMETERS: call.store(props); break; case EAXCONTEXT_PRIMARYFXSLOTID: call.store(props.guidPrimaryFXSlotID); break; case EAXCONTEXT_DISTANCEFACTOR: call.store(props.flDistanceFactor); break; case EAXCONTEXT_AIRABSORPTIONHF: call.store(props.flAirAbsorptionHF); break; case EAXCONTEXT_HFREFERENCE: call.store(props.flHFReference); break; default: eax_get_misc(call); break; } } void Context::eax5_get(const EaxCall& call, const Eax5Props& props) { switch(call.get_property_id()) { case EAXCONTEXT_ALLPARAMETERS: call.store(props); break; case EAXCONTEXT_PRIMARYFXSLOTID: call.store(props.guidPrimaryFXSlotID); break; case EAXCONTEXT_DISTANCEFACTOR: call.store(props.flDistanceFactor); break; case EAXCONTEXT_AIRABSORPTIONHF: call.store(props.flAirAbsorptionHF); break; case EAXCONTEXT_HFREFERENCE: call.store(props.flHFReference); break; case EAXCONTEXT_MACROFXFACTOR: call.store(props.flMacroFXFactor); break; default: eax_get_misc(call); break; } } void Context::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 Context::eax_context_commit_primary_fx_slot_id() { mEaxPrimaryFxSlotIndex = mEax.guidPrimaryFXSlotID; } void Context::eax_context_commit_distance_factor() { /* mEax.flDistanceFactor was changed, so the context props are dirty. */ mPropsDirty = true; } void Context::eax_context_commit_air_absorption_hf() { const auto new_value = level_mb_to_gain(mEax.flAirAbsorptionHF); if(mAirAbsorptionGainHF == new_value) return; mAirAbsorptionGainHF = new_value; mPropsDirty = true; } void Context::eax_context_commit_hf_reference() { // TODO } void Context::eax_context_commit_macro_fx_factor() { // TODO } void Context::eax_initialize_fx_slots() { mEaxFxSlots.initialize(gsl::make_not_null(this)); mEaxPrimaryFxSlotIndex = mEax.guidPrimaryFXSlotID; } void Context::eax_update_sources() { const auto srclock = std::lock_guard{mSourceLock}; ForEachSource(this, &Source::eaxCommit); } void Context::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 Context::eax4_defer_all(const EaxCall& call, Eax4State& state) { const auto &src = call.load(); Eax4AllValidator{}(src); const auto &dst_i = state.i; auto &dst_d = state.d; dst_d = src; if(dst_i.guidPrimaryFXSlotID != dst_d.guidPrimaryFXSlotID) mEaxDf.set(eax_primary_fx_slot_id_dirty_bit); if(dst_i.flDistanceFactor != dst_d.flDistanceFactor) mEaxDf.set(eax_distance_factor_dirty_bit); if(dst_i.flAirAbsorptionHF != dst_d.flAirAbsorptionHF) mEaxDf.set(eax_air_absorption_hf_dirty_bit); if(dst_i.flHFReference != dst_d.flHFReference) mEaxDf.set(eax_hf_reference_dirty_bit); } void Context::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, eax_primary_fx_slot_id_dirty_bit, &EAX40CONTEXTPROPERTIES::guidPrimaryFXSlotID); break; case EAXCONTEXT_DISTANCEFACTOR: eax_defer(call, state, eax_distance_factor_dirty_bit, &EAX40CONTEXTPROPERTIES::flDistanceFactor); break; case EAXCONTEXT_AIRABSORPTIONHF: eax_defer(call, state, eax_air_absorption_hf_dirty_bit, &EAX40CONTEXTPROPERTIES::flAirAbsorptionHF); break; case EAXCONTEXT_HFREFERENCE: eax_defer(call, state, eax_hf_reference_dirty_bit, &EAX40CONTEXTPROPERTIES::flHFReference); break; default: eax_set_misc(call); break; } } void Context::eax5_defer_all(const EaxCall& call, Eax5State& state) { const auto &src = call.load(); Eax4AllValidator{}(src); const auto &dst_i = state.i; auto &dst_d = state.d; dst_d = src; if(dst_i.guidPrimaryFXSlotID != dst_d.guidPrimaryFXSlotID) mEaxDf.set(eax_primary_fx_slot_id_dirty_bit); if(dst_i.flDistanceFactor != dst_d.flDistanceFactor) mEaxDf.set(eax_distance_factor_dirty_bit); if(dst_i.flAirAbsorptionHF != dst_d.flAirAbsorptionHF) mEaxDf.set(eax_air_absorption_hf_dirty_bit); if(dst_i.flHFReference != dst_d.flHFReference) mEaxDf.set(eax_hf_reference_dirty_bit); if(dst_i.flMacroFXFactor != dst_d.flMacroFXFactor) mEaxDf.set(eax_macro_fx_factor_dirty_bit); } void Context::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, eax_primary_fx_slot_id_dirty_bit, &EAX50CONTEXTPROPERTIES::guidPrimaryFXSlotID); break; case EAXCONTEXT_DISTANCEFACTOR: eax_defer(call, state, eax_distance_factor_dirty_bit, &EAX50CONTEXTPROPERTIES::flDistanceFactor); break; case EAXCONTEXT_AIRABSORPTIONHF: eax_defer(call, state, eax_air_absorption_hf_dirty_bit, &EAX50CONTEXTPROPERTIES::flAirAbsorptionHF); break; case EAXCONTEXT_HFREFERENCE: eax_defer(call, state, eax_hf_reference_dirty_bit, &EAX50CONTEXTPROPERTIES::flHFReference); break; case EAXCONTEXT_MACROFXFACTOR: eax_defer(call, state, eax_macro_fx_factor_dirty_bit, &EAX50CONTEXTPROPERTIES::flMacroFXFactor); break; default: eax_set_misc(call); break; } } void Context::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.set(); mEaxVersion = version; } void Context::eax4_context_commit(Eax4State& state, std::bitset& dst_df) { if(mEaxDf.none()) return; eax_context_commit_property(state, dst_df, eax_primary_fx_slot_id_dirty_bit, &EAX40CONTEXTPROPERTIES::guidPrimaryFXSlotID); eax_context_commit_property(state, dst_df, eax_distance_factor_dirty_bit, &EAX40CONTEXTPROPERTIES::flDistanceFactor); eax_context_commit_property(state, dst_df, eax_air_absorption_hf_dirty_bit, &EAX40CONTEXTPROPERTIES::flAirAbsorptionHF); eax_context_commit_property(state, dst_df, eax_hf_reference_dirty_bit, &EAX40CONTEXTPROPERTIES::flHFReference); mEaxDf.reset(); } void Context::eax5_context_commit(Eax5State &state, std::bitset &dst_df) { if(mEaxDf.none()) return; eax_context_commit_property(state, dst_df, eax_primary_fx_slot_id_dirty_bit, &EAX50CONTEXTPROPERTIES::guidPrimaryFXSlotID); eax_context_commit_property(state, dst_df, eax_distance_factor_dirty_bit, &EAX50CONTEXTPROPERTIES::flDistanceFactor); eax_context_commit_property(state, dst_df, eax_air_absorption_hf_dirty_bit, &EAX50CONTEXTPROPERTIES::flAirAbsorptionHF); eax_context_commit_property(state, dst_df, eax_hf_reference_dirty_bit, &EAX50CONTEXTPROPERTIES::flHFReference); eax_context_commit_property(state, dst_df, eax_macro_fx_factor_dirty_bit, &EAX50CONTEXTPROPERTIES::flMacroFXFactor); mEaxDf.reset(); } void Context::eax_context_commit() { auto dst_df = std::bitset{}; 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.none()) return; if(dst_df.test(eax_primary_fx_slot_id_dirty_bit)) eax_context_commit_primary_fx_slot_id(); if(dst_df.test(eax_distance_factor_dirty_bit)) eax_context_commit_distance_factor(); if(dst_df.test(eax_air_absorption_hf_dirty_bit)) eax_context_commit_air_absorption_hf(); if(dst_df.test(eax_hf_reference_dirty_bit)) eax_context_commit_hf_reference(); if(dst_df.test(eax_macro_fx_factor_dirty_bit)) eax_context_commit_macro_fx_factor(); if(dst_df.test(eax_primary_fx_slot_id_dirty_bit)) eax_update_sources(); } void Context::eaxCommit() { mEaxNeedsCommit = false; eax_context_commit(); eaxCommitFxSlots(); eax_update_sources(); } } // namespace al #endif // ALSOFT_EAX kcat-openal-soft-75c0059/alc/context.h000066400000000000000000000442521512220627100175400ustar00rootroot00000000000000#ifndef ALC_CONTEXT_H #define ALC_CONTEXT_H #include "config.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/listener.h" #include "alformat.hpp" #include "alnumeric.h" #include "althreads.h" #include "core/context.h" #include "gsl/gsl" #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 namespace al { struct Effect; struct EffectSlot; } struct DebugGroup; struct EffectSlotSubList; struct SourceSubList; enum class DebugSource : u8; enum class DebugType : u8; enum class DebugSeverity : u8; enum ContextFlags { DebugBit = 0, /* ALC_CONTEXT_DEBUG_BIT_EXT */ }; using ContextFlagBitset = std::bitset; struct DebugLogEntry { DebugSource const mSource; DebugType const mType; DebugSeverity const mSeverity; u32 const mId; std::string mMessage; template DebugLogEntry(DebugSource const source, DebugType const type, u32 const id, DebugSeverity const severity, T&& message) : mSource{source}, mType{type}, mSeverity{severity}, mId{id} , mMessage{std::forward(message)} { } DebugLogEntry(const DebugLogEntry&) = default; DebugLogEntry(DebugLogEntry&&) = default; }; struct ALCcontext { }; namespace al { struct Device; struct Context; struct ContextDeleter { void operator()(gsl::owner context) const noexcept; }; struct Context final : ALCcontext, intrusive_ref, ContextBase { gsl::not_null> const mALDevice; bool mPropsDirty{true}; bool mDeferUpdates{false}; std::mutex mPropLock; tss mLastThreadError{AL_NO_ERROR}; const ContextFlagBitset mContextFlags; std::atomic mDebugEnabled{false}; DistanceModel mDistanceModel{DistanceModel::Default}; bool mSourceDistanceModel{false}; f32 mDopplerFactor{1.0f}; f32 mDopplerVelocity{1.0f}; f32 mSpeedOfSound{SpeedOfSoundMetersPerSec}; f32 mAirAbsorptionGainHF{AirAbsorbGainHF}; std::mutex mEventCbLock; ALEVENTPROCSOFT mEventCb{}; void *mEventParam{nullptr}; std::mutex mDebugCbLock; ALDEBUGPROCEXT mDebugCb{}; void *mDebugParam{nullptr}; std::vector mDebugGroups; std::deque mDebugLog; Listener mListener{}; std::vector mSourceList; u32 mNumSources{0_u32}; std::mutex mSourceLock; std::vector mEffectSlotList; u32 mNumEffectSlots{0_u32}; std::mutex mEffectSlotLock; /* Default effect slot */ std::unique_ptr mDefaultSlot; std::vector mExtensions; std::string mExtensionsString; std::unordered_map mSourceNames; std::unordered_map mEffectSlotNames; /** * Removes the context from being current on the running thread or * globally, and stops the event thread. */ 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, al::string_view fmt, al::format_args args); template void setError(ALenum const errorCode, al::format_string const msg, Args&& ...args) { setErrorImpl(errorCode, msg.get(), al::make_format_args(args...)); } [[noreturn]] void throw_error_impl(ALenum errorCode, al::string_view fmt, al::format_args args); template [[noreturn]] void throw_error(ALenum const errorCode, al::format_string const fmt, Args&&... args) { throw_error_impl(errorCode, fmt.get(), al::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 const source, DebugType const type, u32 const id, DebugSeverity const severity, std::string_view const message) { if(!mDebugEnabled.load(std::memory_order_relaxed)) [[likely]] return; auto debuglock = std::unique_lock{mDebugCbLock}; sendDebugMessage(debuglock, source, type, id, severity, message); } static auto Create(const gsl::not_null> &device, ContextFlagBitset flags) -> intrusive_ptr; /* Process-wide current context */ static std::atomic sGlobalContextLock; static std::atomic sGlobalContext; protected: ~Context(); private: Context(const gsl::not_null> &device, ContextFlagBitset flags); void init(); /* Thread-local current context. */ static inline thread_local Context *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(Context *ctx) const noexcept { sLocalContext = ctx; } /* NOLINTEND(readability-convert-member-functions-to-static) */ }; static thread_local ThreadCtx sThreadContext; friend ContextDeleter; public: static Context *getThreadContext() noexcept { return sLocalContext; } static void setThreadContext(Context *context) noexcept { sThreadContext.set(context); } /* Default effect that applies to sources that don't have an effect on send 0. */ static Effect 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 -> f32 { return mEax.flDistanceFactor; } [[nodiscard]] auto eaxGetPrimaryFxSlotIndex() const noexcept -> EaxFxSlotIndex { return mEaxPrimaryFxSlotIndex; } auto eaxGetFxSlot(EaxFxSlotIndexValue const fx_slot_index) const LIFETIMEBOUND -> EffectSlot const& { return mEaxFxSlots.get(fx_slot_index); } auto eaxGetFxSlot(EaxFxSlotIndexValue const fx_slot_index) LIFETIMEBOUND -> EffectSlot& { return mEaxFxSlots.get(fx_slot_index); } auto eaxNeedsCommit() const noexcept -> bool { return mEaxNeedsCommit; } void eaxCommit(); void eaxCommitFxSlots() const { mEaxFxSlots.commit(); } private: enum { eax_primary_fx_slot_id_dirty_bit, eax_distance_factor_dirty_bit, eax_air_absorption_hf_dirty_bit, eax_hf_reference_dirty_bit, eax_macro_fx_factor_dirty_bit, eax_dirty_bit_count }; using Eax4Props = EAX40CONTEXTPROPERTIES; struct Eax4State { Eax4Props i; // Immediate. Eax4Props d; // Deferred. }; using Eax5Props = EAX50CONTEXTPROPERTIES; struct Eax5State { Eax5Props i; // Immediate. Eax5Props d; // Deferred. }; /* NOLINTNEXTLINE(clazy-copyable-polymorphic) Exceptions must be copyable. */ class ContextException final : public EaxException { public: explicit ContextException(const std::string_view 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()(f32 const flDistanceFactor) const { eax_validate_range( "Distance Factor", flDistanceFactor, EAXCONTEXT_MINDISTANCEFACTOR, EAXCONTEXT_MAXDISTANCEFACTOR); } }; struct Eax4AirAbsorptionHfValidator { void operator()(f32 const flAirAbsorptionHF) const { eax_validate_range( "Air Absorption HF", flAirAbsorptionHF, EAXCONTEXT_MINAIRABSORPTIONHF, EAXCONTEXT_MAXAIRABSORPTIONHF); } }; struct Eax4HfReferenceValidator { void operator()(f32 const 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()(f32 const 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()(eax_ulong const ulEAXVersion) const { eax_validate_range( "EAX version", ulEAXVersion, EAXCONTEXT_MINEAXSESSION, EAXCONTEXT_MAXEAXSESSION); } }; struct Eax5MaxActiveSendsValidator { void operator()(eax_ulong const 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()(eax_ulong const ulSpeakerConfig) const { eax_validate_range( "Speaker Config", ulSpeakerConfig, EAXCONTEXT_MINSPEAKERCONFIG, EAXCONTEXT_MAXSPEAKERCONFIG); } }; bool mEaxIsInitialized{}; bool mEaxIsTried{}; eax_long mEaxLastError{}; eax_ulong mEaxSpeakerConfig{}; EaxFxSlotIndex mEaxPrimaryFxSlotIndex{}; EaxFxSlots mEaxFxSlots{}; int mEaxVersion{}; // Current EAX version. bool mEaxNeedsCommit{}; std::bitset 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(std::string_view 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, auto &property) { const auto &value = call.load>(); TValidator{}(value); property = value; } /* Gets a new value from EAX call, validates it, updates the deferred * value, and updates a dirty flag. */ template void eax_defer(const EaxCall &call, auto &state, usize const dirty_bit, auto member) { static_assert(std::invocable); using TMemberResult = std::invoke_result_t; const auto &src = call.load>(); TValidator{}(src); const auto &dst_i = std::invoke(member, state.i); auto &dst_d = std::invoke(member, state.d); dst_d = src; if(dst_i != dst_d) mEaxDf.set(dirty_bit); } void eax_context_commit_property(auto &state, std::bitset &dst_df, usize const dirty_bit, std::invocable auto member) noexcept { if(mEaxDf.test(dirty_bit)) { dst_df.set(dirty_bit); const auto &src_d = std::invoke(member, state.d); std::invoke(member, state.i) = src_d; std::invoke(member, mEax) = src_d; } } void eax_initialize_extensions(); void eax_initialize(); auto eax_has_no_default_effect_slot() const noexcept -> bool; void eax_ensure_no_default_effect_slot() const; auto eax_has_enough_aux_sends() const noexcept -> bool; void eax_ensure_enough_aux_sends() const; void eax_ensure_compatibility() const; auto eax_detect_speaker_configuration() const -> eax_ulong; 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_absorption_hf(); static void eax_context_commit_hf_reference(); static 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, std::bitset& dst_df); void eax5_context_commit(Eax5State& state, std::bitset& dst_df); void eax_context_commit(); #endif // ALSOFT_EAX }; } // namespace al using ContextRef = al::intrusive_ptr; auto GetContextRef() noexcept -> ContextRef; void UpdateContextProps(al::Context *context); inline constinit auto 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 */ kcat-openal-soft-75c0059/alc/device.cpp000066400000000000000000000070221512220627100176400ustar00rootroot00000000000000 #include "config.h" #include "device.h" #ifdef _WIN32 #include #endif #include #include #include #include #include #include "al/buffer.h" #include "al/effect.h" #include "al/filter.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" #include "gsl/gsl" namespace { using voidp = void*; } // namespace namespace al { void DeviceDeleter::operator()(gsl::owner device) const noexcept { delete device; } auto Device::Create(DeviceType type) -> intrusive_ptr { return intrusive_ptr{new Device{type}}; } Device::Device(DeviceType type) : DeviceBase{type} { } Device::~Device() { TRACE("Freeing device {}", voidp{this}); Backend = nullptr; auto count = std::accumulate(BufferList.cbegin(), BufferList.cend(), 0_uz, [](size_t cur, const BufferSubList &sublist) noexcept -> size_t { return cur + gsl::narrow_cast(std::popcount(~sublist.mFreeMask)); }); 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 + gsl::narrow_cast(std::popcount(~sublist.mFreeMask)); }); 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 + gsl::narrow_cast(std::popcount(~sublist.mFreeMask)); }); 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")) { if(const auto iter = std::ranges::find(mHrtfList, *defhrtfopt); 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; if(std::holds_alternative(mPostProcess)) 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; } void Device::SetError(Device *device, ALCenum errorCode) { WARN("Error generated on device {}, code {:#04x}", voidp{device}, as_unsigned(errorCode)); if(sTrapALCError) { #ifdef _WIN32 /* DebugBreak() will cause an exception if there is no debugger */ if(IsDebuggerPresent()) DebugBreak(); #elif defined(SIGTRAP) raise(SIGTRAP); #endif } if(device) device->mLastError.store(errorCode); else sLastGlobalError.store(errorCode); } } // namespace al kcat-openal-soft-75c0059/alc/device.h000066400000000000000000000106361512220627100173120ustar00rootroot00000000000000#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 { }; namespace al { struct Device; struct DeviceDeleter { void operator()(gsl::owner device) const noexcept; }; struct Device final : ALCdevice, intrusive_ref, DeviceBase { /* This lock protects the device state (format, update size, etc.) 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; u32 NumMonoSources{}; u32 NumStereoSources{}; // Maximum number of sources that can be created u32 SourcesMax{}; // Maximum number of slots that can be created u32 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 }; auto getOutputMode1() const noexcept -> OutputMode1; using OutputMode = OutputMode1; std::atomic mLastError{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 u32 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; void enumerateHrtfs(); auto getConfigValueBool(std::string_view const block, std::string_view const key, bool const def) const -> bool { return GetConfigValueBool(mDeviceName, block, key, def); } template auto configValue(std::string_view block, std::string_view key) const -> std::optional = delete; static auto Create(DeviceType type) -> intrusive_ptr; /** Stores the latest ALC device error. */ static void SetGlobalError(ALCenum const errorCode) { SetError(nullptr, errorCode); } void setError(ALCenum const errorCode) { SetError(this, errorCode); } static inline auto sLastGlobalError = std::atomic{ALC_NO_ERROR}; /* Flag to trap ALC device errors */ static inline auto sTrapALCError = false; protected: ~Device(); private: explicit Device(DeviceType type); static void SetError(Device *device, ALCenum errorCode); friend DeviceDeleter; }; template<> inline auto Device::configValue(std::string_view const block, std::string_view const key) const -> std::optional { return ConfigValueStr(mDeviceName, block, key); } template<> inline auto Device::configValue(std::string_view const block, std::string_view const key) const -> std::optional { return ConfigValueI32(mDeviceName, block, key); } template<> inline auto Device::configValue(std::string_view const block, std::string_view const key) const -> std::optional { return ConfigValueU32(mDeviceName, block, key); } template<> inline auto Device::configValue(std::string_view const block, std::string_view const key) const -> std::optional { return ConfigValueF32(mDeviceName, block, key); } template<> inline auto Device::configValue(std::string_view const block, std::string_view const key) const -> std::optional { return ConfigValueBool(mDeviceName, block, key); } } // namespace al #endif kcat-openal-soft-75c0059/alc/effects/000077500000000000000000000000001512220627100173135ustar00rootroot00000000000000kcat-openal-soft-75c0059/alc/effects/autowah.cpp000066400000000000000000000165271512220627100215020ustar00rootroot00000000000000/** * 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 #include #include "alc/effects/base.h" #include "alnumeric.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 auto GainScale = 31621.0f; constexpr auto MinFreq = 20.0f; constexpr auto MaxFreq = 2500.0f; constexpr auto 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 EffectSlotBase *slot, const EffectProps *props, const EffectTarget target) override; void process(const size_t samplesToDo, const std::span samplesIn, const std::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; mEnv.fill(FilterParam{}); mChans.fill(ChannelData{}); } void AutowahState::update(const ContextBase *context, const EffectSlotBase *slot, const EffectProps *props_, const EffectTarget target) { auto &props = std::get(*props_); auto const device = al::get_not_null(context->mDevice); auto const frequency = static_cast(device->mSampleRate); const auto 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; target.Main->setAmbiMixParams(slot->Wet, slot->Gain, [this](const size_t idx, const uint outchan, const float outgain) { mChans[idx].mTargetChannel = outchan; mChans[idx].mTargetGain = outgain; }); } void AutowahState::process(const size_t samplesToDo, const std::span samplesIn, const std::span samplesOut) { auto env_delay = mEnvDelay; std::ranges::transform(samplesIn[0] | std::views::take(samplesToDo), mEnv.begin(), [attack_rate=mAttackRate,release_rate=mReleaseRate,peak_gain=mPeakGain, freq_min=mFreqMinNorm,bandwidth=mBandwidthNorm,&env_delay](const float x) -> FilterParam { /* Envelope follower described on the book: Audio Effects, Theory, * Implementation and Application. */ const auto sample = peak_gain * std::fabs(x); const auto 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 auto w0 = std::min(bandwidth*env_delay + freq_min, 0.46f) * (std::numbers::pi_v*2.0f); return FilterParam{.cos_w0=std::cos(w0), .alpha=std::sin(w0)*(0.5f/QFactor)}; }); mEnvDelay = env_delay; auto chandata = mChans.begin(); std::ranges::for_each(samplesIn, [&,this](const FloatConstBufferSpan insamples) { const auto outidx = chandata->mTargetChannel; if(outidx == InvalidChannelIndex) { ++chandata; return; } /* This effectively inlines BiquadFilter::setParams for a peaking * filter and BiquadFilter::process. 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. */ auto z1 = chandata->mFilter.z1; auto z2 = chandata->mFilter.z2; std::ranges::transform(insamples | std::views::take(samplesToDo), mEnv, mBufferOut.begin(), [res_gain=mResonanceGain,&z1,&z2](const float input, const FilterParam env) -> float { const auto b = std::array{ 1.0f + env.alpha*res_gain, -2.0f * env.cos_w0, 1.0f - env.alpha*res_gain}; const auto a = std::array{ 1.0f / (1.0f + env.alpha/res_gain), -2.0f * env.cos_w0, 1.0f - env.alpha/res_gain}; const auto 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]); return output; }); chandata->mFilter.z1 = z1; chandata->mFilter.z2 = z2; /* Now, mix the processed sound data to the output. */ MixSamples(std::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 auto AutowahStateFactory_getFactory() -> gsl::not_null { static AutowahStateFactory AutowahFactory{}; return gsl::make_not_null(&AutowahFactory); } kcat-openal-soft-75c0059/alc/effects/base.h000066400000000000000000000025031512220627100203760ustar00rootroot00000000000000#ifndef EFFECTS_BASE_H #define EFFECTS_BASE_H #include "core/effects/base.h" #include "gsl/gsl" /* This is a user config option for modifying the overall output of the reverb * effect. */ inline float ReverbBoost{1.0f}; auto NullStateFactory_getFactory() -> gsl::not_null; auto ReverbStateFactory_getFactory() -> gsl::not_null; auto ChorusStateFactory_getFactory() -> gsl::not_null; auto AutowahStateFactory_getFactory() -> gsl::not_null; auto CompressorStateFactory_getFactory() -> gsl::not_null; auto DistortionStateFactory_getFactory() -> gsl::not_null; auto EchoStateFactory_getFactory() -> gsl::not_null; auto EqualizerStateFactory_getFactory() -> gsl::not_null; auto FshifterStateFactory_getFactory() -> gsl::not_null; auto ModulatorStateFactory_getFactory() -> gsl::not_null; auto PshifterStateFactory_getFactory() -> gsl::not_null; auto VmorpherStateFactory_getFactory() -> gsl::not_null; auto DedicatedStateFactory_getFactory() -> gsl::not_null; auto ConvolutionStateFactory_getFactory() -> gsl::not_null; #endif /* EFFECTS_BASE_H */ kcat-openal-soft-75c0059/alc/effects/chorus.cpp000066400000000000000000000265301512220627100213300ustar00rootroot00000000000000/** * 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 #include #include "alc/effects/base.h" #include "alnumeric.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 / std::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 BufferStorage*) final; void update(const ContextBase *context, const EffectSlotBase *slot, const EffectProps *props_, const EffectTarget target) final; void process(const size_t samplesToDo, const std::span samplesIn, const std::span samplesOut) final; }; void ChorusState::deviceUpdate(const DeviceBase *Device, const BufferStorage*) { static constexpr auto MaxDelay = std::max(ChorusMaxDelay, FlangerMaxDelay); const auto frequency = static_cast(Device->mSampleRate); const auto maxlen = size_t{NextPowerOf2(float2uint(MaxDelay*2.0f*frequency) + 1u)}; if(maxlen != mDelayBuffer.size()) decltype(mDelayBuffer)(maxlen).swap(mDelayBuffer); std::ranges::fill(mDelayBuffer, 0.0f); mGains.fill(OutGains{}); } void ChorusState::update(const ContextBase *context, const EffectSlotBase *slot, const EffectProps *props_, const EffectTarget target) { static constexpr auto mindelay = int{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. */ auto const device = al::get_not_null(context->mDevice); auto const 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 auto ispairwise = device->mRenderMode == RenderMode::Pairwise; const auto lcoeffs = (!ispairwise) ? std::span{lcoeffs_nrml} : std::span{lcoeffs_pw}; const auto rcoeffs = (!ispairwise) ? std::span{rcoeffs_nrml} : std::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/std::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 auto range_limit = std::numeric_limits::max()/360 - 180; const auto range = std::round(frequency / props.Rate); const auto 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 = std::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 auto lfo_range = mLfoRange; const auto lfo_scale = mLfoScale; const auto depth = mDepth; const auto 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); }; auto offset = mLfoOffset; ASSUME(lfo_range > offset); auto ldelays = mModDelays[0].begin(); for(size_t i{0};i < todo;) { const auto 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 auto 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 auto lfo_range = mLfoRange; const auto lfo_scale = mLfoScale; const auto depth = mDepth; const auto 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); }; auto offset = mLfoOffset; ASSUME(lfo_range > offset); auto ldelays = mModDelays[0].begin(); for(size_t i{0};i < todo;) { const auto 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 auto 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 std::span samplesIn, const std::span samplesOut) { const auto delaybuf = std::span{mDelayBuffer}; const auto bufmask = delaybuf.size()-1; const auto feedback = mFeedback; const auto avgdelay = (static_cast(mDelay) + MixerFracHalf) >> MixerFracBits; auto offset = mOffset; if(mWaveform == ChorusWaveform::Sinusoid) calcSinusoidDelays(samplesToDo); else /*if(mWaveform == ChorusWaveform::Triangle)*/ calcTriangleDelays(samplesToDo); const auto ldelays = std::span{mModDelays[0]}; const auto rdelays = std::span{mModDelays[1]}; const auto lbuffer = std::span{mBuffer[0]}; const auto rbuffer = std::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. */ auto delay = offset - (ldelays[i] >> gCubicTable.sTableBits); auto 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 auto ChorusStateFactory_getFactory() -> gsl::not_null { static ChorusStateFactory ChorusFactory{}; return gsl::make_not_null(&ChorusFactory); } kcat-openal-soft-75c0059/alc/effects/compressor.cpp000066400000000000000000000156061512220627100222230ustar00rootroot00000000000000/** * 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 #include #include "alc/effects/base.h" #include "alnumeric.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 auto AmpEnvelopeMin = 0.5f; constexpr auto AmpEnvelopeMax = 2.0f; constexpr auto AttackTime = 0.1f; /* 100ms to rise from min to max */ constexpr auto 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{0.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 EffectSlotBase *slot, const EffectProps *props, const EffectTarget target) override; void process(const size_t samplesToDo, const std::span samplesIn, const std::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 auto attackCount = static_cast(device->mSampleRate) * AttackTime; const auto 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); mChans.fill(TargetGain{}); } void CompressorState::update(const ContextBase*, const EffectSlotBase *slot, const EffectProps *props, const EffectTarget target) { mEnabled = std::get(*props).OnOff; mOutTarget = target.Main->Buffer; target.Main->setAmbiMixParams(slot->Wet, slot->Gain, [this](const size_t idx, const uint outchan, const float outgain) { mChans[idx].mTarget = outchan; mChans[idx].mGain = outgain; }); } void CompressorState::process(const size_t samplesToDo, const std::span samplesIn, const std::span samplesOut) { /* Generate the per-sample gains from the signal envelope. */ auto env = mEnvFollower; if(mEnabled) { std::ranges::transform(samplesIn[0] | std::views::take(samplesToDo), mGains.begin(), [attackmult=mAttackMult,releasemult=mReleaseMult,&env](const float sample) -> float { /* Clamp the absolute amplitude to the defined envelope limits, * then attack or release the envelope to reach it. */ const auto amplitude = std::clamp(std::fabs(sample), AmpEnvelopeMin, AmpEnvelopeMax); if(amplitude > env) env = std::min(env*attackmult, amplitude); else if(amplitude < env) env = std::max(env*releasemult, amplitude); /* Apply the reciprocal of the envelope to normalize the volume * (compress the dynamic range). */ return 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. */ std::ranges::generate(mGains | std::views::take(samplesToDo), [attackmult=mAttackMult,releasemult=mReleaseMult,&env]() -> float { static constexpr auto amplitude = 1.0f; if(amplitude > env) env = std::min(env*attackmult, amplitude); else if(amplitude < env) env = std::max(env*releasemult, amplitude); return 1.0f / env; }); } mEnvFollower = env; /* Now compress the signal amplitude to output. */ auto chan = mChans.cbegin(); std::ranges::for_each(samplesIn, [this,samplesOut,samplesToDo,&chan](const FloatConstBufferSpan input) { const auto outidx = chan->mTarget; if(outidx != InvalidChannelIndex) { const auto dst = std::span{samplesOut[outidx]}; const auto gain = chan->mGain; if(!(std::fabs(gain) > GainSilenceThreshold)) { for(auto i = 0_uz;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 auto CompressorStateFactory_getFactory() -> gsl::not_null { static CompressorStateFactory CompressorFactory{}; return gsl::make_not_null(&CompressorFactory); } kcat-openal-soft-75c0059/alc/effects/convolution.cpp000066400000000000000000000675151512220627100224140ustar00rootroot00000000000000 #include "config.h" #include "config_simd.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #if HAVE_SSE_INTRINSICS #include #elif HAVE_NEON #include #endif #include "alcomplex.h" #include "alnumeric.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 "gsl/gsl" #include "intrusive_ptr.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 std::span dstSamples, const std::span srcData, const size_t channel, const size_t srcstep) noexcept { using TypeTraits = SampleInfo; Expects(channel < srcstep); auto ssrc = srcData.begin(); std::advance(ssrc, channel); dstSamples.front() = TypeTraits::to_float(*ssrc); std::ranges::generate(dstSamples | std::views::drop(1), [&ssrc,srcstep] { std::advance(ssrc, srcstep); return TypeTraits::to_float(*ssrc); }); } void LoadSamples(const std::span dstSamples, const SampleVariant &src, const size_t channel, const size_t srcstep) noexcept { std::visit([&](T&& splvec) { using span_t = std::remove_cvref_t; using sample_t = span_t::value_type; if constexpr(!std::is_same_v && !std::is_same_v) LoadSampleArray(dstSamples, splvec, channel, srcstep); }, src); } constexpr auto GetAmbiScales(AmbiScaling scaletype) noexcept { switch(scaletype) { case AmbiScaling::FuMa: return std::span{AmbiScale::FromFuMa}; case AmbiScaling::SN3D: return std::span{AmbiScale::FromSN3D}; case AmbiScaling::UHJ: return std::span{AmbiScale::FromUHJ}; case AmbiScaling::N3D: break; } return std::span{AmbiScale::FromN3D}; } constexpr auto GetAmbiLayout(AmbiLayout layouttype) noexcept { if(layouttype == AmbiLayout::FuMa) return std::span{AmbiIndex::FromFuMa}; return std::span{AmbiIndex::FromACN}; } constexpr auto GetAmbi2DLayout(AmbiLayout layouttype) noexcept { if(layouttype == AmbiLayout::FuMa) return std::span{AmbiIndex::FromFuMa2D}; return std::span{AmbiIndex::FromACN2D}; } constexpr auto sin30 = 0.5f; constexpr auto cos30 = 0.866025403785f; constexpr auto sin45 = std::numbers::sqrt2_v*0.5f; constexpr auto cos45 = std::numbers::sqrt2_v*0.5f; constexpr auto sin110 = 0.939692620786f; constexpr auto cos110 = -0.342020143326f; struct ChanPosMap { Channel channel; std::array pos; }; constexpr size_t ConvolveUpdateSize{256}; constexpr size_t ConvolveUpdateSamples{ConvolveUpdateSize / 2}; void apply_fir(std::span dst, std::span input, const std::span filter) { #if HAVE_SSE_INTRINSICS std::ranges::generate(dst, [&input,filter] { auto r4 = _mm_setzero_ps(); for(size_t j{0};j < ConvolveUpdateSamples;j+=4) { const auto coeffs = _mm_load_ps(&filter[j]); const auto s = _mm_loadu_ps(&input[j]); r4 = _mm_add_ps(r4, _mm_mul_ps(s, coeffs)); } input = input.subspan(1); 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::ranges::generate(dst, [&input,filter] { auto r4 = vdupq_n_f32(0.0f); for(size_t j{0};j < ConvolveUpdateSamples;j+=4) r4 = vmlaq_f32(r4, vld1q_f32(&input[j]), vld1q_f32(&filter[j])); input = input.subspan(1); r4 = vaddq_f32(r4, vrev64q_f32(r4)); return vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0); }); #else std::ranges::generate(dst, [&input,filter] { auto ret = 0.0f; for(size_t j{0};j < ConvolveUpdateSamples;++j) ret += input[j] * filter[j]; input = input.subspan(1); 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 std::span samplesOut, const size_t samplesToDo); void UpsampleMix(const std::span samplesOut, const size_t samplesToDo); void (ConvolutionState::*mMix)(const std::span,const size_t) {&ConvolutionState::NormalMix}; void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) override; void update(const ContextBase *context, const EffectSlotBase *slot, const EffectProps *props, const EffectTarget target) override; void process(const size_t samplesToDo, const std::span samplesIn, const std::span samplesOut) override; }; void ConvolutionState::NormalMix(const std::span samplesOut, const size_t samplesToDo) { for(auto &chan : mChans) MixSamples(std::span{chan.mBuffer}.first(samplesToDo), samplesOut, chan.Current, chan.Target, samplesToDo, 0); } void ConvolutionState::UpsampleMix(const std::span samplesOut, const size_t samplesToDo) { for(auto &chan : mChans) { const auto src = std::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. */ auto resampler = PPhaseResampler{}; 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 auto splitter = BandSplitter{device->mXOverFreq/static_cast(device->mSampleRate)}; std::ranges::fill(mChans | std::views::transform(&ChannelData::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 auto srclinelength = size_t{RoundFromZero(buffer->mSampleLen+DecoderPadding, 16_uz)}; auto srcsamples = std::vector(srclinelength * numChannels); std::ranges::fill(srcsamples, 0.0f); for(const auto c : std::views::iota(0_uz, std::min(numChannels, realChannels))) LoadSamples(std::span{srcsamples}.subspan(srclinelength*c, buffer->mSampleLen), buffer->mData, c, realChannels); if(IsUHJ(mChannels)) { auto samples = std::array,4>{}; auto decoder = std::make_unique(); for(auto base = 0_uz;base < buffer->mSampleLen;) { const auto todo = std::min(buffer->mSampleLen-base, BufferLineSize); auto srciter = std::next(srcsamples.begin(), gsl::narrow_cast(base)); std::ranges::generate(samples | std::views::take(numChannels), [&srciter,srclinelength,todo] { const auto ret = srciter; std::advance(srciter, srclinelength); return std::span{ret, todo+DecoderPadding}; }); decoder->decode(std::span{samples}.first(numChannels), true); base += todo; } } auto ressamples = std::vector(buffer->mSampleLen + (resampler ? resampledCount : 0)); auto ffttmp = al::vector(ConvolveUpdateSize); auto fftbuffer = std::vector>(ConvolveUpdateSize); auto filteriter = mComplexData.begin(); std::advance(filteriter, mNumConvolveSegs*ConvolveUpdateSize); for(const auto c : std::views::iota(0_uz, size_t{numChannels})) { auto bufsamples = std::span{srcsamples}.subspan(srclinelength*c, buffer->mSampleLen); /* Resample to match the device. */ if(resampler) { auto restmp = std::span{ressamples}.subspan(resampledCount, buffer->mSampleLen); std::ranges::copy(bufsamples, restmp.begin()); resampler.process(restmp, std::span{ressamples}.first(resampledCount)); } else std::ranges::copy(bufsamples, ressamples.begin()); /* Store the first segment's samples in reverse in the time-domain, to * apply as a FIR filter. */ const auto first_size = std::min(size_t{resampledCount}, ConvolveUpdateSamples); auto sampleseg = std::span{ressamples.cbegin(), first_size}; std::ranges::transform(sampleseg, mFilter[c].rbegin(), [](const double d) noexcept -> float { return gsl::narrow_cast(d); }); auto done = first_size; for(const auto s [[maybe_unused]] : std::views::iota(0_uz, mNumConvolveSegs)) { const auto todo = std::min(resampledCount-done, ConvolveUpdateSamples); sampleseg = std::span{ressamples}.subspan(done, todo); /* Apply a double-precision forward FFT for more precise frequency * measurements. */ auto iter = std::ranges::copy(sampleseg, fftbuffer.begin()).out; done += todo; std::ranges::fill(iter, fftbuffer.end(), std::complex{}); forward_fft(std::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 auto fftscale = 1.0f / float{ConvolveUpdateSize}; for(const auto i : std::views::iota(0_uz, ConvolveUpdateSamples)) { 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.begin(), filteriter, PFFFT_BACKWARD); std::advance(filteriter, ConvolveUpdateSize); } } } void ConvolutionState::update(const ContextBase *context, const EffectSlotBase *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; std::ranges::fill(mChans|std::views::transform(&ChannelData::Target)|std::views::join, 0.0f); const float gain{slot->Gain}; if(IsAmbisonic(mChannels)) { auto const device = al::get_not_null(context->mDevice); if(mChannels == FmtUHJ2 && !std::holds_alternative(device->mPostProcess)) { 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; al::Vector N{props.OrientAt[0], props.OrientAt[1], props.OrientAt[2], 0.0f}; N.normalize(); al::Vector V{props.OrientUp[0], props.OrientUp[1], props.OrientUp[2], 0.0f}; V.normalize(); /* Build and normalize right-vector */ al::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) ? std::span{GetAmbi2DLayout(mAmbiLayout)}.subspan(0) : std::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::ranges::transform(mixmatrix[acn], coeffs.begin(), [scale](const float in) -> float { return in * scale; }); ComputePanGains(target.Main, coeffs, gain, mChans[c].Target); } } else { auto const device = al::get_not_null(context->mDevice); auto chanmap = std::span{}; switch(mChannels) { case FmtMono: 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 std::span samplesIn, const std::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::ranges::copy(samplesIn[0] | std::views::drop(base) | std::views::take(todo), (mInput | std::views::drop(ConvolveUpdateSamples+mFifoPos)).begin()); /* 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 = std::span{mChans[c].mBuffer}.subspan(base, todo); apply_fir(outspan, std::span{mInput}.subspan(1+mFifoPos), mFilter[c]); auto fifospan = std::span{mOutput[c]}.subspan(mFifoPos, todo); std::transform(fifospan.begin(), fifospan.end(), outspan.begin(), 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.begin(), &mComplexData[curseg*ConvolveUpdateSize], mFftWorkBuffer.begin(), PFFFT_FORWARD); auto filter = mComplexData.cbegin(); std::advance(filter, 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(); std::advance(input, curseg*ConvolveUpdateSize); for(size_t s{curseg};s < mNumConvolveSegs;++s) { mFft.zconvolve_accumulate(input, filter, mFftBuffer.begin()); std::advance(input, ConvolveUpdateSize); std::advance(filter, ConvolveUpdateSize); } input = mComplexData.cbegin(); for(size_t s{0};s < curseg;++s) { mFft.zconvolve_accumulate(input, filter, mFftBuffer.begin()); std::advance(input, ConvolveUpdateSize); std::advance(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.begin(), mFftBuffer.begin(), mFftWorkBuffer.begin(), PFFFT_BACKWARD); /* The filter was attenuated, so the response is already scaled. */ std::ranges::transform(mFftBuffer | std::views::take(ConvolveUpdateSamples), mOutput[c] | std::views::drop(ConvolveUpdateSamples), mOutput[c].begin(), std::plus{}); std::ranges::copy(mFftBuffer | std::views::drop(ConvolveUpdateSamples), (mOutput[c] | std::views::drop(ConvolveUpdateSamples)).begin()); } /* 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 auto ConvolutionStateFactory_getFactory() -> gsl::not_null { static ConvolutionStateFactory ConvolutionFactory{}; return gsl::make_not_null(&ConvolutionFactory); } kcat-openal-soft-75c0059/alc/effects/dedicated.cpp000066400000000000000000000100741512220627100217270ustar00rootroot00000000000000/** * 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 #include "alc/effects/base.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 { 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 EffectSlotBase *slot, const EffectProps *props_, const EffectTarget target) final; void process(const size_t samplesToDo, const std::span samplesIn, const std::span samplesOut) final; }; void DedicatedState::deviceUpdate(const DeviceBase*, const BufferStorage*) { mCurrentGains.fill(0.0f); } void DedicatedState::update(const ContextBase*, const EffectSlotBase *slot, const EffectProps *props_, const EffectTarget target) { mTargetGains.fill(0.0f); auto &props = std::get(*props_); const auto 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, std::span{mTargetGains}.first()); } } else if(props.Target == DedicatedProps::Lfe) { const auto idx = size_t{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 std::span samplesIn, const std::span samplesOut) { MixSamples(std::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 auto DedicatedStateFactory_getFactory() -> gsl::not_null { static DedicatedStateFactory DedicatedFactory{}; return gsl::make_not_null(&DedicatedFactory); } kcat-openal-soft-75c0059/alc/effects/distortion.cpp000066400000000000000000000151231512220627100222170ustar00rootroot00000000000000/** * 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 #include "alc/effects/base.h" #include "alnumeric.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 mEdgeCoeff{}; alignas(16) std::array mBuffer{}; void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) override; void update(const ContextBase *context, const EffectSlotBase *slot, const EffectProps *props, const EffectTarget target) override; void process(const size_t samplesToDo, const std::span samplesIn, const std::span samplesOut) override; }; void DistortionState::deviceUpdate(const DeviceBase*, const BufferStorage*) { mLowpass.clear(); mBandpass.clear(); } void DistortionState::update(const ContextBase *context, const EffectSlotBase *slot, const EffectProps *props_, const EffectTarget target) { auto &props = std::get(*props_); auto const device = al::get_not_null(context->mDevice); /* Store waveshaper edge settings. */ const auto edge = std::min(std::sin(std::numbers::pi_v*0.5f * props.Edge), 0.99f); mEdgeCoeff = 2.0f * edge / (1.0f-edge); auto cutoff = props.LowpassCutoff; /* Bandwidth value is constant in octaves. */ auto bandwidth = (cutoff * 0.5f) / (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*0.25f, 1.0f, bandwidth); cutoff = props.EQCenter; /* Convert bandwidth in Hz to octaves. */ bandwidth = props.EQBandwidth / (cutoff * 0.67f); mBandpass.setParamsFromBandwidth(BiquadType::BandPass, cutoff/frequency*0.25f, 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 std::span samplesIn, const std::span samplesOut) { for(auto base=0_uz;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. */ auto 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(std::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. */ std::ranges::transform(mBuffer[1] | std::views::take(todo), mBuffer[0].begin(), [fc=mEdgeCoeff](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; }); /* Third step, do bandpass filtering of distorted signal. */ mBandpass.process(std::span{mBuffer[0]}.first(todo), mBuffer[1]); todo >>= 2; auto outgains = mGain.cbegin(); std::ranges::for_each(samplesOut, [this,base,todo,&outgains](const FloatBufferSpan output) { /* Fourth step, final, do attenuation and perform decimation, * storing only one sample out of four. */ const auto gain = *(outgains++); if(!(std::fabs(gain) > GainSilenceThreshold)) return; auto src = mBuffer[1].cbegin(); const auto dst = std::span{output}.subspan(base, todo); std::ranges::transform(dst, dst.begin(), [gain,&src](float sample) noexcept -> float { sample += *src * gain; std::advance(src, 4); return sample; }); }); base += todo; } } struct DistortionStateFactory final : public EffectStateFactory { al::intrusive_ptr create() override { return al::intrusive_ptr{new DistortionState{}}; } }; } // namespace auto DistortionStateFactory_getFactory() -> gsl::not_null { static DistortionStateFactory DistortionFactory{}; return gsl::make_not_null(&DistortionFactory); } kcat-openal-soft-75c0059/alc/effects/echo.cpp000066400000000000000000000137231512220627100207430ustar00rootroot00000000000000/** * 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 "alnumeric.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 EffectSlotBase *slot, const EffectProps *props, const EffectTarget target) override; void process(const size_t samplesToDo, const std::span samplesIn, const std::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::ranges::fill(mSampleBuffer, 0.0f); mGains.fill(OutGains{}); } void EchoState::update(const ContextBase *context, const EffectSlotBase *slot, const EffectProps *props_, const EffectTarget target) { auto &props = std::get(*props_); auto const device = al::get_not_null(context->mDevice); auto const 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 auto 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 auto x = props.Spread; /* +x = left */ const auto 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 std::span samplesIn, const std::span samplesOut) { const auto delaybuf = std::span{mSampleBuffer}; const auto mask = delaybuf.size()-1; auto offset = mOffset; auto tap1 = offset - mDelayTap[0]; auto tap2 = offset - mDelayTap[1]; ASSUME(samplesToDo > 0); const auto filter = mFilter; auto [z1, z2] = mFilter.getComponents(); for(auto i=0_uz;i < samplesToDo;) { offset &= mask; tap1 &= mask; tap2 &= mask; const auto max_offset = std::max(offset, std::max(tap1, tap2)); auto td = std::min(mask+1 - max_offset, 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 auto 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(std::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 auto EchoStateFactory_getFactory() -> gsl::not_null { static EchoStateFactory EchoFactory{}; return gsl::make_not_null(&EchoFactory); } kcat-openal-soft-75c0059/alc/effects/equalizer.cpp000066400000000000000000000207771512220627100220350ustar00rootroot00000000000000/** * 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 #include "alc/effects/base.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 EffectSlotBase *slot, const EffectProps *props, const EffectTarget target) override; void process(const size_t samplesToDo, const std::span samplesIn, const std::span samplesOut) override; }; void EqualizerState::deviceUpdate(const DeviceBase*, const BufferStorage*) { mChans.fill(OutParams{}); } void EqualizerState::update(const ContextBase *context, const EffectSlotBase *slot, const EffectProps *props_, const EffectTarget target) { auto &props = std::get(*props_); auto const device = al::get_not_null(context->mDevice); auto const 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. */ auto gain = std::sqrt(props.LowGain); auto 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. */ std::ranges::for_each(mChans | std::views::take(slot->Wet.Buffer.size()) | std::views::drop(1), [filters=std::span{mChans[0].mFilter}](const std::span targets) { targets[0].copyParamsFrom(filters[0]); targets[1].copyParamsFrom(filters[1]); targets[2].copyParamsFrom(filters[2]); targets[3].copyParamsFrom(filters[3]); }, &OutParams::mFilter); mOutTarget = target.Main->Buffer; target.Main->setAmbiMixParams(slot->Wet, slot->Gain, [this](const size_t idx, const uint outchan, const float outgain) { mChans[idx].mTargetChannel = outchan; mChans[idx].mTargetGain = outgain; }); } void EqualizerState::process(const size_t samplesToDo, const std::span samplesIn, const std::span samplesOut) { const auto buffer = std::span{mSampleBuffer}.first(samplesToDo); auto chan = mChans.begin(); std::ranges::for_each(samplesIn, [=,&chan](const FloatConstBufferSpan input) { if(const auto outidx = size_t{chan->mTargetChannel}; outidx != InvalidChannelIndex) { const auto inbuf = std::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 auto EqualizerStateFactory_getFactory() -> gsl::not_null { static EqualizerStateFactory EqualizerFactory{}; return gsl::make_not_null(&EqualizerFactory); } kcat-openal-soft-75c0059/alc/effects/fshifter.cpp000066400000000000000000000222311512220627100216310ustar00rootroot00000000000000/** * 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 #include #include #include "alc/effects/base.h" #include "alcomplex.h" #include "alnumeric.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" struct BufferStorage; namespace { using uint = unsigned int; using complex_d = std::complex; constexpr auto HilSize = 1024_uz; constexpr auto HilHalfSize = HilSize >> 1; constexpr auto OversampleFactor = 4_uz; static_assert(HilSize%OversampleFactor == 0, "Factor must be a clean divisor of the size"); constexpr auto HilStep{HilSize / OversampleFactor}; /* Define a Hann window, used to filter the HIL input and output. */ struct Windower { alignas(16) std::array mData{}; Windower() noexcept { static constexpr auto scale = std::numbers::pi / double{HilSize}; /* Create lookup table of the Hann window for the desired size. */ std::ranges::transform(std::views::iota(0u, uint{HilHalfSize}), mData.begin(), [](const uint i) -> float { const auto val = std::sin((i+0.5) * scale); return static_cast(val * val); }); std::ranges::copy(mData | std::views::take(HilHalfSize), mData.rbegin()); } }; const Windower gWindow{}; struct FshifterState final : public EffectState { /* Effect parameters */ size_t mCount{}; size_t mPos{}; /* 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 OutParams { uint mPhaseStep{}; uint mPhase{}; double mSign{}; std::array Current{}; std::array Target{}; }; std::array mChans; void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) override; void update(const ContextBase *context, const EffectSlotBase *slot, const EffectProps *props, const EffectTarget target) override; void process(const size_t samplesToDo, const std::span samplesIn, const std::span samplesOut) override; }; void FshifterState::deviceUpdate(const DeviceBase*, const BufferStorage*) { /* (Re-)initializing parameters and clear the buffers. */ mCount = 0; mPos = HilSize - HilStep; mInFIFO.fill(0.0); mOutFIFO.fill(complex_d{}); mOutputAccum.fill(complex_d{}); mAnalytic.fill(complex_d{}); mBufferOut.fill(0.0f); mChans.fill(OutParams{}); } void FshifterState::update(const ContextBase *context, const EffectSlotBase *slot, const EffectProps *props_, const EffectTarget target) { auto &props = std::get(*props_); auto const device = al::get_not_null(context->mDevice); const auto step = props.Frequency / static_cast(device->mSampleRate); std::ranges::fill(mChans | std::views::transform(&OutParams::mPhaseStep), fastf2u(std::min(step, 1.0f) * MixerFracOne)); switch(props.LeftDirection) { case FShifterDirection::Down: mChans[0].mSign = -1.0; break; case FShifterDirection::Up: mChans[0].mSign = 1.0; break; case FShifterDirection::Off: mChans[0].mPhase = 0; mChans[0].mPhaseStep = 0; break; } switch(props.RightDirection) { case FShifterDirection::Down: mChans[1].mSign = -1.0; break; case FShifterDirection::Up: mChans[1].mSign = 1.0; break; case FShifterDirection::Off: mChans[1].mPhase = 0; mChans[1].mPhaseStep = 0; break; } static constexpr auto inv_sqrt2 = static_cast(1.0 / std::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, mChans[0].Target); ComputePanGains(target.Main, rcoeffs, slot->Gain, mChans[1].Target); } void FshifterState::process(const size_t samplesToDo, const std::span samplesIn, const std::span samplesOut) { for(auto base=0_uz;base < samplesToDo;) { const auto todo = std::min(HilStep-mCount, samplesToDo-base); /* Fill FIFO buffer with samples data */ std::ranges::copy(samplesIn[0] | std::views::drop(base) | std::views::take(todo), (mInFIFO | std::views::drop(mPos+mCount)).begin()); std::ranges::copy(mOutFIFO | std::views::drop(mCount) | std::views::take(todo), (mOutdata | std::views::drop(base)).begin()); mCount += todo; base += todo; /* 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 */ const auto [_, windowiter, analyticiter] = std::ranges::transform( mInFIFO | std::views::drop(mPos), gWindow.mData, mAnalytic.begin(), std::multiplies{}); std::ranges::transform(mInFIFO.begin(), mInFIFO.end(), windowiter, gWindow.mData.end(), analyticiter, std::multiplies{}); /* Processing signal by Discrete Hilbert Transform (analytical signal). */ complex_hilbert(mAnalytic); /* Windowing and add to output accumulator */ std::ranges::transform(mAnalytic, gWindow.mData, mAnalytic.begin(), [](const complex_d &a, const double w) noexcept { return 2.0/OversampleFactor*w*a; }); const auto accumrange = mOutputAccum | std::views::drop(mPos); std::ranges::transform(accumrange, mAnalytic, accumrange.begin(), std::plus{}); std::ranges::transform(mOutputAccum, mAnalytic | std::views::drop(HilSize-mPos), mOutputAccum.begin(), std::plus{}); /* Copy out the accumulated result, then clear for the next iteration. */ const auto outrange = accumrange | std::views::take(HilStep); std::ranges::copy(outrange, mOutFIFO.begin()); std::ranges::fill(outrange, 0.0f); } /* Process frequency shifter using the analytic signal obtained. */ std::ranges::for_each(mChans, [&,this](OutParams ¶ms) { const auto sign = params.mSign; const auto phase_step = params.mPhaseStep; auto phase_idx = params.mPhase; std::ranges::transform(mOutdata | std::views::take(samplesToDo), mBufferOut.begin(), [&phase_idx,phase_step,sign](const complex_d &in) -> float { const auto phase = phase_idx * (std::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; }); params.mPhase = phase_idx; /* Now, mix the processed sound data to the output. */ MixSamples(std::span{mBufferOut}.first(samplesToDo), samplesOut, params.Current, params.Target, std::max(samplesToDo, 512_uz), 0); }); } struct FshifterStateFactory final : public EffectStateFactory { al::intrusive_ptr create() override { return al::intrusive_ptr{new FshifterState{}}; } }; } // namespace auto FshifterStateFactory_getFactory() -> gsl::not_null { static FshifterStateFactory FshifterFactory{}; return gsl::make_not_null(&FshifterFactory); } kcat-openal-soft-75c0059/alc/effects/modulator.cpp000066400000000000000000000173021512220627100220300ustar00rootroot00000000000000/** * 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 #include #include #include "alc/effects/base.h" #include "alnumeric.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 "gsl/gsl" #include "intrusive_ptr.h" #include "opthelpers.h" struct BufferStorage; namespace { using uint = unsigned int; struct SinFunc { static auto Get(const uint index, float const scale) noexcept(noexcept(std::sin(0.0f))) -> float { return std::sin(gsl::narrow_cast(index) * scale); } }; struct SawFunc { static constexpr auto Get(const uint index, const float scale) noexcept -> float { return gsl::narrow_cast(index)*scale - 1.0f; } }; struct SquareFunc { static constexpr auto Get(const uint index, const float scale) noexcept -> float { return gsl::narrow_cast(gsl::narrow_cast(index)*scale < 0.5f)*2.0f - 1.0f; } }; struct OneFunc { static constexpr auto Get(const uint, const 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 EffectSlotBase *slot, const EffectProps *props, const EffectTarget target) override; void process(const size_t samplesToDo, const std::span samplesIn, const std::span samplesOut) override; }; void ModulatorState::deviceUpdate(const DeviceBase*, const BufferStorage*) { mChans.fill(OutParams{}); } void ModulatorState::update(const ContextBase *context, const EffectSlotBase *slot, const EffectProps *props_, const EffectTarget target) { auto &props = std::get(*props_); auto const device = al::get_not_null(context->mDevice); auto const samplerate = static_cast(device->mSampleRate); /* 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 auto samplesPerCycle = props.Frequency > 0.0f ? samplerate/props.Frequency + 0.5f : 1.0f; const auto range = static_cast(std::clamp(samplesPerCycle, 1.0f, samplerate)); 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 = std::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(); } const auto f0norm = std::clamp(props.HighPassCutoff / samplerate, 1.0f/512.0f, 0.49f); /* Bandwidth value is constant in octaves. */ mChans[0].mFilter.setParamsFromBandwidth(BiquadType::HighPass, f0norm, 1.0f, 0.75f); std::ranges::for_each(mChans | std::views::take(slot->Wet.Buffer.size()) | std::views::drop(1), [&other=mChans[0].mFilter](BiquadFilter &filter) { filter.copyParamsFrom(other); }, &OutParams::mFilter); mOutTarget = target.Main->Buffer; target.Main->setAmbiMixParams(slot->Wet, slot->Gain, [this](const size_t idx, const uint outchan, const float outgain) { mChans[idx].mTargetChannel = outchan; mChans[idx].mTargetGain = outgain; }); } void ModulatorState::process(const size_t samplesToDo, const std::span samplesIn, const std::span samplesOut) { ASSUME(samplesToDo > 0); std::visit([this,samplesToDo](T&& type [[maybe_unused]]) { using Modulator = std::remove_cvref_t; const auto range = mRange; const auto scale = mIndexScale; auto index = mIndex; ASSUME(range > 1); auto moditer = mModSamples.begin(); for(auto i = 0_uz;i < samplesToDo;) { const auto rem = std::min(static_cast(samplesToDo-i), range-index); moditer = std::ranges::transform(std::views::iota(index, index+rem), moditer, [scale](const uint idx) { return Modulator::Get(idx, scale); }).out; i += rem; index += rem; if(index == range) index = 0; } mIndex = index; }, mSampleGen); auto chandata = mChans.begin(); std::ranges::for_each(samplesIn, [&,this](const FloatConstBufferSpan input) { if(const size_t outidx{chandata->mTargetChannel}; outidx != InvalidChannelIndex) { chandata->mFilter.process(std::span{input}.first(samplesToDo), mBuffer); std::ranges::transform(mBuffer | std::views::take(samplesToDo), mModSamples, mBuffer.begin(), std::multiplies{}); MixSamples(std::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 auto ModulatorStateFactory_getFactory() -> gsl::not_null { static ModulatorStateFactory ModulatorFactory{}; return gsl::make_not_null(&ModulatorFactory); } kcat-openal-soft-75c0059/alc/effects/null.cpp000066400000000000000000000046531512220627100210010ustar00rootroot00000000000000 #include "config.h" #include #include #include "base.h" #include "core/bufferline.h" #include "core/effects/base.h" #include "intrusive_ptr.h" struct BufferStorage; struct ContextBase; struct DeviceBase; struct EffectSlotBase; namespace { struct NullState final : public EffectState { NullState(); ~NullState() override; void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) override; void update(const ContextBase *context, const EffectSlotBase *slot, const EffectProps *props, const EffectTarget target) override; void process(const size_t samplesToDo, const std::span samplesIn, const std::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 EffectSlotBase* /*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 std::span /*samplesIn*/, const std::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 auto NullStateFactory_getFactory() -> gsl::not_null { static NullStateFactory NullFactory{}; return gsl::make_not_null(&NullFactory); } kcat-openal-soft-75c0059/alc/effects/pshifter.cpp000066400000000000000000000307511512220627100216510ustar00rootroot00000000000000/** * 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 #include #include #include "alc/effects/base.h" #include "alnumeric.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 auto StftSize = 1024_uz; constexpr auto StftHalfSize = StftSize >> 1; constexpr auto OversampleFactor = 8_uz; static_assert(StftSize%OversampleFactor == 0, "Factor must be a clean divisor of the size"); constexpr auto StftStep = StftSize / OversampleFactor; /* Define a Hann window, used to filter the STFT input and output. */ struct Windower { alignas(16) std::array mData{}; Windower() noexcept { static constexpr auto scale = std::numbers::pi / double{StftSize}; /* Create lookup table of the Hann window for the desired size. */ std::ranges::transform(std::views::iota(0u, uint{StftHalfSize}), mData.begin(), [](const uint i) -> float { const auto val = std::sin((i+0.5) * scale); return static_cast(val * val); }); std::ranges::copy(mData | std::views::take(StftHalfSize), mData.rbegin()); } }; 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 EffectSlotBase *slot, const EffectProps *props, const EffectTarget target) override; void process(const size_t samplesToDo, const std::span samplesIn, const std::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 EffectSlotBase *slot, const EffectProps *props_, const EffectTarget target) { auto &props = std::get(*props_); const auto tune = props.CoarseTune*100 + props.FineTune; const auto pitch = std::pow(2.0f, static_cast(tune) / 1200.0f); mPitchShiftI = fastf2u(std::clamp(pitch*MixerFracOne, float{MixerFracHalf}, float{MixerFracOne}*2.0f)); 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 std::span samplesIn, const std::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). */ static constexpr auto expected_cycles = std::numbers::pi_v*2.0f / OversampleFactor; for(auto base = 0_uz;base < samplesToDo;) { const auto todo = std::min(StftStep-mCount, samplesToDo-base); /* Retrieve the output samples from the FIFO and fill in the new input * samples. */ auto fiforange = mFIFO | std::views::drop(mPos + mCount) | std::views::take(todo); std::ranges::copy(fiforange, (mBufferOut | std::views::drop(base)).begin()); std::ranges::copy(samplesIn[0] | std::views::drop(base) | std::views::take(todo), fiforange.begin()); 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. */ const auto [_, windowiter, fftbufiter] = std::ranges::transform( mFIFO | std::views::drop(mPos), gWindow.mData, mFftBuffer.begin(), std::multiplies{}); std::ranges::transform(mFIFO.begin(), mFIFO.end(), windowiter, gWindow.mData.end(), fftbufiter, std::multiplies{}); mFft.transform_ordered(mFftBuffer.begin(), mFftBuffer.begin(), mFftWorkBuffer.begin(), PFFFT_FORWARD); /* Analyze the obtained data. Since the real FFT is symmetric, only * StftHalfSize+1 samples are needed. */ for(auto k = 0_uz;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 auto magnitude = std::abs(cplx); const auto 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); auto 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 *= std::numbers::inv_pi_v; auto 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. */ mSynthesisBuffer.fill(FrequencyBin{}); constexpr auto bin_limit = size_t{((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(auto k = 0_uz;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. */ auto 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 *= std::numbers::inv_pi_v; auto qpd = float2int(tmp); tmp -= static_cast(qpd + (qpd%2)); mSumPhase[k] = tmp * std::numbers::pi_v; const auto 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.begin(), mFftBuffer.begin(), mFftWorkBuffer.begin(), PFFFT_BACKWARD); static constexpr auto scale = float{3.0f / OversampleFactor / StftSize}; std::ranges::transform(mFftBuffer, gWindow.mData, mFftBuffer.begin(), [](const float a, const float w) noexcept { return w*a*scale; }); const auto accumrange = mOutputAccum | std::views::drop(mPos); std::ranges::transform(accumrange, mFftBuffer, accumrange.begin(), std::plus{}); std::ranges::transform(mOutputAccum, mFftBuffer | std::views::drop(StftSize-mPos), mOutputAccum.begin(), std::plus{}); /* Copy out the accumulated result, then clear for the next iteration. */ const auto outrange = accumrange | std::views::take(StftStep); std::ranges::copy(outrange, (mFIFO | std::views::drop(mPos)).begin()); std::ranges::fill(outrange, 0.0f); } /* Now, mix the processed sound data to the output. */ MixSamples(std::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 auto PshifterStateFactory_getFactory() -> gsl::not_null { static PshifterStateFactory PshifterFactory{}; return gsl::make_not_null(&PshifterFactory); } kcat-openal-soft-75c0059/alc/effects/reverb.cpp000066400000000000000000002175771512220627100213270ustar00rootroot00000000000000/** * 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 "alnumeric.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 "gsl/gsl" #include "intrusive_ptr.h" #include "opthelpers.h" #include "vector.h" struct BufferStorage; namespace { constexpr auto MaxModulationTime = 4.0f; constexpr auto 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/std::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) / DensityScale); * * 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(1.0, cbrt(density*DensityScale)); * * The density scale below will result in a max line multiplier of 10, for an * effective average size range of 5 to 50 meters. */ constexpr auto DensityScale = 1'000.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 be extended to find 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 5 meters, we get the following taps: */ constexpr std::array EARLY_TAP_LENGTHS{{ 0.000000e+0f, 1.010676e-3f, 2.126553e-3f, 3.358580e-3f }}; /* 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{{ 4.854840e-4f, 5.360178e-4f, 5.918117e-4f, 6.534130e-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 5 meters, we get: */ constexpr std::array EARLY_LINE_LENGTHS{{ 2.992520e-3f, 5.456575e-3f, 7.688329e-3f, 9.709681e-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{{ 8.091400e-4f, 1.019453e-3f, 1.407968e-3f, 1.618280e-3f }}; /* 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 5 meter average room, we get: */ constexpr std::array LATE_LINE_LENGTHS{{ 9.709681e-3f, 1.223343e-2f, 1.689561e-2f, 1.941936e-2f }}; using ReverbUpdateLine = std::array; using ReverbUpdateSpan = std::span; 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. */ std::span mLine; /* Given the allocated sample buffer, this function updates each delay line * offset. */ void realizeLineOffset(std::span const sampleBuffer) noexcept { mLine = sampleBuffer; } /* Calculate the length of a delay line and store its mask and offset. */ static auto calcLineLength(f32 const length, f32 const frequency, u32 const extra) -> usize { /* All line lengths are powers of 2, calculated from their lengths in * seconds, rounded up. */ auto samples = float2uint(std::ceil(length*frequency)); samples = NextPowerOf2(samples + extra); /* Return the sample count for accumulation. */ return samples*NUM_LINES; } }; struct DelayLineU { std::span mLine; void realizeLineOffset(std::span const sampleBuffer) noexcept { Expects(sampleBuffer.size() > 4 && !(sampleBuffer.size() & (sampleBuffer.size()-1))); mLine = sampleBuffer; } static auto calcLineLength(f32 const length, f32 const frequency, u32 const extra) -> usize { auto samples = float2uint(std::ceil(length*frequency)); samples = NextPowerOf2(samples + extra); return samples*NUM_LINES; } [[nodiscard]] auto get(usize const chan) const noexcept { const auto stride = mLine.size() / NUM_LINES; return mLine.subspan(chan*stride, stride); } void write(usize offset, usize const c, std::span const in) const noexcept { const auto stride = mLine.size() / NUM_LINES; const auto output = mLine.subspan(c*stride, stride); auto input = in.begin(); offset &= stride-1; while(const auto rem = std::distance(input, in.end())) { const auto td = std::min(gsl::narrow_cast(stride-offset), rem); input = std::ranges::copy(std::views::counted(input, td), (output | std::views::drop(offset)).begin()).in; /* Either wrapping back to 0 with more input, or it's done. */ offset = 0; } } /* 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(usize offset, std::span const in, usize const count) const noexcept { const auto stride = mLine.size() / NUM_LINES; offset &= stride-1; for(auto i = 0_uz;i < count;) { auto td = std::min(stride - offset, count - i); do { const auto src = std::array{in[0][i], in[1][i], in[2][i], in[3][i]}; ++i; const auto f = std::array{ (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); offset = 0; } } }; struct VecAllpass { DelayLineI Delay; f32 Coeff{0.0f}; std::array Offset{}; void process(std::span samples, usize offset, f32 xCoeff, f32 yCoeff, usize todo) const noexcept; }; struct Allpass4 { DelayLineU Delay; f32 Coeff{0.0f}; std::array Offset{}; void process(std::span samples, usize offset, usize 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. */ f32 mMidGain{0.0f}; BiquadFilter mHFFilter, mLFFilter; void calcCoeffs(f32 length, f32 lfDecayTime, f32 mfDecayTime, f32 hfDecayTime, f32 lf0norm, f32 hf0norm); /* Applies the two T60 damping filter sections. */ void process(std::span const samples) { DualBiquad{mHFFilter, mLFFilter}.process(samples, samples); } void clear() noexcept { mHFFilter.clear(); mLFFilter.clear(); } }; struct EarlyReflections { Allpass4 Allpass; /* An echo line is used to complete the second half of the early * reflections. */ DelayLineU Delay; std::array Offset{}; f32 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(f32 density_mult, f32 diffusion, f32 decayTime, f32 frequency); void clear() { std::ranges::fill(Allpass.Delay.mLine, 0.0f); std::ranges::fill(Delay.mLine, 0.0f); std::ranges::for_each(Gains, &OutGains::clear); } }; struct Modulation { /* The vibrato time is tracked with an index over a (MOD_FRACONE) * normalized range. */ u32 Index{0_u32}, Step{1_u32}; /* The depth of frequency change, in samples. */ f32 Depth{0.0f}; std::array ModDelays{}; void updateModulator(f32 modTime, f32 modDepth, f32 frequency); auto calcDelays(usize todo) -> std::span; void clear() noexcept { Index = 0_u32; Step = 1_u32; 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. */ f32 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(f32 density_mult, f32 diffusion, f32 lfDecayTime, f32 mfDecayTime, f32 hfDecayTime, f32 lf0norm, f32 hf0norm, f32 frequency); void clear() { std::ranges::fill(VecAp.Delay.mLine, 0.0f); std::ranges::fill(Delay.mLine, 0.0f); std::ranges::for_each(T60, &T60Filter::clear); Mod.clear(); std::ranges::for_each(Gains, &OutGains::clear); } }; struct ReverbPipeline { /* Master effect filters */ struct FilterPair { BiquadFilter Lp; BiquadFilter Hp; void clear() noexcept { Lp.clear(); Hp.clear(); } void process(std::span const src, std::span const dst) { DualBiquad{Lp, Hp}.process(src, dst); } }; 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. */ f32 mMixX{1.0f}; f32 mMixY{0.0f}; EarlyReflections mEarly; LateReverb mLate; std::array,2> mAmbiSplitter; usize mFadeSampleCount{1}; void updateDelayLine(f32 gain, f32 earlyDelay, f32 lateDelay, f32 density_mult, f32 frequency); void update3DPanning(std::span ReflectionsPan, std::span LateReverbPan, f32 earlyGain, f32 lateGain, bool doUpmix, MixParams const *mainMix); void processEarly(DelayLineU const &main_delay, usize offset, usize samplesToDo, std::span tempSamples, std::span outSamples); void processLate(usize offset, usize samplesToDo, std::span tempSamples, std::span outSamples); void clear() noexcept { std::ranges::fill(mLateDelayIn.mLine, 0.0f); std::ranges::for_each(mFilter, &FilterPair::clear); mEarlyDelayTap = {}; mEarlyDelayCoeff = {}; mLateDelayTap = {}; mEarly.clear(); mLate.clear(); std::ranges::for_each(mAmbiSplitter | std::views::join, &BandSplitter::clear); } }; struct ReverbState final : 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. */ f32 Density{1.0f}; f32 Diffusion{1.0f}; f32 DecayTime{1.49f}; f32 HFDecayTime{0.83f * 1.49f}; f32 LFDecayTime{1.0f * 1.49f}; f32 ModulationTime{0.25f}; f32 ModulationDepth{0.0f}; f32 HFReference{5000.0f}; f32 LFReference{250.0f}; }; Params mParams; enum PipelineState : u8 { 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. */ usize 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, std::span const samplesOut, usize const todo) const { /* When not upsampling, the panning gains convert to B-Format and pan * at the same time. */ std::ignore = std::ranges::mismatch(pipeline.mEarly.Gains, mEarlySamples, [samplesOut,todo](EarlyReflections::OutGains &gains, FloatConstBufferSpan const inBuffer) { MixSamples(inBuffer.first(todo), samplesOut, gains.Current, gains.Target, todo, 0); return true; }); std::ignore = std::ranges::mismatch(pipeline.mLate.Gains, mLateSamples, [samplesOut,todo](LateReverb::OutGains &gains, FloatConstBufferSpan const inBuffer) { MixSamples(inBuffer.first(todo), samplesOut, gains.Current, gains.Target, todo, 0); return true; }); } void MixOutAmbiUp(ReverbPipeline &pipeline, std::span const samplesOut, usize const todo) { static constexpr auto DoMixRow = [](std::span const OutBuffer, const std::span Gains, const std::span InSamples) { std::ranges::fill(OutBuffer, 0.0f); std::ignore = std::ranges::mismatch(Gains, InSamples, [OutBuffer](f32 const gain, FloatConstBufferSpan const inBuffer) { if(std::fabs(gain) > GainSilenceThreshold) { std::ranges::transform(OutBuffer, inBuffer, OutBuffer.begin(), [gain](f32 const sample, f32 const in) noexcept { return sample + in*gain; }); } return true; }); }; /* 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 = std::span{mTempLine}.first(todo); auto hfscale = mOrderScales[0]; auto splitter = pipeline.mAmbiSplitter[0].begin(); std::ignore = std::ranges::mismatch(pipeline.mEarly.Gains, EarlyA2B, [this,samplesOut,todo,tmpspan,&splitter,&hfscale](EarlyReflections::OutGains &gains, std::span const a2bcoeffs) { 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); return true; }); hfscale = mOrderScales[0]; splitter = pipeline.mAmbiSplitter[1].begin(); std::ignore = std::ranges::mismatch(pipeline.mLate.Gains, LateA2B, [this,samplesOut,todo,tmpspan,&splitter,&hfscale](LateReverb::OutGains &gains, std::span const a2bcoeffs) { DoMixRow(tmpspan, a2bcoeffs, mLateSamples); (splitter++)->processHfScale(tmpspan, hfscale); hfscale = mOrderScales[1]; MixSamples(tmpspan, samplesOut, gains.Current, gains.Target, todo, 0); return true; }); } void mixOut(ReverbPipeline &pipeline, std::span const samplesOut, usize const todo) { if(mUpmixOutput) MixOutAmbiUp(pipeline, samplesOut, todo); else MixOutPlain(pipeline, samplesOut, todo); } void allocLines(f32 frequency); void deviceUpdate(DeviceBase const *device, BufferStorage const *buffer) override; void update(ContextBase const *context, EffectSlotBase const *slot, EffectProps const *props, EffectTarget target) override; void process(usize samplesToDo, std::span samplesIn, std::span samplesOut) override; }; /************************************** * Device Update * **************************************/ auto CalcDelayLengthMult(f32 const density) -> f32 { return std::max(1.0f, std::cbrt(density*DensityScale)); } /* Calculates the delay line metrics and allocates the shared sample buffer * for all lines given the sample rate (frequency). */ void ReverbState::allocLines(f32 const frequency) { /* Multiplier for the maximum density value, i.e. density=1, which is * actually the least density... */ const auto 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 auto max_mod_delay = MaxModulationTime*MODULATION_DEPTH_COEFF / 2.0f; /* The max number of samples done per iteration of the late reverb's vector * all-pass. */ const auto late_vecap_extra = float2uint(std::ceil(LATE_ALLPASS_LENGTHS.front() * multiplier * frequency)); auto linelengths = std::array{}; auto oidx = 0_uz; auto totalSamples = 0_uz; /* 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. */ auto length = ReverbMaxReflectionsDelay + EARLY_TAP_LENGTHS.back()*multiplier; auto count = mMainDelay.calcLineLength(length, frequency, BufferLineSize); linelengths[oidx++] = count; totalSamples += count; for(auto &pipeline : mPipelines) { static constexpr auto LateDiffAvg = (LATE_LINE_LENGTHS.back()-LATE_LINE_LENGTHS.front()) / f32{NUM_LINES}; length = ReverbMaxLateReverbDelay + LateDiffAvg*multiplier; count = pipeline.mLateDelayIn.calcLineLength(length, frequency, BufferLineSize); linelengths[oidx++] = count; totalSamples += count; /* The early reflection all-pass line. */ length = EARLY_ALLPASS_LENGTHS.back() * multiplier; count = pipeline.mEarly.Allpass.Delay.calcLineLength(length, frequency, 0); linelengths[oidx++] = count; totalSamples += count; /* The early reflection delay 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, late_vecap_extra); 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; } Ensures(oidx == linelengths.size()); if(totalSamples != mSampleBuffer.size()) decltype(mSampleBuffer)(totalSamples).swap(mSampleBuffer); std::ranges::fill(mSampleBuffer, 0.0f); /* Update all delays to reflect the new sample buffer. */ auto bufferspan = std::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.Allpass.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++]); } Ensures(oidx == linelengths.size()); } void ReverbState::deviceUpdate(DeviceBase const *device, BufferStorage const*) { const auto frequency = static_cast(device->mSampleRate); /* Allocate the delay lines. */ allocLines(frequency); std::ranges::for_each(mPipelines, &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); } const auto splitter = BandSplitter{device->mXOverFreq / frequency}; std::ranges::for_each(mPipelines, [splitter](ReverbPipeline &pipeline) { pipeline.mAmbiSplitter[0].fill(splitter); pipeline.mAmbiSplitter[1].fill(splitter); }); } /************************************** * Effect Update * **************************************/ /* Calculate a decay coefficient given the length of each cycle and the time * until the decay reaches -60 dB. */ auto CalcDecayCoeff(f32 const length, f32 const decayTime) -> f32 { return std::pow(ReverbDecayGain, length/decayTime); } /* Calculate a decay length from a coefficient and the time until the decay * reaches -60 dB. */ auto CalcDecayLength(f32 const coeff, f32 const decayTime) -> f32 { static constexpr auto 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. */ auto CalcDensityGain(f32 const a) -> f32 { /* 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. */ void CalcMatrixCoeffs(f32 const diffusion, f32 *const x, f32 *const y) { /* The matrix is of order 4, so n is sqrt(4 - 1). */ static constexpr auto n = std::numbers::sqrt3_v; const auto 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. */ auto CalcLimitedHfRatio(f32 const hfRatio, f32 const airAbsorptionGainHF, f32 const decayTime) -> f32 { /* 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. */ const auto 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(f32 const length, f32 const lfDecayTime, f32 const mfDecayTime, f32 const hfDecayTime, f32 const lf0norm, f32 const hf0norm) { const auto mfGain = CalcDecayCoeff(length, mfDecayTime); const auto lfGain = CalcDecayCoeff(length, lfDecayTime) / mfGain; const auto hfGain = CalcDecayCoeff(length, hfDecayTime) / mfGain; mMidGain = mfGain; mLFFilter.setParamsFromSlope(BiquadType::LowShelf, lf0norm, lfGain, 1.0f); mHFFilter.setParamsFromSlope(BiquadType::HighShelf, hf0norm, hfGain, 1.0f); } /* Update the early reflection line lengths and gain coefficients. */ void EarlyReflections::updateLines(f32 const density_mult, f32 const diffusion, f32 const decayTime, f32 const frequency) { /* Calculate the all-pass feed-back/forward coefficient. */ Allpass.Coeff = diffusion*diffusion * InvSqrt2; /* Calculate the delay length of each all-pass line. */ std::ranges::transform(EARLY_ALLPASS_LENGTHS, Allpass.Offset.begin(), [density_mult,frequency](f32 const length) -> u32 { return float2uint(length * density_mult * frequency); }); /* Calculate the delay length of each delay line. */ std::ranges::transform(EARLY_LINE_LENGTHS, Offset.begin(), [density_mult,frequency](f32 const length) -> u32 { return float2uint(length * density_mult * 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) / f32{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(f32 const modTime, f32 const modDepth, f32 const 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)), 1_u32); /* 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(f32 const density_mult, f32 const diffusion, f32 const lfDecayTime, f32 const mfDecayTime, f32 const hfDecayTime, f32 const lf0norm, f32 const hf0norm, f32 const frequency) { /* Scaling factor to convert the normalized reference frequencies from * representing 0...freq to 0...max_reference. */ static constexpr auto MaxHFReference = 20000.0f; const auto norm_weight_factor = frequency / MaxHFReference; static constexpr auto allpass_avg = std::reduce(LATE_ALLPASS_LENGTHS.begin(), LATE_ALLPASS_LENGTHS.end(), 0.0f) / f32{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. */ static constexpr auto delay_avg = std::reduce(LATE_LINE_LENGTHS.begin(), LATE_LINE_LENGTHS.end(), 0.0f) / f32{NUM_LINES} + allpass_avg; /* 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 auto decayTimeWeighted = lf0norm*norm_weight_factor*lfDecayTime + (hf0norm - lf0norm)*norm_weight_factor*mfDecayTime + (1.0f - hf0norm*norm_weight_factor)*hfDecayTime; DensityGain = CalcDensityGain(CalcDecayCoeff(delay_avg*density_mult, decayTimeWeighted)); /* Calculate the all-pass feed-back/forward coefficient. */ VecAp.Coeff = diffusion*diffusion * InvSqrt2; /* Calculate the delay length of each all-pass line. */ std::ranges::transform(LATE_ALLPASS_LENGTHS, VecAp.Offset.begin(), [density_mult,frequency](f32 const length) -> u32 { return float2uint(length*density_mult * frequency); }); /* Calculate the 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. */ auto lengths = std::array{}; std::ranges::transform(LATE_LINE_LENGTHS, lengths.begin(), [density_mult](f32 const length) noexcept -> f32 { return length * density_mult; }); std::ranges::transform(lengths, Offset.begin(), [frequency](f32 const length) -> u32 { return std::max(float2uint(length*frequency + 0.5f), 1_u32) - 1_u32; }); /* 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). */ std::ranges::transform(LATE_ALLPASS_LENGTHS, lengths, lengths.begin(), [density_mult,diffusion,moddepth=Mod.Depth/frequency](f32 const length, f32 const curlength) -> f32 { return lerpf(length, allpass_avg, diffusion)*density_mult + moddepth + curlength; }); /* Calculate the T60 damping coefficients for each line. */ std::ignore = std::ranges::mismatch(T60, lengths, [lfDecayTime,mfDecayTime,hfDecayTime,lf0norm,hf0norm](T60Filter &filter, f32 const length) { filter.calcCoeffs(length, lfDecayTime, mfDecayTime, hfDecayTime, lf0norm, hf0norm); return true; }); } /* Update the offsets for the main effect delay line. */ void ReverbPipeline::updateDelayLine(f32 const gain, f32 const earlyDelay, f32 const lateDelay, f32 const density_mult, f32 const 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; std::ranges::transform(EARLY_TAP_LENGTHS, (mEarlyDelayTap | std::views::elements<1>).begin(), [earlyDelay,frequency,density_mult](f32 const length) -> u32 { return float2uint((length*density_mult + earlyDelay) * frequency); }); std::ranges::transform(LATE_LINE_LENGTHS, (mLateDelayTap | std::views::elements<1>).begin(), [lateDelay,frequency,density_mult](f32 length) noexcept -> u32 { /* Reduce the late delay tap by the shortest late delay line length to * compensate for the late line input being fed by the delayed early * output. */ length -= LATE_LINE_LENGTHS.front(); length = length*f32{1.0f/NUM_LINES}*density_mult + lateDelay; return 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. */ using Array4x4 = std::array, 4>; auto GetTransformFromVector(const std::span vec) -> Array4x4 { /* 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. */ auto norm = std::array{vec[0], vec[1], vec[2]}; auto mag = std::sqrt(vec[0]*vec[0] + vec[1]*vec[1] + vec[2]*vec[2]); if(mag > 1.0f) { const auto scale = std::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] *= -std::numbers::sqrt3_v; norm[1] *= std::numbers::sqrt3_v; norm[2] *= std::numbers::sqrt3_v; } /* NOTE: This is transposed from a typical transform matrix. */ return Array4x4{{ {{1.0f, norm[0], norm[1], norm[2]}}, {{0.0f, 1.0f-mag, 0.0f, 0.0f}}, {{0.0f, 0.0f, 1.0f-mag, 0.0f}}, {{0.0f, 0.0f, 0.0f, 1.0f-mag}} }}; } /* Update the early and late 3D panning gains. */ void ReverbPipeline::update3DPanning(std::span const ReflectionsPan, std::span const LateReverbPan, f32 const earlyGain, f32 const lateGain, bool const doUpmix, MixParams const *const mainMix) { /* Create matrices that transform a B-Format signal according to the * panning vectors. */ auto const earlymat = GetTransformFromVector(ReflectionsPan); auto const latemat = GetTransformFromVector(LateReverbPan); auto get_coeffs = [doUpmix](std::span const, 4> const a2bmatrix, std::span const, 4> const matrix) { auto res = std::array, NUM_LINES>{}; 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. */ const auto mtx2 = std::span{AmbiScale::FirstOrderUp}; for(const auto i : std::views::iota(0_uz, matrix.size())) { const auto dst = std::span{res[i]}; static_assert(dst.size() >= std::tuple_size_v); for(const auto j : std::views::iota(0_uz, matrix[i].size())) { auto const a = matrix[i][j]; std::ranges::transform(mtx2[j], dst, dst.begin(), [a](f32 const in, f32 const out) noexcept -> f32 { return a*in + out; }); } } } else { /* When not upsampling, combine the A-to-B-Format conversion with * its respective transform. This results in panning gains that * convert A-Format to B-Format, which is then panned. */ for(const auto i : std::views::iota(0_uz, a2bmatrix[0].size())) { const auto dst = std::span{res[i]}; static_assert(dst.size() >= std::tuple_size_v); for(const auto j : std::views::iota(0_uz, a2bmatrix.size())) { auto const a = a2bmatrix[j][i]; std::ranges::transform(matrix[j], dst, dst.begin(), [a](f32 const in, f32 const out) noexcept -> f32 { return a*in + out; }); } } } return res; }; std::ignore = std::ranges::mismatch(get_coeffs(EarlyA2B, earlymat), mEarly.Gains, [mainMix,earlyGain](const auto &coeffs, EarlyReflections::OutGains &earlygains) { ComputePanGains(mainMix, coeffs, earlyGain, earlygains.Target); return true; }); std::ignore = std::ranges::mismatch(get_coeffs(LateA2B, latemat), mLate.Gains, [mainMix,lateGain](const auto &coeffs, LateReverb::OutGains &lategains) { ComputePanGains(mainMix, coeffs, lateGain, lategains.Target); return true; }); } void ReverbState::update(ContextBase const *const context, EffectSlotBase const *const slot, EffectProps const *const props, EffectTarget const target) { auto &reverbprops = std::get(*props); const auto device = al::get_not_null(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. */ auto hfRatio = reverbprops.DecayHFRatio; if(reverbprops.DecayHFLimit && reverbprops.AirAbsorptionGainHF < 1.0f) hfRatio = CalcLimitedHfRatio(hfRatio, reverbprops.AirAbsorptionGainHF, reverbprops.DecayTime); /* Calculate the LF/HF decay times. */ static constexpr auto MinDecayTime = 0.1f; static constexpr auto MaxDecayTime = 20.0f; const auto lfDecayTime = std::clamp(reverbprops.DecayTime*reverbprops.DecayLFRatio, MinDecayTime, MaxDecayTime); const auto hfDecayTime = std::clamp(reverbprops.DecayTime*hfRatio, MinDecayTime, MaxDecayTime); /* Determine if a full update is required. */ const auto fullUpdate = mPipelineState == DeviceClear || /* Density is essentially a master control for the feedback delays, so * changes the offsets of many delay lines. */ mParams.Density != reverbprops.Density || /* Diffusion and decay times influences the decay rate (gain) of the * late reverb T60 filter. */ mParams.Diffusion != reverbprops.Diffusion || mParams.DecayTime != reverbprops.DecayTime || mParams.HFDecayTime != hfDecayTime || mParams.LFDecayTime != lfDecayTime || /* Modulation time and depth both require fading the modulation delay. */ mParams.ModulationTime != reverbprops.ModulationTime || mParams.ModulationDepth != reverbprops.ModulationDepth || /* HF/LF References control the weighting used to calculate the density * gain. */ mParams.HFReference != reverbprops.HFReference || mParams.LFReference != reverbprops.LFReference; if(fullUpdate) { mParams.Density = reverbprops.Density; mParams.Diffusion = reverbprops.Diffusion; mParams.DecayTime = reverbprops.DecayTime; mParams.HFDecayTime = hfDecayTime; mParams.LFDecayTime = lfDecayTime; mParams.ModulationTime = reverbprops.ModulationTime; mParams.ModulationDepth = reverbprops.ModulationDepth; mParams.HFReference = reverbprops.HFReference; mParams.LFReference = reverbprops.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 auto density_mult = CalcDelayLengthMult(reverbprops.Density); /* Update the main effect delay and associated taps. */ pipeline.updateDelayLine(reverbprops.Gain, reverbprops.ReflectionsDelay, reverbprops.LateReverbDelay, density_mult, frequency); /* Update early and late 3D panning. */ mOutTarget = target.Main->Buffer; const auto gain = slot->Gain * ReverbBoost; pipeline.update3DPanning(reverbprops.ReflectionsPan, reverbprops.LateReverbPan, reverbprops.ReflectionsGain*gain, reverbprops.LateReverbGain*gain, mUpmixOutput, target.Main); /* Calculate the master filters */ auto const hf0norm = std::min(reverbprops.HFReference/frequency, 0.49f); auto const lf0norm = std::min(reverbprops.LFReference/frequency, 0.49f); pipeline.mFilter[0].Lp.setParamsFromSlope(BiquadType::HighShelf, hf0norm, reverbprops.GainHF, 1.0f); pipeline.mFilter[0].Hp.setParamsFromSlope(BiquadType::LowShelf, lf0norm, reverbprops.GainLF, 1.0f); std::ranges::for_each(pipeline.mFilter | std::views::drop(1), [&filter=pipeline.mFilter[0]](ReverbPipeline::FilterPair &targetpair) noexcept { targetpair.Lp.copyParamsFrom(filter.Lp); targetpair.Hp.copyParamsFrom(filter.Hp); }); if(fullUpdate) { /* Update the early lines. */ pipeline.mEarly.updateLines(density_mult, reverbprops.Diffusion, reverbprops.DecayTime, frequency); /* Get the mixing matrix coefficients. */ CalcMatrixCoeffs(reverbprops.Diffusion, &pipeline.mMixX, &pipeline.mMixY); /* Update the modulator rate and depth. */ pipeline.mLate.Mod.updateModulator(reverbprops.ModulationTime, reverbprops.ModulationDepth, frequency); /* Update the late lines. */ pipeline.mLate.updateLines(density_mult, reverbprops.Diffusion, lfDecayTime, reverbprops.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 auto decayBase = slot->Gain * reverbprops.Gain * reverbprops.LateReverbGain; const auto decayDiff = ReverbDecayGain / std::max(decayBase, ReverbDecayGain); /* 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 auto diffTime = !(decayDiff < 1.0f) ? 0.0f : (std::log10(decayDiff)*(20.0f / -60.0f) * reverbprops.DecayTime); const auto decaySamples = (reverbprops.ReflectionsDelay+reverbprops.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. */ auto VectorPartialScatter(std::array const &in, f32 const xCoeff, f32 const 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] ) }; } /* Applies the above to the given number of samples. */ void VectorScatter(f32 const xCoeff, f32 const yCoeff, std::span const samples, usize const count) noexcept { ASSUME(count > 0); for(auto i = 0_uz;i < count;++i) { auto src = std::array{samples[0][i], samples[1][i], samples[2][i], samples[3][i]}; src = VectorPartialScatter(src, xCoeff, yCoeff); samples[0][i] = src[0]; samples[1][i] = src[1]; samples[2][i] = src[2]; samples[3][i] = src[3]; } } /* Same as the above, but also applies a line-based reflection on the input * channels (swapping 0<->3 and 1<->2). */ void VectorScatterRev(f32 const xCoeff, f32 const yCoeff, std::span const samples, usize const count) noexcept { ASSUME(count > 0); for(auto i = 0_uz;i < count;++i) { auto src = std::array{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 std::span samples, usize offset, f32 const xCoeff, f32 const yCoeff, usize const todo) const noexcept { const auto delaymask = usize{Delay.mLine.size()/NUM_LINES - 1_uz}; const auto delaybuf = Delay.mLine; const auto feedCoeff = Coeff; ASSUME(todo > 0); for(auto base = 0_uz;base < todo;) { auto vap_offset = std::array{}; std::ranges::transform(Offset, vap_offset.begin(), [offset,delaymask](usize const delay) noexcept -> usize { return (offset-delay) & delaymask; }); offset &= delaymask; const auto maxoff = std::max(std::ranges::max(vap_offset), offset); /* Limit the number of samples to the minimum line delay, so that each * line's all-pass can be processed individually before scattering for * the feedback. */ const auto td = std::min(Offset.front(), std::min(delaymask+1_uz - maxoff, todo - base)); for(const auto c : std::views::iota(0_uz, NUM_LINES)) { const auto sampleline = std::span{samples[c]}.subspan(base, td); auto out_offset = vap_offset[c]; auto in_offset = offset; std::ranges::transform(sampleline, sampleline.begin(), [delaybuf,feedCoeff,c,&out_offset,&in_offset](f32 const input) { const auto out = delaybuf[(out_offset++)*NUM_LINES + c] - feedCoeff*input; delaybuf[(in_offset++)*NUM_LINES + c] = input + feedCoeff*out; return out; }); vap_offset[c] = out_offset; } auto delayIn = delaybuf.begin(); std::advance(delayIn, offset*NUM_LINES); for(const auto j [[maybe_unused]] : std::views::iota(0_uz, td)) { const auto f = VectorPartialScatter({delayIn[0], delayIn[1], delayIn[2], delayIn[3]}, xCoeff, yCoeff); delayIn = std::ranges::copy(f, delayIn).out; } offset += td; base += td; } } /* This applies a more typical all-pass to each line, without the scattering * matrix. */ void Allpass4::process(std::span const samples, usize const offset, usize const todo) const noexcept { const auto delay = Delay; const auto feedCoeff = Coeff; ASSUME(todo > 0); for(const auto j : std::views::iota(0_uz, NUM_LINES)) { auto lineio = std::span{samples[j]}.first(todo); const auto delaybuf = delay.get(j); auto dstoffset = offset; auto vap_offset = offset - Offset[j]; while(!lineio.empty()) { vap_offset &= delaybuf.size()-1; dstoffset &= delaybuf.size()-1; const auto maxoff = std::max(dstoffset, vap_offset); const auto td = std::min(lineio.size(), delaybuf.size()-maxoff); std::ranges::transform(lineio.first(td), lineio.begin(), [delaybuf,feedCoeff,&vap_offset,&dstoffset](f32 const x) -> f32 { const auto y = delaybuf[vap_offset++] - feedCoeff*x; delaybuf[dstoffset++] = x + feedCoeff*y; return y; }); lineio = lineio.subspan(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(DelayLineU const &main_delay, usize offset, usize const samplesToDo, std::span const tempSamples, std::span const outSamples) { const auto early_delay = mEarly.Delay; const auto in_delay = main_delay; const auto mixX = mMixX; const auto mixY = mMixY; ASSUME(samplesToDo <= BufferLineSize); for(auto base = 0_uz;base < samplesToDo;) { const auto todo = std::min(samplesToDo-base, MAX_UPDATE_SAMPLES); /* First, load decorrelated samples from the main delay line as the * primary reflections, applying the main room/hf/lf gains. */ const auto fadeStep = 1.0f / static_cast(todo); const auto earlycoeff0 = mEarlyDelayCoeff[0]; const auto earlycoeff1 = mEarlyDelayCoeff[1]; mEarlyDelayCoeff[0] = mEarlyDelayCoeff[1]; for(const auto j : std::views::iota(0_uz, NUM_LINES)) { const auto input = in_delay.get(j); auto early_delay_tap0 = offset - mEarlyDelayTap[j][0]; auto early_delay_tap1 = offset - mEarlyDelayTap[j][1]; mEarlyDelayTap[j][0] = mEarlyDelayTap[j][1]; auto fadeCount = 0.0f; auto tmp = tempSamples[j].begin(); for(auto i = 0_uz;i < todo;) { early_delay_tap0 &= input.size()-1; early_delay_tap1 &= input.size()-1; const auto max_tap = std::max(early_delay_tap0, early_delay_tap1); const auto td = std::min(input.size()-max_tap, todo-i); tmp = std::ranges::transform(input.subspan(early_delay_tap0, td), input.subspan(early_delay_tap1, td), tmp, [earlycoeff0,earlycoeff1,fadeStep,&fadeCount](f32 const in0, f32 const in1) noexcept -> f32 { const auto ret = lerpf(in0*earlycoeff0, in1*earlycoeff1, fadeStep*fadeCount); fadeCount += 1.0f; return ret; }).out; early_delay_tap0 += td; early_delay_tap1 += td; i += td; } /* Band-pass the incoming samples. */ mFilter[j].process(std::span{tempSamples[j]}.first(todo), tempSamples[j]); } /* Apply an all-pass, to help color the initial reflections. */ mEarly.Allpass.process(tempSamples, offset, todo); /* Apply a reflection and delay to generate secondary reflections.*/ early_delay.writeReflected(offset, tempSamples, todo); const auto delay_coeff = mEarly.Coeff; for(const auto j : std::views::iota(0_uz, NUM_LINES)) { const auto delaybuf = early_delay.get(j); auto delay_tap = usize{offset - mEarly.Offset[j]}; auto out = outSamples[j].begin() + base; auto tmp = std::span{tempSamples[j]}.first(todo); while(!tmp.empty()) { delay_tap &= delaybuf.size()-1; const auto delaySrc = delaybuf | std::views::drop(delay_tap) | std::views::take(tmp.size()); /* Combine the main input with the attenuated delayed echo for * the early output. */ out = std::ranges::transform(delaySrc, tmp, out, [delay_coeff](f32 const delayspl, f32 const mainspl) noexcept -> f32 { return delayspl*delay_coeff + mainspl; }).out; tmp = tmp.subspan(delaySrc.size()); delay_tap += delaySrc.size(); } } /* Finally, apply a scatter to the main input to improve the initial * diffusion in the late reverb, writing the result to the late delay * line input. */ VectorScatter(mixX, mixY, tempSamples, todo); std::ignore = std::ranges::mismatch(std::views::iota(0_uz, NUM_LINES), tempSamples, [late_in=mLateDelayIn,offset,todo](usize const idx, ReverbUpdateSpan const tmpline) { late_in.write(offset, idx, tmpline.first(todo)); return true; }); base += todo; offset += todo; } } auto Modulation::calcDelays(usize const todo) -> std::span { auto idx = Index; const auto step = Step; const auto depth = Depth * f32{gCubicTable.sTableSteps}; const auto delays = std::span{ModDelays}.first(todo); std::ranges::generate(delays, [step,depth,&idx] { 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); idx += step; 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(usize offset, usize const samplesToDo, std::span const tempSamples, std::span const outSamples) { const auto late_delay = mLate.Delay; const auto in_delay = mLateDelayIn; const auto mixX = mMixX; const auto mixY = mMixY; ASSUME(samplesToDo <= BufferLineSize); for(auto base = 0_uz;base < samplesToDo;) { const auto 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(const auto j : std::views::iota(0_uz, NUM_LINES)) { const auto input = late_delay.get(j); const auto midGain = mLate.T60[j].mMidGain; auto late_feedb_tap = usize{offset - mLate.Offset[j]}; std::ranges::transform(delays, tempSamples[j].begin(), [input,midGain,&late_feedb_tap](usize const idelay) -> f32 { /* 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 = usize{idelay & gCubicTable.sTableMask}; ++late_feedb_tap; /* Get the samples around the delayed offset, interpolated for * output. */ const auto out0 = input[(delay ) & (input.size()-1)]; const auto out1 = input[(delay-1) & (input.size()-1)]; const auto out2 = input[(delay-2) & (input.size()-1)]; const auto out3 = 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; }); mLate.T60[j].process(std::span{tempSamples[j]}.first(todo)); } /* Next load decorrelated samples from the main delay lines. */ const auto fadeStep = 1.0f / static_cast(todo); for(const auto j : std::views::iota(0_uz, NUM_LINES)) { const auto input = in_delay.get(j); auto late_delay_tap0 = usize{offset - mLateDelayTap[j][0]}; auto late_delay_tap1 = usize{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 = std::span{tempSamples[j]}.first(todo); while(!samples.empty()) { late_delay_tap0 &= input.size()-1; late_delay_tap1 &= input.size()-1; const auto max_delay_tap = std::max(late_delay_tap0, late_delay_tap1); const auto td = std::min(samples.size(), input.size()-max_delay_tap); std::ranges::transform(samples.first(td), samples.begin(), [input,densityGain,densityStep,&late_delay_tap0,&late_delay_tap1,&fadeCount] (f32 const sample) noexcept -> f32 { const auto fade0 = densityGain - densityStep*fadeCount; const auto fade1 = densityStep*fadeCount; fadeCount += 1.0f; return input[late_delay_tap0++]*fade0 + input[late_delay_tap1++]*fade1 + sample; }); samples = samples.subspan(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); std::ignore = std::ranges::mismatch(tempSamples, outSamples, [base,todo](ReverbUpdateLine &tmpline, FloatBufferSpan const outline) { std::ranges::copy(tmpline | std::views::take(todo), (outline | std::views::drop(base)).begin()); return true; }); /* Finally, scatter and bounce the results to refeed the feedback buffer. */ VectorScatterRev(mixX, mixY, tempSamples, todo); std::ignore = std::ranges::mismatch(std::views::iota(0_uz, NUM_LINES), tempSamples, [late_delay,offset,todo](usize const idx, ReverbUpdateLine const &tmpline) { late_delay.write(offset, idx, std::span{tmpline}.first(todo)); return true; }); base += todo; offset += todo; } } void ReverbState::process(usize const samplesToDo, std::span const samplesIn, std::span const samplesOut) { const auto offset = mOffset; ASSUME(samplesToDo <= BufferLineSize); auto &oldpipeline = mPipelines[!mCurrentPipeline]; auto &pipeline = mPipelines[mCurrentPipeline]; /* Convert B-Format to A-Format for processing. */ const auto numInput = std::min(samplesIn.size(), NUM_LINES); for(const auto c : std::views::iota(0_uz, NUM_LINES)) { const auto tmpspan = std::span{std::assume_aligned<16>(mTempLine.data()), samplesToDo}; std::ranges::fill(tmpspan, 0.0f); for(const auto i : std::views::iota(0_uz, numInput)) { std::ranges::transform(tmpspan, samplesIn[i], tmpspan.begin(), [gain=B2A[c][i]](f32 const sample, f32 const in) noexcept -> f32 { return sample + in*gain; }); } 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) { 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) { std::ranges::fill(oldpipeline.mEarly.Gains | std::views::transform(&EarlyReflections::OutGains::Target) | std::views::join, 0.0f); std::ranges::fill(oldpipeline.mLate.Gains | std::views::transform(&LateReverb::OutGains::Target) | std::views::join, 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 : EffectStateFactory { al::intrusive_ptr create() override { return al::intrusive_ptr{new ReverbState{}}; } }; } // namespace auto ReverbStateFactory_getFactory() -> gsl::not_null { static ReverbStateFactory ReverbFactory{}; return gsl::make_not_null(&ReverbFactory); } kcat-openal-soft-75c0059/alc/effects/vmorpher.cpp000066400000000000000000000314541512220627100216700ustar00rootroot00000000000000/** * 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 #include #include #include "alc/effects/base.h" #include "alnumeric.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 auto MaxUpdateSamples = 256_uz; constexpr auto NumFormants = 4_uz; constexpr auto RcpQFactor = 1.0f / 5.0f; enum : size_t { VowelAIndex, VowelBIndex, NumFilters }; constexpr auto WaveformFracBits{24_uz}; constexpr auto WaveformFracOne{1_uz< float { static constexpr auto scale = std::numbers::pi_v*2.0f / float{WaveformFracOne}; return std::sin(static_cast(index) * scale)*0.5f + 0.5f; } inline auto Saw(uint index) -> float { return static_cast(index) / float{WaveformFracOne}; } inline auto Triangle(uint index) -> float { return std::fabs(static_cast(index)*(2.0f/WaveformFracOne) - 1.0f); } inline auto Half(uint) -> float { return 0.5f; } template void Oscillate(const std::span dst, uint index, const uint step) { std::ranges::generate(dst, [&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(std::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 auto g = mCoeff; const auto gain = mGain; const auto h = 1.0f / (1.0f + (g*RcpQFactor) + (g*g)); const auto coeff = RcpQFactor + g; auto s1 = mS1; auto s2 = mS2; const auto input = std::span{samplesIn, numInput}; const auto output = std::span{samplesOut, numInput}; std::ranges::transform(input, output, output.begin(), [g,gain,h,coeff,&s1,&s2](const float in, const float out) noexcept -> float { const auto H = (in - coeff*s1 - s2)*h; const auto B = g*H + s1; const auto 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 std::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 EffectSlotBase *slot, const EffectProps *props, const EffectTarget target) override; void process(const size_t samplesToDo, const std::span samplesIn, const std::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*) { mChans.fill(OutParams{}); } void VmorpherState::update(const ContextBase *context, const EffectSlotBase *slot, const EffectProps *props_, const EffectTarget target) { auto &props = std::get(*props_); const auto device = al::get_not_null(context->mDevice); const auto frequency = static_cast(device->mSampleRate); const auto 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 auto pitchA = std::pow(2.0f, static_cast(props.PhonemeACoarseTuning) / 12.0f); const auto 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(auto i=0_uz;i < slot->Wet.Buffer.size();++i) { std::ranges::copy(vowelA, mChans[i].mFormants[VowelAIndex].begin()); std::ranges::copy(vowelB, mChans[i].mFormants[VowelBIndex].begin()); } mOutTarget = target.Main->Buffer; target.Main->setAmbiMixParams(slot->Wet, slot->Gain, [this](const size_t idx, const uint outchan, const float outgain) { mChans[idx].mTargetChannel = outchan; mChans[idx].mTargetGain = outgain; }); } void VmorpherState::process(const size_t samplesToDo, const std::span samplesIn, const std::span samplesOut) { alignas(16) auto blended = std::array{}; /* 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(auto base=0_uz;base < samplesToDo;) { const auto td = std::min(MaxUpdateSamples, samplesToDo-base); mGetSamples(std::span{mLfo}.first(td), mIndex, mStep); mIndex += static_cast(mStep * td); mIndex &= WaveformFracMask; auto chandata = mChans.begin(); std::ranges::for_each(samplesIn, [&,this](const FloatConstBufferSpan input) { const auto outidx = chandata->mTargetChannel; if(outidx == InvalidChannelIndex) { ++chandata; return; } const auto vowelA = std::span{chandata->mFormants[VowelAIndex]}; const auto vowelB = std::span{chandata->mFormants[VowelBIndex]}; /* Process first vowel. */ std::ranges::fill(mSampleBufferA | std::views::take(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::ranges::fill(mSampleBufferB | std::views::take(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(auto i=0_uz;i < td;++i) blended[i] = lerpf(mSampleBufferA[i], mSampleBufferB[i], mLfo[i]); /* Now, mix the processed sound data to the output. */ MixSamples(std::span{blended}.first(td), std::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 auto VmorpherStateFactory_getFactory() -> gsl::not_null { static VmorpherStateFactory VmorpherFactory{}; return gsl::make_not_null(&VmorpherFactory); } kcat-openal-soft-75c0059/alc/events.cpp000066400000000000000000000061161512220627100177100ustar00rootroot00000000000000 #include "config.h" #include "events.h" #include #include #include "alformat.hpp" #include "alnumeric.h" #include "core/logging.h" #include "device.h" #include "gsl/gsl" namespace { auto EnumFromEventType(const alc::EventType type) -> ALCenum { 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{al::format("Invalid EventType: {}", int{al::to_underlying(type)})}; } } // namespace namespace alc { auto GetEventType(ALCenum type) -> std::optional { 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, /* NOLINTNEXTLINE(bugprone-suspicious-stringview-data-usage) */ gsl::narrow_cast(message.size()), message.data(), EventUserPtr); } } // namespace alc FORCE_ALIGN auto ALC_APIENTRY alcEventControlSOFT(ALCsizei count, const ALCenum *events, ALCboolean enable) noexcept -> ALCboolean { if(enable != ALC_FALSE && enable != ALC_TRUE) { al::Device::SetGlobalError(ALC_INVALID_ENUM); return ALC_FALSE; } if(count < 0) { al::Device::SetGlobalError(ALC_INVALID_VALUE); return ALC_FALSE; } if(count == 0) return ALC_TRUE; if(!events) { al::Device::SetGlobalError(ALC_INVALID_VALUE); return ALC_FALSE; } auto eventSet = alc::EventBitSet{0}; auto eventrange = std::views::counted(events, count); const auto invalidevent = std::ranges::find_if_not(eventrange, [&eventSet](ALCenum type) { const auto etype = alc::GetEventType(type); if(!etype) return false; eventSet.set(al::to_underlying(*etype)); return true; }); if(invalidevent != eventrange.end()) { WARN("Invalid event type: {:#04x}", as_unsigned(*invalidevent)); al::Device::SetGlobalError(ALC_INVALID_ENUM); return ALC_FALSE; } 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; } kcat-openal-soft-75c0059/alc/events.h000066400000000000000000000021461512220627100173540ustar00rootroot00000000000000#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 */ kcat-openal-soft-75c0059/alc/export_list.h000066400000000000000000000621241512220627100204260ustar00rootroot00000000000000#ifndef ALC_EXPORT_LIST_H #define ALC_EXPORT_LIST_H #include "config.h" #include #include #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 { std::string_view funcName; void *address; }; #define DECL(x) FuncExport{#x, reinterpret_cast(&x)} inline const auto alcFunctions = std::to_array({ /* NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast) */ 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(alGetProcAddressDirect), 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 auto eaxFunctions = std::array{ DECL(EAXGet), DECL(EAXSet), DECL(EAXGetBufferMode), DECL(EAXSetBufferMode), DECL(EAXGetDirect), DECL(EAXSetDirect), DECL(EAXGetBufferModeDirect), DECL(EAXSetBufferModeDirect), /* NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast) */ }; #endif #undef DECL struct EnumExport { std::string_view enumName; int value; }; #define DECL(x) EnumExport{#x, (x)} inline constexpr auto alcEnumerations = std::to_array({ 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), EnumExport{ "AL_INVALID", -1 }, /* Deprecated enum */ 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_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 constexpr auto eaxEnumerations = std::array{ 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 */ kcat-openal-soft-75c0059/alc/inprogext.h000066400000000000000000000133761512220627100200760ustar00rootroot00000000000000#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 */ #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 */ kcat-openal-soft-75c0059/alc/panning.cpp000066400000000000000000002353501512220627100200420ustar00rootroot00000000000000/** * 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/alext.h" #include "alc/context.h" #include "alnumeric.h" #include "alstring.h" #include "alu.h" #include "core/ambdec.h" #include "core/ambidefs.h" #include "core/bformatdec.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 "gsl/gsl" #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 const 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 const 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 const 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; } [[nodiscard]] auto CreateStablizer(usize const outchans, u32 const srate) -> std::unique_ptr { 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 / gsl::narrow_cast(srate)); std::ranges::fill(stablizer->ChannelFilters, stablizer->MidFilter); return stablizer; } void AllocChannels(al::Device *const device, usize const main_chans, usize const real_chans) { TRACE("Channel config, Main: {}, Real: {}", main_chans, real_chans); /* Allocate extra channels for any post-filter output. */ const auto num_chans = main_chans + real_chans; TRACE("Allocating {} channels, {} bytes", num_chans, num_chans*sizeof(device->MixBuffer[0])); device->MixBuffer.resize(num_chans); auto buffer = std::span{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, DualBand }; enum SpatialMode : bool { Pantaphonic, /* 2D */ Periphonic /* 3D */ }; template struct DecoderConfig; template struct DecoderConfig { u8 mOrder{}; SpatialMode m3DMode{}; std::array mChannels{}; DevAmbiScaling mScaling{}; std::array mOrderGain{}; std::array mCoeffs{}; }; template struct DecoderConfig { u8 mOrder{}; SpatialMode m3DMode{}; std::array mChannels{}; DevAmbiScaling mScaling{}; std::array mOrderGain{}; std::array mCoeffs{}; std::array mOrderGainLF{}; std::array mCoeffsLF{}; }; template<> struct DecoderConfig { u8 mOrder{}; SpatialMode m3DMode{}; std::span mChannels; DevAmbiScaling mScaling{}; std::span mOrderGain; std::span mCoeffs; std::span mOrderGainLF; std::span mCoeffsLF; template auto operator=(const DecoderConfig &rhs) & noexcept -> DecoderConfig& { mOrder = rhs.mOrder; m3DMode = rhs.m3DMode; mChannels = rhs.mChannels; mScaling = rhs.mScaling; mOrderGain = rhs.mOrderGain; mCoeffs = rhs.mCoeffs; mOrderGainLF = {}; mCoeffsLF = {}; return *this; } template auto operator=(const DecoderConfig &rhs) & noexcept -> DecoderConfig& { mOrder = rhs.mOrder; m3DMode = rhs.m3DMode; 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 *const device, f32 const ctrl_dist, u32 const order, SpatialMode const mode) { static constexpr auto chans_per_order2d = std::array{1u, 2u, 2u, 2u, 2u}; static constexpr auto chans_per_order3d = std::array{1u, 3u, 5u, 7u, 9u}; static_assert(chans_per_order2d.size() == MaxAmbiOrder+1); static_assert(chans_per_order3d.size() == MaxAmbiOrder+1); /* 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 auto w1 = SpeedOfSoundMetersPerSec / device->AvgSpeakerDist / gsl::narrow_cast(device->mSampleRate); device->mNFCtrlFilter.init(w1); std::ranges::fill(device->NumChannelsPerOrder, 0_u32); std::ranges::copy(((mode == Periphonic) ? chans_per_order3d : chans_per_order2d) | std::views::take(order+1u), device->NumChannelsPerOrder.begin()); } void InitDistanceComp(al::Device *const device, std::span const channels, const std::span dists) { const auto maxdist = std::ranges::max(dists); if(!device->getConfigValueBool("decoder", "distance-comp", true) || !(maxdist > 0.0f)) return; const auto distSampleScale = gsl::narrow_cast(device->mSampleRate) / SpeedOfSoundMetersPerSec; struct DistCoeffs { u32 Length{0u}; f32 Gain{1.0f}; }; auto ChanDelay = std::vector{}; ChanDelay.reserve(device->RealOut.Buffer.size()); auto total = 0_uz; for(auto chidx = 0_uz;chidx < channels.size();++chidx) { const auto ch = channels[chidx]; const auto idx = usize{device->RealOut.ChannelIndex[ch]}; if(idx == InvalidChannelIndex) continue; const auto 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. */ auto delay = std::floor((maxdist - distance)*distSampleScale + 0.5f); if(delay > f32{DistanceComp::MaxDelay-1}) { ERR("Delay for channel {} ({}) exceeds buffer length ({} > {})", idx, GetLabelFromChannel(ch), delay, DistanceComp::MaxDelay-1); delay = f32{DistanceComp::MaxDelay-1}; } ChanDelay.resize(std::max(ChanDelay.size(), idx+1_uz)); if(distance > 0.0f) { ChanDelay[idx].Length = gsl::narrow_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 += RoundFromZero(ChanDelay[idx].Length, 4); } if(total > 0) { auto chandelays = DistanceComp::Create(total); auto chanbuffer = chandelays->mSamples.begin(); std::ranges::transform(ChanDelay, chandelays->mChannels.begin(), [&chanbuffer](DistCoeffs const &data) { auto ret = DistanceComp::ChanData{}; ret.Buffer = std::span{chanbuffer, data.Length}; ret.Gain = data.Gain; std::advance(chanbuffer, RoundFromZero(data.Length, 4)); return ret; }); device->ChannelDelays = std::move(chandelays); } } constexpr auto GetAmbiScales(DevAmbiScaling const scaletype) noexcept { switch(scaletype) { case DevAmbiScaling::FuMa: return std::span{AmbiScale::FromFuMa}; case DevAmbiScaling::SN3D: return std::span{AmbiScale::FromSN3D}; case DevAmbiScaling::N3D: break; } return std::span{AmbiScale::FromN3D}; } constexpr auto GetAmbiLayout(DevAmbiLayout const layouttype) noexcept { switch(layouttype) { case DevAmbiLayout::FuMa: return std::span{AmbiIndex::FromFuMa}; case DevAmbiLayout::ACN: break; } return std::span{AmbiIndex::FromACN}; } auto MakeDecoderView(al::Device const *const device, AmbDecConf const *const conf, DecoderConfig &decoder) -> DecoderView { auto ret = DecoderView{}; decoder.mOrder = (conf->ChanMask > Ambi3OrderMask) ? 4_u8 : (conf->ChanMask > Ambi2OrderMask) ? 3_u8 : (conf->ChanMask > Ambi1OrderMask) ? 2_u8 : 1_u8; decoder.m3DMode = (conf->ChanMask&AmbiPeriphonicMask) ? Periphonic : Pantaphonic; 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; } std::ranges::copy(conf->HFOrderGain | std::views::take(decoder.mOrderGain.size()), decoder.mOrderGain.begin()); std::ranges::copy(conf->LFOrderGain | std::views::take(decoder.mOrderGainLF.size()), decoder.mOrderGainLF.begin()); const auto num_coeffs = (decoder.m3DMode==Periphonic) ? AmbiChannelsFromOrder(decoder.mOrder) : Ambi2DChannelsFromOrder(decoder.mOrder); const auto idx_map = (decoder.m3DMode == Periphonic) ? std::span{AmbiIndex::FromACN} : std::span{AmbiIndex::FromACN2D}; const auto hfmatrix = conf->HFMatrix; const auto lfmatrix = conf->LFMatrix; auto chan_count = 0u; for(const auto &speaker : 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. */ auto ch = Channel{}; 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 { auto idx = std::numeric_limits::max(); if(speaker.Name.size() > 3 && speaker.Name.starts_with("AUX"sv)) { const auto res = std::from_chars(std::to_address(speaker.Name.begin()+3), std::to_address(speaker.Name.end()), idx); if(res.ptr != std::to_address(speaker.Name.end())) idx = std::numeric_limits::max(); } if(idx >= u32{MaxChannels-Aux0}) { ERR("AmbDec speaker label \"{}\" not recognized", speaker.Name); continue; } ch = gsl::narrow_cast(Aux0+idx); } decoder.mChannels[chan_count] = ch; for(auto dst = 0_uz;dst < num_coeffs;++dst) { const auto src = usize{idx_map[dst]}; decoder.mCoeffs[chan_count][dst] = hfmatrix[chan_count][src]; } if(conf->FreqBands > 1) { for(auto dst = 0_uz;dst < num_coeffs;++dst) { const auto src = usize{idx_map[dst]}; decoder.mCoeffsLF[chan_count][dst] = lfmatrix[chan_count][src]; } } ++chan_count; } if(chan_count > 0) { ret.mOrder = decoder.mOrder; ret.m3DMode = decoder.m3DMode; ret.mScaling = decoder.mScaling; ret.mChannels = std::span{decoder.mChannels}.first(chan_count); ret.mOrderGain = decoder.mOrderGain; ret.mCoeffs = std::span{decoder.mCoeffs}.first(chan_count); if(conf->FreqBands > 1) { ret.mOrderGainLF = decoder.mOrderGainLF; ret.mCoeffsLF = std::span{decoder.mCoeffsLF}.first(chan_count); } } return ret; } constexpr auto MonoConfig = DecoderConfig{ 0, Pantaphonic, {{FrontCenter}}, DevAmbiScaling::N3D, {{1.0f}}, {{ {{1.0f}} }} }; constexpr auto StereoConfig = DecoderConfig{ 1, Pantaphonic, {{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 auto QuadConfig = DecoderConfig{ 1, Pantaphonic, {{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 auto X51Config = DecoderConfig{ 2, Pantaphonic, {{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 auto X61Config = DecoderConfig{ 2, Pantaphonic, {{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 auto X71Config = DecoderConfig{ 2, Pantaphonic, {{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 auto X3D71Config = DecoderConfig{ 1, Periphonic, {{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 auto X714Config = DecoderConfig{ 1, Periphonic, {{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 auto X7144Config = DecoderConfig{ 1, Periphonic, {{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}}, }} }; struct PanningProc { std::unique_ptr decoder; std::unique_ptr stablizer; }; [[nodiscard]] auto InitPanning(al::Device *const device, bool const hqdec=false, bool const stablize=false, DecoderView decoder={}) -> PanningProc { 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 auto count = AmbiChannelsFromOrder(device->mAmbiOrder); const auto acnmap = GetAmbiLayout(device->mAmbiLayout).first(count); const auto n3dscale = GetAmbiScales(device->mAmbiScale); std::ranges::transform(acnmap, device->Dry.AmbiMap.begin(), [n3dscale](u8 const acn) noexcept -> BFChannelConfig { return BFChannelConfig{1.0f/n3dscale[acn], acn}; }); AllocChannels(device, count, 0); device->m2DMixing = false; auto avg_dist = f32{}; if(auto const distopt = device->configValue("decoder", "speaker-dist")) avg_dist = *distopt; else if(auto const 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, Periphonic); return {}; } } const auto ambicount = usize{(decoder.m3DMode == Periphonic) ? AmbiChannelsFromOrder(decoder.mOrder) : Ambi2DChannelsFromOrder(decoder.mOrder)}; const auto dual_band = hqdec && !decoder.mCoeffsLF.empty(); auto chancoeffs = std::vector{}; auto chancoeffslf = std::vector{}; for(const auto i : std::views::iota(0_uz, decoder.mChannels.size())) { const auto idx = usize{device->RealOut.ChannelIndex[decoder.mChannels[i]]}; if(idx == InvalidChannelIndex) { ERR("Failed to find {} channel in device", GetLabelFromChannel(decoder.mChannels[i])); continue; } const auto ordermap = (decoder.m3DMode == Periphonic) ? std::span{AmbiIndex::OrderFromChannel} : std::span{AmbiIndex::OrderFrom2DChannel}; chancoeffs.resize(std::max(chancoeffs.size(), idx+1_zu), ChannelDec{}); std::ranges::transform(decoder.mCoeffs[i] | std::views::take(ambicount), ordermap, chancoeffs[idx].begin(), [&decoder](f32 const coeff, usize const order) -> f32 { return coeff * decoder.mOrderGain[order]; }); if(!dual_band) continue; chancoeffslf.resize(std::max(chancoeffslf.size(), idx+1_zu), ChannelDec{}); std::ranges::transform(decoder.mCoeffsLF[i] | std::views::take(ambicount), ordermap, chancoeffslf[idx].begin(), [&decoder](f32 const coeff, usize const order) -> f32 { return coeff * decoder.mOrderGainLF[order]; }); } /* For non-DevFmtAmbi3D, set the ambisonic order. */ device->mAmbiOrder = decoder.mOrder; device->m2DMixing = decoder.m3DMode == Pantaphonic; const auto acnmap = (decoder.m3DMode == Periphonic) ? std::span{AmbiIndex::FromACN}.first(ambicount) : std::span{AmbiIndex::FromACN2D}.first(ambicount); const auto coeffscale = GetAmbiScales(decoder.mScaling); std::ranges::transform(acnmap, device->Dry.AmbiMap.begin(), [coeffscale](u8 const acn) noexcept { return BFChannelConfig{1.0f/coeffscale[acn], acn}; }); AllocChannels(device, ambicount, device->channelsFromFmt()); auto stablizer = std::unique_ptr{}; if(stablize) { /* Only enable the stablizer if the decoder does not output to the * front-center channel. */ const auto cidx = usize{device->RealOut.ChannelIndex[FrontCenter]}; auto 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.m3DMode == Periphonic) ? " periphonic" : ""); auto bformatdec = std::make_unique(ambicount, chancoeffs, chancoeffslf, device->mXOverFreq/gsl::narrow_cast(device->mSampleRate)); return {std::move(bformatdec), std::move(stablizer)}; } [[nodiscard]] auto InitHrtfPanning(al::Device *const device) -> std::unique_ptr { static constexpr auto Deg180 = std::numbers::pi_v; static constexpr auto Deg_90 = Deg180 / 2.0f /* 90 degrees*/; static constexpr auto Deg_45 = Deg_90 / 2.0f /* 45 degrees*/; static constexpr auto Deg135 = Deg_45 * 3.0f /*135 degrees*/; static constexpr auto Deg_21 = 3.648638281e-01f /* 20~ 21 degrees*/; static constexpr auto Deg_32 = 5.535743589e-01f /* 31~ 32 degrees*/; static constexpr auto Deg_35 = 6.154797087e-01f /* 35~ 36 degrees*/; static constexpr auto Deg_58 = 1.017221968e+00f /* 58~ 59 degrees*/; static constexpr auto Deg_69 = 1.205932499e+00f /* 69~ 70 degrees*/; static constexpr auto Deg111 = 1.935660155e+00f /*110~111 degrees*/; static constexpr auto Deg122 = 2.124370686e+00f /*121~122 degrees*/; static constexpr auto Deg148 = 2.588018295e+00f /*148~149 degrees*/; static constexpr auto AmbiPoints1O = std::array{ 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 auto AmbiPoints2O = std::array{ 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 auto AmbiPoints3O = std::array{ 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 auto AmbiPoints4O = std::array{ AngularPoint{EvRadians{ Deg_69}, AzRadians{ Deg_90}}, AngularPoint{EvRadians{ Deg_69}, AzRadians{-Deg_90}}, AngularPoint{EvRadians{ Deg_58}, AzRadians{ 0.0f}}, AngularPoint{EvRadians{ Deg_58}, 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_32}, AzRadians{ Deg_90}}, AngularPoint{EvRadians{ Deg_32}, AzRadians{-Deg_90}}, AngularPoint{EvRadians{ Deg_21}, AzRadians{ 0.0f}}, AngularPoint{EvRadians{ Deg_21}, AzRadians{ Deg180}}, AngularPoint{EvRadians{ 0.0f}, AzRadians{ Deg_32}}, AngularPoint{EvRadians{ 0.0f}, AzRadians{ Deg148}}, AngularPoint{EvRadians{ 0.0f}, AzRadians{-Deg_32}}, AngularPoint{EvRadians{ 0.0f}, AzRadians{-Deg148}}, AngularPoint{EvRadians{ 0.0f}, AzRadians{ Deg_69}}, AngularPoint{EvRadians{ 0.0f}, AzRadians{-Deg_69}}, AngularPoint{EvRadians{ 0.0f}, AzRadians{ Deg111}}, AngularPoint{EvRadians{ 0.0f}, AzRadians{-Deg111}}, AngularPoint{EvRadians{-Deg_21}, AzRadians{ 0.0f}}, AngularPoint{EvRadians{-Deg_21}, AzRadians{ Deg180}}, AngularPoint{EvRadians{-Deg_32}, AzRadians{ Deg_90}}, AngularPoint{EvRadians{-Deg_32}, AzRadians{-Deg_90}}, 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_58}, AzRadians{ 0.0f}}, AngularPoint{EvRadians{-Deg_58}, AzRadians{ Deg180}}, AngularPoint{EvRadians{-Deg_69}, AzRadians{ Deg_90}}, AngularPoint{EvRadians{-Deg_69}, AzRadians{-Deg_90}}, }; static constexpr auto AmbiMatrix1O = std::array{ 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 auto AmbiMatrix2O = std::array{ 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 auto AmbiMatrix3O = std::array{ 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 auto AmbiMatrix4O = std::array{ ChannelCoeffs{3.125000000e-02f, -1.931356215e-02f, 5.056356215e-02f, 0.000000000e+00f, 0.000000000e+00f, -4.149625014e-02f, 5.814697482e-02f, 0.000000000e+00f, -7.925078574e-03f, 1.522452112e-03f, 0.000000000e+00f, -6.187332918e-02f, 5.384041069e-02f, 0.000000000e+00f, -2.013501509e-02f, 0.000000000e+00f, 0.000000000e+00f, 8.560063208e-03f, 0.000000000e+00f, -7.899684062e-02f, 4.188014710e-02f, 0.000000000e+00f, -3.506295521e-02f, 0.000000000e+00f, 1.155996975e-03f}, ChannelCoeffs{3.125000000e-02f, 1.931356215e-02f, 5.056356215e-02f, 0.000000000e+00f, 0.000000000e+00f, 4.149625014e-02f, 5.814697482e-02f, 0.000000000e+00f, -7.925078574e-03f, -1.522452112e-03f, 0.000000000e+00f, 6.187332918e-02f, 5.384041069e-02f, 0.000000000e+00f, -2.013501509e-02f, 0.000000000e+00f, 0.000000000e+00f, -8.560063208e-03f, 0.000000000e+00f, 7.899684062e-02f, 4.188014710e-02f, 0.000000000e+00f, -3.506295521e-02f, 0.000000000e+00f, 1.155996975e-03f}, ChannelCoeffs{3.125000000e-02f, 0.000000000e+00f, 4.604282561e-02f, 2.845603117e-02f, 0.000000000e+00f, 0.000000000e+00f, 3.895883912e-02f, 5.154913118e-02f, 1.592955758e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, 2.095745091e-02f, 6.719846732e-02f, 3.629936978e-02f, 9.158741881e-03f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, -4.263013518e-03f, 6.519422195e-02f, 5.608172276e-02f, 2.308412203e-02f, 5.044065618e-03f}, ChannelCoeffs{3.125000000e-02f, 0.000000000e+00f, 4.604282561e-02f, -2.845603117e-02f, 0.000000000e+00f, 0.000000000e+00f, 3.895883912e-02f, -5.154913118e-02f, 1.592955758e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, 2.095745091e-02f, -6.719846732e-02f, 3.629936978e-02f, -9.158741881e-03f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, -4.263013518e-03f, -6.519422195e-02f, 5.608172276e-02f, -2.308412203e-02f, 5.044065618e-03f}, ChannelCoeffs{3.125000000e-02f, -3.125000000e-02f, 3.125000000e-02f, 3.125000000e-02f, -4.149625014e-02f, -4.149625014e-02f, 0.000000000e+00f, 4.149625014e-02f, 0.000000000e+00f, -2.493065047e-02f, -6.338656910e-02f, -2.043172564e-02f, -3.222123536e-02f, 1.903106711e-02f, 8.858542336e-04f, -2.601559590e-02f, 0.000000000e+00f, -4.482107285e-02f, -4.791574237e-02f, 1.694077318e-02f, -3.750000000e-02f, -1.694077318e-02f, 0.000000000e+00f, -4.482107285e-02f, -3.169328455e-02f}, ChannelCoeffs{3.125000000e-02f, -3.125000000e-02f, 3.125000000e-02f, -3.125000000e-02f, 4.149625014e-02f, -4.149625014e-02f, 0.000000000e+00f, -4.149625014e-02f, 0.000000000e+00f, -2.493065047e-02f, 6.338656910e-02f, -2.043172564e-02f, -3.222123536e-02f, -1.903106711e-02f, 8.858542336e-04f, 2.601559590e-02f, 0.000000000e+00f, -4.482107285e-02f, 4.791574237e-02f, 1.694077318e-02f, -3.750000000e-02f, 1.694077318e-02f, 0.000000000e+00f, 4.482107285e-02f, -3.169328455e-02f}, ChannelCoeffs{3.125000000e-02f, 3.125000000e-02f, 3.125000000e-02f, 3.125000000e-02f, 4.149625014e-02f, 4.149625014e-02f, 0.000000000e+00f, 4.149625014e-02f, 0.000000000e+00f, 2.493065047e-02f, 6.338656910e-02f, 2.043172564e-02f, -3.222123536e-02f, 1.903106711e-02f, 8.858542336e-04f, -2.601559590e-02f, 0.000000000e+00f, 4.482107285e-02f, 4.791574237e-02f, -1.694077318e-02f, -3.750000000e-02f, -1.694077318e-02f, 0.000000000e+00f, -4.482107285e-02f, -3.169328455e-02f}, ChannelCoeffs{3.125000000e-02f, 3.125000000e-02f, 3.125000000e-02f, -3.125000000e-02f, -4.149625014e-02f, 4.149625014e-02f, 0.000000000e+00f, -4.149625014e-02f, 0.000000000e+00f, 2.493065047e-02f, -6.338656910e-02f, 2.043172564e-02f, -3.222123536e-02f, -1.903106711e-02f, 8.858542336e-04f, 2.601559590e-02f, 0.000000000e+00f, 4.482107285e-02f, -4.791574237e-02f, -1.694077318e-02f, -3.750000000e-02f, 1.694077318e-02f, 0.000000000e+00f, 4.482107285e-02f, -3.169328455e-02f}, ChannelCoeffs{3.125000000e-02f, -4.604282561e-02f, 2.845603117e-02f, 0.000000000e+00f, 0.000000000e+00f, -5.154913118e-02f, -5.684018025e-03f, 0.000000000e+00f, -4.170412317e-02f, 3.879705320e-02f, 0.000000000e+00f, -1.586340627e-02f, -3.390986790e-02f, 0.000000000e+00f, -5.873361407e-02f, 0.000000000e+00f, 0.000000000e+00f, 6.043501607e-02f, 0.000000000e+00f, 3.362695493e-02f, -2.921912934e-02f, 0.000000000e+00f, -3.376029419e-02f, 0.000000000e+00f, 3.457254007e-02f}, ChannelCoeffs{3.125000000e-02f, 4.604282561e-02f, 2.845603117e-02f, 0.000000000e+00f, 0.000000000e+00f, 5.154913118e-02f, -5.684018025e-03f, 0.000000000e+00f, -4.170412317e-02f, -3.879705320e-02f, 0.000000000e+00f, 1.586340627e-02f, -3.390986790e-02f, 0.000000000e+00f, -5.873361407e-02f, 0.000000000e+00f, 0.000000000e+00f, -6.043501607e-02f, 0.000000000e+00f, -3.362695493e-02f, -2.921912934e-02f, 0.000000000e+00f, -3.376029419e-02f, 0.000000000e+00f, 3.457254007e-02f}, ChannelCoeffs{3.125000000e-02f, 0.000000000e+00f, 1.931356215e-02f, 5.056356215e-02f, 0.000000000e+00f, 0.000000000e+00f, -2.221016804e-02f, 4.149625014e-02f, 5.431929663e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, -3.668591722e-02f, -1.705225633e-02f, 4.984746936e-02f, 5.489471022e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, -3.040861381e-03f, -5.358568085e-02f, -5.115616222e-03f, 5.867154607e-02f, 5.430725099e-02f}, ChannelCoeffs{3.125000000e-02f, 0.000000000e+00f, 1.931356215e-02f, -5.056356215e-02f, 0.000000000e+00f, 0.000000000e+00f, -2.221016804e-02f, -4.149625014e-02f, 5.431929663e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, -3.668591722e-02f, 1.705225633e-02f, 4.984746936e-02f, -5.489471022e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, -3.040861381e-03f, 5.358568085e-02f, -5.115616222e-03f, -5.867154607e-02f, 5.430725099e-02f}, ChannelCoeffs{3.125000000e-02f, -2.845603117e-02f, 0.000000000e+00f, 4.604282561e-02f, -5.154913118e-02f, 0.000000000e+00f, -3.327482109e-02f, 0.000000000e+00f, 2.577456559e-02f, -6.277495073e-02f, 0.000000000e+00f, 2.566753052e-02f, 0.000000000e+00f, -4.153093679e-02f, 0.000000000e+00f, -5.660413777e-03f, -5.282214092e-02f, 0.000000000e+00f, 4.464285714e-02f, 0.000000000e+00f, 3.348214286e-02f, 0.000000000e+00f, -2.232142857e-02f, 0.000000000e+00f, -3.961660569e-02f}, ChannelCoeffs{3.125000000e-02f, -2.845603117e-02f, 0.000000000e+00f, -4.604282561e-02f, 5.154913118e-02f, 0.000000000e+00f, -3.327482109e-02f, 0.000000000e+00f, 2.577456559e-02f, -6.277495073e-02f, 0.000000000e+00f, 2.566753052e-02f, 0.000000000e+00f, 4.153093679e-02f, 0.000000000e+00f, 5.660413777e-03f, 5.282214092e-02f, 0.000000000e+00f, -4.464285714e-02f, 0.000000000e+00f, 3.348214286e-02f, 0.000000000e+00f, -2.232142857e-02f, 0.000000000e+00f, -3.961660569e-02f}, ChannelCoeffs{3.125000000e-02f, 2.845603117e-02f, 0.000000000e+00f, 4.604282561e-02f, 5.154913118e-02f, 0.000000000e+00f, -3.327482109e-02f, 0.000000000e+00f, 2.577456559e-02f, 6.277495073e-02f, 0.000000000e+00f, -2.566753052e-02f, 0.000000000e+00f, -4.153093679e-02f, 0.000000000e+00f, -5.660413777e-03f, 5.282214092e-02f, 0.000000000e+00f, -4.464285714e-02f, 0.000000000e+00f, 3.348214286e-02f, 0.000000000e+00f, -2.232142857e-02f, 0.000000000e+00f, -3.961660569e-02f}, ChannelCoeffs{3.125000000e-02f, 2.845603117e-02f, 0.000000000e+00f, -4.604282561e-02f, -5.154913118e-02f, 0.000000000e+00f, -3.327482109e-02f, 0.000000000e+00f, 2.577456559e-02f, 6.277495073e-02f, 0.000000000e+00f, -2.566753052e-02f, 0.000000000e+00f, 4.153093679e-02f, 0.000000000e+00f, 5.660413777e-03f, -5.282214092e-02f, 0.000000000e+00f, 4.464285714e-02f, 0.000000000e+00f, 3.348214286e-02f, 0.000000000e+00f, -2.232142857e-02f, 0.000000000e+00f, -3.961660569e-02f}, ChannelCoeffs{3.125000000e-02f, -5.056356215e-02f, 0.000000000e+00f, 1.931356215e-02f, -4.149625014e-02f, 0.000000000e+00f, -3.593680678e-02f, 0.000000000e+00f, -4.639421806e-02f, 3.023445375e-02f, 0.000000000e+00f, 4.888851054e-02f, 0.000000000e+00f, -1.694244021e-02f, 0.000000000e+00f, -5.952798034e-02f, 7.086833869e-02f, 0.000000000e+00f, 3.593680678e-02f, 0.000000000e+00f, 3.616071429e-02f, 0.000000000e+00f, 4.017857143e-02f, 0.000000000e+00f, 7.923321138e-03f}, ChannelCoeffs{3.125000000e-02f, 5.056356215e-02f, 0.000000000e+00f, 1.931356215e-02f, 4.149625014e-02f, 0.000000000e+00f, -3.593680678e-02f, 0.000000000e+00f, -4.639421806e-02f, -3.023445375e-02f, 0.000000000e+00f, -4.888851054e-02f, 0.000000000e+00f, -1.694244021e-02f, 0.000000000e+00f, -5.952798034e-02f, -7.086833869e-02f, 0.000000000e+00f, -3.593680678e-02f, 0.000000000e+00f, 3.616071429e-02f, 0.000000000e+00f, 4.017857143e-02f, 0.000000000e+00f, 7.923321138e-03f}, ChannelCoeffs{3.125000000e-02f, -5.056356215e-02f, 0.000000000e+00f, -1.931356215e-02f, 4.149625014e-02f, 0.000000000e+00f, -3.593680678e-02f, 0.000000000e+00f, -4.639421806e-02f, 3.023445375e-02f, 0.000000000e+00f, 4.888851054e-02f, 0.000000000e+00f, 1.694244021e-02f, 0.000000000e+00f, 5.952798034e-02f, -7.086833869e-02f, 0.000000000e+00f, -3.593680678e-02f, 0.000000000e+00f, 3.616071429e-02f, 0.000000000e+00f, 4.017857143e-02f, 0.000000000e+00f, 7.923321138e-03f}, ChannelCoeffs{3.125000000e-02f, 5.056356215e-02f, 0.000000000e+00f, -1.931356215e-02f, -4.149625014e-02f, 0.000000000e+00f, -3.593680678e-02f, 0.000000000e+00f, -4.639421806e-02f, -3.023445375e-02f, 0.000000000e+00f, -4.888851054e-02f, 0.000000000e+00f, 1.694244021e-02f, 0.000000000e+00f, 5.952798034e-02f, 7.086833869e-02f, 0.000000000e+00f, 3.593680678e-02f, 0.000000000e+00f, 3.616071429e-02f, 0.000000000e+00f, 4.017857143e-02f, 0.000000000e+00f, 7.923321138e-03f}, ChannelCoeffs{3.125000000e-02f, 0.000000000e+00f, -1.931356215e-02f, 5.056356215e-02f, 0.000000000e+00f, 0.000000000e+00f, -2.221016804e-02f, -4.149625014e-02f, 5.431929663e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, 3.668591722e-02f, -1.705225633e-02f, -4.984746936e-02f, 5.489471022e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, -3.040861381e-03f, 5.358568085e-02f, -5.115616222e-03f, -5.867154607e-02f, 5.430725099e-02f}, ChannelCoeffs{3.125000000e-02f, 0.000000000e+00f, -1.931356215e-02f, -5.056356215e-02f, 0.000000000e+00f, 0.000000000e+00f, -2.221016804e-02f, 4.149625014e-02f, 5.431929663e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, 3.668591722e-02f, 1.705225633e-02f, -4.984746936e-02f, -5.489471022e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, -3.040861381e-03f, -5.358568085e-02f, -5.115616222e-03f, 5.867154607e-02f, 5.430725099e-02f}, ChannelCoeffs{3.125000000e-02f, -4.604282561e-02f, -2.845603117e-02f, 0.000000000e+00f, 0.000000000e+00f, 5.154913118e-02f, -5.684018025e-03f, 0.000000000e+00f, -4.170412317e-02f, 3.879705320e-02f, 0.000000000e+00f, -1.586340627e-02f, 3.390986790e-02f, 0.000000000e+00f, 5.873361407e-02f, 0.000000000e+00f, 0.000000000e+00f, -6.043501607e-02f, 0.000000000e+00f, -3.362695493e-02f, -2.921912934e-02f, 0.000000000e+00f, -3.376029419e-02f, 0.000000000e+00f, 3.457254007e-02f}, ChannelCoeffs{3.125000000e-02f, 4.604282561e-02f, -2.845603117e-02f, 0.000000000e+00f, 0.000000000e+00f, -5.154913118e-02f, -5.684018025e-03f, 0.000000000e+00f, -4.170412317e-02f, -3.879705320e-02f, 0.000000000e+00f, 1.586340627e-02f, 3.390986790e-02f, 0.000000000e+00f, 5.873361407e-02f, 0.000000000e+00f, 0.000000000e+00f, 6.043501607e-02f, 0.000000000e+00f, 3.362695493e-02f, -2.921912934e-02f, 0.000000000e+00f, -3.376029419e-02f, 0.000000000e+00f, 3.457254007e-02f}, ChannelCoeffs{3.125000000e-02f, -3.125000000e-02f, -3.125000000e-02f, 3.125000000e-02f, -4.149625014e-02f, 4.149625014e-02f, 0.000000000e+00f, -4.149625014e-02f, 0.000000000e+00f, -2.493065047e-02f, 6.338656910e-02f, -2.043172564e-02f, 3.222123536e-02f, 1.903106711e-02f, -8.858542336e-04f, -2.601559590e-02f, 0.000000000e+00f, 4.482107285e-02f, -4.791574237e-02f, -1.694077318e-02f, -3.750000000e-02f, 1.694077318e-02f, 0.000000000e+00f, 4.482107285e-02f, -3.169328455e-02f}, ChannelCoeffs{3.125000000e-02f, -3.125000000e-02f, -3.125000000e-02f, -3.125000000e-02f, 4.149625014e-02f, 4.149625014e-02f, 0.000000000e+00f, 4.149625014e-02f, 0.000000000e+00f, -2.493065047e-02f, -6.338656910e-02f, -2.043172564e-02f, 3.222123536e-02f, -1.903106711e-02f, -8.858542336e-04f, 2.601559590e-02f, 0.000000000e+00f, 4.482107285e-02f, 4.791574237e-02f, -1.694077318e-02f, -3.750000000e-02f, -1.694077318e-02f, 0.000000000e+00f, -4.482107285e-02f, -3.169328455e-02f}, ChannelCoeffs{3.125000000e-02f, 3.125000000e-02f, -3.125000000e-02f, 3.125000000e-02f, 4.149625014e-02f, -4.149625014e-02f, 0.000000000e+00f, -4.149625014e-02f, 0.000000000e+00f, 2.493065047e-02f, -6.338656910e-02f, 2.043172564e-02f, 3.222123536e-02f, 1.903106711e-02f, -8.858542336e-04f, -2.601559590e-02f, 0.000000000e+00f, -4.482107285e-02f, 4.791574237e-02f, 1.694077318e-02f, -3.750000000e-02f, 1.694077318e-02f, 0.000000000e+00f, 4.482107285e-02f, -3.169328455e-02f}, ChannelCoeffs{3.125000000e-02f, 3.125000000e-02f, -3.125000000e-02f, -3.125000000e-02f, -4.149625014e-02f, -4.149625014e-02f, 0.000000000e+00f, 4.149625014e-02f, 0.000000000e+00f, 2.493065047e-02f, 6.338656910e-02f, 2.043172564e-02f, 3.222123536e-02f, -1.903106711e-02f, -8.858542336e-04f, 2.601559590e-02f, 0.000000000e+00f, -4.482107285e-02f, -4.791574237e-02f, 1.694077318e-02f, -3.750000000e-02f, -1.694077318e-02f, 0.000000000e+00f, -4.482107285e-02f, -3.169328455e-02f}, ChannelCoeffs{3.125000000e-02f, 0.000000000e+00f, -4.604282561e-02f, 2.845603117e-02f, 0.000000000e+00f, 0.000000000e+00f, 3.895883912e-02f, -5.154913118e-02f, 1.592955758e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, -2.095745091e-02f, 6.719846732e-02f, -3.629936978e-02f, 9.158741881e-03f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, -4.263013518e-03f, -6.519422195e-02f, 5.608172276e-02f, -2.308412203e-02f, 5.044065618e-03f}, ChannelCoeffs{3.125000000e-02f, 0.000000000e+00f, -4.604282561e-02f, -2.845603117e-02f, 0.000000000e+00f, 0.000000000e+00f, 3.895883912e-02f, 5.154913118e-02f, 1.592955758e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, -2.095745091e-02f, -6.719846732e-02f, -3.629936978e-02f, -9.158741881e-03f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, -4.263013518e-03f, 6.519422195e-02f, 5.608172276e-02f, 2.308412203e-02f, 5.044065618e-03f}, ChannelCoeffs{3.125000000e-02f, -1.931356215e-02f, -5.056356215e-02f, 0.000000000e+00f, 0.000000000e+00f, 4.149625014e-02f, 5.814697482e-02f, 0.000000000e+00f, -7.925078574e-03f, 1.522452112e-03f, 0.000000000e+00f, -6.187332918e-02f, -5.384041069e-02f, 0.000000000e+00f, 2.013501509e-02f, 0.000000000e+00f, 0.000000000e+00f, -8.560063208e-03f, 0.000000000e+00f, 7.899684062e-02f, 4.188014710e-02f, 0.000000000e+00f, -3.506295521e-02f, 0.000000000e+00f, 1.155996975e-03f}, ChannelCoeffs{3.125000000e-02f, 1.931356215e-02f, -5.056356215e-02f, 0.000000000e+00f, 0.000000000e+00f, -4.149625014e-02f, 5.814697482e-02f, 0.000000000e+00f, -7.925078574e-03f, -1.522452112e-03f, 0.000000000e+00f, 6.187332918e-02f, -5.384041069e-02f, 0.000000000e+00f, 2.013501509e-02f, 0.000000000e+00f, 0.000000000e+00f, 8.560063208e-03f, 0.000000000e+00f, -7.899684062e-02f, 4.188014710e-02f, 0.000000000e+00f, -3.506295521e-02f, 0.000000000e+00f, 1.155996975e-03f}, }; 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 constexpr std::array AmbiOrderHFGain4O{ /*ENRGY 1.947005434e+00f, 1.764337084e+00f, 1.424707344e+00f, 9.755104127e-01f, 4.784482742e-01f*/ /*AMP*/ 1.000000000e+00f, 9.061798459e-01f, 7.317428698e-01f, 5.010311710e-01f, 2.457354591e-01f /*RMS 7.696214736e-01f, 6.974154684e-01f, 5.631650257e-01f, 3.856043482e-01f, 1.891232861e-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"); static_assert(AmbiPoints4O.size() == AmbiMatrix4O.size(), "Fourth-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; auto ambi_order = 1u; if(auto modeopt = device->configValue({}, "hrtf-mode")) { struct HrtfModeEntry { std::string_view name; RenderMode mode; u8 order; }; constexpr auto hrtf_modes = std::array{ HrtfModeEntry{"full"sv, RenderMode::Hrtf, 1}, HrtfModeEntry{"ambi1"sv, RenderMode::Normal, 1}, HrtfModeEntry{"ambi2"sv, RenderMode::Normal, 2}, HrtfModeEntry{"ambi3"sv, RenderMode::Normal, 3}, HrtfModeEntry{"ambi4"sv, RenderMode::Normal, 4}, }; auto mode = std::string_view{*modeopt}; if(al::case_compare(mode, "basic"sv) == 0) { ERR(R"(HRTF mode "{}" deprecated, substituting "{}")", *modeopt, "ambi2"); mode = "ambi2"; } auto iter = std::ranges::find_if(hrtf_modes, [mode](const HrtfModeEntry &entry) -> bool { return al::case_compare(mode, entry.name) == 0; }); 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); auto perHrirMin = false; auto AmbiPoints = std::span{AmbiPoints1O}.subspan(0); auto AmbiMatrix = std::span{AmbiMatrix1O}.subspan(0); auto AmbiOrderHFGain = std::span{AmbiOrderHFGain1O}; if(ambi_order >= 4) { perHrirMin = true; AmbiPoints = AmbiPoints4O; AmbiMatrix = AmbiMatrix4O; AmbiOrderHFGain = AmbiOrderHFGain4O; } else 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 auto count = AmbiChannelsFromOrder(ambi_order); std::ranges::transform(AmbiIndex::FromACN|std::views::take(count), device->Dry.AmbiMap.begin(), [](u8 const index) noexcept { return BFChannelConfig{1.0f, index}; }); AllocChannels(device, count, device->channelsFromFmt()); auto const *const Hrtf = device->mHrtf.get(); auto hrtfstate = DirectHrtfState::Create(count); hrtfstate->build(Hrtf, device->mIrSize, perHrirMin, AmbiPoints, AmbiMatrix, device->mXOverFreq, AmbiOrderHFGain); InitNearFieldCtrl(device, Hrtf->mFields[0].distance, ambi_order, Periphonic); return hrtfstate; } void InitUhjPanning(al::Device *const device) { /* UHJ is always 2D first-order. */ static constexpr auto count = Ambi2DChannelsFromOrder(1); device->mAmbiOrder = 1; device->m2DMixing = true; std::ranges::transform(AmbiIndex::FromFuMa2D | std::views::take(count), device->Dry.AmbiMap.begin(), [](u8 const acn) noexcept -> BFChannelConfig { return BFChannelConfig{1.0f/AmbiScale::FromUHJ[acn], acn}; }); AllocChannels(device, count, device->channelsFromFmt()); /* TODO: Should this default to something else? This is simply a regular * (first-order) B-Format mix which just happens to be UHJ-encoded. As I * understand it, a proper first-order B-Format signal essentially has an * infinite control distance, which we can't really do. However, from what * I've read, 2 meters or so should be sufficient as the near-field effect * becomes inconsequential beyond that. */ const auto spkr_dist = ConfigValueF32({}, "uhj"sv, "distance-ref"sv).value_or(2.0f); InitNearFieldCtrl(device, spkr_dist, device->mAmbiOrder, Pantaphonic); } auto LoadAmbDecConfig(std::string_view const config, al::Device *const device, std::unique_ptr> &decoder_store, DecoderView &decoder, std::span const speakerdists) -> bool { auto conf = AmbDecConf{}; if(auto status = conf.load(config); !status) { ERR("Failed to load layout file {}", config); ERR(" {}", status.error()); return false; } if(conf.Speakers.size() > MaxOutputChannels) { ERR("Unsupported decoder speaker count {} (max {})", conf.Speakers.size(), MaxOutputChannels); return false; } if(conf.ChanMask > Ambi4OrderMask) { ERR("Unsupported decoder channel mask {:#x} (max {:#x})", conf.ChanMask, Ambi4OrderMask); return false; } if(conf.ChanMask > Ambi3OrderMask && conf.CoeffScale == AmbDecScale::FuMa) { ERR("FuMa decoder scaling unsupported with 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); std::ranges::transform(conf.Speakers | std::views::take(decoder.mChannels.size()), speakerdists.begin(), &AmbDecConf::SpeakerConf::Distance); return true; } } // namespace void aluInitRenderer(al::Device *const device, i32 const hrtf_id, std::optional const stereomode) { /* Hold the HRTF the device last used, in case it's used again. */ auto old_hrtf = std::move(device->mHrtf); 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; auto layout = std::string_view{}; switch(device->FmtChans) { case DevFmtQuad: layout = "quad"sv; break; case DevFmtX51: layout = "surround51"sv; break; case DevFmtX61: layout = "surround61"sv; break; case DevFmtX71: layout = "surround71"sv; break; case DevFmtX714: layout = "surround714"sv; break; case DevFmtX7144: layout = "surround7144"sv; break; case DevFmtX3D71: layout = "3d71"sv; break; /* Mono, Stereo, and Ambisonics output don't use custom decoders. */ case DevFmtMono: case DevFmtStereo: case DevFmtAmbi3D: break; } auto decoder_store = std::unique_ptr>{}; auto decoder = DecoderView{}; auto speakerdists = std::array{}; auto usingCustom = false; if(!layout.empty()) { auto decopt = device->configValue("decoder", layout); if(!decopt && layout == "3d71"sv) decopt = device->configValue("decoder", "surround3d71"); if(decopt) usingCustom = LoadAmbDecConfig(*decopt, device, decoder_store, decoder, speakerdists); } 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. */ auto const stablize = device->RealOut.ChannelIndex[FrontCenter] != InvalidChannelIndex && device->RealOut.ChannelIndex[FrontLeft] != InvalidChannelIndex && device->RealOut.ChannelIndex[FrontRight] != InvalidChannelIndex && device->getConfigValueBool({}, "front-stablizer", false); auto const hqdec = device->getConfigValueBool("decoder", "hq-mode", true); auto postproc = InitPanning(device, hqdec, stablize, decoder); if(decoder) { auto dist_scale = 0.0f; const auto accum_dist = std::accumulate(speakerdists.begin(), speakerdists.end(), 0.0f, [&dist_scale](f32 const curvalue, f32 const dist) noexcept -> f32 { if(!(dist > 0.0f)) return curvalue; dist_scale += 1.0f; return std::lerp(curvalue, dist, 1.0f/dist_scale); }); auto const avg_dist = (accum_dist > 0.0f) ? accum_dist : device->configValue("decoder", "speaker-dist").value_or(1.0f); InitNearFieldCtrl(device, avg_dist, decoder.mOrder, decoder.m3DMode); if(accum_dist > 0.0f) InitDistanceComp(device, decoder.mChannels, speakerdists); } if(postproc.stablizer) device->mPostProcess.emplace(std::move(postproc.decoder), std::move(postproc.stablizer)); else if(postproc.decoder) device->mPostProcess.emplace(std::move(postproc.decoder)); 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 && std::cmp_less(hrtf_id, device->mHrtfList.size())) { const auto hrtfname = std::string_view{ device->mHrtfList[gsl::narrow_cast(hrtf_id)]}; if(auto 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(auto hrtf = GetLoadedHrtf(hrtfname, device->mSampleRate)) { device->mHrtf = std::move(hrtf); device->mHrtfName = hrtfname; break; } } } if(device->mHrtf) { old_hrtf = nullptr; auto *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); } auto proc = InitHrtfPanning(device); device->mPostProcess.emplace(std::move(proc)); device->mHrtfStatus = ALC_HRTF_ENABLED_SOFT; return; } } old_hrtf = nullptr; if(stereomode.value_or(StereoEncoding::Default) == StereoEncoding::Uhj) { static constexpr auto init_encoder = [](T arg [[maybe_unused]]) -> std::pair, std::string_view> { using encoder_t = T::encoder_t; return {std::make_unique(), encoder_t::TypeName()}; }; auto proc = std::unique_ptr{}; auto ftype = std::string_view{}; switch(UhjEncodeQuality) { case UhjQualityType::IIR: std::tie(proc, ftype) = init_encoder(UhjEncoderIIR::Tag{}); break; case UhjQualityType::FIR256: std::tie(proc, ftype) = init_encoder(UhjEncoder::Tag{}); break; case UhjQualityType::FIR512: std::tie(proc, ftype) = init_encoder(UhjEncoder::Tag{}); break; } Ensures(proc != nullptr); TRACE("UHJ enabled ({} encoder)", ftype); InitUhjPanning(device); device->mPostProcess.emplace(std::move(proc)); return; } device->mRenderMode = RenderMode::Pairwise; if(device->Type != DeviceType::Loopback) { if(auto cflevopt = device->configValue({}, "cf_level"); cflevopt && *cflevopt > 0 && *cflevopt <= 6) { auto bs2b = std::make_unique(); bs2b->set_params(*cflevopt, gsl::narrow_cast(device->mSampleRate)); TRACE("BS2B enabled"); auto proc = InitPanning(device); device->mPostProcess.emplace(std::move(proc.decoder),std::move(bs2b)); return; } } TRACE("Stereo rendering"); auto proc = InitPanning(device); device->mPostProcess.emplace(std::move(proc.decoder)); } void aluInitEffectPanning(EffectSlotBase *slot, al::Context *context) { auto const device = al::get_not_null(context->mDevice); auto const count = AmbiChannelsFromOrder(device->mAmbiOrder); slot->mWetBuffer.resize(count); slot->Wet.AmbiMap.fill(BFChannelConfig{}); std::ranges::transform(AmbiIndex::FromACN | std::views::take(count), slot->Wet.AmbiMap.begin(), [](u8 const acn) noexcept { return BFChannelConfig{1.0f, acn}; }); slot->Wet.Buffer = slot->mWetBuffer; } kcat-openal-soft-75c0059/alsoft-modules/000077500000000000000000000000001512220627100200735ustar00rootroot00000000000000kcat-openal-soft-75c0059/alsoft-modules/gsl.cppm000066400000000000000000000004321512220627100215400ustar00rootroot00000000000000module; #include "gsl/gsl" export module gsl; export namespace gsl { using gsl::at; using gsl::cwzstring; using gsl::czstring; using gsl::finally; using gsl::make_not_null; using gsl::narrow; using gsl::narrow_cast; using gsl::not_null; using gsl::owner; } /* namespace gsl */ kcat-openal-soft-75c0059/alsoft-modules/router.cppm000066400000000000000000000212311512220627100222730ustar00rootroot00000000000000module; #include #include #include #include #include #include #include #include #include #include "fmt/base.h" #include "fmt/ostream.h" export module alsoft.router; import openal; export { 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}; LPALCLOOPBACKOPENDEVICESOFT alcLoopbackOpenDeviceSOFT{nullptr}; LPALCISRENDERFORMATSUPPORTEDSOFT alcIsRenderFormatSupportedSOFT{nullptr}; LPALCRENDERSAMPLESSOFT alcRenderSamplesSOFT{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; using LPCDriverIface = DriverIface const*; inline auto DriverList = std::vector{}; inline thread_local auto ThreadCtxDriver = LPCDriverIface{}; inline auto CurrentCtxDriver = std::atomic{}; inline auto GetThreadDriver() noexcept -> LPCDriverIface { return ThreadCtxDriver; } inline void SetThreadDriver(LPCDriverIface const driver) noexcept { ThreadCtxDriver = driver; } enum class eLogLevel { None = 0, Error = 1, Warn = 2, Trace = 3, }; inline eLogLevel LogLevel{eLogLevel::Error}; inline std::ofstream LogFile; /* NOLINT(cert-err58-cpp) */ template inline void TRACE(fmt::format_string fmt, Args&& ...args) { if(LogLevel >= eLogLevel::Trace) { auto &file = LogFile ? LogFile : std::cerr; auto msg = fmt::vformat(fmt, fmt::make_format_args(args...)); fmt::vprint(file, "AL Router (II): {}\n", fmt::make_format_args(msg)); file.flush(); } } template inline void WARN(fmt::format_string fmt, Args&& ...args) { if(LogLevel >= eLogLevel::Warn) { auto &file = LogFile ? LogFile : std::cerr; auto msg = fmt::vformat(fmt, fmt::make_format_args(args...)); fmt::vprint(file, "AL Router (WW): {}\n", fmt::make_format_args(msg)); file.flush(); } } template inline void ERR(fmt::format_string fmt, Args&& ...args) { if(LogLevel >= eLogLevel::Error) { auto &file = LogFile ? LogFile : std::cerr; auto msg = fmt::vformat(fmt, fmt::make_format_args(args...)); fmt::vprint(file, "AL Router (EE): {}\n", fmt::make_format_args(msg)); file.flush(); } } void LoadDriverList(); } /* export */ kcat-openal-soft-75c0059/alsoftrc.sample000066400000000000000000000672171512220627100201720ustar00rootroot00000000000000# 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, 3d71, ambi1, ambi2, ambi3, ambi4. 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 512 sample frames (~10.7ms). #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, ambi3, or ambi4 will # instead mix to an ambisonic buffer of the given order, 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. Higher ambisonic orders # 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 # bsinc48 - extrapolates samples using a band-limited Sinc filter (48 points, # with anti-aliasing) # fast_bsinc48 - same as bsinc48, except without interpolation between down- # sampling scales #resampler = spline ## 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 = ## 3d71: # Decoder configuration file for 3D7.1 channel output. See docs/ambdec.txt for # a description of the file format. See also docs/3D7.1.txt for information # about 3D7.1. #3d71 = ## ## 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 = ## format: (global) # Specifies the output file format. Available options: # wav - Microsoft RIFF WAVE with WAVE_FORMAT_EXTENSIBLE format. # caf - Apple Core Audio Format # When outputing ambisonic format (one of the ambi* channel configurations), # wav will be .amb format and caf will be .ambix format. #format = wav ## bformat: (global) # Creates .amb (for wav) or .ambix (for caf) format files using B-Format # 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 ## trace-commits: (global) # Sets whether log EAX property commits with trace messages. This can # significantly increase the amount of log messages for apps that use EAX. #trace-commits = false ## ## 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 = kcat-openal-soft-75c0059/appveyor.yml000066400000000000000000000012211512220627100175210ustar00rootroot00000000000000version: 1.25.0.{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 kcat-openal-soft-75c0059/build/000077500000000000000000000000001512220627100162345ustar00rootroot00000000000000kcat-openal-soft-75c0059/build/.empty000066400000000000000000000000001512220627100173610ustar00rootroot00000000000000kcat-openal-soft-75c0059/cmake/000077500000000000000000000000001512220627100162155ustar00rootroot00000000000000kcat-openal-soft-75c0059/cmake/FindALSA.cmake000066400000000000000000000063211512220627100205420ustar00rootroot00000000000000# - 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) kcat-openal-soft-75c0059/cmake/FindAudioIO.cmake000066400000000000000000000011051512220627100213060ustar00rootroot00000000000000# - 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) kcat-openal-soft-75c0059/cmake/FindFFmpeg.cmake000066400000000000000000000163721512220627100211750ustar00rootroot00000000000000# 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) elseif(EXISTS "${${_component}_INCLUDE_DIRS}/${_pkgconfig}/version.h") 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}) kcat-openal-soft-75c0059/cmake/FindJACK.cmake000066400000000000000000000050621512220627100205330ustar00rootroot00000000000000# - 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) kcat-openal-soft-75c0059/cmake/FindMySOFA.cmake000066400000000000000000000064741512220627100210710ustar00rootroot00000000000000# - 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) kcat-openal-soft-75c0059/cmake/FindOSS.cmake000066400000000000000000000015221512220627100204640ustar00rootroot00000000000000# - 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) kcat-openal-soft-75c0059/cmake/FindOboe.cmake000066400000000000000000000015641512220627100207120ustar00rootroot00000000000000# - 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) kcat-openal-soft-75c0059/cmake/FindOpenSL.cmake000066400000000000000000000054221512220627100211630ustar00rootroot00000000000000# - 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) kcat-openal-soft-75c0059/cmake/FindPortAudio.cmake000066400000000000000000000017041512220627100217300ustar00rootroot00000000000000# - 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) kcat-openal-soft-75c0059/cmake/FindPulseAudio.cmake000066400000000000000000000022511512220627100220720ustar00rootroot00000000000000# - 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 ) kcat-openal-soft-75c0059/cmake/FindSndFile.cmake000066400000000000000000000015341512220627100213470ustar00rootroot00000000000000# - 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) kcat-openal-soft-75c0059/cmake/FindSndIO.cmake000066400000000000000000000014671512220627100210040ustar00rootroot00000000000000# - 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) kcat-openal-soft-75c0059/cmake/bin2h.script.cmake000066400000000000000000000007561512220627100215340ustar00rootroot00000000000000# 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 separator. # TODO: Prettify this. Should group a number of bytes per line instead of one # per line. string(REGEX REPLACE "(..)" " static_cast\(0x\\1\),\n" output "${indata}") # Write the list of hex chars to the output file file(WRITE "${OUTPUT_FILE}" "${output}") kcat-openal-soft-75c0059/common/000077500000000000000000000000001512220627100164255ustar00rootroot00000000000000kcat-openal-soft-75c0059/common/albit.h000066400000000000000000000007761512220627100177030ustar00rootroot00000000000000#ifndef AL_BIT_H #define AL_BIT_H #include #include #include #include #include #include #include namespace al { template constexpr auto byteswap(T value) noexcept -> T { static_assert(std::has_unique_object_representations_v); auto bytes = std::bit_cast>(value); std::ranges::reverse(bytes); return std::bit_cast(bytes); } } // namespace al #endif /* AL_BIT_H */ kcat-openal-soft-75c0059/common/alcomplex.cpp000066400000000000000000000171121512220627100211170ustar00rootroot00000000000000 #include "config.h" #include "alcomplex.h" #include #include #include #include #include #include "alnumeric.h" #include "gsl/gsl" namespace { using u16x2 = std::array; using complex_d = std::complex; [[nodiscard]] constexpr auto BitReverseCounter(usize const log2_size) noexcept -> usize { /* 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(u16)*8, "Too many bits for the bit-reversal table."); std::array mData{}; constexpr BitReverser() { auto const fftsize = 1_uz << N; auto ret_i = 0_uz; /* Bit-reversal permutation applied to a sequence of fftsize items. */ for(auto const idx : std::views::iota(1_uz, fftsize-1_uz)) { auto 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] = gsl::narrow(idx); mData[ret_i][1] = gsl::narrow(revidx); ++ret_i; } } Ensures(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 auto BitReverser2 = BitReverser<2>{}; constexpr auto BitReverser3 = BitReverser<3>{}; constexpr auto BitReverser4 = BitReverser<4>{}; constexpr auto BitReverser5 = BitReverser<5>{}; constexpr auto BitReverser6 = BitReverser<6>{}; constexpr auto BitReverser7 = BitReverser<7>{}; constexpr auto BitReverser8 = BitReverser<8>{}; constexpr auto BitReverser9 = BitReverser<9>{}; constexpr auto BitReverser10 = BitReverser<10>{}; constexpr auto BitReverser11 = BitReverser<11>{}; constexpr auto gBitReverses = std::array, 12>{{ {}, {}, 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 auto gArgAngle = std::array, gBitReverses.size()-1>{{ {gsl::narrow_cast(-1.00000000000000000e+00), gsl::narrow_cast(0.00000000000000000e+00)}, {gsl::narrow_cast( 0.00000000000000000e+00), gsl::narrow_cast(1.00000000000000000e+00)}, {gsl::narrow_cast( 7.07106781186547524e-01), gsl::narrow_cast(7.07106781186547524e-01)}, {gsl::narrow_cast( 9.23879532511286756e-01), gsl::narrow_cast(3.82683432365089772e-01)}, {gsl::narrow_cast( 9.80785280403230449e-01), gsl::narrow_cast(1.95090322016128268e-01)}, {gsl::narrow_cast( 9.95184726672196886e-01), gsl::narrow_cast(9.80171403295606020e-02)}, {gsl::narrow_cast( 9.98795456205172393e-01), gsl::narrow_cast(4.90676743274180143e-02)}, {gsl::narrow_cast( 9.99698818696204220e-01), gsl::narrow_cast(2.45412285229122880e-02)}, {gsl::narrow_cast( 9.99924701839144541e-01), gsl::narrow_cast(1.22715382857199261e-02)}, {gsl::narrow_cast( 9.99981175282601143e-01), gsl::narrow_cast(6.13588464915447536e-03)}, {gsl::narrow_cast( 9.99995293809576172e-01), gsl::narrow_cast(3.06795676296597627e-03)} }}; } // namespace void complex_fft(std::span> const buffer, f64 const sign) { auto const fftsize = buffer.size(); /* Get the number of bits used for indexing. Simplifies bit-reversal and * the main loop count. */ if(auto const log2_size = gsl::narrow_cast(std::countr_zero(fftsize)); 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(auto const i : std::views::iota(0_uz, log2_size)) { auto const step2 = 1_uz << i; auto const 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(auto k = 0_uz;k < fftsize;k+=step) { auto const temp = buffer[k+step2]; buffer[k+step2] = buffer[k] - temp; buffer[k] += temp; } auto const w = complex_d{gArgAngle[i].real(),gArgAngle[i].imag()*sign}; auto u = w; for(auto const j : std::views::iota(1_uz, step2)) { for(auto k = j;k < fftsize;k+=step) { auto const temp = buffer[k+step2] * u; buffer[k+step2] = buffer[k] - temp; buffer[k] += temp; } u *= w; } } } else { Expects(log2_size < 32); for(auto const idx : std::views::iota(1_uz, fftsize-1)) { auto 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]); } auto const pi = std::numbers::pi * sign; for(auto const i : std::views::iota(0_uz, log2_size)) { auto const step2 = 1_uz << i; auto const step = 2_uz << i; for(auto k = 0_uz;k < fftsize;k+=step) { auto const temp = buffer[k+step2]; buffer[k+step2] = buffer[k] - temp; buffer[k] += temp; } auto const arg = pi / gsl::narrow_cast(step2); auto const w = std::polar(1.0, arg); auto u = w; for(auto const j : std::views::iota(1_uz, step2)) { for(auto k = j;k < fftsize;k+=step) { auto const temp = buffer[k+step2] * u; buffer[k+step2] = buffer[k] - temp; buffer[k] += temp; } u *= w; } } } } void complex_hilbert(std::span> const buffer) { inverse_fft(buffer); auto const inverse_size = 1.0 / gsl::narrow_cast(buffer.size()); auto bufiter = buffer.begin(); auto const halfiter = std::next(bufiter, gsl::narrow_cast(buffer.size()>>1)); *bufiter *= inverse_size; ++bufiter; bufiter = std::transform(bufiter, halfiter, bufiter, [scale=inverse_size*2.0](complex_d const d){ return d * scale; }); *bufiter *= inverse_size; ++bufiter; std::fill(bufiter, buffer.end(), complex_d{}); forward_fft(buffer); } kcat-openal-soft-75c0059/common/alcomplex.h000066400000000000000000000024441512220627100205660ustar00rootroot00000000000000#ifndef ALCOMPLEX_H #define ALCOMPLEX_H #include #include #include "alnumeric.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(std::span> buffer, f64 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(std::span> const 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(std::span> const 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(std::span> buffer); #endif /* ALCOMPLEX_H */ kcat-openal-soft-75c0059/common/alformat.hpp000066400000000000000000000013431512220627100207440ustar00rootroot00000000000000#ifndef AL_FORMAT_HPP #define AL_FORMAT_HPP #if defined(__APPLE__) #include #endif /* On macOS, std::format requires std::to_chars, which isn't available prior * to macOS 13.3. */ #if defined(MAC_OS_X_VERSION_MIN_REQUIRED) && MAC_OS_X_VERSION_MIN_REQUIRED < 130300 #include "fmt/format.h" namespace al { using fmt::format; using fmt::format_args; using fmt::format_string; using fmt::make_format_args; using fmt::string_view; using fmt::vformat; } /* namespace al */ #else #include namespace al { using std::format; using std::format_args; using std::format_string; using std::make_format_args; using std::string_view; using std::vformat; } /* namespace al */ #endif #endif /* AL_FORMAT_HPP */ kcat-openal-soft-75c0059/common/almalloc.cpp000066400000000000000000000032471512220627100207230ustar00rootroot00000000000000 #include "almalloc.h" #if defined(__APPLE__) #include #if defined(MAC_OS_X_VERSION_MIN_REQUIRED) && MAC_OS_X_VERSION_MIN_REQUIRED < 101300 #include auto operator new(std::size_t const len, std::align_val_t const align, std::nothrow_t const&) noexcept -> gsl::owner { gsl::owner mem; if(posix_memalign(&mem, static_cast(align), len)) return nullptr; return mem; } auto operator new[](std::size_t const len, std::align_val_t const align, std::nothrow_t const &tag) noexcept -> gsl::owner { return operator new(len, align, tag); } auto operator new(std::size_t const len, std::align_val_t const align) -> gsl::owner { gsl::owner mem; if(posix_memalign(&mem, static_cast(align), len)) throw std::bad_alloc(); return mem; } auto operator new[](std::size_t const len, std::align_val_t const align) -> gsl::owner { return operator new(len, align); } auto operator delete(gsl::owner const ptr, std::align_val_t) noexcept -> void { free(ptr); } auto operator delete[](gsl::owner const ptr, std::align_val_t) noexcept -> void { free(ptr); } auto operator delete(gsl::owner const ptr, std::size_t, std::align_val_t) noexcept -> void { free(ptr); } auto operator delete[](gsl::owner const ptr, std::size_t, std::align_val_t) noexcept -> void { free(ptr); } auto operator delete(gsl::owner const ptr, std::align_val_t, std::nothrow_t const&) noexcept -> void { free(ptr); } auto operator delete[](gsl::owner const ptr, std::align_val_t, std::nothrow_t const&) noexcept -> void { free(ptr); } #endif #endif kcat-openal-soft-75c0059/common/almalloc.h000066400000000000000000000137621512220627100203730ustar00rootroot00000000000000#ifndef AL_MALLOC_H #define AL_MALLOC_H #include #include #include #include #include #include #include "gsl/gsl" #define DISABLE_ALLOC \ auto operator new(std::size_t) -> void* = delete; \ auto operator new[](std::size_t) -> void* = delete; \ auto operator delete(void*) noexcept -> void = delete; \ auto operator delete[](void*) noexcept -> void = delete; enum FamCount : size_t { }; #define DEF_FAM_NEWDEL(T, FamMem) \ static constexpr auto Sizeof(std::size_t count) noexcept -> std::size_t \ { \ static_assert(&Sizeof == &T::Sizeof, \ "Incorrect container type specified"); \ return std::max(decltype(FamMem)::Sizeof(count, offsetof(T, FamMem)), \ sizeof(T)); \ } \ \ auto operator new(std::size_t /*size*/, FamCount count) \ -> gsl::owner \ { \ const auto alignment = std::align_val_t{alignof(T)}; \ return ::operator new[](T::Sizeof(count), alignment); \ } \ auto operator delete(gsl::owner block, FamCount) noexcept -> void \ { ::operator delete[](block, std::align_val_t{alignof(T)}); } \ auto operator delete(gsl::owner block) noexcept -> void \ { ::operator delete[](block, std::align_val_t{alignof(T)}); } \ auto operator new[](std::size_t /*size*/) -> void* = delete; \ auto operator delete[](void* /*block*/) -> void = 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_cvref_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 requires(alignof(U) <= Alignment) struct rebind { using other = allocator; }; constexpr explicit allocator() noexcept = default; template constexpr explicit allocator(const allocator&) noexcept { static_assert(Alignment == allocator::Alignment); } static constexpr auto allocate(std::size_t n) -> gsl::owner { if(n > std::numeric_limits::max()/sizeof(T)) throw std::bad_alloc(); return static_cast>(::operator new[](n*sizeof(T), AlignVal)); } static constexpr 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; } 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() { std::visit([this](auto &ptr) { mRes.reset(static_cast(ptr)); }, mPtr); } out_ptr_t() = delete; 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, Args&& ...args) { static_assert(sizeof...(args) == 0); if constexpr(std::is_same_v) { using ptype = SP::element_type*; return out_ptr_t{res}; } else return out_ptr_t{res}; } template class inout_ptr_t { static_assert(!std::is_same_v); SP &mRes; std::variant mPtr; public: explicit inout_ptr_t(SP &res) : mRes{res}, mPtr{res.get()} { } ~inout_ptr_t() { mRes.release(); std::visit([this](auto &ptr) { mRes.reset(static_cast(ptr)); }, mPtr); } inout_ptr_t() = delete; inout_ptr_t(const inout_ptr_t&) = delete; inout_ptr_t& operator=(const inout_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(mRes.get()); } }; template auto inout_ptr(SP &res, Args&& ...args) { static_assert(sizeof...(args) == 0); if constexpr(std::is_same_v) { using ptype = SP::element_type*; return inout_ptr_t{res}; } else return inout_ptr_t{res}; } } // namespace al #endif /* AL_MALLOC_H */ kcat-openal-soft-75c0059/common/alnumeric.h000066400000000000000000000211271512220627100205600ustar00rootroot00000000000000#ifndef AL_NUMERIC_H #define AL_NUMERIC_H #include "config_simd.h" #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_INTRIN_H #include #endif #if HAVE_SSE_INTRINSICS #include #endif #include "altypes.hpp" #include "gsl/gsl" #include "opthelpers.h" namespace al { #if HAS_BUILTIN(__builtin_add_overflow) template [[nodiscard]] constexpr auto add_sat(T const lhs, T const rhs) noexcept -> T { T res; if(!__builtin_add_overflow(lhs, rhs, &res)) return res; if constexpr(std::is_signed_v) { if(rhs < 0) return std::numeric_limits::min(); } return std::numeric_limits::max(); } #else template [[nodiscard]] constexpr auto add_sat(T lhs, T rhs) noexcept -> T { if constexpr(std::is_signed_v) { if(rhs < 0) { if(lhs < std::numeric_limits::min()-rhs) return std::numeric_limits::min(); return lhs + rhs; } if(lhs > std::numeric_limits::max()-rhs) return std::numeric_limits::max(); return lhs + rhs; } else { const auto res = static_cast(lhs + rhs); if(res < lhs) return std::numeric_limits::max(); return res; } } #endif template [[nodiscard]] constexpr auto saturate_cast(T val) noexcept -> R { if constexpr(std::numeric_limits::digits < std::numeric_limits::digits) { if constexpr(std::is_signed_v && std::is_signed_v) { if(val < std::numeric_limits::min()) return std::numeric_limits::min(); } if(val > T{std::numeric_limits::max()}) return std::numeric_limits::max(); } if constexpr(std::is_unsigned_v && std::is_signed_v) { if(val < 0) return R{0}; } return gsl::narrow_cast(val); } } /* namespace al */ template [[nodiscard]] constexpr auto as_unsigned(T value) noexcept { return static_cast>(value); } template [[nodiscard]] constexpr auto as_signed(T value) noexcept { return static_cast>(value); } [[nodiscard]] constexpr auto GetCounterSuffix(usize const 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; } [[nodiscard]] constexpr auto lerpf(f32 const val1, f32 const val2, f32 const mu) noexcept -> f32 { return val1 + (val2-val1)*mu; } /** Find the next power-of-2 for non-power-of-2 numbers. */ [[nodiscard]] constexpr auto NextPowerOf2(u32 value) noexcept -> u32 { 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 toward zero to the next * multiple. */ template [[nodiscard]] constexpr auto RoundToZero(T value, std::type_identity_t r) noexcept -> T { return value - (value%r); } /** * If the value is not already a multiple of r, round away from zero to the * next multiple. */ template [[nodiscard]] constexpr auto RoundFromZero(T value, std::type_identity_t r) noexcept -> T { if(value >= 0) return RoundToZero(value + r-1, r); return RoundToZero(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. */ [[nodiscard]] inline auto fastf2i(f32 const f) noexcept -> i32 { #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 gsl::narrow_cast(f); #endif } [[nodiscard]] inline auto fastf2u(f32 const f) noexcept -> u32 { return gsl::narrow_cast(fastf2i(f)); } /** * Converts float-to-int using standard behavior (truncation). Out of range * values are clamped. */ [[nodiscard]] inline auto float2int(f32 const f) noexcept -> i32 { /* We can't rely on SSE or the compiler generated conversion if we want * clamping behavior with overflow and underflow. */ const auto conv_i = std::bit_cast(f); const auto sign = (conv_i>>31) | 1; const auto shift = ((conv_i>>23)&0xff) - (127+23); /* Too small. */ if(shift < -23) [[unlikely]] return 0; /* Too large (or NaN). */ if(shift > 7) [[unlikely]] return (sign > 0) ? std::numeric_limits::max() : std::numeric_limits::min(); const auto mant = (conv_i&0x7f'ff'ff) | 0x80'00'00; if(shift < 0) [[likely]] return (mant >> -shift) * sign; return (mant << shift) * sign; } /** * Converts float-to-uint using standard behavior (truncation). Out of range * values are clamped. */ [[nodiscard]] inline auto float2uint(f32 const f) noexcept -> u32 { const auto conv_i = std::bit_cast(f); /* A 0 mask for negative values creates a 0 result. */ const auto mask = static_cast(conv_i>>31) ^ 0xff'ff'ff'ff_u32; const auto shift = ((conv_i>>23)&0xff) - (127+23); if(shift < -23) [[unlikely]] return 0; if(shift > 8) [[unlikely]] return std::numeric_limits::max() & mask; const auto mant = gsl::narrow_cast(conv_i&0x7f'ff'ff) | 0x80'00'00_u32; if(shift < 0) [[likely]] return (mant >> -shift) & mask; return (mant << shift) & mask; } /** * 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). */ [[nodiscard]] inline auto fast_roundf(f32 f) noexcept -> f32 { #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 auto ilim = std::array{ 8388608.0f /* 0x1.0p+23 */, -8388608.0f /* -0x1.0p+23 */ }; const auto conv_u = std::bit_cast(f); const auto sign = (conv_u>>31u)&0x01u; const auto expo = (conv_u>>23u)&0xffu; 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. [[nodiscard]] inline auto level_mb_to_gain(f32 const x) -> f32 { if(x <= -10'000.0f) return 0.0f; return std::pow(10.0f, x / 2'000.0f); } // Converts gain to level (mB). [[nodiscard]] inline auto gain_to_level_mb(f32 const x) -> f32 { if(x <= 1e-05f) return -10'000.0f; return std::max(std::log10(x) * 2'000.0f, -10'000.0f); } #endif /* AL_NUMERIC_H */ kcat-openal-soft-75c0059/common/alstring.cpp000066400000000000000000000021061512220627100207530ustar00rootroot00000000000000 #include "config.h" #include "alstring.h" #include #include #include #include #include namespace al { auto case_compare(const std::string_view str0, const std::string_view str1) noexcept -> std::weak_ordering { return std::lexicographical_compare_three_way(str0.cbegin(), str0.cend(), str1.cbegin(), str1.cend(), [](const char ch0, const char ch1) -> std::weak_ordering { using Traits = std::string_view::traits_type; return std::toupper(Traits::to_int_type(ch0)) <=> std::toupper(Traits::to_int_type(ch1)); }); } auto case_compare(const std::wstring_view str0, const std::wstring_view str1) noexcept -> std::weak_ordering { return std::lexicographical_compare_three_way(str0.cbegin(), str0.cend(), str1.cbegin(), str1.cend(), [](const wchar_t ch0, const wchar_t ch1) -> std::weak_ordering { using Traits = std::wstring_view::traits_type; return std::towupper(Traits::to_int_type(ch0)) <=> std::towupper(Traits::to_int_type(ch1)); }); } } // namespace al kcat-openal-soft-75c0059/common/alstring.h000066400000000000000000000026311512220627100204230ustar00rootroot00000000000000#ifndef AL_STRING_H #define AL_STRING_H #include #include "opthelpers.h" namespace al { [[nodiscard]] constexpr bool contains(const std::string_view str0, const std::string_view str1) noexcept { return str0.find(str1) != std::string_view::npos; } [[nodiscard]] auto case_compare(const std::string_view str0, const std::string_view str1) noexcept -> std::weak_ordering; [[nodiscard]] auto case_compare(const std::wstring_view str0, const std::wstring_view str1) noexcept -> std::weak_ordering; /* NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast) * C++20 changes path::u8string() to return a string using a new/distinct * char8_t type for UTF-8 strings, and deprecates u8path in favor of using * fs::path(char8_t*). However, support for this type with standard string * functions is totally inadequate, and we already hold UTF-8 with plain char* * strings. So these functions are used to reinterpret between char and char8_t * string views. */ inline auto char_as_u8(const std::string_view str LIFETIMEBOUND) -> std::u8string_view { return std::u8string_view{reinterpret_cast(str.data()), str.size()}; } inline auto u8_as_char(const std::u8string_view str LIFETIMEBOUND) -> std::string_view { return std::string_view{reinterpret_cast(str.data()), str.size()}; } /* NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast) */ } // namespace al #endif /* AL_STRING_H */ kcat-openal-soft-75c0059/common/althrd_setname.cpp000066400000000000000000000035741512220627100221340ustar00rootroot00000000000000 #include "config.h" #include "althrd_setname.h" #ifdef _WIN32 #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 kcat-openal-soft-75c0059/common/althrd_setname.h000066400000000000000000000002161512220627100215670ustar00rootroot00000000000000#ifndef COMMON_ALTHRD_SETNAME_H #define COMMON_ALTHRD_SETNAME_H void althrd_setname(const char *name); #endif /* COMMON_ALTHRD_SETNAME_H */ kcat-openal-soft-75c0059/common/althreads.h000066400000000000000000000072771512220627100205620ustar00rootroot00000000000000#ifndef AL_THREADS_H #define AL_THREADS_H #include #include #include #include #ifdef _WIN32 #include #elif defined(__STDC_NO_THREADS__) || !__has_include() #include #else #include #endif 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 std::bit_cast(value); else if constexpr(std::is_integral_v) return std::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 std::bit_cast(ptr); else if constexpr(std::is_integral_v) return static_cast(std::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(const auto res = pthread_key_create(&mTss, nullptr); res != 0) throw std::runtime_error{"al::tss::tss()"}; } explicit tss(const T &init) : tss{} { if(const auto 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(const auto 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(const auto res = tss_create(&mTss, nullptr); res != thrd_success) throw std::runtime_error{"al::tss::tss()"}; } explicit tss(const T &init) : tss{} { if(const auto 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(const auto 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 */ kcat-openal-soft-75c0059/common/altypes.hpp000066400000000000000000000036031512220627100206210ustar00rootroot00000000000000#ifndef AL_TYPES_HPP #define AL_TYPES_HPP #include #include #include "gsl/gsl" using i8 = std::int8_t; using u8 = std::uint8_t; using i16 = std::int16_t; using u16 = std::uint16_t; using i32 = std::int32_t; using u32 = std::uint32_t; using i64 = std::int64_t; using u64 = std::uint64_t; using isize = std::make_signed_t; using usize = std::size_t; using f32 = float; using f64 = double; [[nodiscard]] consteval auto operator ""_i8(unsigned long long const n) noexcept { return gsl::narrow(n); } [[nodiscard]] consteval auto operator ""_u8(unsigned long long const n) noexcept { return gsl::narrow(n); } [[nodiscard]] consteval auto operator ""_i16(unsigned long long const n) noexcept { return gsl::narrow(n); } [[nodiscard]] consteval auto operator ""_u16(unsigned long long const n) noexcept { return gsl::narrow(n); } [[nodiscard]] consteval auto operator ""_i32(unsigned long long const n) noexcept { return gsl::narrow(n); } [[nodiscard]] consteval auto operator ""_u32(unsigned long long const n) noexcept { return gsl::narrow(n); } [[nodiscard]] consteval auto operator ""_i64(unsigned long long const n) noexcept { return gsl::narrow(n); } [[nodiscard]] consteval auto operator ""_u64(unsigned long long const n) noexcept { return gsl::narrow(n); } [[nodiscard]] consteval auto operator ""_z(unsigned long long const n) noexcept { return gsl::narrow(n); } [[nodiscard]] consteval auto operator ""_uz(unsigned long long const n) noexcept { return gsl::narrow(n); } [[nodiscard]] consteval auto operator ""_zu(unsigned long long const n) noexcept { return gsl::narrow(n); } [[nodiscard]] consteval auto operator ""_f32(long double const n) noexcept { return static_cast(n); } [[nodiscard]] consteval auto operator ""_f64(long double const n) noexcept { return static_cast(n); } #endif /* AL_TYPES_HPP */ kcat-openal-soft-75c0059/common/atomic.h000066400000000000000000000175201512220627100200570ustar00rootroot00000000000000#ifndef AL_ATOMIC_H #define AL_ATOMIC_H #include #include #include #include "alnumeric.h" #include "gsl/gsl" #ifdef __APPLE__ #include #endif #if defined(MAC_OS_X_VERSION_MIN_REQUIRED) && MAC_OS_X_VERSION_MIN_REQUIRED < 110000 /* For macOS versions before 11, we can't rely on atomic::wait and friends, and * need to use custom methods. */ #include #include /* These could be put into a table where, rather than one mutex/condvar/counter * set that all atomic waiters use, individual waiters will use one of a number * of different sets dependent on their address, reducing the number of waiters * on any given set. However, we use this as a fallback for much older macOS * systems and only expect one atomic waiter per device, and one device per * process, which itself won't wake the atomic terribly often, so the extra * complexity and resources may be best avoided if not necessary. */ inline auto gAtomicWaitMutex = std::mutex{}; inline auto gAtomicWaitCondVar = std::condition_variable{}; inline auto gAtomicWaitCounter = 0_u32; /* See: https://outerproduct.net/futex-dictionary.html */ #define UL_COMPARE_AND_WAIT 1 #define UL_UNFAIR_LOCK 2 #define UL_COMPARE_AND_WAIT_SHARED 3 #define UL_UNFAIR_LOCK64_SHARED 4 #define UL_COMPARE_AND_WAIT64 5 #define UL_COMPARE_AND_WAIT64_SHARED 6 #define ULF_WAKE_ALL 0x00000100 extern "C" { auto __attribute__((weak_import)) __ulock_wait(u32 op, void *addr, u64 value, u32 timeout) -> int; auto __attribute__((weak_import)) __ulock_wake(u32 op, void *addr, u64 wake_value) -> int; } /* extern "C" */ #endif 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 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 auto atomic_wait(std::atomic &aval, T const value, std::memory_order const order = std::memory_order_seq_cst) noexcept -> void { #if defined(MAC_OS_X_VERSION_MIN_REQUIRED) && MAC_OS_X_VERSION_MIN_REQUIRED < 110000 static_assert(sizeof(aval) == sizeof(T)); if(sizeof(T) == sizeof(u32) && __ulock_wait != nullptr) { while(aval.load(order) == value) __ulock_wait(UL_COMPARE_AND_WAIT, &aval, value, 0); } #if MAC_OS_X_VERSION_MIN_REQUIRED >= 101500 else if(sizeof(T) == sizeof(u64) && __ulock_wait != nullptr) { while(aval.load(order) == value) __ulock_wait(UL_COMPARE_AND_WAIT64, &aval, value, 0); } #endif else { auto lock = std::unique_lock{gAtomicWaitMutex}; ++gAtomicWaitCounter; while(aval.load(order) == value) gAtomicWaitCondVar.wait(lock); --gAtomicWaitCounter; } #else aval.wait(value, order); #endif } template auto atomic_notify_one(std::atomic &aval) noexcept -> void { #if defined(MAC_OS_X_VERSION_MIN_REQUIRED) && MAC_OS_X_VERSION_MIN_REQUIRED < 110000 static_assert(sizeof(aval) == sizeof(T)); if(sizeof(T) == sizeof(u32) && __ulock_wake != nullptr) __ulock_wake(UL_COMPARE_AND_WAIT, &aval, 0); #if MAC_OS_X_VERSION_MIN_REQUIRED >= 101500 else if(sizeof(T) == sizeof(u64) && __ulock_wake != nullptr) __ulock_wake(UL_COMPARE_AND_WAIT64, &aval, 0); #endif else { auto lock = std::unique_lock{gAtomicWaitMutex}; auto const numwaits = gAtomicWaitCounter; lock.unlock(); if(numwaits > 0) { /* notify_all since we can't guarantee notify_one will wake a * waiter waiting on this particular object. With notify_all, we * just act as if all waiters were spuriously woken up and they'll * recheck. */ gAtomicWaitCondVar.notify_all(); } } #else aval.notify_one(); #endif } template auto atomic_notify_all(std::atomic &aval) noexcept -> void { #if defined(MAC_OS_X_VERSION_MIN_REQUIRED) && MAC_OS_X_VERSION_MIN_REQUIRED < 110000 static_assert(sizeof(aval) == sizeof(T)); if(sizeof(T) == sizeof(u32) && __ulock_wake != nullptr) __ulock_wake(UL_COMPARE_AND_WAIT | ULF_WAKE_ALL, &aval, 0); #if defined(MAC_OS_X_VERSION_MIN_REQUIRED) && MAC_OS_X_VERSION_MIN_REQUIRED >= 101500 else if(sizeof(T) == sizeof(u64) && __ulock_wake != nullptr) __ulock_wake(UL_COMPARE_AND_WAIT64 | ULF_WAKE_ALL, &aval, 0); #endif else { auto lock = std::unique_lock{gAtomicWaitMutex}; auto const numwaits = gAtomicWaitCounter; lock.unlock(); if(numwaits > 0) gAtomicWaitCondVar.notify_all(); } #else aval.notify_all(); #endif } 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{std::move(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(std::move(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(std::move(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(std::move(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 */ kcat-openal-soft-75c0059/common/comptr.h000066400000000000000000000065431512220627100201120ustar00rootroot00000000000000#ifndef COMMON_COMPTR_H #define COMMON_COMPTR_H #ifdef _WIN32 #include #include #include #include class ComWrapper { HRESULT mStatus{}; public: ComWrapper(void *const reserved, DWORD const coinit) : mStatus{CoInitializeEx(reserved, coinit)} { } explicit ComWrapper(DWORD const coinit=COINIT_APARTMENTTHREADED) : mStatus{CoInitializeEx(nullptr, coinit)} { } ComWrapper(ComWrapper&& rhs) noexcept { mStatus = std::exchange(rhs.mStatus, E_FAIL); } ComWrapper(const ComWrapper&) = delete; ~ComWrapper() { if(SUCCEEDED(mStatus)) CoUninitialize(); } auto operator=(ComWrapper&& rhs) noexcept -> ComWrapper& { if(this != &rhs) [[likely]] { if(SUCCEEDED(mStatus)) CoUninitialize(); mStatus = std::exchange(rhs.mStatus, E_FAIL); } return *this; } auto operator=(const ComWrapper&) -> ComWrapper& = delete; [[nodiscard]] auto status() const noexcept -> HRESULT { return mStatus; } [[nodiscard]] 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 */ class ComPtr { T *mPtr{nullptr}; public: 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 *const ptr) noexcept : mPtr{ptr} { } ~ComPtr() { if(mPtr) mPtr->Release(); } /* NOLINTNEXTLINE(bugprone-unhandled-self-assignment) Yes it is. */ auto operator=(const ComPtr &rhs) noexcept(RefIsNoexcept) -> ComPtr& { if constexpr(RefIsNoexcept) { if(rhs.mPtr) rhs.mPtr->AddRef(); if(mPtr) mPtr->Release(); mPtr = rhs.mPtr; return *this; } else { auto tmp = rhs; if(mPtr) mPtr->Release(); mPtr = tmp.release(); return *this; } } auto operator=(ComPtr&& rhs) noexcept(RefIsNoexcept) -> ComPtr& { if(&rhs != this) [[likely]] { if(mPtr) mPtr->Release(); mPtr = std::exchange(rhs.mPtr, nullptr); } return *this; } void reset(T *const ptr=nullptr) noexcept(RefIsNoexcept) { if(mPtr) mPtr->Release(); mPtr = ptr; } [[nodiscard]] 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; } [[nodiscard]] auto release() noexcept -> T* { return std::exchange(mPtr, nullptr); } void swap(ComPtr &rhs) noexcept { std::swap(mPtr, rhs.mPtr); } }; template void swap(ComPtr &lhs, ComPtr &rhs) noexcept { lhs.swap(rhs); } #endif /* _WIN32 */ #endif kcat-openal-soft-75c0059/common/dlopennote.h000066400000000000000000000116351512220627100207530ustar00rootroot00000000000000/* Simple DirectMedia Layer Copyright (C) 1997-2025 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ #ifndef OAL_dynapi_dlopennote_h #define OAL_dynapi_dlopennote_h #define OAL_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED "required" #define OAL_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED "recommended" #define OAL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED "suggested" #if defined(__ELF__) && defined(HAVE_DLOPEN_NOTES) /* Modified here to avoid unsafe C arrays and use proper C++ types. */ #include #include #define OAL_ELF_NOTE_DLOPEN_VENDOR "FDO" #define OAL_ELF_NOTE_DLOPEN_TYPE 0x407c0c0aU #define OAL_ELF_NOTE_INTERNAL2(json, variable_name) \ [[gnu::aligned(4), gnu::used, gnu::section(".note.dlopen")]] \ const struct { \ struct { \ std::uint32_t n_namesz; \ std::uint32_t n_descsz; \ std::uint32_t n_type; \ } nhdr; \ std::array name; \ [[gnu::aligned(4)]] \ std::array dlopen_json; \ } variable_name = { \ { \ sizeof(OAL_ELF_NOTE_DLOPEN_VENDOR), \ sizeof(json), \ OAL_ELF_NOTE_DLOPEN_TYPE \ }, \ std::to_array(OAL_ELF_NOTE_DLOPEN_VENDOR), \ std::to_array(json) \ } #define OAL_ELF_NOTE_INTERNAL(json, variable_name) \ OAL_ELF_NOTE_INTERNAL2(json, variable_name) /* TODO: A constexpr function should be able to concatenate a variable number * of char arrays, instead of a set of macros like this. */ #define OAL_SONAME_ARRAY1(N1) "[\"" N1 "\"]" #define OAL_SONAME_ARRAY2(N1,N2) "[\"" N1 "\",\"" N2 "\"]" #define OAL_SONAME_ARRAY3(N1,N2,N3) "[\"" N1 "\",\"" N2 "\",\"" N3 "\"]" #define OAL_SONAME_ARRAY4(N1,N2,N3,N4) "[\"" N1 "\",\"" N2 "\",\"" N3 "\",\"" N4 "\"]" #define OAL_SONAME_ARRAY5(N1,N2,N3,N4,N5) "[\"" N1 "\",\"" N2 "\",\"" N3 "\",\"" N4 "\",\"" N5 "\"]" #define OAL_SONAME_ARRAY6(N1,N2,N3,N4,N5,N6) "[\"" N1 "\",\"" N2 "\",\"" N3 "\",\"" N4 "\",\"" N5 "\",\"" N6 "\"]" #define OAL_SONAME_ARRAY7(N1,N2,N3,N4,N5,N6,N7) "[\"" N1 "\",\"" N2 "\",\"" N3 "\",\"" N4 "\",\"" N5 "\",\"" N6 "\",\"" N7 "\"]" #define OAL_SONAME_ARRAY8(N1,N2,N3,N4,N5,N6,N7,N8) "[\"" N1 "\",\"" N2 "\",\"" N3 "\",\"" N4 "\",\"" N5 "\",\"" N6 "\",\"" N7 "\",\"" N8 "\"]" #define OAL_SONAME_ARRAY_GET(N1,N2,N3,N4,N5,N6,N7,N8,NAME,...) NAME #define OAL_SONAME_ARRAY(...) \ OAL_SONAME_ARRAY_GET(__VA_ARGS__, \ OAL_SONAME_ARRAY8, \ OAL_SONAME_ARRAY7, \ OAL_SONAME_ARRAY6, \ OAL_SONAME_ARRAY5, \ OAL_SONAME_ARRAY4, \ OAL_SONAME_ARRAY3, \ OAL_SONAME_ARRAY2, \ OAL_SONAME_ARRAY1 \ )(__VA_ARGS__) // Create "unique" variable name using __LINE__, // so creating elf notes on the same line is not supported #define OAL_ELF_NOTE_JOIN2(A,B) A##B #define OAL_ELF_NOTE_JOIN(A,B) OAL_ELF_NOTE_JOIN2(A,B) #define OAL_ELF_NOTE_UNIQUE_NAME OAL_ELF_NOTE_JOIN(s_dlopen_note_, __LINE__) #define OAL_ELF_NOTE_DLOPEN(feature, description, priority, ...) \ OAL_ELF_NOTE_INTERNAL( \ "[{\"feature\":\"" feature \ "\",\"description\":\"" description \ "\",\"priority\":\"" priority \ "\",\"soname\":" OAL_SONAME_ARRAY(__VA_ARGS__) "}]", \ OAL_ELF_NOTE_UNIQUE_NAME) #else #if defined (__GNUC__) && __GNUC__ < 3 #define OAL_ELF_NOTE_DLOPEN #else #define OAL_ELF_NOTE_DLOPEN(...) #endif #endif #endif kcat-openal-soft-75c0059/common/dynload.cpp000066400000000000000000000044631512220627100205720ustar00rootroot00000000000000 #include "config.h" #include "dynload.h" #ifdef _WIN32 #include #include "alformat.hpp" #include "gsl/gsl" #include "strutils.hpp" auto LoadLib(gsl::czstring const name) -> al::expected { if(auto const res = LoadLibraryW(utf8_to_wstr(name).c_str())) [[likely]] return res; auto const err = GetLastError(); auto message = std::wstring{}; message.resize(1024u); auto const res = FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), message.data(), gsl::narrow_cast(message.size()), nullptr); if(res > 0) { message.resize(res); return al::unexpected(wstr_to_utf8(message)); } return al::unexpected(al::format("LoadLibraryW error: {}", err)); } void CloseLib(void *const handle) { FreeLibrary(static_cast(handle)); } auto GetSymbol(void *const handle, gsl::czstring const name) -> al::expected { if(auto const sym = GetProcAddress(static_cast(handle), name)) [[likely]] { /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) */ return reinterpret_cast(sym); } auto const err = GetLastError(); auto message = std::wstring{}; message.resize(1024u); auto const res = FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), message.data(), gsl::narrow_cast(message.size()), nullptr); if(res > 0) { message.resize(res); return al::unexpected(wstr_to_utf8(message)); } return al::unexpected(al::format("GetProcAddress error: {}", err)); } #elif defined(HAVE_DLFCN_H) #include auto LoadLib(gsl::czstring const name) -> al::expected { dlerror(); auto *const handle = dlopen(name, RTLD_NOW); if(auto *const err = dlerror()) return al::unexpected(err); return handle; } void CloseLib(void *const handle) { dlclose(handle); } auto GetSymbol(void *const handle, gsl::czstring const name) -> al::expected { dlerror(); auto *const sym = dlsym(handle, name); if(auto *const err = dlerror()) return al::unexpected(err); return sym; } #endif kcat-openal-soft-75c0059/common/dynload.h000066400000000000000000000007651512220627100202400ustar00rootroot00000000000000#ifndef AL_DYNLOAD_H #define AL_DYNLOAD_H #include "config.h" #if defined(_WIN32) || defined(HAVE_DLFCN_H) #include #include "expected.hpp" #include "gsl/gsl" #define HAVE_DYNLOAD 1 #include "dlopennote.h" [[nodiscard]] auto LoadLib(gsl::czstring name) -> al::expected; void CloseLib(void *handle); [[nodiscard]] auto GetSymbol(void *handle, gsl::czstring name) -> al::expected; #else #define HAVE_DYNLOAD 0 #endif #endif /* AL_DYNLOAD_H */ kcat-openal-soft-75c0059/common/expected.hpp000066400000000000000000000115251512220627100207430ustar00rootroot00000000000000#ifndef AL_EXPECTED_HPP #define AL_EXPECTED_HPP #include #include #include namespace al { template class unexpected { E mError; public: constexpr unexpected(const unexpected&) = default; constexpr unexpected(unexpected&&) = default; template requires(!std::is_same_v, unexpected> && !std::is_same_v, std::in_place_t> && std::is_constructible_v) constexpr explicit unexpected(E2&& rhs) : mError{std::forward(rhs)} { } template requires(std::is_constructible_v) constexpr explicit unexpected(std::in_place_t, Args&& ...args) : mError{std::forward(args)...} { } template requires(std::is_constructible_v&, Args...>) constexpr explicit unexpected(std::in_place_t, std::initializer_list il, Args&& ...args) : mError{il, std::forward(args)...} { } [[nodiscard]] constexpr auto error() const& noexcept -> const E& { return mError; } [[nodiscard]] constexpr auto error() & noexcept -> E& { return mError; } [[nodiscard]] constexpr auto error() const&& noexcept -> const E&& { return std::move(mError); } [[nodiscard]] constexpr auto error() && noexcept -> E&& { return std::move(mError); } constexpr void swap(unexpected& other) noexcept(std::is_nothrow_swappable_v) { std::swap(mError, other.mError); } template friend constexpr auto operator==(const unexpected& lhs, const unexpected& rhs) -> bool { return lhs.error() == rhs.error(); } friend constexpr void swap(unexpected& lhs, unexpected& rhs) noexcept(noexcept(lhs.swap(rhs))) { lhs.swap(rhs); } }; template unexpected(E) -> unexpected; template class expected { using variant_type = std::variant; std::variant mValues; public: constexpr expected() noexcept(std::is_nothrow_default_constructible_v) = default; constexpr expected(const expected &rhs) noexcept(std::is_nothrow_copy_constructible_v) = default; constexpr expected(expected&& rhs) noexcept(std::is_nothrow_move_constructible_v) = default; /* Value constructors */ template requires(!std::is_same_v, std::in_place_t> && !std::is_same_v> && std::is_constructible_v) constexpr explicit(!std::is_convertible_v) expected(U&& v) : mValues{std::in_place_index<0>, std::forward(v)} { } /* Error constructors */ template requires(std::is_constructible_v) constexpr explicit(!std::is_convertible_v) expected(const unexpected &rhs) : mValues{std::in_place_index<1>, rhs.error()} { } template requires(std::is_constructible_v) constexpr explicit(!std::is_convertible_v) expected(unexpected&& rhs) : mValues{std::in_place_index<1>, std::move(rhs).error()} { } [[nodiscard]] constexpr auto has_value() const noexcept -> bool { return mValues.index() == 0; } [[nodiscard]] constexpr explicit operator bool() const noexcept { return has_value(); } [[nodiscard]] constexpr auto value() & -> S& { return std::get<0>(mValues); } [[nodiscard]] constexpr auto value() const& -> const S& { return std::get<0>(mValues); } [[nodiscard]] constexpr auto value() && -> S&& { return std::move(std::get<0>(mValues)); } [[nodiscard]] constexpr auto value() const&& -> const S&& { return std::move(std::get<0>(mValues)); } [[nodiscard]] constexpr auto operator->() noexcept -> S* { return &std::get<0>(mValues); } [[nodiscard]] constexpr auto operator->() const noexcept -> const S* { return &std::get<0>(mValues); } [[nodiscard]] constexpr auto operator*() noexcept -> S& { return std::get<0>(mValues); } [[nodiscard]] constexpr auto operator*() const noexcept -> const S& { return std::get<0>(mValues); } template [[nodiscard]] constexpr auto value_or(U&& defval) const& -> S { return bool{*this} ? **this : static_cast(std::forward(defval)); } template [[nodiscard]] constexpr auto value_or(U&& defval) && -> S { return bool{*this} ? std::move(**this) : static_cast(std::forward(defval)); } [[nodiscard]] constexpr auto error() & -> F& { return std::get<1>(mValues); } [[nodiscard]] constexpr auto error() const& -> const F& { return std::get<1>(mValues); } [[nodiscard]] constexpr auto error() && -> F&& { return std::move(std::get<1>(mValues)); } [[nodiscard]] constexpr auto error() const&& -> const F&& { return std::move(std::get<1>(mValues)); } }; } /* namespace al */ #endif /* AL_EXPECTED_HPP */ kcat-openal-soft-75c0059/common/filesystem.cpp000066400000000000000000000071661512220627100213270ustar00rootroot00000000000000//--------------------------------------------------------------------------------------- // // 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 kcat-openal-soft-75c0059/common/filesystem.h000066400000000000000000000102661512220627100207670ustar00rootroot00000000000000//--------------------------------------------------------------------------------------- // // 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 kcat-openal-soft-75c0059/common/flexarray.h000066400000000000000000000151331512220627100205760ustar00rootroot00000000000000#ifndef AL_FLEXARRAY_H #define AL_FLEXARRAY_H #include #include #include #include #include #include #include "almalloc.h" namespace al { /* Storage for flexible array data. This is trivially destructible if type T is * trivially destructible. */ template> struct alignas(alignment) FlexArrayStorage : std::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) : std::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 : std::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) : std::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 = Storage_t_::iterator; using reverse_iterator = Storage_t_::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(); } /* FIXME: We want to act like a container here, rather than a container * view. A const FlexArray should return const iterators, but std::span * doesn't have those until C++23. So for now, don't bother with them. */ #if 0 [[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(); } #else [[nodiscard]] auto begin() const noexcept -> iterator { return mStore.begin(); } [[nodiscard]] auto end() const noexcept -> iterator { return mStore.end(); } [[nodiscard]] auto rbegin() const noexcept -> reverse_iterator { return mStore.rbegin(); } [[nodiscard]] auto rend() const noexcept -> reverse_iterator { return mStore.rend(); } #endif 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 */ kcat-openal-soft-75c0059/common/ghc_filesystem.h000066400000000000000000005666551512220627100216320ustar00rootroot00000000000000//--------------------------------------------------------------------------------------- // // 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 kcat-openal-soft-75c0059/common/intrusive_ptr.h000066400000000000000000000123101512220627100215100ustar00rootroot00000000000000#ifndef INTRUSIVE_PTR_H #define INTRUSIVE_PTR_H #include #include #include #include #include #include "gsl/gsl" namespace al { template> class intrusive_ref : protected D { std::atomic mRef{1u}; intrusive_ref() = default; ~intrusive_ref() = default; friend T; public: unsigned int inc_ref() noexcept { return mRef.fetch_add(1, std::memory_order_acq_rel)+1; } unsigned int dec_ref() noexcept { auto ref = mRef.fetch_sub(1, std::memory_order_acq_rel)-1; if(ref == 0) [[unlikely]] D::operator()(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 class intrusive_ptr { T *mPtr{nullptr}; public: constexpr intrusive_ptr() noexcept = default; constexpr intrusive_ptr(const intrusive_ptr &rhs) noexcept : mPtr{rhs.mPtr} { if(mPtr) mPtr->inc_ref(); } constexpr intrusive_ptr(intrusive_ptr&& rhs) noexcept : mPtr{rhs.mPtr} { rhs.mPtr = nullptr; } constexpr intrusive_ptr(std::nullptr_t) noexcept { } /* NOLINT(google-explicit-constructor) */ explicit constexpr intrusive_ptr(T *ptr) noexcept : mPtr{ptr} { } constexpr ~intrusive_ptr() { if(mPtr) mPtr->dec_ref(); } constexpr auto operator=(const intrusive_ptr &rhs) noexcept -> intrusive_ptr& { static_assert(noexcept(std::declval()->dec_ref()), "dec_ref must be noexcept"); if(&rhs != this) [[likely]] { if(rhs.mPtr) rhs.mPtr->inc_ref(); if(mPtr) mPtr->dec_ref(); mPtr = rhs.mPtr; } return *this; } constexpr auto operator=(intrusive_ptr&& rhs) noexcept -> intrusive_ptr& { static_assert(noexcept(std::declval()->dec_ref()), "dec_ref must be noexcept"); if(&rhs != this) [[likely]] { if(mPtr) mPtr->dec_ref(); mPtr = std::exchange(rhs.mPtr, nullptr); } return *this; } explicit constexpr operator bool() const noexcept { return mPtr != nullptr; } [[nodiscard]] constexpr auto operator*() const noexcept -> T& { return *mPtr; } [[nodiscard]] constexpr auto operator->() const noexcept -> T* { return mPtr; } [[nodiscard]] constexpr auto get() const noexcept -> T* { return mPtr; } constexpr void reset(T *ptr=nullptr) noexcept { static_assert(noexcept(std::declval()->dec_ref()), "dec_ref must be noexcept"); if(mPtr) mPtr->dec_ref(); mPtr = ptr; } constexpr auto release() noexcept -> T* { return std::exchange(mPtr, nullptr); } constexpr void swap(intrusive_ptr &rhs) noexcept { std::swap(mPtr, rhs.mPtr); } }; template void swap(intrusive_ptr &lhs, intrusive_ptr &rhs) noexcept { lhs.swap(rhs); } template requires std::three_way_comparable_with [[nodiscard]] constexpr auto operator<=>(const intrusive_ptr &lhs, std::nullptr_t) noexcept { return std::compare_three_way{}(lhs.get(), nullptr); } template [[nodiscard]] constexpr auto operator==(const intrusive_ptr &lhs, std::nullptr_t) noexcept { return !lhs; } template [[nodiscard]] constexpr auto operator!=(const intrusive_ptr &lhs, std::nullptr_t) noexcept { return !(lhs == nullptr); } template requires std::three_way_comparable_with [[nodiscard]] constexpr auto operator<=>(std::nullptr_t, const intrusive_ptr &rhs) noexcept { return std::compare_three_way{}(nullptr, rhs.get()); } template [[nodiscard]] constexpr auto operator==(std::nullptr_t, const intrusive_ptr &rhs) noexcept { return !rhs; } template [[nodiscard]] constexpr auto operator!=(std::nullptr_t, const intrusive_ptr &rhs) noexcept { return !(rhs == nullptr); } template requires std::three_way_comparable_with [[nodiscard]] constexpr auto operator<=>(const intrusive_ptr &lhs, const intrusive_ptr& rhs) noexcept { return std::compare_three_way{}(lhs.get(), rhs.get()); } template requires std::equality_comparable_with [[nodiscard]] constexpr auto operator==(const intrusive_ptr &lhs, const intrusive_ptr& rhs) noexcept { return lhs.get() == rhs.get(); } template requires std::equality_comparable_with [[nodiscard]] constexpr auto operator!=(const intrusive_ptr &lhs, const intrusive_ptr& rhs) noexcept { return !(lhs == rhs); } } // namespace al #endif /* INTRUSIVE_PTR_H */ kcat-openal-soft-75c0059/common/opthelpers.h000066400000000000000000000041401512220627100207620ustar00rootroot00000000000000#ifndef OPTHELPERS_H #define OPTHELPERS_H #include #include #include "gsl/gsl" #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 /* 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(false) #else #define ASSUME(x) (static_cast(0)) #endif #if !defined(_WIN32) && HAS_ATTRIBUTE(gnu::visibility) #define DECL_HIDDEN [[gnu::visibility("hidden")]] #else #define DECL_HIDDEN #endif #if HAS_ATTRIBUTE(clang::lifetimebound) #define LIFETIMEBOUND [[clang::lifetimebound]] #elif HAS_ATTRIBUTE(msvc::lifetimebound) #define LIFETIMEBOUND [[msvc::lifetimebound]] #elif HAS_ATTRIBUTE(lifetimebound) #define LIFETIMEBOUND [[lifetimebound]] #else #define LIFETIMEBOUND #endif namespace al { template constexpr std::underlying_type_t to_underlying(T e) noexcept { return static_cast>(e); } /** * Gets a not_null from a not_null>, hopefully avoiding ths * extraneous null check from not_null's constructor. */ template constexpr auto get_not_null(const gsl::not_null &val) noexcept { auto *tmp = std::to_address(val); ASSUME(tmp != nullptr); return gsl::make_not_null(tmp); } } // namespace al #endif /* OPTHELPERS_H */ kcat-openal-soft-75c0059/common/pffft.cpp000066400000000000000000002362701512220627100202500ustar00rootroot00000000000000//$ 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 #include #include #include #include "almalloc.h" #include "alnumeric.h" #include "fmt/core.h" #include "fmt/ranges.h" #include "gsl/gsl" #include "opthelpers.h" #include "pragmadefs.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 auto SimdSize = 4u; force_inline auto vzero() noexcept -> v4sf { return (vector float)vec_splat_u8(0); } force_inline auto vmul(v4sf a, v4sf b) noexcept -> v4sf { return vec_madd(a, b, vzero()); } force_inline auto vadd(v4sf a, v4sf b) noexcept -> v4sf { return vec_add(a, b); } force_inline auto vmadd(v4sf a, v4sf b, v4sf c) noexcept -> v4sf { return vec_madd(a, b, c); } force_inline auto vsub(v4sf a, v4sf b) noexcept -> v4sf { return vec_sub(a, b); } force_inline auto ld_ps1(float a) noexcept -> v4sf { return vec_splats(a); } force_inline auto vset4(float a, float b, float c, float d) noexcept -> v4sf { /* There a more efficient way to do this? */ alignas(16) auto vals = std::array{a, b, c, d}; return vec_ld(0, vals.data()); } force_inline auto vinsert0(v4sf v, float a) noexcept -> v4sf { return vec_insert(a, v, 0); } force_inline auto vextract0(v4sf v) noexcept -> float { return vec_extract(v, 0); } force_inline auto vswaphl(v4sf a, v4sf b) noexcept -> v4sf { return vec_perm(a,b, (vector unsigned char){16,17,18,19,20,21,22,23,8,9,10,11,12,13,14,15}); } 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 { auto y0 = vec_mergeh(x0, x2); auto y1 = vec_mergel(x0, x2); auto y2 = vec_mergeh(x1, x3); auto 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); } /* * 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 auto SimdSize = 4u; force_inline auto vzero() noexcept -> v4sf { return _mm_setzero_ps(); } force_inline auto vmul(v4sf a, v4sf b) noexcept -> v4sf { return _mm_mul_ps(a, b); } force_inline auto vadd(v4sf a, v4sf b) noexcept -> v4sf { return _mm_add_ps(a, b); } force_inline auto vmadd(v4sf a, v4sf b, v4sf c) noexcept -> v4sf { return _mm_add_ps(_mm_mul_ps(a,b), c); } force_inline auto vsub(v4sf a, v4sf b) noexcept -> v4sf { return _mm_sub_ps(a, b); } force_inline auto ld_ps1(float a) noexcept -> v4sf { return _mm_set1_ps(a); } force_inline auto vset4(float a, float b, float c, float d) noexcept -> v4sf { return _mm_setr_ps(a, b, c, d); } force_inline auto vinsert0(const v4sf v, const float a) noexcept -> v4sf { return _mm_move_ss(v, _mm_set_ss(a)); } force_inline auto vextract0(v4sf v) noexcept -> float { return _mm_cvtss_f32(v); } force_inline auto vswaphl(v4sf a, v4sf b) noexcept -> v4sf { return _mm_shuffle_ps(b, a, _MM_SHUFFLE(3,2,1,0)); } 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); } /* * ARM NEON support macros */ #elif defined(__ARM_NEON) || defined(__aarch64__) || defined(__arm64) || defined(_M_ARM64) #include using v4sf = float32x4_t; constexpr auto SimdSize = 4u; force_inline auto vzero() noexcept -> v4sf { return vdupq_n_f32(0.0f); } force_inline auto vmul(v4sf a, v4sf b) noexcept -> v4sf { return vmulq_f32(a, b); } force_inline auto vadd(v4sf a, v4sf b) noexcept -> v4sf { return vaddq_f32(a, b); } force_inline auto vmadd(v4sf a, v4sf b, v4sf c) noexcept -> v4sf { return vmlaq_f32(c, a, b); } force_inline auto vsub(v4sf a, v4sf b) noexcept -> v4sf { return vsubq_f32(a, b); } force_inline auto ld_ps1(float a) noexcept -> v4sf { return vdupq_n_f32(a); } force_inline auto vset4(float a, float b, float c, float d) noexcept -> v4sf { auto 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 auto vinsert0(v4sf v, float a) noexcept -> v4sf { return vsetq_lane_f32(a, v, 0); } force_inline auto vextract0(v4sf v) noexcept -> float { return vgetq_lane_f32(v, 0); } force_inline auto vswaphl(v4sf a, v4sf b) noexcept -> v4sf { return vcombine_f32(vget_low_f32(b), vget_high_f32(a)); } force_inline void interleave2(v4sf in1, v4sf in2, v4sf &out1, v4sf &out2) noexcept { auto 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 { auto 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)::); */ auto t0_ = vzipq_f32(x0, x2); auto t1_ = vzipq_f32(x1, x3); auto u0_ = vzipq_f32(t0_.val[0], t1_.val[0]); auto 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]; } /* * Generic GCC vector macros */ #elif defined(__GNUC__) using v4sf [[gnu::vector_size(16), gnu::aligned(16)]] = float; constexpr auto SimdSize = 4u; force_inline constexpr auto vzero() noexcept -> v4sf { return v4sf{0.0f, 0.0f, 0.0f, 0.0f}; } force_inline constexpr auto vmul(v4sf a, v4sf b) noexcept -> v4sf { return a * b; } force_inline constexpr auto vadd(v4sf a, v4sf b) noexcept -> v4sf { return a + b; } force_inline constexpr auto vmadd(v4sf a, v4sf b, v4sf c) noexcept -> v4sf { return a*b + c; } force_inline constexpr auto vsub(v4sf a, v4sf b) noexcept -> v4sf { return a - b; } force_inline constexpr auto ld_ps1(float a) noexcept -> v4sf { return v4sf{a, a, a, a}; } force_inline constexpr auto vset4(float a, float b, float c, float d) noexcept -> v4sf { return v4sf{a, b, c, d}; } force_inline constexpr auto vinsert0(v4sf v, float a) noexcept -> v4sf { return v4sf{a, v[1], v[2], v[3]}; } force_inline auto vextract0(v4sf v) noexcept -> float { return v[0]; } force_inline auto unpacklo(v4sf a, v4sf b) noexcept -> v4sf { return v4sf{a[0], b[0], a[1], b[1]}; } force_inline auto unpackhi(v4sf a, v4sf b) noexcept -> v4sf { return v4sf{a[2], b[2], a[3], b[3]}; } force_inline auto vswaphl(v4sf a, v4sf b) noexcept -> v4sf { return v4sf{b[0], b[1], a[2], a[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 { auto tmp0 = unpacklo(x0, x1); auto tmp2 = unpacklo(x2, x3); auto tmp1 = unpackhi(x0, x1); auto 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]}; } #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 auto SimdSize = 1u; force_inline constexpr auto vmul(v4sf a, v4sf b) noexcept -> v4sf { return a * b; } force_inline constexpr auto vadd(v4sf a, v4sf b) noexcept -> v4sf { return a + b; } force_inline constexpr auto vmadd(v4sf a, v4sf b, v4sf c) noexcept -> v4sf { return a*b + c; } force_inline constexpr auto vsub(v4sf a, v4sf b) noexcept -> v4sf { return a - b; } force_inline constexpr auto ld_ps1(float a) noexcept -> v4sf { return a; } #else [[maybe_unused, nodiscard]] inline auto valigned(const float *ptr) noexcept -> bool { static constexpr auto alignmask = uintptr_t{SimdSize*sizeof(float) - 1}; return (std::bit_cast(ptr) & alignmask) == 0; } #endif // shortcuts for complex multiplications force_inline void vcplxmul(v4sf &ar, v4sf &ai, v4sf br, v4sf bi) noexcept { auto 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 { auto 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 std::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]]) { Expects(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{gsl::narrow(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 = std::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 = std::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 = std::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 = std::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 = std::bit_cast(t_v); auto u_f = std::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 = std::bit_cast(t_v); u_f = std::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 = std::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 = std::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 = std::bit_cast(a0_v); auto a1_f = std::bit_cast(a1_v); auto a2_f = std::bit_cast(a2_v); auto a3_f = std::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 = 64_uz; constexpr auto V4sfAlignVal = gsl::narrow(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 auto l1ido = l1*ido; if(ido <= 2) { for(auto k = 0_uz;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(auto k = 0_uz;k < l1ido;k += ido, ch += ido, cc += 2*ido) { for(auto i = 0_uz;i < ido-1;i += 2) { auto tr2 = vsub(cc[i+0], cc[i+ido+0]); auto ti2 = vsub(cc[i+1], cc[i+ido+1]); auto wr = ld_ps1(wa1[i]); auto 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) { Expects(ido > 2); const auto taur = ld_ps1(-0.5f); const auto taui = ld_ps1(0.866025403784439f*fsign); const auto l1ido = l1*ido; const auto wa2 = wa1 + ido; for(auto k = 0_uz;k < l1ido;k += ido, cc += 3*ido, ch +=ido) { for(auto i = 0_uz;i < ido-1;i += 2) { auto tr2 = vadd(cc[i+ido], cc[i+2*ido]); auto cr2 = vmadd(taur, tr2, cc[i]); ch[i] = vadd(tr2, cc[i]); auto ti2 = vadd(cc[i+ido+1], cc[i+2*ido+1]); auto ci2 = vmadd(taur, ti2, cc[i+1]); ch[i+1] = vadd(cc[i+1], ti2); auto cr3 = vmul(taui, vsub(cc[i+ido], cc[i+2*ido])); auto ci3 = vmul(taui, vsub(cc[i+ido+1], cc[i+2*ido+1])); auto dr2 = vsub(cr2, ci3); auto dr3 = vadd(cr2, ci3); auto di2 = vadd(ci2, cr3); auto di3 = vsub(ci2, cr3); auto wr1 = wa1[i]; auto wi1 = wa1[i+1]*fsign; auto wr2 = wa2[i]; auto wi2 = wa2[i+1]*fsign; 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 auto vsign = ld_ps1(fsign); const auto l1ido = l1*ido; if(ido == 2) { for(auto k = 0_uz;k < l1ido;k += ido, ch += ido, cc += 4*ido) { auto tr1 = vsub(cc[0], cc[2*ido + 0]); auto tr2 = vadd(cc[0], cc[2*ido + 0]); auto ti1 = vsub(cc[1], cc[2*ido + 1]); auto ti2 = vadd(cc[1], cc[2*ido + 1]); auto ti4 = vmul(vsub(cc[1*ido + 0], cc[3*ido + 0]), vsign); auto tr4 = vmul(vsub(cc[3*ido + 1], cc[1*ido + 1]), vsign); auto tr3 = vadd(cc[ido + 0], cc[3*ido + 0]); auto 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(auto k = 0_uz;k < l1ido;k += ido, ch+=ido, cc += 4*ido) { for(auto i = 0_uz;i < ido-1;i+=2) { auto tr1 = vsub(cc[i + 0], cc[i + 2*ido + 0]); auto tr2 = vadd(cc[i + 0], cc[i + 2*ido + 0]); auto ti1 = vsub(cc[i + 1], cc[i + 2*ido + 1]); auto ti2 = vadd(cc[i + 1], cc[i + 2*ido + 1]); auto tr4 = vmul(vsub(cc[i + 3*ido + 1], cc[i + 1*ido + 1]), vsign); auto ti4 = vmul(vsub(cc[i + 1*ido + 0], cc[i + 3*ido + 0]), vsign); auto tr3 = vadd(cc[i + ido + 0], cc[i + 3*ido + 0]); auto ti3 = vadd(cc[i + ido + 1], cc[i + 3*ido + 1]); ch[i] = vadd(tr2, tr3); auto cr3 = vsub(tr2, tr3); ch[i + 1] = vadd(ti2, ti3); auto ci3 = vsub(ti2, ti3); auto cr2 = vadd(tr1, tr4); auto cr4 = vsub(tr1, tr4); auto ci2 = vadd(ti1, ti4); auto ci4 = vsub(ti1, ti4); auto wr1 = wa1[i]; auto wi1 = fsign*wa1[i+1]; vcplxmul(cr2, ci2, ld_ps1(wr1), ld_ps1(wi1)); auto wr2 = wa2[i]; auto wi2 = fsign*wa2[i+1]; ch[i + l1ido] = cr2; ch[i + l1ido + 1] = ci2; vcplxmul(cr3, ci3, ld_ps1(wr2), ld_ps1(wi2)); auto wr3 = wa3[i]; auto 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 auto tr11 = ld_ps1(0.309016994374947f); const auto tr12 = ld_ps1(-0.809016994374947f); const auto ti11 = ld_ps1(0.951056516295154f*fsign); const auto 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]; }; Expects(ido > 2); const auto wa2 = wa1 + ido; const auto wa3 = wa2 + ido; const auto wa4 = wa3 + ido; for(auto k = 0_uz;k < l1;++k, cc += 5*ido, ch += ido) { for(auto i = 0_uz;i < ido-1;i += 2) { auto ti5 = vsub(cc_ref(i , 2), cc_ref(i , 5)); auto ti2 = vadd(cc_ref(i , 2), cc_ref(i , 5)); auto ti4 = vsub(cc_ref(i , 3), cc_ref(i , 4)); auto ti3 = vadd(cc_ref(i , 3), cc_ref(i , 4)); auto tr5 = vsub(cc_ref(i-1, 2), cc_ref(i-1, 5)); auto tr2 = vadd(cc_ref(i-1, 2), cc_ref(i-1, 5)); auto tr4 = vsub(cc_ref(i-1, 3), cc_ref(i-1, 4)); auto 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)); auto cr2 = vadd(cc_ref(i-1, 1), vmadd(tr11, tr2, vmul(tr12, tr3))); auto ci2 = vadd(cc_ref(i , 1), vmadd(tr11, ti2, vmul(tr12, ti3))); auto cr3 = vadd(cc_ref(i-1, 1), vmadd(tr12, tr2, vmul(tr11, tr3))); auto ci3 = vadd(cc_ref(i , 1), vmadd(tr12, ti2, vmul(tr11, ti3))); auto cr5 = vmadd(ti11, tr5, vmul(ti12, tr4)); auto ci5 = vmadd(ti11, ti5, vmul(ti12, ti4)); auto cr4 = vsub(vmul(ti12, tr5), vmul(ti11, tr4)); auto ci4 = vsub(vmul(ti12, ti5), vmul(ti11, ti4)); auto dr3 = vsub(cr3, ci4); auto dr4 = vadd(cr3, ci4); auto di3 = vadd(ci3, cr4); auto di4 = vsub(ci3, cr4); auto dr5 = vadd(cr2, ci5); auto dr2 = vsub(cr2, ci5); auto di5 = vsub(ci2, cr5); auto di2 = vadd(ci2, cr5); auto wr1 = wa1[i]; auto wi1 = fsign*wa1[i+1]; auto wr2 = wa2[i]; auto wi2 = fsign*wa2[i+1]; auto wr3 = wa3[i]; auto wi3 = fsign*wa3[i+1]; auto wr4 = wa4[i]; auto 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 auto l1ido = l1*ido; for(auto k = 0_uz;k < l1ido;k += ido) { auto a = cc[k]; auto 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(auto k = 0_uz;k < l1ido;k += ido) { for(auto i = 2_uz;i < ido;i += 2) { auto tr2 = cc[i - 1 + k + l1ido]; auto ti2 = cc[i + k + l1ido]; auto br = cc[i - 1 + k]; auto 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 auto minus_one = ld_ps1(-1.0f); for(auto k = 0_uz;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 auto l1ido = l1*ido; for(auto k = 0_uz;k < l1ido;k += ido) { auto a = cc[2*k]; auto 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(auto k = 0_uz;k < l1ido;k += ido) { for(auto i = 2_uz;i < ido;i += 2) { auto a = cc[i-1 + 2*k]; auto b = cc[2*(k + ido) - i - 1]; auto c = cc[i+0 + 2*k]; auto d = cc[2*(k + ido) - i + 0]; ch[i-1 + k] = vadd(a, b); auto tr2 = vsub(a, b); ch[i+0 + k] = vsub(c, d); auto 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 auto minus_two = ld_ps1(-2.0f); for(auto k = 0_uz;k < l1ido;k += ido) { auto a = cc[2*k + ido-1]; auto 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 auto taur = ld_ps1(-0.5f); const auto taui = ld_ps1(0.866025403784439f); for(auto k = 0_uz;k < l1;++k) { auto 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(auto k = 0_uz;k < l1;++k) { for(auto i = 2_uz;i < ido;i += 2) { const auto ic = ido - i; auto wr1 = ld_ps1(wa1[i - 2]); auto wi1 = ld_ps1(wa1[i - 1]); auto dr2 = cc[i - 1 + (k + l1)*ido]; auto di2 = cc[i + (k + l1)*ido]; vcplxmulconj(dr2, di2, wr1, wi1); auto wr2 = ld_ps1(wa2[i - 2]); auto wi2 = ld_ps1(wa2[i - 1]); auto dr3 = cc[i - 1 + (k + l1*2)*ido]; auto di3 = cc[i + (k + l1*2)*ido]; vcplxmulconj(dr3, di3, wr2, wi2); auto cr2 = vadd(dr2, dr3); auto 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); auto tr2 = vmadd(taur, cr2, cc[i - 1 + k*ido]); auto ti2 = vmadd(taur, ci2, cc[i + k*ido]); auto tr3 = vmul(taui, vsub(di2, di3)); auto 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 auto taur = -0.5f; static constexpr auto taui = 0.866025403784439f; static constexpr auto taui_2 = taui*2.0f; const auto vtaur = ld_ps1(taur); const auto vtaui_2 = ld_ps1(taui_2); for(auto k = 0_uz;k < l1;++k) { auto tr2 = cc[ido-1 + (3*k + 1)*ido]; tr2 = vadd(tr2,tr2); auto cr2 = vmadd(vtaur, tr2, cc[3*k*ido]); ch[k*ido] = vadd(cc[3*k*ido], tr2); auto 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 auto vtaui = ld_ps1(taui); for(auto k = 0_uz;k < l1;++k) { for(auto i = 2_uz;i < ido;i += 2) { const auto ic = ido - i; auto tr2 = vadd(cc[i - 1 + (3*k + 2)*ido], cc[ic - 1 + (3*k + 1)*ido]); auto cr2 = vmadd(vtaur, tr2, cc[i - 1 + 3*k*ido]); ch[i - 1 + k*ido] = vadd(cc[i - 1 + 3*k*ido], tr2); auto ti2 = vsub(cc[i + (3*k + 2)*ido], cc[ic + (3*k + 1)*ido]); auto ci2 = vmadd(vtaur, ti2, cc[i + 3*k*ido]); ch[i + k*ido] = vadd(cc[i + 3*k*ido], ti2); auto cr3 = vmul(vtaui, vsub(cc[i - 1 + (3*k + 2)*ido], cc[ic - 1 + (3*k + 1)*ido])); auto ci3 = vmul(vtaui, vadd(cc[i + (3*k + 2)*ido], cc[ic + (3*k + 1)*ido])); auto dr2 = vsub(cr2, ci3); auto dr3 = vadd(cr2, ci3); auto di2 = vadd(ci2, cr3); auto 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 auto l1ido = l1*ido; { const auto *RESTRICT cc_ = cc; const auto *RESTRICT cc_end = cc + l1ido; auto *RESTRICT ch_ = ch; while(cc != cc_end) { // this loop represents between 25% and 40% of total radf4_ps cost ! auto a0 = cc[0]; auto a1 = cc[l1ido]; auto a2 = cc[2*l1ido]; auto a3 = cc[3*l1ido]; auto tr1 = vadd(a1, a3); auto 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(auto k = 0_uz;k < l1ido;k += ido) { const auto *RESTRICT pc = cc + 1 + k; for(auto i = 2_uz;i < ido;i += 2, pc += 2) { const auto ic = ido - i; auto cr2 = pc[1*l1ido+0]; auto ci2 = pc[1*l1ido+1]; auto wr = ld_ps1(wa1[i - 2]); auto wi = ld_ps1(wa1[i - 1]); vcplxmulconj(cr2, ci2, wr, wi); auto cr3 = pc[2*l1ido+0]; auto ci3 = pc[2*l1ido+1]; wr = ld_ps1(wa2[i-2]); wi = ld_ps1(wa2[i-1]); vcplxmulconj(cr3, ci3, wr, wi); auto cr4 = pc[3*l1ido]; auto 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 */ auto tr1 = vadd(cr2,cr4); auto tr4 = vsub(cr4,cr2); auto tr2 = vadd(pc[0],cr3); auto 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 auto ti1 = vadd(ci2,ci4); auto 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 auto ti2 = vadd(pc[1],ci3); auto 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 auto minus_hsqt2 = ld_ps1(std::numbers::sqrt2_v * -0.5f); for(auto k = 0_uz;k < l1ido;k += ido) { auto a = cc[ido-1 + k + l1ido]; auto b = cc[ido-1 + k + 3*l1ido]; auto c = cc[ido-1 + k]; auto d = cc[ido-1 + k + 2*l1ido]; auto ti1 = vmul(minus_hsqt2, vadd(b, a)); auto 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 auto two = ld_ps1(2.0f); const auto l1ido = l1*ido; { const auto *RESTRICT cc_ = cc; const auto *RESTRICT ch_end = ch + l1ido; auto *ch_ = ch; while(ch != ch_end) { auto a = cc[0]; auto b = cc[4*ido-1]; auto c = cc[2*ido]; auto d = cc[2*ido-1]; auto tr3 = vmul(two, d); auto tr2 = vadd(a, b); auto tr1 = vsub(a, b); auto 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(auto k = 0_uz;k < l1ido;k += ido) { const auto *RESTRICT pc = cc - 1 + 4*k; auto *RESTRICT ph = ch + k + 1; for(auto i = 2_uz;i < ido;i += 2) { auto tr1 = vsub(pc[ i], pc[4*ido - i]); auto tr2 = vadd(pc[ i], pc[4*ido - i]); auto ti4 = vsub(pc[2*ido + i], pc[2*ido - i]); auto tr3 = vadd(pc[2*ido + i], pc[2*ido - i]); ph[0] = vadd(tr2, tr3); auto cr3 = vsub(tr2, tr3); auto ti3 = vsub(pc[2*ido + i + 1], pc[2*ido - i + 1]); auto tr4 = vadd(pc[2*ido + i + 1], pc[2*ido - i + 1]); auto cr2 = vsub(tr1, tr4); auto cr4 = vadd(tr1, tr4); auto ti1 = vadd(pc[i + 1], pc[4*ido - i + 1]); auto ti2 = vsub(pc[i + 1], pc[4*ido - i + 1]); ph[1] = vadd(ti2, ti3); ph += l1ido; auto ci3 = vsub(ti2, ti3); auto ci2 = vadd(ti1, ti4); auto 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 auto minus_sqrt2 = ld_ps1(-1.414213562373095f); for(auto k = 0_uz;k < l1ido;k += ido) { const auto i0 = 4*k + ido; auto c = cc[i0-1]; auto d = cc[i0 + 2*ido-1]; auto a = cc[i0+0]; auto b = cc[i0 + 2*ido+0]; auto tr1 = vsub(c, d); auto tr2 = vadd(c, d); auto ti1 = vadd(b, a); auto 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 auto tr11 = ld_ps1(0.309016994374947f); const auto ti11 = ld_ps1(0.951056516295154f); const auto tr12 = ld_ps1(-0.809016994374947f); const auto 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(auto k = 1_uz;k <= l1;++k) { auto cr2 = vadd(cc_ref(1, k, 5), cc_ref(1, k, 2)); auto ci5 = vsub(cc_ref(1, k, 5), cc_ref(1, k, 2)); auto cr3 = vadd(cc_ref(1, k, 4), cc_ref(1, k, 3)); auto 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 auto idp2 = ido + 2_uz; for(auto k = 1_uz;k <= l1;++k) { for(auto i = 3_uz;i <= ido;i += 2) { const auto ic = idp2 - i; auto dr2 = ld_ps1(wa1[i-3]); auto di2 = ld_ps1(wa1[i-2]); auto dr3 = ld_ps1(wa2[i-3]); auto di3 = ld_ps1(wa2[i-2]); auto dr4 = ld_ps1(wa3[i-3]); auto di4 = ld_ps1(wa3[i-2]); auto dr5 = ld_ps1(wa4[i-3]); auto 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)); auto cr2 = vadd(dr2, dr5); auto ci5 = vsub(dr5, dr2); auto cr5 = vsub(di2, di5); auto ci2 = vadd(di2, di5); auto cr3 = vadd(dr3, dr4); auto ci4 = vsub(dr4, dr3); auto cr4 = vsub(di3, di4); auto 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)); auto tr2 = vadd(cc_ref(i - 1, k, 1), vmadd(tr11, cr2, vmul(tr12, cr3))); auto ti2 = vsub(cc_ref(i, k, 1), vmadd(tr11, ci2, vmul(tr12, ci3))); auto tr3 = vadd(cc_ref(i - 1, k, 1), vmadd(tr12, cr2, vmul(tr11, cr3))); auto ti3 = vsub(cc_ref(i, k, 1), vmadd(tr12, ci2, vmul(tr11, ci3))); auto tr5 = vmadd(ti11, cr5, vmul(ti12, cr4)); auto ti5 = vmadd(ti11, ci5, vmul(ti12, ci4)); auto tr4 = vsub(vmul(ti12, cr5), vmul(ti11, cr4)); auto 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 auto tr11 = ld_ps1(0.309016994374947f); const auto ti11 = ld_ps1(0.951056516295154f); const auto tr12 = ld_ps1(-0.809016994374947f); const auto 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(auto k = 1_uz;k <= l1;++k) { auto ti5 = vadd(cc_ref( 1, 3, k), cc_ref(1, 3, k)); auto ti4 = vadd(cc_ref( 1, 5, k), cc_ref(1, 5, k)); auto tr2 = vadd(cc_ref(ido, 2, k), cc_ref(ido, 2, k)); auto 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)); auto cr2 = vadd(cc_ref(1, 1, k), vmadd(tr11, tr2, vmul(tr12, tr3))); auto cr3 = vadd(cc_ref(1, 1, k), vmadd(tr12, tr2, vmul(tr11, tr3))); auto ci5 = vmadd(ti11, ti5, vmul(ti12, ti4)); auto 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 auto idp2 = ido + 2_uz; for(auto k = 1_uz;k <= l1;++k) { for(auto i = 3_uz;i <= ido;i += 2) { const auto ic = idp2 - i; auto ti5 = vadd(cc_ref(i , 3, k), cc_ref(ic , 2, k)); auto ti2 = vsub(cc_ref(i , 3, k), cc_ref(ic , 2, k)); auto ti4 = vadd(cc_ref(i , 5, k), cc_ref(ic , 4, k)); auto ti3 = vsub(cc_ref(i , 5, k), cc_ref(ic , 4, k)); auto tr5 = vsub(cc_ref(i-1, 3, k), cc_ref(ic-1, 2, k)); auto tr2 = vadd(cc_ref(i-1, 3, k), cc_ref(ic-1, 2, k)); auto tr4 = vsub(cc_ref(i-1, 5, k), cc_ref(ic-1, 4, k)); auto 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)); auto cr2 = vadd(cc_ref(i-1, 1, k), vmadd(tr11, tr2, vmul(tr12, tr3))); auto ci2 = vadd(cc_ref(i , 1, k), vmadd(tr11, ti2, vmul(tr12, ti3))); auto cr3 = vadd(cc_ref(i-1, 1, k), vmadd(tr12, tr2, vmul(tr11, tr3))); auto ci3 = vadd(cc_ref(i , 1, k), vmadd(tr12, ti2, vmul(tr11, ti3))); auto cr5 = vmadd(ti11, tr5, vmul(ti12, tr4)); auto ci5 = vmadd(ti11, ti5, vmul(ti12, ti4)); auto cr4 = vsub(vmul(ti12, tr5), vmul(ti11, tr4)); auto ci4 = vsub(vmul(ti12, ti5), vmul(ti11, ti4)); auto dr3 = vsub(cr3, ci4); auto dr4 = vadd(cr3, ci4); auto di3 = vadd(ci3, cr4); auto di4 = vsub(ci3, cr4); auto dr5 = vadd(cr2, ci5); auto dr2 = vsub(cr2, ci5); auto di5 = vsub(ci2, cr5); auto 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 auto rfftf1_ps(const size_t n, const v4sf *input_readonly, v4sf *work1, v4sf *work2, const float *wa, const std::span ifac) -> v4sf* { Expects(work1 != work2); const auto *in = input_readonly; auto *out = in == work2 ? work1 : work2; const auto nf = size_t{ifac[1]}; auto l2 = n; auto iw = n - 1_uz; auto k1 = 1_uz; while(true) { const auto kh = nf - k1; const auto ip = size_t{ifac[kh + 2]}; const auto l1 = l2 / ip; const auto 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: std::abort(); } 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 std::span ifac) { Expects(work1 != work2); const auto *in = input_readonly; auto *out = in == work2 ? work1 : work2; const auto nf = size_t{ifac[1]}; auto l1 = 1_uz; auto iw = 0_uz; auto k1 = 1_uz; while(true) { const auto ip = size_t{ifac[k1 + 1]}; const auto l2 = ip * l1; const auto 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: std::abort(); } 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 std::span ifac, const float fsign) { Expects(work1 != work2); const auto *in = input_readonly; auto *out = in == work2 ? work1 : work2; const auto nf = size_t{ifac[1]}; auto l1 = 1_uz; auto iw = 0_uz; auto k1 = 2_uz; while(true) { const auto ip = size_t{ifac[k1]}; const auto l2 = ip * l1; const auto ido = n / l2; const auto 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: std::abort(); } if(++k1 > nf+1) return out; l1 = l2; iw += (ip - 1)*idot; if(out == work2) { out = work1; in = work2; } else { out = work2; in = work1; } } } auto decompose(const uint n, const std::span ifac, const std::span ntryh) -> uint { auto nl = n; auto nf = 0u; for(const auto ntry : ntryh) { while(nl != 1) { const auto nq = nl / ntry; const auto nr = nl % ntry; if(nr != 0) break; ifac[2 + nf++] = ntry; nl = nq; if(ntry == 2 && nf != 1) { for(auto i = 2_uz;i <= nf;++i) { auto 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 std::span ifac) { static constexpr std::array ntryh{4u, 2u, 3u, 5u}; const auto nf = size_t{decompose(n, ifac, ntryh)}; const auto argh = 2.0*std::numbers::pi / n; auto is = 0_uz; auto nfm1 = nf - 1_uz; auto l1 = 1_uz; for(auto k1 = 0_uz;k1 < nfm1;++k1) { const auto ip = size_t{ifac[k1+2]}; const auto l2 = l1 * ip; const auto ido = n / l2; const auto ipm = ip - 1; auto ld = 0_uz; for(auto j = 0_uz;j < ipm;++j) { auto i = is; ld += l1; const auto argld = gsl::narrow_cast(ld)*argh; auto fi = 0.0; for(auto ii = 2_uz;ii < ido;ii += 2) { fi += 1.0; wa[i++] = gsl::narrow_cast(std::cos(fi*argld)); wa[i++] = gsl::narrow_cast(std::sin(fi*argld)); } is += ido; } l1 = l2; } } /* rffti1 */ void cffti1_ps(const uint n, float *wa, const std::span ifac) { static constexpr auto ntryh = std::array{5u, 3u, 4u, 2u}; const auto nf = size_t{decompose(n, ifac, ntryh)}; const auto argh = 2.0*std::numbers::pi / n; auto i = 1_uz; auto l1 = 1_uz; for(auto k1 = 0_uz;k1 < nf;++k1) { const auto ip = size_t{ifac[k1+2]}; const auto l2 = l1 * ip; const auto ido = n / l2; const auto idot = ido + ido + 2_uz; const auto ipm = ip - 1_uz; auto ld = 0_uz; for(auto j = 0_uz;j < ipm;++j) { auto i1 = i; wa[i-1] = 1.0f; wa[i] = 0.0f; ld += l1; const auto argld = gsl::narrow_cast(ld)*argh; auto fi = 0.0; for(auto ii = 3_uz;ii < idot;ii += 2) { fi += 1.0; wa[++i] = gsl::narrow_cast(std::cos(fi*argld)); wa[++i] = gsl::narrow_cast(std::sin(fi*argld)); } if(ip > 5) { wa[i1-1] = wa[i-1]; wa[i1] = wa[i]; } } l1 = l2; } } /* cffti1 */ } // namespace struct alignas(V4sfAlignment) 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 */ DIAGNOSTIC_PUSH std_pragma("GCC diagnostic ignored \"-Wignored-attributes\"") std::span e; /* N/4*3 elements */ DIAGNOSTIC_POP }; auto pffft_new_setup(const unsigned int N, const pffft_transform_t transform) -> PFFFTSetupPtr { Expects(transform == PFFFT_REAL || transform == PFFFT_COMPLEX); /* 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) { Expects(N >= 2u*SimdSize*SimdSize); Expects((N%(2u*SimdSize*SimdSize)) == 0); } else { Expects(N >= SimdSize*SimdSize); Expects((N%(SimdSize*SimdSize)) == 0); } const auto Ncvec = uint{(transform == PFFFT_REAL ? N/2 : N) / SimdSize}; Expects(Ncvec > 0u); const auto storelen = sizeof(PFFFT_Setup) + 2_zu*Ncvec*sizeof(v4sf); auto storage = static_cast>(::operator new(storelen, V4sfAlignVal)); auto extrastore = std::span{&storage[sizeof(PFFFT_Setup)], 2_zu*Ncvec*sizeof(v4sf)}; auto s = PFFFTSetupPtr{::new(storage) PFFFT_Setup{}}; s->N = N; s->transform = transform; s->Ncvec = Ncvec; const auto ecount = 2_zu*Ncvec*(SimdSize-1)/SimdSize; /* NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast) */ s->e = {std::launder(reinterpret_cast(extrastore.data())), ecount}; s->twiddle = std::launder(reinterpret_cast(&extrastore[ecount*sizeof(v4sf)])); /* NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast) */ #ifndef PFFFT_SIMD_DISABLE if constexpr(SimdSize > 1) { auto e = std::vector(s->e.size()*SimdSize, 0.0f); for(auto k = 0_uz;k < s->Ncvec;++k) { const auto i = k / SimdSize; const auto j = k % SimdSize; for(auto m = 0_uz;m < SimdSize-1;++m) { const auto A = -2.0*std::numbers::pi*gsl::narrow_cast((m+1)*k) / N; e[((i*3 + m)*2 + 0)*SimdSize + j] = gsl::narrow_cast(std::cos(A)); e[((i*3 + m)*2 + 1)*SimdSize + j] = gsl::narrow_cast(std::sin(A)); } } auto eiter = e.begin(); std::ranges::generate(s->e, [&eiter] { const auto a = *(eiter++); const auto b = *(eiter++); const auto c = *(eiter++); const auto d = *(eiter++); return vset4(a, b, c, d); }); } #endif 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 */ auto m = 1_uz; for(auto k = 0_uz;k < s->ifac[1];++k) m *= s->ifac[2+k]; if(m != N/SimdSize) s = nullptr; return s; } void PFFFTSetupDeleter::operator()(gsl::owner setup) const noexcept { std::destroy_at(setup); ::operator delete(gsl::owner{setup}, 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) { auto g0 = v4sf{}; auto g1 = v4sf{}; interleave2(in[0], in[1], g0, g1); in += in_stride; *--out = vswaphl(g0, g1); // [g0l, g0h], [g1l g1h] -> [g1l, g0h] for(auto k = 1_uz;k < N;++k) { auto h0 = v4sf{}; auto h1 = v4sf{}; 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) { auto g0 = in[0]; auto g1 = g0; ++in; for(auto k = 1_uz;k < N;++k) { auto h0 = *in++; auto h1 = *in++; g1 = vswaphl(g1, h0); h0 = vswaphl(h0, h1); uninterleave2(h0, g1, out[0], out[1]); out += out_stride; g1 = h1; } auto h0 = *in++; auto 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) { Expects(in != out); const auto dk = size_t{Ncvec/SimdSize}; // number of 4x4 matrix blocks for(auto k = 0_uz;k < dk;++k) { auto r0 = in[8*k + 0]; auto i0 = in[8*k + 1]; auto r1 = in[8*k + 2]; auto i1 = in[8*k + 3]; auto r2 = in[8*k + 4]; auto i2 = in[8*k + 5]; auto r3 = in[8*k + 6]; auto 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]); auto sr0 = vadd(r0, r2); auto dr0 = vsub(r0, r2); auto sr1 = vadd(r1, r3); auto dr1 = vsub(r1, r3); auto si0 = vadd(i0, i2); auto di0 = vsub(i0, i2); auto si1 = vadd(i1, i3); auto 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) { Expects(in != out); const auto dk = size_t{Ncvec/SimdSize}; // number of 4x4 matrix blocks for(size_t k{0};k < dk;++k) { auto r0 = in[8*k + 0]; auto i0 = in[8*k + 1]; auto r1 = in[8*k + 2]; auto i1 = in[8*k + 3]; auto r2 = in[8*k + 4]; auto i2 = in[8*k + 5]; auto r3 = in[8*k + 6]; auto i3 = in[8*k + 7]; auto sr0 = vadd(r0, r2); auto dr0 = vsub(r0, r2); auto sr1 = vadd(r1, r3); auto dr1 = vsub(r1, r3); auto si0 = vadd(i0, i2); auto di0 = vsub(i0, i2); auto si1 = vadd(i1, i3); auto 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) { auto r0 = *in0; auto i0 = *in1; auto r1 = *in++; auto i1 = *in++; auto r2 = *in++; auto i2 = *in++; auto r3 = *in++; auto 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"; auto sr0 = vadd(r0, r2); auto dr0 = vsub(r0, r2); auto sr1 = vadd(r1, r3); auto dr1 = vsub(r3, r1); auto si0 = vadd(i0, i2); auto di0 = vsub(i0, i2); auto si1 = vadd(i1, i3); auto 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 auto s = std::numbers::sqrt2_v/2.0f; Expects(in != out); const auto dk = size_t{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 auto zero = vzero(); const auto cr = std::bit_cast>(in[0]); const auto ci = std::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] */ out[0] = vinsert0(out[0], (cr[0]+cr[2]) + (cr[1]+cr[3])); out[1] = vinsert0(out[1], (cr[0]+cr[2]) - (cr[1]+cr[3])); out[4] = vinsert0(out[4], (cr[0]-cr[2])); out[5] = vinsert0(out[5], (cr[3]-cr[1])); out[2] = vinsert0(out[2], ci[0] + s*(ci[1]-ci[3])); out[3] = vinsert0(out[3], -ci[2] - s*(ci[1]+ci[3])); out[6] = vinsert0(out[6], ci[0] - s*(ci[1]-ci[3])); out[7] = vinsert0(out[7], ci[2] - s*(ci[1]+ci[3])); for(auto k = 1_uz;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) { auto r0 = in[0]; auto i0 = in[1]; auto r1 = in[2]; auto i1 = in[3]; auto r2 = in[4]; auto i2 = in[5]; auto r3 = in[6]; auto 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] */ auto sr0 = vadd(r0, r3); auto dr0 = vsub(r0, r3); auto sr1 = vadd(r1, r2); auto dr1 = vsub(r1, r2); auto si0 = vadd(i0, i3); auto di0 = vsub(i0, i3); auto si1 = vadd(i1, i2); auto 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 auto sqrt2 = std::numbers::sqrt2_v; Expects(in != out); const auto dk = size_t{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 */ auto Xr = std::array{}; auto Xi = std::array{}; for(auto k = 0_uz;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(auto k = 1_uz;k < dk;++k) pffft_real_preprocess_4x4(in+8*k, e + k*6, out-1+k*8, false); const auto cr0 = (Xr[0]+Xi[0]) + 2*Xr[2]; const auto cr1 = (Xr[0]-Xi[0]) - 2*Xi[2]; const auto cr2 = (Xr[0]+Xi[0]) - 2*Xr[2]; const auto cr3 = (Xr[0]-Xi[0]) + 2*Xi[2]; out[0] = vset4(cr0, cr1, cr2, cr3); const auto ci0 = 2*(Xr[1]+Xr[3]); const auto ci1 = sqrt2*(Xr[1]-Xr[3]) - sqrt2*(Xi[1]+Xi[3]); const auto ci2 = 2*(Xi[3]-Xi[1]); const auto ci3 = -sqrt2*(Xr[1]-Xr[3]) - sqrt2*(Xi[1]+Xi[3]); out[2*Ncvec-1] = vset4(ci0, ci1, ci2, ci3); } void pffft_zreorder_internal(const PFFFT_Setup *setup, const v4sf *vin, v4sf *RESTRICT vout, pffft_direction_t direction) { const auto N = size_t{setup->N}; const auto Ncvec = size_t{setup->Ncvec}; if(setup->transform == PFFFT_REAL) { const auto dk = N/32; if(direction == PFFFT_FORWARD) { for(auto k = 0_uz;k < dk;++k) { interleave2(vin[k*8 + 0],vin[k*8 + 1], vout[2*(0*dk + k)],vout[2*(0*dk + k) + 1]); interleave2(vin[k*8 + 4],vin[k*8 + 5], vout[2*(2*dk + k)],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(auto k = 0_uz;k < dk;++k) { uninterleave2(vin[2*(0*dk + k)],vin[2*(0*dk + k) + 1],vout[k*8 + 0],vout[k*8 + 1]); uninterleave2(vin[2*(2*dk + k)],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(auto k = 0_uz;k < Ncvec;++k) { const auto kk = (k/4) + (k%4)*(Ncvec/4); interleave2(vin[k*2], vin[k*2+1], vout[kk*2], vout[kk*2+1]); } } else { for(auto k = 0_uz;k < Ncvec;++k) { const auto kk = (k/4) + (k%4)*(Ncvec/4); uninterleave2(vin[kk*2], vin[kk*2+1], vout[k*2], vout[k*2+1]); } } } } void pffft_transform_internal(const PFFFT_Setup *setup, const v4sf *vinput, v4sf *voutput, v4sf *scratch, const pffft_direction_t direction, const bool ordered) { Expects(scratch != nullptr); Expects(voutput != scratch); const auto Ncvec = size_t{setup->Ncvec}; const auto nf_odd = (setup->ifac[1]&1) != 0; auto buff = std::array{voutput, scratch}; auto 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 { auto *tmp = buff[ib]; for(auto k = 0_uz;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_internal(setup, buff[!ib], buff[ib], PFFFT_FORWARD); else ib = !ib; } else { if(vinput == buff[ib]) ib = !ib; // may happen when finput == foutput if(ordered) { pffft_zreorder_internal(setup, vinput, 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(auto k = 0_uz;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 */ Expects(vinput == voutput); for(auto k = 0_uz;k < Ncvec;++k) { const auto a = buff[ib][2*k]; const auto b = buff[ib][2*k+1]; voutput[2*k] = a; voutput[2*k+1] = b; } } } void pffft_zconvolve_scale_accumulate_internal(const PFFFT_Setup *s, const v4sf *RESTRICT va, const v4sf *RESTRICT vb, v4sf *RESTRICT vab, float scaling) { const auto Ncvec = size_t{s->Ncvec}; #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 auto ar1 = vextract0(va[0]); const auto ai1 = vextract0(va[1]); const auto br1 = vextract0(vb[0]); const auto bi1 = vextract0(vb[1]); const auto abr1 = vextract0(vab[0]); const auto abi1 = vextract0(vab[1]); const auto vscale = ld_ps1(scaling); for(auto i = 0_uz;i < Ncvec;i += 2) { auto ar4 = va[2*i + 0]; auto ai4 = va[2*i + 1]; auto br4 = vb[2*i + 0]; auto bi4 = vb[2*i + 1]; vcplxmul(ar4, ai4, br4, bi4); vab[2*i + 0] = vmadd(ar4, vscale, vab[2*i + 0]); vab[2*i + 1] = vmadd(ai4, vscale, 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, vscale, vab[2*i + 2]); vab[2*i + 3] = vmadd(ai4, vscale, vab[2*i + 3]); } 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_internal(const PFFFT_Setup *s, const v4sf *RESTRICT va, const v4sf *RESTRICT vb, v4sf *RESTRICT vab) { const auto Ncvec = size_t{s->Ncvec}; #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 auto ar1 = vextract0(va[0]); const auto ai1 = vextract0(va[1]); const auto br1 = vextract0(vb[0]); const auto bi1 = vextract0(vb[1]); const auto abr1 = vextract0(vab[0]); const auto abi1 = vextract0(vab[1]); for(auto i = 0_uz;i < Ncvec;i += 2) { auto ar4 = va[2*i + 0]; auto ai4 = va[2*i + 1]; auto br4 = vb[2*i + 0]; auto 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); } } } // namespace /* NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast) * Without some way to "safely" reinterpret float buffers as SIMD float * vectors, these casts are needed. */ void pffft_zreorder(const PFFFT_Setup *setup, const float *in, float *out, pffft_direction_t direction) { Expects(in != out); Expects(valigned(in) && valigned(out)); pffft_zreorder_internal(setup, reinterpret_cast(in), reinterpret_cast(out), direction); } void pffft_zconvolve_scale_accumulate(const PFFFT_Setup *s, const float *a, const float *b, float *ab, float scaling) { Expects(valigned(a) && valigned(b) && valigned(ab)); pffft_zconvolve_scale_accumulate_internal(s, reinterpret_cast(a), reinterpret_cast(b), reinterpret_cast(ab), scaling); } void pffft_zconvolve_accumulate(const PFFFT_Setup *s, const float *a, const float *b, float *ab) { Expects(valigned(a) && valigned(b) && valigned(ab)); pffft_zconvolve_accumulate_internal(s, reinterpret_cast(a), reinterpret_cast(b), reinterpret_cast(ab)); } void pffft_transform(const PFFFT_Setup *setup, const float *input, float *output, float *work, pffft_direction_t direction) { Expects(valigned(input) && valigned(output) && valigned(work)); pffft_transform_internal(setup, reinterpret_cast(std::assume_aligned<16>(input)), reinterpret_cast(std::assume_aligned<16>(output)), reinterpret_cast(std::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) { Expects(valigned(input) && valigned(output) && valigned(work)); pffft_transform_internal(setup, reinterpret_cast(std::assume_aligned<16>(input)), reinterpret_cast(std::assume_aligned<16>(output)), reinterpret_cast(std::assume_aligned<16>(work)), direction, true); } /* NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast) */ #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 auto Ncvec = size_t{setup->Ncvec}; const auto nf_odd = (setup->ifac[1]&1) != 0; Expects(scratch != nullptr); /* z-domain data for complex transforms is already ordered without SIMD. */ if(setup->transform == PFFFT_COMPLEX) ordered = false; const auto buff = std::array{output, scratch}; auto 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 Expects(input == output); std::ranges::copy(std::span{buff[ib], Ncvec*2_uz}, output); } } } // namespace void pffft_zreorder(const PFFFT_Setup *setup, const float *in, float *RESTRICT out, pffft_direction_t direction) { const auto N = size_t{setup->N}; if(setup->transform == PFFFT_COMPLEX) { for(auto k = 0_uz;k < 2*N;++k) out[k] = in[k]; } else if(direction == PFFFT_FORWARD) { const auto x_N = in[N-1]; for(auto k = N-1_uz;k > 1;--k) out[k] = in[k-1]; out[0] = in[0]; out[1] = x_N; } else { const auto x_N = in[1]; for(auto k = 1_uz;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) { auto Ncvec = size_t{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(auto i = 0_uz;i < Ncvec;++i) { auto ar = a[2*i+0]; auto ai = a[2*i+1]; const auto br = b[2*i+0]; const auto 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) { auto Ncvec = size_t{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(auto i = 0_uz;i < Ncvec;++i) { auto ar = a[2*i+0]; auto ai = a[2*i+1]; const auto br = b[2*i+0]; const auto 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) */ kcat-openal-soft-75c0059/common/pffft.h000066400000000000000000000221441512220627100177060ustar00rootroot00000000000000/* 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 #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 : bool { PFFFT_FORWARD, PFFFT_BACKWARD }; /* type of transform */ enum pffft_transform_t : bool { PFFFT_REAL, PFFFT_COMPLEX }; struct PFFFTSetupDeleter { void operator()(gsl::owner setup) const noexcept; }; 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(std::contiguous_iterator auto input, std::contiguous_iterator auto output, std::contiguous_iterator auto work, pffft_direction_t direction) const { pffft_transform(mSetup.get(), std::to_address(input), std::to_address(output), std::to_address(work), direction); } void transform_ordered(std::contiguous_iterator auto input, std::contiguous_iterator auto output, std::contiguous_iterator auto work, pffft_direction_t direction) const { pffft_transform_ordered(mSetup.get(), std::to_address(input), std::to_address(output), std::to_address(work), direction); } void zreorder(std::contiguous_iterator auto input, std::contiguous_iterator auto output, pffft_direction_t direction) const { pffft_zreorder(mSetup.get(), std::to_address(input), std::to_address(output), direction); } void zconvolve_scale_accumulate(std::contiguous_iterator auto dft_a, std::contiguous_iterator auto dft_b, std::contiguous_iterator auto dft_ab, std::floating_point auto scaling) const { pffft_zconvolve_scale_accumulate(mSetup.get(), std::to_address(dft_a), std::to_address(dft_b), std::to_address(dft_ab), scaling); } void zconvolve_accumulate(std::contiguous_iterator auto dft_a, std::contiguous_iterator auto dft_b, std::contiguous_iterator auto dft_ab) const { pffft_zconvolve_accumulate(mSetup.get(), std::to_address(dft_a), std::to_address(dft_b), std::to_address(dft_ab)); } }; #endif // PFFFT_H kcat-openal-soft-75c0059/common/phase_shifter.h000066400000000000000000000170331512220627100214260ustar00rootroot00000000000000#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 #include #include #include #include "alnumeric.h" #include "gsl/gsl" #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 class 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{}; #if HAVE_NEON static auto load4(float32_t a, float32_t b, float32_t c, float32_t d) -> float32x4_t { auto 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) { const auto t0_ = vzipq_f32(x0, x2); const auto t1_ = vzipq_f32(x1, x3); const auto u0_ = vzipq_f32(t0_.val[0], t1_.val[0]); const auto 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 public: PhaseShifterT() noexcept { /* 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(const auto i : std::views::iota(0_uz, FilterSize/2)) { const auto k = gsl::narrow_cast(i*2 + 1) - int{FilterSize/2}; /* Calculate the Blackman window value for this coefficient. */ const auto w = 2.0*std::numbers::pi/double{FilterSize} * gsl::narrow_cast(i*2 + 1); const auto window = 0.3635819 - 0.4891775*std::cos(w) + 0.1365995*std::cos(2.0*w) - 0.0106411*std::cos(3.0*w); const auto pk = std::numbers::pi * gsl::narrow_cast(k); mCoeffs[i] = gsl::narrow_cast(window * (1.0-std::cos(pk)) / pk); } } NOINLINE void process(const std::span dst, std::span src) const { #if HAVE_SSE_INTRINSICS /* NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast) * Need to be able to cast floats to SIMD float types. */ if(const auto todo = dst.size()>>2_uz) { auto out = std::span{reinterpret_cast<__m128*>(dst.data()), todo}; std::ranges::generate(out, [&src,this] { auto r0 = _mm_setzero_ps(); auto r1 = _mm_setzero_ps(); auto r2 = _mm_setzero_ps(); auto r3 = _mm_setzero_ps(); for(auto j = 0_uz;j < mCoeffs.size();j+=4) { const auto coeffs = _mm_load_ps(&mCoeffs[j]); const auto s0 = _mm_loadu_ps(&src[j*2]); const auto s1 = _mm_loadu_ps(&src[j*2 + 4]); const auto s2 = _mm_movehl_ps(_mm_movelh_ps(s1, s1), s0); const auto s3 = _mm_loadh_pi(_mm_movehl_ps(s1, s1), reinterpret_cast(&src[j*2 + 8])); auto 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)); } src = src.subspan(4); _MM_TRANSPOSE4_PS(r0, r1, r2, r3); return _mm_add_ps(_mm_add_ps(r0, r1), _mm_add_ps(r2, r3)); }); } if(const auto todo = dst.size()&3) { std::ranges::generate(dst.last(todo), [&src,this] { auto r4 = _mm_setzero_ps(); for(auto j = 0_uz;j < mCoeffs.size();j+=4) { const auto coeffs = _mm_load_ps(&mCoeffs[j]); const auto s = _mm_setr_ps(src[j*2], src[j*2 + 2], src[j*2 + 4], src[j*2 + 6]); r4 = _mm_add_ps(r4, _mm_mul_ps(s, coeffs)); } src = src.subspan(1); 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 = std::span{reinterpret_cast(dst.data()), todo}; std::generate(out.begin(), out.end(), [&src,this] { auto r0 = vdupq_n_f32(0.0f); auto r1 = vdupq_n_f32(0.0f); auto r2 = vdupq_n_f32(0.0f); auto r3 = vdupq_n_f32(0.0f); for(auto j = 0_uz;j < mCoeffs.size();j+=4) { const auto coeffs = vld1q_f32(&mCoeffs[j]); const auto s0 = vld1q_f32(&src[j*2]); const auto s1 = vld1q_f32(&src[j*2 + 4]); const auto s2 = vcombine_f32(vget_high_f32(s0), vget_low_f32(s1)); const auto s3 = vcombine_f32(vget_high_f32(s1), vld1_f32(&src[j*2 + 8])); const auto values0 = vuzpq_f32(s0, s1); const auto 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); } src = src.subspan(4); vtranspose4(r0, r1, r2, r3); return vaddq_f32(vaddq_f32(r0, r1), vaddq_f32(r2, r3)); }); } if(const auto todo = dst.size()&3) { std::ranges::generate(dst.last(todo), [&src,this] { auto r4 = vdupq_n_f32(0.0f); for(auto j = 0_uz;j < mCoeffs.size();j+=4) { const auto coeffs = vld1q_f32(&mCoeffs[j]); const auto s = load4(src[j*2], src[j*2 + 2], src[j*2 + 4], src[j*2 + 6]); r4 = vmlaq_f32(r4, s, coeffs); } src = src.subspan(1); r4 = vaddq_f32(r4, vrev64q_f32(r4)); return vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0); }); } /* NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast) */ #else std::ranges::generate(dst, [&src,this] { auto ret = 0.0f; for(auto j = 0_uz;j < mCoeffs.size();++j) ret += src[j*2] * mCoeffs[j]; src = src.subspan(1); return ret; }); #endif } }; #endif /* PHASE_SHIFTER_H */ kcat-openal-soft-75c0059/common/polyphase_resampler.cpp000066400000000000000000000156351512220627100232210ustar00rootroot00000000000000 #include "polyphase_resampler.h" #include #include #include #include #include #include #include #include #include "gsl/gsl" using uint = unsigned int; namespace { constexpr auto 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 auto x2 = x/2.0; auto term = 1.0; auto sum = 1.0; auto k = 1; /* Let the integration converge until the term of the sum is no longer * significant. */ auto last_sum = 0.0; do { const auto y = x2 / k; ++k; last_sum = sum; term *= y * y; sum += term; } while(sum != last_sum); return gsl::narrow_cast(sum); } /* This is the normalized cardinal sine (sinc) function. * * sinc(x) = { 1, x = 0 * { sin(pi x) / (pi x), otherwise. */ auto Sinc(const double x) -> double { if(std::abs(x) < Epsilon) [[unlikely]] return 1.0; return std::sin(std::numbers::pi*x) / (std::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. */ auto Kaiser(const double beta, const double k, const double besseli_0_beta) -> double { 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 auto CalcKaiserOrder(const double rejection, const double transition) -> uint { const auto w_t = 2.0 * std::numbers::pi * transition; if(rejection > 21.0) [[likely]] return gsl::narrow_cast(std::ceil((rejection - 7.95) / (2.285 * w_t))); return gsl::narrow_cast(std::ceil(5.79 / w_t)); } // Calculates the beta value of the Kaiser window. Rejection is in dB. constexpr auto CalcKaiserBeta(const double rejection) -> double { 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) */ auto SincFilter(const uint l, const double beta, const double besseli_0_beta, const double gain, const double cutoff, const uint i) -> double { const auto x = gsl::narrow_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 auto gcd = std::gcd(srcRate, dstRate); mP = dstRate / gcd; mQ = srcRate / gcd; /* The cutoff is adjusted by the transition width, so the transition ends * at nyquist (0.5). Both are scaled by the downsampling factor. */ const auto [cutoff, width] = (mP > mQ) ? std::make_tuple(0.47 / mP, 0.03 / mP) : std::make_tuple(0.47 / mQ, 0.03 / 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. static constexpr auto rejection = 180.0; const auto l = (CalcKaiserOrder(rejection, width)+1u) / 2u; static constexpr auto beta = CalcKaiserBeta(rejection); static constexpr auto besseli_0_beta = ::cyl_bessel_i(0, beta); mM = l*2u + 1u; mL = l; mF.resize(mM); std::ranges::transform(std::views::iota(0u, mM), mF.begin(), [this,cutoff](const uint i) { return SincFilter(mL, beta, besseli_0_beta, mP, cutoff, i); }); } // Perform the upsample-filter-downsample resampling operation using a // polyphase filter implementation. void PPhaseResampler::process(const std::span in, const std::span out) const { if(out.empty()) [[unlikely]] return; // Handle in-place operation. auto workspace = std::vector{}; auto work = std::span{out}; if(work.data() == in.data()) [[unlikely]] { workspace.resize(out.size()); work = workspace; } const auto f = std::span{mF}; const auto p = size_t{mP}; const auto q = size_t{mQ}; const auto m = size_t{mM}; /* Input starts at l to compensate for the filter delay. This will drop any * build-up from the first half of the filter. */ auto l = size_t{mL}; std::ranges::generate(work, [in,f,p,q,m,&l] { auto j_s = l / p; auto j_f = l % p; l += q; // Only take input when 0 <= j_s < in.size(). if(j_f >= m) [[unlikely]] return 0.0; auto filt_len = (m - j_f - 1)/p + 1; if(j_s+1 > in.size()) [[likely]] { const auto skip = std::min(j_s+1-in.size(), filt_len); j_f += p*skip; j_s -= skip; filt_len -= skip; } /* Get the range of input samples being used for this output sample. * j_s is the first sample and iterates backwards toward 0. */ const auto src = in.first(j_s+1).last(std::min(j_s+1, filt_len)); return std::accumulate(src.rbegin(), src.rend(), 0.0, [p,f,&j_f](const double cur, const double smp) -> double { const auto ret = cur + f[j_f]*smp; j_f += p; return ret; }); }); // Clean up after in-place operation. if(work.data() != out.data()) std::ranges::copy(work, out.begin()); } kcat-openal-soft-75c0059/common/polyphase_resampler.h000066400000000000000000000032061512220627100226550ustar00rootroot00000000000000#ifndef POLYPHASE_RESAMPLER_H #define POLYPHASE_RESAMPLER_H #include #include 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 std::span in, const std::span out) const; explicit operator bool() const noexcept { return !mF.empty(); } private: uint mP{}, mQ{}, mM{}, mL{}; std::vector mF; }; #endif /* POLYPHASE_RESAMPLER_H */ kcat-openal-soft-75c0059/common/pragmadefs.h000066400000000000000000000010021512220627100207000ustar00rootroot00000000000000#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 */ kcat-openal-soft-75c0059/common/ringbuffer.h000066400000000000000000000535731512220627100207440ustar00rootroot00000000000000#ifndef RINGBUFFER_H #define RINGBUFFER_H #include #include #include #include #include #include #include #include #include #include #include #include "almalloc.h" #include "alnumeric.h" #include "flexarray.h" #include "gsl/gsl" #include "opthelpers.h" /* NOTE: This lockless ringbuffer implementation is copied from JACK, extended * to include a storage type and an element size. Consequently, parameters and * return values for a size or count are in 'elements', not bytes. Also, it * only supports single-consumer/single-provider operation. */ template class RingBuffer { static_assert(std::is_trivially_copyable_v); #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{0_uz}; alignas(sCacheAlignment) std::atomic mReadCount{0_uz}; alignas(sCacheAlignment) const std::size_t mWriteSize; const std::size_t mSizeMask; const std::size_t mElemSize; al::FlexArray mBuffer; RingBuffer(const std::size_t writesize, const std::size_t mask, const std::size_t elemsize, const std::size_t numvals) : mWriteSize{writesize}, mSizeMask{mask}, mElemSize{elemsize}, mBuffer{numvals} { } public: using DataPair = std::array,2>; /** Reset the read and write pointers to zero. This is not thread safe. */ auto reset() noexcept -> void { mWriteCount.store(0_uz, std::memory_order_relaxed); mReadCount.store(0_uz, std::memory_order_relaxed); std::ranges::fill(mBuffer, T{}); } /** * 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 auto w = mWriteCount.load(std::memory_order_acquire); const auto r = mReadCount.load(std::memory_order_acquire); /* mWriteCount is never more than mWriteSize greater than mReadCount. */ return w - r; } /** * The copying data reader. Copy as many complete elements into `dest' as * possible. Returns the actual number of elements (not values!) copied. */ [[nodiscard]] NOINLINE auto read(const std::span dest) noexcept -> std::size_t { const auto w = mWriteCount.load(std::memory_order_acquire); const auto r = mReadCount.load(std::memory_order_relaxed); const auto readable = w - r; if(readable == 0) return 0; const auto to_read = std::min(dest.size()/mElemSize, readable); const auto read_idx = r & mSizeMask; const auto 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 outiter = std::ranges::copy(mBuffer | std::views::drop(read_idx*mElemSize) | std::views::take(n1*mElemSize), dest.begin()).out; std::ranges::copy(mBuffer | std::views::take(n2*mElemSize), outiter); mReadCount.store(r+n1+n2, std::memory_order_release); return to_read; } /** * The copying data reader w/o read pointer advance. Copy as many complete * elements into `dest' as possible. Returns the actual number of elements * (not values!) copied. */ [[nodiscard]] NOINLINE auto peek(const std::span dest) noexcept -> std::size_t { const auto w = mWriteCount.load(std::memory_order_acquire); const auto r = mReadCount.load(std::memory_order_relaxed); const auto readable = w - r; if(readable == 0) return 0; const auto to_read = std::min(dest.size()/mElemSize, readable); const auto read_idx = r & mSizeMask; const auto 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 outiter = std::ranges::copy(mBuffer | std::views::drop(read_idx*mElemSize) | std::views::take(n1*mElemSize), dest.begin()).out; std::ranges::copy(mBuffer | std::views::take(n2*mElemSize), outiter); return to_read; } /** * The non-copying data reader. Returns two spans that hold the current * readable data. If the readable data is fully in one segment, the second * segment has zero length. */ [[nodiscard]] NOINLINE auto getReadVector() noexcept -> DataPair { const auto w = mWriteCount.load(std::memory_order_acquire); const auto r = mReadCount.load(std::memory_order_relaxed); const auto readable = w - r; const auto read_idx = r & mSizeMask; const auto 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{std::span{mBuffer}.subspan(read_idx*mElemSize), std::span{mBuffer}.first(rdend&mSizeMask)}; } return DataPair{std::span{mBuffer}.subspan(read_idx*mElemSize, readable), {}}; } /** Advance the read pointer `count' places. */ auto readAdvance(std::size_t count) noexcept -> void { const auto w = mWriteCount.load(std::memory_order_acquire); const auto r = mReadCount.load(std::memory_order_relaxed); const auto readable = w - r; Expects(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 as many elements from `src' as possible. * Returns the actual number of elements (not values!) copied. */ [[nodiscard]] NOINLINE auto write(const std::span src) noexcept -> std::size_t { const auto w = mWriteCount.load(std::memory_order_relaxed); const auto r = mReadCount.load(std::memory_order_acquire); const auto writable = mWriteSize - (w - r); if(writable == 0) return 0; const auto to_write = std::min(src.size()/mElemSize, writable); const auto write_idx = w & mSizeMask; const auto 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}; std::ranges::copy(src.first(n1*mElemSize), (mBuffer | std::views::drop(write_idx*mElemSize)).begin()); std::ranges::copy(src.subspan(n1*mElemSize, n2*mElemSize), mBuffer.begin()); mWriteCount.store(w+n1+n2, std::memory_order_release); return to_write; } /** * 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]] NOINLINE auto getWriteVector() noexcept -> DataPair { const auto w = mWriteCount.load(std::memory_order_relaxed); const auto r = mReadCount.load(std::memory_order_acquire); const auto writable = mWriteSize - (w - r); const auto write_idx = w & mSizeMask; const auto 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{std::span{mBuffer}.subspan(write_idx*mElemSize), std::span{mBuffer}.first(wrend&mSizeMask)}; } return DataPair{std::span{mBuffer}.subspan(write_idx*mElemSize, writable), {}}; } /** Advance the write pointer `count' places. */ auto writeAdvance(std::size_t count) noexcept -> void { const auto w = mWriteCount.load(std::memory_order_relaxed); const auto r = mReadCount.load(std::memory_order_acquire); const auto writable = mWriteSize - (w - r); Expects(writable >= count); mWriteCount.store(w+count, std::memory_order_release); } /** Returns the number of values per element. */ [[nodiscard]] auto getElemSize() const noexcept -> std::size_t { return mElemSize; } /** * Create a new ringbuffer to hold at least `sz' elements of `elem_sz' * values. 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]] NOINLINE static auto Create(std::size_t sz, std::size_t elem_sz, bool limit_writes) -> std::unique_ptr { if(sz > std::numeric_limits::max()>>1) throw std::overflow_error{"Ring buffer size too large"}; auto power_of_two = 0_uz; 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(std::uint32_t)) power_of_two |= (power_of_two>>16) >> 16; } ++power_of_two; if(power_of_two > std::numeric_limits::max()/elem_sz) throw std::overflow_error{"Ring buffer size overflow"}; const auto numvals = power_of_two * elem_sz; return std::unique_ptr{new(FamCount{numvals}) RingBuffer{ limit_writes ? sz : power_of_two, power_of_two-1, elem_sz, numvals}}; } DEF_FAM_NEWDEL(RingBuffer, mBuffer) }; template using RingBufferPtr = std::unique_ptr>; /* A FIFO buffer, modelled after the above but retains type information, and * can work with non-trivial types, and does not support multiple values per * element. Unreadable elements are in a destructed state. */ template class FifoBuffer { #if defined(__cpp_lib_hardware_interference_size) && !defined(_LIBCPP_VERSION) static constexpr auto sCacheAlignment = std::hardware_destructive_interference_size; #else /* Assume a 64-byte cache line, the most common/likely value. */ static constexpr auto sCacheAlignment = 64_uz; #endif alignas(sCacheAlignment) std::atomic mWriteCount{0_uz}; alignas(sCacheAlignment) std::atomic mReadCount{0_uz}; alignas(sCacheAlignment) const std::size_t mWriteSize; const std::size_t mSizeMask; const std::span mStorage; FifoBuffer(const std::size_t writesize, const std::size_t mask, const std::span storage) : mWriteSize{writesize}, mSizeMask{mask}, mStorage{storage} { } NOINLINE ~FifoBuffer() { const auto w = mWriteCount.load(std::memory_order_acquire); const auto r = mReadCount.load(std::memory_order_relaxed); const auto readable = w - r; if(w == r) return; const auto read_idx = r & mSizeMask; const auto rdend = read_idx + readable; const auto [n1, n2] = (rdend <= mSizeMask+1) ? std::array{readable, 0_uz} : std::array{mSizeMask+1 - read_idx, rdend&mSizeMask}; std::destroy_n(mStorage.subspan(read_idx).begin(), n1); std::destroy_n(mStorage.begin(), n2); } public: using value_type = T; using DataPair = std::array,2>; /** Reset the read and write pointers to zero. This is not thread safe. */ NOINLINE auto reset() noexcept -> void { const auto w = mWriteCount.load(std::memory_order_relaxed); const auto r = mReadCount.load(std::memory_order_relaxed); const auto readable = w - r; if(w == r) return; const auto read_idx = r & mSizeMask; const auto rdend = read_idx + readable; const auto [n1, n2] = (rdend <= mSizeMask+1) ? std::array{readable, 0_uz} : std::array{mSizeMask+1 - read_idx, rdend&mSizeMask}; std::destroy_n(mStorage.subspan(read_idx).begin(), n1); std::destroy_n(mStorage.begin(), n2); mWriteCount.store(0_uz, std::memory_order_relaxed); mReadCount.store(0_uz, std::memory_order_relaxed); } /** * 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 auto w = mWriteCount.load(std::memory_order_acquire); const auto r = mReadCount.load(std::memory_order_acquire); /* mWriteCount is never more than mWriteSize greater than mReadCount. */ return w - r; } /** * The copying data reader. Move as many elements into `dest' as are * available and can fit. Returns the actual number of elements moved. */ [[nodiscard]] NOINLINE auto read(const std::span dest) noexcept -> std::size_t { const auto w = mWriteCount.load(std::memory_order_acquire); const auto r = mReadCount.load(std::memory_order_relaxed); const auto readable = w - r; if(w == r) return 0_uz; const auto to_read = std::min(dest.size(), readable); const auto read_idx = r & mSizeMask; const auto 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 firstrange = mStorage.subspan(read_idx, n1); auto dstiter = std::ranges::move(firstrange, dest.begin()).out; std::ranges::move(mStorage.first(n2), dstiter); std::destroy_n(firstrange.begin(), n1); std::destroy_n(mStorage.begin(), n2); mReadCount.store(r+n1+n2, std::memory_order_release); return to_read; } /** * The copying data reader w/o read pointer advance. Copy as many elements * into `dest' as are available and can fit. Returns the actual number of * elements copied. */ [[nodiscard]] NOINLINE auto peek(const std::span dest) noexcept -> std::size_t { const auto w = mWriteCount.load(std::memory_order_acquire); const auto r = mReadCount.load(std::memory_order_relaxed); const auto readable = w - r; if(w == r) return 0_uz; const auto to_read = std::min(dest.size(), readable); const auto read_idx = r & mSizeMask; const auto 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 firstrange = mStorage.subspan(read_idx, n1); auto dstiter = std::ranges::copy(firstrange, dest.begin()).out; std::ranges::copy(mStorage.first(n2), dstiter); return to_read; } /** * The non-copying data reader. Returns two spans that hold the current * readable data. If the readable data is in one segment the second segment * has zero length. */ [[nodiscard]] NOINLINE auto getReadVector() noexcept -> DataPair { const auto w = mWriteCount.load(std::memory_order_acquire); const auto r = mReadCount.load(std::memory_order_relaxed); const auto readable = w - r; const auto read_idx = r & mSizeMask; const auto 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{mStorage.subspan(read_idx), mStorage.first(rdend&mSizeMask)}; } return DataPair{mStorage.subspan(read_idx, readable), {}}; } /** Advance the read pointer `count' places. */ auto readAdvance(std::size_t count) noexcept -> void { const auto w = mWriteCount.load(std::memory_order_acquire); const auto r = mReadCount.load(std::memory_order_relaxed); const auto readable = w - r; Expects(readable >= count); const auto to_read = std::min(count, readable); const auto read_idx = r & mSizeMask; const auto 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}; std::destroy_n(mStorage.subspan(read_idx).begin(), n1); std::destroy_n(mStorage.begin(), n2); 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 as many elements from `src' as can fit. * Returns the actual number of elements copied. */ [[nodiscard]] NOINLINE auto write(const std::span src) noexcept -> std::size_t { const auto w = mWriteCount.load(std::memory_order_relaxed); const auto r = mReadCount.load(std::memory_order_acquire); const auto writable = mWriteSize - (w - r); if(writable == 0) return 0_uz; const auto to_write = std::min(src.size(), writable); const auto write_idx = w & mSizeMask; const auto 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}; std::ranges::uninitialized_copy(src.first(n1), mStorage.subspan(write_idx)); std::ranges::uninitialized_copy(src.subspan(n1), mStorage.first(n2)); mWriteCount.store(w+n1+n2, std::memory_order_release); return to_write; } /** * The non-copying data writer. Returns two FIFO buffer spans that hold the * current writeable *uninitialized* elements. If the writeable data is all * in one segment the second segment has zero length. */ [[nodiscard]] NOINLINE auto getWriteVector() noexcept -> DataPair { const auto w = mWriteCount.load(std::memory_order_relaxed); const auto r = mReadCount.load(std::memory_order_acquire); const auto writable = mWriteSize - (w - r); const auto write_idx = w & mSizeMask; const auto 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{mStorage.subspan(write_idx), mStorage.first(wrend&mSizeMask)}; } return DataPair{mStorage.subspan(write_idx, writable), {}}; } /** * Advance the write pointer `count' places. The caller is responsible for * having initialized the elements through getWriteVector. */ auto writeAdvance(std::size_t count) noexcept -> void { const auto w = mWriteCount.load(std::memory_order_relaxed); const auto r = mReadCount.load(std::memory_order_acquire); const auto writable = mWriteSize - (w - r); Expects(writable >= count); mWriteCount.store(w+count, std::memory_order_release); } NOINLINE static void Destroy(gsl::owner fifo) noexcept { fifo->~FifoBuffer(); ::operator delete(fifo, std::align_val_t{alignof(FifoBuffer)}); } struct Deleter { void operator()(gsl::owner ptr) const noexcept { Destroy(ptr); } }; /** * Create a new FIFO buffer to hold at least `count' elements of the given * type. The number of elements is rounded up to a power of two. If * `limit_writes' is true, the writable space will be limited to `count' * elements regardless of the rounded size. */ [[nodiscard]] NOINLINE static auto Create(std::size_t count, bool limit_writes) -> std::unique_ptr { if(count > std::numeric_limits::max()>>1) throw std::overflow_error{"FIFO buffer size too large"}; auto power_of_two = 0_uz; if(count > 0) { power_of_two = count - 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(std::uint32_t)) power_of_two |= (power_of_two>>16) >> 16; } ++power_of_two; if(power_of_two > std::numeric_limits::max()/sizeof(T) - sizeof(FifoBuffer)) throw std::overflow_error{"FIFO buffer size overflow"}; const auto numbytes = sizeof(FifoBuffer) + power_of_two*sizeof(T); auto storage = static_cast>(::operator new(numbytes, std::align_val_t{alignof(FifoBuffer)})); /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast,cppcoreguidelines-pro-bounds-pointer-arithmetic) */ auto extrastore = std::span{reinterpret_cast(&storage[sizeof(FifoBuffer)]), power_of_two}; return std::unique_ptr(::new(storage) FifoBuffer{ limit_writes ? count : power_of_two, power_of_two-1, extrastore}); } }; template using FifoBufferPtr = std::unique_ptr, typename FifoBuffer::Deleter>; #endif /* RINGBUFFER_H */ kcat-openal-soft-75c0059/common/strutils.cpp000066400000000000000000000042601512220627100210240ustar00rootroot00000000000000 #include "config.h" #include "strutils.hpp" #include #ifdef _WIN32 #include #include "gsl/gsl" /* NOLINTBEGIN(bugprone-suspicious-stringview-data-usage) */ auto wstr_to_utf8(std::wstring_view wstr) -> std::string { static constexpr auto flags = DWORD{WC_ERR_INVALID_CHARS}; auto ret = std::string{}; if(wstr.empty()) [[unlikely]] return ret; const auto u16len = gsl::narrow(wstr.size()); auto len = WideCharToMultiByte(CP_UTF8, flags, wstr.data(), u16len, nullptr, 0, nullptr, nullptr); if(len < 1) [[unlikely]] return ret; ret.resize(gsl::narrow(len)); len = WideCharToMultiByte(CP_UTF8, flags, wstr.data(), u16len, ret.data(), len, nullptr, nullptr); if(len < 1) [[unlikely]] { ret.clear(); return ret; } return ret; } auto utf8_to_wstr(std::string_view str) -> std::wstring { static constexpr auto flags = DWORD{MB_ERR_INVALID_CHARS}; auto ret = std::wstring{}; if(str.empty()) [[unlikely]] return ret; const auto u8len = gsl::narrow(str.size()); auto len = MultiByteToWideChar(CP_UTF8, flags, str.data(), u8len, nullptr, 0); if(len < 1) [[unlikely]] return ret; ret.resize(gsl::narrow(len)); len = MultiByteToWideChar(CP_UTF8, flags, str.data(), u8len, ret.data(), len); if(len < 1) [[unlikely]] { ret.clear(); return ret; } return ret; } /* NOLINTEND(bugprone-suspicious-stringview-data-usage) */ namespace al { auto getenv(const gsl::czstring envname) -> std::optional { auto *str = _wgetenv(utf8_to_wstr(envname).c_str()); if(str && *str != L'\0') return wstr_to_utf8(str); return std::nullopt; } auto getenv(const gsl::cwzstring envname) -> std::optional { auto *str = _wgetenv(envname); if(str && *str != L'\0') return str; return std::nullopt; } } /* namespace al */ #else namespace al { auto getenv(const gsl::czstring envname) -> std::optional { auto *str = std::getenv(envname); if(str && *str != '\0') return str; return std::nullopt; } } /* namespace al */ #endif kcat-openal-soft-75c0059/common/strutils.hpp000066400000000000000000000010121512220627100210210ustar00rootroot00000000000000#ifndef AL_STRUTILS_HPP #define AL_STRUTILS_HPP #include #include #include "gsl/gsl" #ifdef _WIN32 #include auto wstr_to_utf8(std::wstring_view wstr) -> std::string; auto utf8_to_wstr(std::string_view str) -> std::wstring; namespace al { auto getenv(const gsl::cwzstring envname) -> std::optional; } /* namespace al */ #endif namespace al { auto getenv(const gsl::czstring envname) -> std::optional; } /* namespace al */ #endif /* AL_STRUTILS_HPP */ kcat-openal-soft-75c0059/common/vecmat.h000066400000000000000000000103511512220627100200550ustar00rootroot00000000000000#ifndef COMMON_VECMAT_H #define COMMON_VECMAT_H #include #include #include #include #include "altypes.hpp" #include "opthelpers.h" namespace al { 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(f32 const a, f32 const b, f32 const c, f32 const d) noexcept : mVals{{a,b,c,d}} { } constexpr auto operator=(const Vector&) & noexcept LIFETIMEBOUND -> Vector& = default; constexpr auto operator=(Vector&&) & noexcept LIFETIMEBOUND -> Vector& = default; [[nodiscard]] constexpr auto operator[](usize const idx) noexcept LIFETIMEBOUND -> f32& { return mVals[idx]; } [[nodiscard]] constexpr auto operator[](usize const idx) const noexcept LIFETIMEBOUND -> f32 const& { 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() -> f32 { auto const length_sqr = f32{mVals[0]*mVals[0] + mVals[1]*mVals[1] + mVals[2]*mVals[2]}; if(length_sqr > std::numeric_limits::epsilon()) { auto const length = std::sqrt(length_sqr); auto const inv_length = 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 -> f32 { 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( f32 const aa, f32 const ab, f32 const ac, f32 const ad, f32 const ba, f32 const bb, f32 const bc, f32 const bd, f32 const ca, f32 const cb, f32 const cc, f32 const cd, f32 const da, f32 const db, f32 const dc, f32 const 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 LIFETIMEBOUND -> Matrix& = default; constexpr auto operator=(Matrix&&) & noexcept LIFETIMEBOUND -> Matrix& = default; [[nodiscard]] constexpr auto operator[](usize const idx) noexcept LIFETIMEBOUND { return std::span{&mVals[idx*4], 4}; } [[nodiscard]] constexpr auto operator[](usize const idx) const noexcept LIFETIMEBOUND { return std::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 al #endif /* COMMON_VECMAT_H */ kcat-openal-soft-75c0059/common/vector.h000066400000000000000000000004261512220627100201020ustar00rootroot00000000000000#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 */ kcat-openal-soft-75c0059/common/win_main_utf8.h000066400000000000000000000102111512220627100213400ustar00rootroot00000000000000#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 may have limited availability of * characters). * * For MinGW, you must link with -municode */ #ifdef _WIN32 #include "config.h" #include #include #include #ifndef __cplusplus #ifdef __GNUC__ __attribute__((__unused__)) #endif static FILE *my_fopen(const char *fname, const wchar_t *wmode) { wchar_t *wname; int namelen; errno_t err; FILE *file; namelen = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, fname, -1, NULL, 0); if(namelen <= 0) { fprintf(stderr, "Failed to convert UTF-8 fname \"%s\"\n", fname); return NULL; } wname = (wchar_t*)calloc((size_t)namelen, sizeof(wchar_t)); if(!wname) { fprintf(stderr, "Failed to allocate %zu bytes for fname conversion\n", sizeof(wchar_t)*(size_t)namelen); return NULL; } MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, fname, -1, wname, namelen); err = _wfopen_s(&file, wname, wmode); if(err) { errno = err; file = NULL; } free(wname); return file; } #define fopen(f, m) my_fopen((f), (L##m)) #endif /* 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 #include #include #include #include #include #include "alnumeric.h" #include "fmt/base.h" #include "fmt/ostream.h" #if HAVE_CXXMODULES import gsl; #else #include "gsl/gsl" #endif extern "C" auto wmain(int argc, wchar_t **wargv) -> int { static constexpr auto flags = DWORD{WC_ERR_INVALID_CHARS}; const auto wargs = std::span{wargv, gsl::narrow(argc)}; auto argstr = std::string{}; try { for(std::wstring_view arg : wargs) { if(arg.empty()) { argstr.push_back('\0'); continue; } const auto u16len = gsl::narrow(arg.size()); const auto u8len = WideCharToMultiByte(CP_UTF8, flags, arg.data(), u16len, nullptr, 0, nullptr, nullptr); if(u8len < 1) throw std::runtime_error{"WideCharToMultiByte failed"}; const auto curlen = argstr.empty() ? argstr.size() : (argstr.size()+1); const auto newlen = curlen + gsl::narrow(u8len); argstr.resize(newlen, '\0'); if(WideCharToMultiByte(CP_UTF8, flags, arg.data(), u16len, &argstr[curlen], u8len, nullptr, nullptr) < 1) throw std::runtime_error{"WideCharToMultiByte failed"}; } } catch(std::exception& e) { fmt::println(std::cerr, "Failed to convert command line to UTF-8: {}", e.what()); return -1; } auto argv = std::vector(wargs.size()); auto stridx = 0_uz; std::ranges::generate(argv, [&argstr,&stridx] { auto *ret = &argstr[stridx]; stridx = argstr.find('\0', stridx) + 1; return ret; }); return main(argc, argv.data()); } #else /* __cplusplus */ int wmain(int argc, wchar_t **wargv) { char **argv; size_t total; int i; total = sizeof(*argv) * (size_t)argc; for(i = 0;i < argc;i++) total += (size_t)WideCharToMultiByte(CP_UTF8, 0, wargv[i], -1, NULL, 0, NULL, NULL); argv = (char**)calloc(1, total); argv[0] = (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); i = main(argc, argv); free(argv); return i; } #endif /* __cplusplus */ #endif /* !defined(SDL_MAIN_NEEDED) && !defined(SDL_MAIN_AVAILABLE) */ #endif /* _WIN32 */ #endif /* WIN_MAIN_UTF8_H */ kcat-openal-soft-75c0059/config.h.in000066400000000000000000000027711512220627100171670ustar00rootroot00000000000000/* 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 if the compiler supports ELF notes */ #cmakedefine HAVE_DLOPEN_NOTES /* Define to 1 if we have C++20 modules, else 0 */ #cmakedefine01 HAVE_CXXMODULES /* 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 kcat-openal-soft-75c0059/config_backends.h.in000066400000000000000000000010221512220627100210050ustar00rootroot00000000000000/* 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_WAVE #cmakedefine01 HAVE_SDL3 #cmakedefine01 HAVE_SDL2 kcat-openal-soft-75c0059/config_simd.h.in000066400000000000000000000004301512220627100201710ustar00rootroot00000000000000/* 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 kcat-openal-soft-75c0059/configs/000077500000000000000000000000001512220627100165655ustar00rootroot00000000000000kcat-openal-soft-75c0059/configs/HRTF+WASAPI Exclusive/000077500000000000000000000000001512220627100222405ustar00rootroot00000000000000kcat-openal-soft-75c0059/configs/HRTF+WASAPI Exclusive/alsoft.ini000066400000000000000000000001701512220627100242270ustar00rootroot00000000000000[General] channels=stereo stereo-mode=headphones stereo-encoding=hrtf period_size=1 [wasapi] exclusive-mode=truekcat-openal-soft-75c0059/configs/HRTF/000077500000000000000000000000001512220627100173305ustar00rootroot00000000000000kcat-openal-soft-75c0059/configs/HRTF/alsoft.ini000066400000000000000000000001121512220627100213130ustar00rootroot00000000000000[general] channels=stereo stereo-mode=headphones stereo-encoding=hrtf kcat-openal-soft-75c0059/configs/WASAPI Exclusive/000077500000000000000000000000001512220627100215015ustar00rootroot00000000000000kcat-openal-soft-75c0059/configs/WASAPI Exclusive/alsoft.ini000066400000000000000000000000711512220627100234700ustar00rootroot00000000000000[General] period_size=1 [wasapi] exclusive-mode=truekcat-openal-soft-75c0059/core/000077500000000000000000000000001512220627100160655ustar00rootroot00000000000000kcat-openal-soft-75c0059/core/ambdec.cpp000066400000000000000000000235521512220627100200130ustar00rootroot00000000000000 #include "config.h" #include "ambdec.h" #include #include #include #include #include #include #include #include #include #include #include #include "alformat.hpp" #include "alnumeric.h" #include "alstring.h" #include "filesystem.h" #include "gsl/gsl" namespace { auto read_word(std::istream &f) -> std::string { auto ret = std::string{}; f >> ret; return ret; } auto is_at_end(const std::string &buffer, std::size_t endpos) -> bool { 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, al::format_string fmt, Args&& ...args) -> al::unexpected { auto str = al::format("Line {}: ", linenum); str += al::format(std::move(fmt), std::forward(args)...); return al::unexpected(std::move(str)); } } // namespace auto AmbDecConf::load(const std::string_view fname) noexcept -> al::expected { auto f = fs::ifstream{fs::path(al::char_as_u8(fname))}; if(!f.is_open()) return al::unexpected(al::format("Failed to open file \"{}\"", fname)); auto scope = ReaderScope::Global; auto speaker_pos = 0_uz; auto lfmatrix_pos = 0_uz; auto hfmatrix_pos = 0_uz; auto linenum = 0_uz; auto buffer = std::string{}; while(f.good() && std::getline(f, buffer)) { ++linenum; auto istr = std::istringstream{buffer}; auto 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"); auto &spkr = Speakers[speaker_pos++]; istr >> spkr.Name; istr >> spkr.Distance; istr >> spkr.Azimuth; istr >> spkr.Elevation; istr >> spkr.Connection; if(!(spkr.Distance >= 0.0f && std::isfinite(spkr.Distance))) return make_error(linenum, "Invalid speaker {} distance: {}", speaker_pos, spkr.Distance); } 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") { auto toread = (ChanMask > Ambi3OrderMask) ? 5_uz : 4_uz; auto curgain = 0_uz; auto value = float{}; 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"); auto mask = ChanMask; auto &mtxrow = matrix[pos++]; mtxrow.fill(0.0f); auto value = float{}; while(mask) { auto idx = gsl::narrow_cast(std::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"); auto numspeakers = size_t{}; 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"); auto 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 = std::span{Matrix}.first(Speakers.size()); HFMatrix = std::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 = gsl::narrow_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::monostate{}; } else return make_error(linenum, "Unexpected command: {}", command); istr.clear(); const auto endpos = gsl::narrow_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"); } kcat-openal-soft-75c0059/core/ambdec.h000066400000000000000000000024571512220627100174610ustar00rootroot00000000000000#ifndef CORE_AMBDEC_H #define CORE_AMBDEC_H #include #include #include #include #include #include #include "expected.hpp" #include "core/ambidefs.h" #include "opthelpers.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{}; std::span LFMatrix; std::array HFOrderGain{}; std::span HFMatrix; NOINLINE ~AmbDecConf() = default; auto load(const std::string_view fname) noexcept -> al::expected; }; #endif /* CORE_AMBDEC_H */ kcat-openal-soft-75c0059/core/ambidefs.cpp000066400000000000000000000661731512220627100203600ustar00rootroot00000000000000 #include "config.h" #include "ambidefs.h" #include #include #include #include #include #include "alnumeric.h" #include "gsl/gsl" namespace { static_assert(AmbiScale::FromN3D.size() == MaxAmbiChannels); static_assert(AmbiScale::FromSN3D.size() == MaxAmbiChannels); static_assert(AmbiScale::FromFuMa.size() == MaxAmbiChannels); static_assert(AmbiScale::FromUHJ.size() == MaxAmbiChannels); static_assert(AmbiIndex::FromFuMa.size() == MaxAmbiChannels); static_assert(AmbiIndex::FromFuMa2D.size() == MaxAmbi2DChannels); using AmbiChannelFloatArray = std::array; constexpr auto inv_sqrt3f = gsl::narrow_cast(1.0/std::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 auto HFScales = std::array{ std::array{4.000000000e+00f, 2.309401077e+00f, 1.192569588e+00f, 7.189495850e-01f, 4.784482742e-01f}, std::array{4.000000000e+00f, 2.309401077e+00f, 1.192569588e+00f, 7.189495850e-01f, 4.784482742e-01f}, std::array{2.981423970e+00f, 2.309401077e+00f, 1.192569588e+00f, 7.189495850e-01f, 4.784482742e-01f}, std::array{2.359168820e+00f, 2.031565936e+00f, 1.444598386e+00f, 7.189495850e-01f, 4.784482742e-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 auto HFScales2D = std::array{ std::array{2.236067977e+00f, 1.581138830e+00f, 9.128709292e-01f, 6.050756345e-01f, 4.370160244e-01f}, std::array{2.236067977e+00f, 1.581138830e+00f, 9.128709292e-01f, 6.050756345e-01f, 4.370160244e-01f}, std::array{1.825741858e+00f, 1.581138830e+00f, 9.128709292e-01f, 6.050756345e-01f, 4.370160244e-01f}, std::array{1.581138830e+00f, 1.460781803e+00f, 1.118033989e+00f, 6.050756345e-01f, 4.370160244e-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 auto Order1Dec = std::array{ 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 auto Order1Enc = std::array{ 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(Order1Dec.size() == Order1Enc.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 auto Order1Dec2D = std::array{ 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 auto Order1Enc2D = std::array{ 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(Order1Dec2D.size() == Order1Enc2D.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 auto Order2Dec = std::array{ 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 auto Order2Enc = std::array{ 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(Order2Dec.size() == Order2Enc.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 auto Order2Dec2D = std::array{ 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}, 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}, 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}, 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}, 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}, 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}, 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}, 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}, }; constexpr auto Order2Enc2D = std::array{ 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(Order2Dec2D.size() == Order2Enc2D.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 auto Order3Dec = std::array{ 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 auto Order3Enc = std::array{ 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(Order3Dec.size() == Order3Enc.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 auto Order3Dec2D = std::array{ 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}, 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}, 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}, 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}, 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}, 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}, 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}, 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}, 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}, 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}, }; constexpr auto Order3Enc2D = std::array{ CalcAmbiCoeffs( 3.090169944e-01f, 0.0f, 9.510565163e-01f), CalcAmbiCoeffs( 8.090169944e-01f, 0.0f, 5.877852523e-01f), CalcAmbiCoeffs( 1.000000000e+00f, 0.0f, 0.000000000e+00f), CalcAmbiCoeffs( 8.090169944e-01f, 0.0f, -5.877852523e-01f), CalcAmbiCoeffs( 3.090169944e-01f, 0.0f, -9.510565163e-01f), CalcAmbiCoeffs(-3.090169944e-01f, 0.0f, -9.510565163e-01f), CalcAmbiCoeffs(-8.090169944e-01f, 0.0f, -5.877852523e-01f), CalcAmbiCoeffs(-1.000000000e+00f, 0.0f, 0.000000000e+00f), CalcAmbiCoeffs(-8.090169944e-01f, 0.0f, 5.877852523e-01f), CalcAmbiCoeffs(-3.090169944e-01f, 0.0f, 9.510565163e-01f), }; static_assert(Order3Dec2D.size() == Order3Enc2D.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 auto Order4Dec2D = std::array{ 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 auto Order4Enc2D = std::array{ 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(Order4Dec2D.size() == Order4Enc2D.size(), "Fourth-order 2D mismatch"); template constexpr auto CalcAmbiUpsampler(const std::array, M> &decoder, const std::array &encoder) noexcept { auto res = std::array{}; for(const auto i : std::views::iota(0_uz, decoder[0].size())) { for(const auto j : std::views::iota(0_uz, encoder[0].size())) { auto sum = 0.0; for(const auto k : std::views::iota(0_uz, decoder.size())) sum += f64{decoder[k][i]} * encoder[k][j]; res[i][j] = gsl::narrow_cast(sum); } } return res; } } // namespace namespace AmbiScale { constinit UpsamplerArrays<4> const FirstOrderUp{CalcAmbiUpsampler(Order1Dec, Order1Enc)}; constinit UpsamplerArrays<4> const FirstOrder2DUp{CalcAmbiUpsampler(Order1Dec2D, Order1Enc2D)}; constinit UpsamplerArrays<9> const SecondOrderUp{CalcAmbiUpsampler(Order2Dec, Order2Enc)}; constinit UpsamplerArrays<9> const SecondOrder2DUp{CalcAmbiUpsampler(Order2Dec2D, Order2Enc2D)}; constinit UpsamplerArrays<16> const ThirdOrderUp{CalcAmbiUpsampler(Order3Dec, Order3Enc)}; constinit UpsamplerArrays<16> const ThirdOrder2DUp{CalcAmbiUpsampler(Order3Dec2D, Order3Enc2D)}; constinit UpsamplerArrays<25> const FourthOrder2DUp{CalcAmbiUpsampler(Order4Dec2D, Order4Enc2D)}; auto GetHFOrderScales(u32 const src_order, u32 const dev_order, bool const horizontalOnly) noexcept -> std::array { auto res = std::array{}; auto const scales = horizontalOnly ? std::span{HFScales2D} : std::span{HFScales}; std::ranges::transform(scales[src_order], scales[dev_order], res.begin(), std::divides{}); return res; } } /* namespace AmbiScale */ kcat-openal-soft-75c0059/core/ambidefs.h000066400000000000000000000274251512220627100200220ustar00rootroot00000000000000#ifndef CORE_AMBIDEFS_H #define CORE_AMBIDEFS_H #include #include #include "alnumeric.h" #include "opthelpers.h" /* 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(usize const order) noexcept -> usize { return (order+1) * (order+1); } inline constexpr auto MaxAmbiOrder = 4_u8; inline constexpr auto MaxAmbiChannels = 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 auto Ambi0OrderMask = 0x00000001_u32; inline constexpr auto Ambi1OrderMask = 0x0000000f_u32; inline constexpr auto Ambi2OrderMask = 0x000001ff_u32; inline constexpr auto Ambi3OrderMask = 0x0000ffff_u32; inline constexpr auto Ambi4OrderMask = 0x01ffffff_u32; /* 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 auto AmbiPeriphonicMask = 0xfe7ce4_u32; /* 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(usize const order) noexcept -> usize { 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. */ namespace AmbiScale { inline 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, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, }; inline 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) */ 3.000000000f, /* ACN 16, sqrt(9) */ 3.000000000f, /* ACN 17, sqrt(9) */ 3.000000000f, /* ACN 18, sqrt(9) */ 3.000000000f, /* ACN 19, sqrt(9) */ 3.000000000f, /* ACN 20, sqrt(9) */ 3.000000000f, /* ACN 21, sqrt(9) */ 3.000000000f, /* ACN 22, sqrt(9) */ 3.000000000f, /* ACN 23, sqrt(9) */ 3.000000000f, /* ACN 24, sqrt(9) */ }; inline 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) */ /* Higher orders not relevant for FuMa. Although maxN (which is what * FuMa uses aside from an extra -3dB factor on W) is defined such that * any one component never exceeds a gain of 1.0 for a panned mono * source, allowing scaling factors to be calculated. But unless and * until I hear of software using FuMa in fourth-order and above, or * otherwise get confirmation on its accuracy, I don't want to make * that assumption. * * These ratios were calculated by brute-forcing the maximum N3D value * (testing various directions to find the largest absolute result), * which will be the maxN/FuMa->N3D scaling factor, and then searching * for a fraction fitting the square of that factor. There may be more * accurate or formal methods to find these values, but that aside, * these scalings have less than 1e-8 error from the calculated scaling * factor. */ 1.0f, /* 2.218529919f, ACN 16, sqrt(315)/8 */ 1.0f, /* 2.037849855f, ACN 17, sqrt(8505/2048) */ 1.0f, /* 2.156208407f, ACN 18, sqrt(3645)/28 */ 1.0f, /* 2.504586102f, ACN 19, sqrt(35599/5675) */ 1.0f, /* 3.000000000f, ACN 20, sqrt(9) */ 1.0f, /* 2.504586102f, ACN 21, sqrt(35599/5675) */ 1.0f, /* 2.156208407f, ACN 22, sqrt(3645)/28 */ 1.0f, /* 2.037849855f, ACN 23, sqrt(8505/2048) */ 1.0f, /* 2.218529919f, ACN 24, sqrt(315)/8 */ }; inline 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, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, }; template using UpsamplerArrays = std::array, N>; DECL_HIDDEN extern constinit UpsamplerArrays<4> const FirstOrderUp; DECL_HIDDEN extern constinit UpsamplerArrays<4> const FirstOrder2DUp; DECL_HIDDEN extern constinit UpsamplerArrays<9> const SecondOrderUp; DECL_HIDDEN extern constinit UpsamplerArrays<9> const SecondOrder2DUp; DECL_HIDDEN extern constinit UpsamplerArrays<16> const ThirdOrderUp; DECL_HIDDEN extern constinit UpsamplerArrays<16> const ThirdOrder2DUp; DECL_HIDDEN extern constinit UpsamplerArrays<25> const FourthOrder2DUp; /* Retrieves per-order HF scaling factors for "upsampling" ambisonic data. */ auto GetHFOrderScales(u32 const src_order, u32 const dev_order, bool const horizontalOnly) noexcept -> std::array; } /* namespace AmbiScale */ namespace AmbiIndex { inline constexpr auto FromFuMa = std::array{ 0_u8, /* W */ 3_u8, /* X */ 1_u8, /* Y */ 2_u8, /* Z */ 6_u8, /* R */ 7_u8, /* S */ 5_u8, /* T */ 8_u8, /* U */ 4_u8, /* V */ 12_u8, /* K */ 13_u8, /* L */ 11_u8, /* M */ 14_u8, /* N */ 10_u8, /* O */ 15_u8, /* P */ 9_u8, /* Q */ /* Higher orders not relevant for FuMa. The previous orders form a * pattern suggesting 20,21,19,22,18,23,17,24,16, but as above, unless * I hear otherwise, I don't want to make assumptions here. */ 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, }; inline constexpr auto FromFuMa2D = std::array{ 0_u8, /* W */ 3_u8, /* X */ 1_u8, /* Y */ 8_u8, /* U */ 4_u8, /* V */ 15_u8, /* P */ 9_u8, /* Q */ /* Higher orders not relevant for FuMa. Though the previous orders form * a pattern suggesting 24,16. */ 0_u8, 0_u8, }; inline constexpr auto FromACN = std::array{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, }; inline constexpr auto FromACN2D = std::array{ 0, 1,3, 4,8, 9,15, 16,24, }; inline constexpr auto OrderFromChannel = std::array{ 0, 1,1,1, 2,2,2,2,2, 3,3,3,3,3,3,3, 4,4,4,4,4,4,4,4,4, }; inline constexpr auto OrderFrom2DChannel = std::array{ 0, 1,1, 2,2, 3,3, 4,4, }; } /* namespace AmbiIndex */ /** * 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(f32 const y, f32 const z, f32 const x) -> std::array { auto const xx = x*x; auto const yy = y*y; auto const zz = z*z; auto const xy = x*y; auto const yz = y*z; auto const xz = x*z; auto const xxxx = xx*xx; auto const yyyy = yy*yy; auto const xxyy = xx*yy; auto const zzzz = zz*zz; return std::array{{ /* Zeroth-order */ 1.0f, /* ACN 0 = 1 */ /* First-order */ std::numbers::sqrt3_v * y, /* ACN 1 = sqrt(3) * Y */ std::numbers::sqrt3_v * z, /* ACN 2 = sqrt(3) * Z */ std::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 */ 8.874119675e+00f * (xy*(xx - yy)), /* ACN 16 = sqrt(35)*3/2 * X * Y * (X*X - Y*Y) */ 6.274950199e+00f * ((3.0f*xx - yy) * yz), /* ACN 17 = sqrt(35/2)*3/2 * (3*X*X - Y*Y) * Y * Z */ 3.354101966e+00f * (xy * (7.0f*zz - 1.0f)), /* ACN 18 = sqrt(5)*3/2 * X * Y * (7*Z*Z - 1) */ 2.371708245e+00f * (yz * (7.0f*zz - 3.0f)), /* ACN 19 = sqrt(5/2)*3/2 * Y * Z * (7*Z*Z - 3) */ 3.750000000e-01f * (35.0f*zzzz - 30.0f*zz + 3.0f), /* ACN 20 = 3/8 * (35*Z*Z*Z*Z - 30*Z*Z + 3) */ 2.371708245e+00f * (xz * (7.0f*zz - 3.0f)), /* ACN 21 = sqrt(5/2)*3/2 * X * Z * (7*Z*Z - 3) */ 1.677050983e+00f * ((xx - yy) * (7.0f*zz - 1.0f)), /* ACN 22 = sqrt(5)*3/4 * (X*X - Y*Y) * (7*Z*Z - 1) */ 6.274950199e+00f * ((xx - 3.0f*yy) * xz), /* ACN 23 = sqrt(35/2)*3/2 * (X*X - 3*Y*Y) * X * Z */ 2.218529919e+00f * (xxxx - 6.0f*xxyy + yyyy), /* ACN 24 = sqrt(35)*3/8 * (X*X*X*X - 6*X*X*Y*Y + Y*Y*Y*Y) */ }}; } #endif /* CORE_AMBIDEFS_H */ kcat-openal-soft-75c0059/core/async_event.h000066400000000000000000000021021512220627100205470ustar00rootroot00000000000000#ifndef CORE_EVENT_H #define CORE_EVENT_H #include #include #include #include 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(AsyncEvent &event, Args&& ...args) { auto *evt = std::construct_at(&event, std::in_place_type, std::forward(args)...); return std::get(*evt); } #endif kcat-openal-soft-75c0059/core/bformatdec.cpp000066400000000000000000000062101512220627100206760ustar00rootroot00000000000000 #include "config.h" #include "bformatdec.h" #include #include #include #include #include "alnumeric.h" #include "bufferline.h" #include "filters/splitter.h" #include "mixer.h" #include "opthelpers.h" namespace { template struct overloaded : Ts... { using Ts::operator()...; }; } // namespace BFormatDec::BFormatDec(const size_t inchans, const std::span coeffs, const std::span coeffslf, const float xover_f0norm) { if(coeffslf.empty()) { auto &decoder = mChannelDec.emplace(inchans); for(const auto j : std::views::iota(0_uz, decoder.size())) { std::ranges::transform(coeffs, decoder[j].mGains.begin(), [j](const ChannelDec &incoeffs) { return incoeffs[j]; }); } } else { using decoder_t = DBandDecoderVector::value_type; auto &decoder = mChannelDec.emplace(inchans); decoder[0].mXOver.init(xover_f0norm); std::ranges::fill(decoder|std::views::drop(1) | std::views::transform(&decoder_t::mXOver), decoder[0].mXOver); for(const auto j : std::views::iota(0_uz, decoder.size())) { std::ranges::transform(coeffs, decoder[j].mGains[sHFBand].begin(), [j](const ChannelDec &incoeffs) { return incoeffs[j]; }); std::ranges::transform(coeffslf, decoder[j].mGains[sLFBand].begin(), [j](const ChannelDec &incoeffs) { return incoeffs[j]; }); } } } void BFormatDec::process(const std::span OutBuffer, const std::span InSamples, const size_t SamplesToDo) { ASSUME(SamplesToDo > 0); std::visit(overloaded { [=,this](DBandDecoderVector &decoder) { using decoder_t = DBandDecoderVector::value_type; const auto hfSamples = std::span{mSamples[sHFBand]}.first(SamplesToDo); const auto lfSamples = std::span{mSamples[sLFBand]}.first(SamplesToDo); std::ignore = std::ranges::mismatch(decoder, InSamples, [OutBuffer,SamplesToDo,hfSamples,lfSamples](decoder_t &chandec, FloatConstBufferSpan input) { chandec.mXOver.process(input.first(SamplesToDo), hfSamples, lfSamples); MixSamples(hfSamples, OutBuffer, chandec.mGains[sHFBand], chandec.mGains[sHFBand], 0_uz, 0_uz); MixSamples(lfSamples, OutBuffer, chandec.mGains[sLFBand], chandec.mGains[sLFBand], 0_uz, 0_uz); return true; }); }, [=](SBandDecoderVector &decoder) { using decoder_t = SBandDecoderVector::value_type; std::ignore = std::ranges::mismatch(decoder, InSamples, [OutBuffer,SamplesToDo](decoder_t &chandec, FloatConstBufferSpan input) { MixSamples(input.first(SamplesToDo), OutBuffer, chandec.mGains, chandec.mGains, 0_uz, 0_uz); return true; }); }, }, mChannelDec); } kcat-openal-soft-75c0059/core/bformatdec.h000066400000000000000000000025151512220627100203470ustar00rootroot00000000000000#ifndef CORE_BFORMATDEC_H #define CORE_BFORMATDEC_H #include #include #include #include #include #include "ambidefs.h" #include "bufferline.h" #include "devformat.h" #include "filters/splitter.h" using ChannelDec = std::array; class BFormatDec { static constexpr size_t sHFBand{0}; static constexpr size_t sLFBand{1}; static constexpr size_t sNumBands{2}; struct ChannelDecoderSingle { std::array mGains{}; }; using SBandDecoderVector = std::vector; struct ChannelDecoderDual { BandSplitter mXOver; std::array,sNumBands> mGains{}; }; using DBandDecoderVector = std::vector; alignas(16) std::array mSamples{}; std::variant mChannelDec; public: BFormatDec(const size_t inchans, const std::span coeffs, const std::span coeffslf, const float xover_f0norm); /* Decodes the ambisonic input to the given output channels. */ void process(const std::span OutBuffer, const std::span InSamples, const size_t SamplesToDo); }; #endif /* CORE_BFORMATDEC_H */ kcat-openal-soft-75c0059/core/bs2b.cpp000066400000000000000000000124331512220627100174240ustar00rootroot00000000000000/*- * 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 #include #include #include #include "bs2b.h" #include "gsl/gsl" namespace { /* Set up all data. */ void init(Bs2b::bs2b_processor *bs2b) { const auto [Fc_lo, Fc_hi, G_lo, G_hi] = std::invoke([bs2b] { switch(bs2b->level) { case Bs2b::LowCLevel: /* Low crossfeed level */ return std::array{360.0f, 501.0f, 0.398107170553497f, 0.205671765275719f}; case Bs2b::MiddleCLevel: /* Middle crossfeed level */ return std::array{500.0f, 711.0f, 0.459726988530872f, 0.228208484414988f}; case Bs2b::HighCLevel: /* High crossfeed level (virtual speakers are closer to itself) */ return std::array{700.0f, 1021.0f, 0.530884444230988f, 0.250105790667544f}; case Bs2b::LowECLevel: /* Low easy crossfeed level */ return std::array{360.0f, 494.0f, 0.316227766016838f, 0.168236228897329f}; case Bs2b::MiddleECLevel: /* Middle easy crossfeed level */ return std::array{500.0f, 689.0f, 0.354813389233575f, 0.187169483835901f}; case Bs2b::HighECLevel: /* High easy crossfeed level */ default: bs2b->level = Bs2b::HighECLevel; return std::array{700.0f, 975.0f, 0.398107170553497f, 0.205671765275719f}; } }); const auto g = 1.0f / (1.0f - G_hi + G_lo); /* $fc = $Fc / $s; * $d = 1 / 2 / pi / $fc; * $x = exp(-1 / $d); */ auto x = std::exp(-std::numbers::pi_v*2.0f*Fc_lo/gsl::narrow_cast(bs2b->srate)); bs2b->b1_lo = x; bs2b->a0_lo = G_lo * (1.0f - x) * g; x = std::exp(-std::numbers::pi_v*2.0f*Fc_hi/gsl::narrow_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_processor::set_params(int level_, int srate_) { if(srate_ < 1) throw std::runtime_error{"BS2B srate < 1"}; level = level_; srate = srate_; init(this); } void bs2b_processor::clear() { history.fill(t_last_sample{}); } void bs2b_processor::cross_feed(const std::span Left, const std::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(std::ssize(samples), rem); /* Process left input */ auto z_lo = history[0].lo; auto z_hi = history[0].hi; std::ranges::transform(std::views::counted(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::ranges::transform(std::views::counted(rightio, todo), samples, samples.begin(), [a0hi,a1hi,b1hi,a0lo,b1lo,&z_lo,&z_hi](const float x, const std::span cur) { 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{cur[0]+y0, cur[1]+y1}; }); history[1].lo = z_lo; history[1].hi = z_hi; const auto samples_todo = samples | std::views::take(todo); leftio = std::ranges::copy(samples_todo | std::views::elements<0>, leftio).out; rightio = std::ranges::copy(samples_todo | std::views::elements<1>, rightio).out; } } } // namespace Bs2b kcat-openal-soft-75c0059/core/bs2b.h000066400000000000000000000052501512220627100170700ustar00rootroot00000000000000/*- * 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 "bufferline.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_processor { 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 std::span Left, const std::span Right); }; struct bs2b : public bs2b_processor { std::array mStorage{}; }; } // namespace Bs2b #endif /* CORE_BS2B_H */ kcat-openal-soft-75c0059/core/bsinc_defs.h000066400000000000000000000005761512220627100203450ustar00rootroot00000000000000#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 */ kcat-openal-soft-75c0059/core/bsinc_tables.cpp000066400000000000000000000351541512220627100212310ustar00rootroot00000000000000 #include "bsinc_tables.h" #include #include #include #include #include #include #include #include #include #include #include "alnumeric.h" #include "bsinc_defs.h" #include "gsl/gsl" #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 auto x2 = x/2.0; auto term = 1.0; auto sum = 1.0; auto k = 1; /* Let the integration converge until the term of the sum is no longer * significant. */ auto last_sum = double{}; do { const auto y = x2 / k; ++k; last_sum = sum; term *= y * y; sum += term; } while(sum != last_sum); return gsl::narrow_cast(sum); } /* This is the normalized cardinal sine (sinc) function. * * sinc(x) = { 1, x = 0 * { sin(pi x) / (pi x), otherwise. */ constexpr auto Sinc(const double x) -> double { constexpr auto epsilon = std::numeric_limits::epsilon(); if(!(x > epsilon || x < -epsilon)) return 1.0; return std::sin(std::numbers::pi*x) / (std::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 auto Kaiser(const double beta, const double k, const double besseli_0_beta) -> double { 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 auto CalcKaiserWidth(const double rejection, const uint order) noexcept -> double { if(rejection > 21.19) return (rejection - 7.95) / (2.285 * std::numbers::pi*2.0 * order); /* This enforces a minimum rejection of just above 21.18dB */ return 5.79 / (std::numbers::pi*2.0 * order); } /* Calculates the beta value of the Kaiser window. Rejection is in dB. */ constexpr auto CalcKaiserBeta(const double rejection) -> double { 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 beta{}; double scaleBase{}; double scaleLimit{}; std::array a{}; std::array m{}; uint total_size{}; constexpr BSincHeader(uint rejection, uint order, uint maxScale) noexcept : beta{CalcKaiserBeta(rejection)}, scaleBase{CalcKaiserWidth(rejection, order) / 2.0} , scaleLimit{1.0 / maxScale} { const auto base_a = (order+1.0) / 2.0; for(const auto si : std::views::iota(0u, BSincScaleCount)) { const auto scale = std::lerp(scaleBase, 1.0, (si+1u) / double{BSincScaleCount}); a[si] = std::min(base_a/scale, base_a*maxScale); /* std::ceil() isn't constexpr until C++23, this should behave the * same. */ auto a_ = gsl::narrow_cast(a[si]); a_ += (gsl::narrow_cast(a_) != a[si]); m[si] = a_ * 2u; total_size += 4u * BSincPhaseCount * ((m[si]+3u) & ~3u); } } }; /* 11th and 23rd order filters (12 and 24-point respectively) with a 60dB drop * at nyquist. Each filter will scale up to double size when downsampling, to * 23rd and 47th order respectively. */ constexpr auto bsinc12_hdr = BSincHeader{60, 11, 2}; constexpr auto bsinc24_hdr = BSincHeader{60, 23, 2}; /* 47th order filter (48-point) with an 80dB drop at nyquist. The filter order * doesn't increase when downsampling. */ constexpr auto bsinc48_hdr = BSincHeader{80, 47, 1}; template struct BSincFilterArray { alignas(16) std::array mTable{}; BSincFilterArray() noexcept { static constexpr auto BSincPointsMax = (hdr.m[0]+3u) & ~3u; static_assert(BSincPointsMax <= MaxResamplerPadding, "MaxResamplerPadding is too small"); using filter_type = std::array,BSincPhaseCount>; auto filter = std::vector(BSincScaleCount); static constexpr auto besseli_0_beta = ::cyl_bessel_i(0, hdr.beta); /* Calculate the Kaiser-windowed Sinc filter coefficients for each * scale and phase index. */ for(const auto si : std::views::iota(0_uz, BSincScaleCount)) { const auto a = hdr.a[si]; const auto m = hdr.m[si]; const auto l = std::floor(m*0.5) - 1.0; const auto o = size_t{BSincPointsMax-m} / 2u; const auto scale = std::lerp(hdr.scaleBase, 1.0, gsl::narrow_cast(si+1u)/double{BSincScaleCount}); /* Calculate an appropriate cutoff frequency. An explanation may be * in order here. * * When up-sampling, or down-sampling by less than the max scaling * factor (when scale >= scaleLimit), the filter order increases as * the down-sampling factor is reduced, enabling a consistent * filter response output. * * When down-sampling by more than the max scale factor, the filter * order stays constant to avoid further increasing the processing * cost, causing the transition width to increase. This would * normally be compensated for by reducing the cutoff frequency, * to keep the transition band under the nyquist frequency and * avoid aliasing. However, this has the side-effect of attenuating * more of the original high frequency content, which can be * significant with more extreme down-sampling scales. * * To combat this, we can allow for some aliasing to keep the * cutoff frequency higher than it would otherwise be. We can allow * the transition band to "wrap around" the nyquist frequency, so * the output would have some low-level aliasing that overlays with * the attenuated frequencies in the transition band. This allows * the cutoff frequency to remain fixed as the transition width * increases, until the stop frequency aliases back to the cutoff * frequency and the transition band becomes fully wrapped over * itself, at which point the cutoff frequency will lower at half * the rate the transition width increases. * * This has an additional benefit when dealing with typical output * rates like 44 or 48khz. Since human hearing maxes out at 20khz, * and these rates handle frequencies up to 22 or 24khz, this lets * some aliasing get masked. For example, the bsinc24 filter with * 48khz output has a cutoff of 20khz when down-sampling, and a * 4khz transition band. When down-sampling by more extreme scales, * the cutoff frequency can stay at 20khz while the transition * width doubles before any aliasing noise may become audible. * * This is what we do here. * * 'max_cutoff` is the upper bound normalized cutoff frequency for * this scale factor, that aligns with the same absolute frequency * as nominal resample factors. When up-sampling (scale == 1), the * cutoff can't be raised further than this, or else it would * prematurely add audible aliasing noise. * * 'width' is the normalized transition width for this scale * factor. * * '(scale - width)*0.5' calculates the cutoff frequency necessary * for the transition band to fully wrap on itself around the * nyquist frequency. If this is larger than max_cutoff, the * transition band is not fully wrapped at this scale and the * cutoff doesn't need adjustment. */ const auto max_cutoff = (0.5 - hdr.scaleBase)*scale; const auto width = hdr.scaleBase * std::max(hdr.scaleLimit, scale); const auto cutoff2 = std::min(max_cutoff, (scale - width)*0.5) * 2.0; for(const auto pi : std::views::iota(0_uz, BSincPhaseCount)) { const auto phase = l + gsl::narrow_cast(pi)/double{BSincPhaseCount}; for(const auto i : std::views::iota(0_uz, m)) { const auto x = gsl::narrow_cast(i) - phase; filter[si][pi][o+i] = Kaiser(hdr.beta, x/a, besseli_0_beta) * cutoff2 * Sinc(cutoff2*x); } } } auto idx = 0_uz; for(const auto si : std::views::iota(0_uz, BSincScaleCount)) { const auto m = (hdr.m[si]+3_uz) & ~3_uz; const auto o = size_t{BSincPointsMax-m} / 2u; /* Write out each phase index's filter and phase delta for this * quality scale. */ for(const auto pi : std::views::iota(0_uz, BSincPhaseCount)) { for(const auto i : std::views::iota(0_uz, m)) mTable[idx++] = gsl::narrow_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(const auto i : std::views::iota(0_uz, m)) { const auto phDelta = filter[si][pi+1][o+i] - filter[si][pi][o+i]; mTable[idx++] = gsl::narrow_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++] = gsl::narrow_cast(0.0 - filter[si][pi][o]); for(const auto i : std::views::iota(1_uz, m)) { const auto phDelta = filter[si][0][o+i-1] - filter[si][pi][o+i]; mTable[idx++] = gsl::narrow_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(const auto pi : std::views::iota(0_uz, BSincPhaseCount)) { for(const auto i : std::views::iota(0_uz, m)) { const auto scDelta = filter[si+1][pi][o+i] - filter[si][pi][o+i]; mTable[idx++] = gsl::narrow_cast(scDelta); } if(pi < BSincPhaseCount-1) { for(const auto i : std::views::iota(0_uz, m)) { const auto 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++] = gsl::narrow_cast(spDelta); } } else { mTable[idx++] = gsl::narrow_cast((0.0 - filter[si+1][pi][o]) - (0.0 - filter[si][pi][o])); for(const auto i : std::views::iota(1_uz, m)) { const auto 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++] = gsl::narrow_cast(spDelta); } } } } else { /* The last scale index doesn't have scale-related deltas. */ for(const auto i [[maybe_unused]] : std::views::iota(0_uz, BSincPhaseCount*m*2)) mTable[idx++] = 0.0f; } } Ensures(idx == hdr.total_size); } [[nodiscard]] static constexpr auto getHeader() noexcept -> const BSincHeader& { return hdr; } [[nodiscard]] constexpr auto getTable() const noexcept { return std::span{mTable}; } }; const auto bsinc12_filter = BSincFilterArray{}; const auto bsinc24_filter = BSincFilterArray{}; const auto bsinc48_filter = BSincFilterArray{}; template constexpr auto GenerateBSincTable(const T &filter) noexcept -> BSincTable { auto ret = BSincTable{}; const BSincHeader &hdr = filter.getHeader(); ret.scaleBase = gsl::narrow_cast(hdr.scaleBase); ret.scaleRange = gsl::narrow_cast(1.0 / (1.0 - hdr.scaleBase)); for(const auto i : std::views::iota(0_uz, BSincScaleCount)) ret.m[i] = (hdr.m[i]+3u) & ~3u; ret.filterOffset[0] = 0; for(const auto i : std::views::iota(1_uz, BSincScaleCount)) ret.filterOffset[i] = ret.filterOffset[i-1] + ret.m[i-1]*4*BSincPhaseCount; ret.Tab = filter.getTable(); return ret; } } // namespace constinit const BSincTable gBSinc12{GenerateBSincTable(bsinc12_filter)}; constinit const BSincTable gBSinc24{GenerateBSincTable(bsinc24_filter)}; constinit const BSincTable gBSinc48{GenerateBSincTable(bsinc48_filter)}; kcat-openal-soft-75c0059/core/bsinc_tables.h000066400000000000000000000010321512220627100206620ustar00rootroot00000000000000#ifndef CORE_BSINC_TABLES_H #define CORE_BSINC_TABLES_H #include #include #include "bsinc_defs.h" #include "opthelpers.h" struct BSincTable { float scaleBase, scaleRange; std::array m; std::array filterOffset; std::span Tab; }; DECL_HIDDEN extern constinit const BSincTable gBSinc12; DECL_HIDDEN extern constinit const BSincTable gBSinc24; DECL_HIDDEN extern constinit const BSincTable gBSinc48; #endif /* CORE_BSINC_TABLES_H */ kcat-openal-soft-75c0059/core/buffer_storage.h000066400000000000000000000047031512220627100212370ustar00rootroot00000000000000#ifndef CORE_BUFFER_STORAGE_H #define CORE_BUFFER_STORAGE_H #include #include #include "alnumeric.h" #include "fmt_traits.h" #include "storage_formats.h" constexpr auto IsBFormat(FmtChannels const chans) noexcept -> bool { 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 auto IsUHJ(FmtChannels const chans) noexcept -> bool { return chans == FmtUHJ2 || chans == FmtUHJ3 || chans == FmtUHJ4 || chans == FmtSuperStereo; } /** Ambisonic formats are either B-Format or UHJ formats. */ constexpr auto IsAmbisonic(FmtChannels const chans) noexcept -> bool { return IsBFormat(chans) || IsUHJ(chans); } constexpr auto Is2DAmbisonic(FmtChannels const chans) noexcept -> bool { return chans == FmtBFormat2D || chans == FmtUHJ2 || chans == FmtUHJ3 || chans == FmtSuperStereo; } using CallbackType = auto(*)(void*, void*, i32) noexcept -> i32; using SampleVariant = std::variant, std::span, std::span, std::span, std::span, std::span, std::span, std::span, std::span>; struct BufferStorage { CallbackType mCallback{nullptr}; void *mUserData{nullptr}; SampleVariant mData; u32 mSampleRate{0_u32}; FmtChannels mChannels{FmtMono}; FmtType mType{FmtShort}; u32 mSampleLen{0_u32}; u32 mBlockAlign{0_u32}; AmbiLayout mAmbiLayout{AmbiLayout::FuMa}; AmbiScaling mAmbiScaling{AmbiScaling::FuMa}; u32 mAmbiOrder{0_u32}; [[nodiscard]] auto bytesFromFmt() const noexcept -> u32 { return BytesFromFmt(mType); } [[nodiscard]] auto channelsFromFmt() const noexcept -> u32 { return ChannelsFromFmt(mChannels, mAmbiOrder); } [[nodiscard]] auto frameSizeFromFmt() const noexcept -> u32 { return channelsFromFmt() * bytesFromFmt(); } [[nodiscard]] auto blockSizeFromFmt() const noexcept -> u32 { 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 */ kcat-openal-soft-75c0059/core/bufferline.h000066400000000000000000000010311512220627100203520ustar00rootroot00000000000000#ifndef CORE_BUFFERLINE_H #define CORE_BUFFERLINE_H #include #include /* 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 auto BufferLineSize = size_t{1024}; using FloatBufferLine = std::array; using FloatBufferSpan = std::span; using FloatConstBufferSpan = std::span; #endif /* CORE_BUFFERLINE_H */ kcat-openal-soft-75c0059/core/context.cpp000066400000000000000000000144331512220627100202620ustar00rootroot00000000000000 #include "config.h" #include #include #include #include #include #include #include "context.h" #include "device.h" #include "effectslot.h" #include "gsl/gsl" #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(gsl::not_null device) : mDevice{device} { Expects(mEnabledEvts.is_lock_free()); } ContextBase::~ContextBase() { mActiveAuxSlots.store(nullptr, std::memory_order_relaxed); mVoices.store(nullptr, std::memory_order_relaxed); if(mAsyncEvents) { if(const auto count = mAsyncEvents->readSpace(); count > 0) TRACE("Destructing {} orphaned event{}", count, (count==1)?"":"s"); mAsyncEvents = nullptr; } } void ContextBase::allocVoiceChanges() { static constexpr auto clustersize = std::tuple_size_v; auto clusterptr = std::make_unique(); const auto cluster = std::span{*clusterptr}; for(const auto i : std::views::iota(1_uz, clustersize)) 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 = std::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::ranges::transform(*cluster, voice_iter, [](Voice &voice) noexcept -> Voice* { return std::addressof(voice); }).out; 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 = std::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); } auto ContextBase::getEffectSlot() -> gsl::not_null { for(auto &clusterptr : mEffectSlotClusters) { const auto cluster = std::span{*clusterptr}; if(const auto iter = std::ranges::find_if_not(cluster, &EffectSlotBase::InUse); iter != cluster.end()) return gsl::make_not_null(std::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 gsl::make_not_null(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 = std::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); } kcat-openal-soft-75c0059/core/context.h000066400000000000000000000133431512220627100177260ustar00rootroot00000000000000#ifndef CORE_CONTEXT_H #define CORE_CONTEXT_H #include "config.h" #include #include #include #include #include #include #include #include "alnumeric.h" #include "async_event.h" #include "atomic.h" #include "flexarray.h" #include "gsl/gsl" #include "opthelpers.h" #include "ringbuffer.h" #include "vecmat.h" struct DeviceBase; struct EffectSlotBase; struct EffectSlotProps; struct Voice; struct VoiceChange; struct VoicePropsItem; inline constexpr auto SpeedOfSoundMetersPerSec = 343.3f; inline constexpr auto AirAbsorbGainHF = 0.99426f; /* -0.05dB */ enum class DistanceModel : u8 { Disable, Inverse, InverseClamped, Linear, LinearClamped, Exponent, ExponentClamped, Default = InverseClamped }; struct ContextProps { std::array Position; std::array Velocity; std::array OrientAt; std::array OrientUp; f32 Gain; f32 MetersPerUnit; f32 AirAbsorptionGainHF; f32 DopplerFactor; f32 DopplerVelocity; f32 SpeedOfSound; #if ALSOFT_EAX f32 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}; al::Vector Position; al::Matrix Matrix{al::Matrix::Identity()}; al::Vector Velocity; f32 Gain{1.0f}; f32 MetersPerUnit{1.0f}; f32 AirAbsorptionGainHF{AirAbsorbGainHF}; f32 DopplerFactor{1.0f}; f32 SpeedOfSound{SpeedOfSoundMetersPerSec}; /* in units per sec! */ bool SourceDistanceModel{false}; DistanceModel mDistanceModel{}; }; struct ContextBase { gsl::not_null const mDevice; /* Counter for the pre-mixing updates, in 31.1 fixed point (lowest bit * indicates if updates are currently happening). */ std::atomic mUpdateCount{0_u32}; std::atomic mHoldUpdates{false}; std::atomic mStopVoicesOnDisconnect{true}; f32 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(usize addcount); [[nodiscard]] auto getVoicesSpan() const noexcept LIFETIMEBOUND -> std::span { return {mVoices.load(std::memory_order_relaxed)->data(), mActiveVoiceCount.load(std::memory_order_relaxed)}; } [[nodiscard]] auto getVoicesSpanAcquired() const noexcept LIFETIMEBOUND -> std::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; FifoBufferPtr mAsyncEvents; /* u32 to work with macOS wait/notify wrappers, but really just a bool. */ std::atomic mEventsPending; 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; auto getEffectSlot() LIFETIMEBOUND -> gsl::not_null; 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; ContextBase(const ContextBase&) = delete; ContextBase& operator=(const ContextBase&) = delete; protected: explicit ContextBase(gsl::not_null device LIFETIMEBOUND); ~ContextBase(); }; #endif /* CORE_CONTEXT_H */ kcat-openal-soft-75c0059/core/converter.cpp000066400000000000000000000427351512220627100206130ustar00rootroot00000000000000 #include "config.h" #include "converter.h" #include #include #include #include #include #include #include #include #include #include "alnumeric.h" #include "fpu_ctrl.h" #include "gsl/gsl" namespace { constexpr auto MaxPitch = 10u; 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 auto LoadSample(DevFmtType_t val) noexcept -> f32 = delete; template<> constexpr auto LoadSample(i8 const val) noexcept -> f32 { return gsl::narrow_cast(val) * (1.0f/128.0f); } template<> constexpr auto LoadSample(i16 const val) noexcept -> f32 { return gsl::narrow_cast(val) * (1.0f/32768.0f); } template<> constexpr auto LoadSample(i32 const val) noexcept -> f32 { return gsl::narrow_cast(val) * (1.0f/2147483648.0f); } template<> constexpr auto LoadSample(f32 const val) noexcept -> f32 { return val; } template<> constexpr auto LoadSample(u8 const val) noexcept -> f32 { return LoadSample(gsl::narrow_cast(val - 128)); } template<> constexpr auto LoadSample(u16 const val) noexcept -> f32 { return LoadSample(gsl::narrow_cast(val - 32768)); } template<> constexpr auto LoadSample(u32 const val) noexcept -> f32 { return LoadSample(as_signed(val - 2147483648u)); } template void LoadSampleArray(std::span const dst, void const *const src, usize const channel, usize const srcstep) noexcept { Expects(channel < srcstep); const auto srcspan = std::span{static_cast*>(src), dst.size()*srcstep}; auto ssrc = srcspan.begin(); std::advance(ssrc, channel); dst.front() = LoadSample(*ssrc); std::ranges::generate(dst | std::views::drop(1), [&ssrc,srcstep] { std::advance(ssrc, srcstep); return LoadSample(*ssrc); }); } void LoadSamples(std::span const dst, void const *const src, usize const channel, usize const srcstep, DevFmtType const 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 auto StoreSample(f32) noexcept -> DevFmtType_t = delete; template<> auto StoreSample(f32 const val) noexcept -> f32 { return val; } template<> auto StoreSample(f32 const val) noexcept -> i32 { return fastf2i(std::clamp(val*2147483648.0f, -2147483648.0f, 2147483520.0f)); } template<> auto StoreSample(f32 const val) noexcept -> i16 { return gsl::narrow_cast(fastf2i(std::clamp(val*32768.0f, -32768.0f, 32767.0f))); } template<> auto StoreSample(f32 const val) noexcept -> i8 { return gsl::narrow_cast(fastf2i(std::clamp(val*128.0f, -128.0f, 127.0f))); } /* Define unsigned output variations. */ template<> auto StoreSample(f32 const val) noexcept -> u32 { return as_unsigned(StoreSample(val)) + 2147483648u; } template<> auto StoreSample(f32 const val) noexcept -> u16 { return gsl::narrow_cast(StoreSample(val) + 32768); } template<> auto StoreSample(f32 const val) noexcept -> u8 { return gsl::narrow_cast(StoreSample(val) + 128); } template void StoreSampleArray(void *const dst, std::span const src, usize const channel, usize const dststep) noexcept { Expects(channel < dststep); const auto dstspan = std::span{static_cast*>(dst), src.size()*dststep}; auto sdst = dstspan.begin(); std::advance(sdst, channel); *sdst = StoreSample(src.front()); std::ranges::for_each(src | std::views::drop(1), [&sdst,dststep](f32 const in) { std::advance(sdst, dststep); *sdst = StoreSample(in); }); } void StoreSamples(void *const dst, std::span const src, usize const channel, usize const dststep, DevFmtType const 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(std::span const dst, void const *const src) noexcept { const auto srcspan = std::span{static_cast const*>(src), dst.size()>>1}; auto sdst = dst.begin(); std::ranges::for_each(srcspan, [&sdst](f32 const in) { sdst = std::fill_n(sdst, 2, in*0.707106781187f); }, &LoadSample); } template void Multi2Mono(u32 chanmask, usize const step, std::span const dst, void const *const src) noexcept { const auto scale = std::sqrt(1.0f / gsl::narrow_cast(std::popcount(chanmask))); const auto srcspan = std::span{static_cast const*>(src), step*dst.size()}; std::ranges::fill(dst, 0.0f); while(chanmask) { const auto c = std::countr_zero(chanmask); chanmask &= ~(1_u32 << c); auto ssrc = srcspan.begin(); std::advance(ssrc, c); dst.front() += LoadSample(*ssrc); std::ranges::for_each(dst, [&ssrc,step](f32 &sample) { std::advance(ssrc, step); sample += LoadSample(*ssrc); }); } std::ranges::transform(dst, dst.begin(), [scale](f32 const sample) noexcept -> f32 { return sample * scale; }); } } // namespace auto SampleConverter::Create(DevFmtType const srcType, DevFmtType const dstType, usize const numchans, u32 const srcRate, u32 const dstRate, Resampler const resampler) -> SampleConverterPtr { auto converter = SampleConverterPtr{}; 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; std::ranges::fill(converter->mChan | std::views::transform(&ChanSamples::PrevSamples) | std::views::join, 0.0f); /* Have to set the mixer FPU mode since that's what the resampler code expects. */ auto mixer_mode = FPUCtl{}; const auto step = std::clamp(std::round(srcRate*f64{MixerFracOne}/dstRate), 1.0, MaxPitch*f64{MixerFracOne}); converter->mIncrement = gsl::narrow_cast(step); if(converter->mIncrement == MixerFracOne) { converter->mResample = [](InterpState const*, std::span const src, u32, u32 const, std::span const dst) { std::ranges::copy(src | std::views::drop(MaxResamplerEdge) | std::views::take(dst.size()), dst.begin()); }; } else converter->mResample = PrepareResampler(resampler, converter->mIncrement, &converter->mState); return converter; } auto SampleConverter::availableOut(u32 const srcframes) const -> u32 { if(srcframes < 1) { /* No output samples if there's no input samples. */ return 0; } auto const prepcount = mSrcPrepCount; if(prepcount < MaxResamplerPadding && MaxResamplerPadding - prepcount >= srcframes) { /* Not enough input samples to generate an output sample. */ return 0; } auto DataSize64 = u64{prepcount}; DataSize64 += srcframes; DataSize64 -= MaxResamplerPadding; DataSize64 <<= MixerFracBits; DataSize64 -= mFracOffset; /* If we have a full prep, we can generate at least one sample. */ return gsl::narrow_cast(std::clamp((DataSize64 + mIncrement-1)/mIncrement, 1_u64, u64{std::numeric_limits::max()})); } auto SampleConverter::convert(const void **const src, u32 *const srcframes, void *const dst, u32 const dstframes) -> u32 { const auto SrcFrameSize = mChan.size() * mSrcTypeSize; const auto DstFrameSize = mChan.size() * mDstTypeSize; const auto increment = mIncrement; auto NumSrcSamples = *srcframes; auto SamplesIn = std::span{static_cast(*src), NumSrcSamples*SrcFrameSize}; auto SamplesOut = std::span{static_cast(dst), dstframes*DstFrameSize}; const auto mixer_mode = FPUCtl{}; auto pos = 0_u32; while(pos < dstframes && NumSrcSamples > 0) { const auto prepcount = mSrcPrepCount; const auto readable = std::min(NumSrcSamples, u32{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(const auto chan : std::views::iota(0_uz, mChan.size())) LoadSamples(std::span{mChan[chan].PrevSamples}.subspan(prepcount, readable), SamplesIn.data(), chan, mChan.size(), mSrcType); mSrcPrepCount = prepcount + readable; NumSrcSamples = 0; break; } const auto SrcData = std::span{mSrcSamples}; const auto DstData = std::span{mDstSamples}; const auto DataPosFrac = mFracOffset; auto DataSize64 = u64{prepcount}; DataSize64 += readable; DataSize64 -= MaxResamplerPadding; DataSize64 <<= MixerFracBits; DataSize64 -= DataPosFrac; /* If we have a full prep, we can generate at least one sample. */ auto DstSize = gsl::narrow_cast(std::clamp((DataSize64 + increment-1)/increment, 1_u64, u64{BufferLineSize})); DstSize = std::min(DstSize, dstframes-pos); const auto DataPosEnd = DstSize*increment + DataPosFrac; const auto SrcDataEnd = DataPosEnd>>MixerFracBits; Expects(prepcount+readable >= SrcDataEnd); const auto nextprep = std::min(prepcount+readable-SrcDataEnd, MaxResamplerPadding); for(const auto chan : std::views::iota(0_uz, mChan.size())) { /* 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 const previter = std::ranges::copy(SrcData | std::views::drop(SrcDataEnd) | std::views::take(nextprep), mChan[chan].PrevSamples.begin()).out; 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. */ auto const 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; } auto SampleConverter::convertPlanar(void const **const src, u32 *const srcframes, void *const *const dst, u32 const dstframes) -> u32 { const auto srcs = std::span{src, mChan.size()}; const auto dsts = std::span{dst, mChan.size()}; const auto increment = mIncrement; auto NumSrcSamples = *srcframes; const auto mixer_mode = FPUCtl{}; auto pos = 0_u32; while(pos < dstframes && NumSrcSamples > 0) { const auto prepcount = mSrcPrepCount; const auto readable = std::min(NumSrcSamples, u32{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(const auto chan : std::views::iota(0_uz, mChan.size())) { auto samples = std::span{static_cast(srcs[chan]), NumSrcSamples*usize{mSrcTypeSize}}; LoadSamples(std::span{mChan[chan].PrevSamples}.subspan(prepcount, readable), samples.data(), 0, 1, mSrcType); srcs[chan] = samples.subspan(usize{mSrcTypeSize}*readable).data(); } mSrcPrepCount = prepcount + readable; NumSrcSamples = 0; break; } const auto SrcData = std::span{mSrcSamples}; const auto DstData = std::span{mDstSamples}; const auto DataPosFrac = mFracOffset; auto DataSize64 = u64{prepcount}; DataSize64 += readable; DataSize64 -= MaxResamplerPadding; DataSize64 <<= MixerFracBits; DataSize64 -= DataPosFrac; /* If we have a full prep, we can generate at least one sample. */ auto DstSize = gsl::narrow_cast(std::clamp((DataSize64 + increment-1)/increment, 1_u64, u64{BufferLineSize})); DstSize = std::min(DstSize, dstframes-pos); const auto DataPosEnd = DstSize*increment + DataPosFrac; const auto SrcDataEnd = DataPosEnd>>MixerFracBits; Expects(prepcount+readable >= SrcDataEnd); const auto nextprep = std::min(prepcount+readable-SrcDataEnd, MaxResamplerPadding); for(const auto chan : std::views::iota(0_uz, mChan.size())) { /* 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 const previter = std::ranges::copy(SrcData | std::views::drop(SrcDataEnd) | std::views::take(nextprep), mChan[chan].PrevSamples.begin()).out; 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 = std::span{static_cast(dsts[chan]), usize{mDstTypeSize}*dstframes}.subspan(pos*usize{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. */ auto const srcread = std::min(NumSrcSamples, SrcDataEnd + mSrcPrepCount - prepcount); std::ranges::for_each(srcs, [this,NumSrcSamples,srcread](void const *&srcref) { auto const srcspan = std::span{static_cast(srcref), usize{mSrcTypeSize}*NumSrcSamples}; srcref = srcspan.subspan(usize{mSrcTypeSize}*srcread).data(); }); NumSrcSamples -= srcread; pos += DstSize; } *srcframes = NumSrcSamples; return pos; } void ChannelConverter::convert(void const *const src, f32 *const dst, u32 const frames) const { if(!frames) return; if(mDstChans == DevFmtMono) { switch(mSrcType) { #define HANDLE_FMT(T) case T: Multi2Mono(mChanMask, mSrcStep, {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 } } } kcat-openal-soft-75c0059/core/converter.h000066400000000000000000000036661512220627100202600ustar00rootroot00000000000000#ifndef CORE_CONVERTER_H #define CORE_CONVERTER_H #include #include #include "almalloc.h" #include "altypes.hpp" #include "devformat.h" #include "flexarray.h" #include "mixer/defs.h" #include "resampler_limits.h" class SampleConverter { explicit SampleConverter(usize const numchans) : mChan{numchans} { } public: DevFmtType mSrcType{}; DevFmtType mDstType{}; u32 mSrcTypeSize{}; u32 mDstTypeSize{}; u32 mSrcPrepCount{}; u32 mFracOffset{}; u32 mIncrement{}; InterpState mState; ResamplerFunc mResample{}; alignas(16) FloatBufferLine mSrcSamples{}; alignas(16) FloatBufferLine mDstSamples{}; struct ChanSamples { alignas(16) std::array PrevSamples; }; al::FlexArray mChan; [[nodiscard]] auto convert(const void **src, u32 *srcframes, void *dst, u32 dstframes) -> u32; [[nodiscard]] auto convertPlanar(const void **src, uint *srcframes, void *const*dst, uint dstframes) -> u32; [[nodiscard]] auto availableOut(u32 srcframes) const -> u32; using SampleOffset = std::chrono::duration>; [[nodiscard]] auto currentInputDelay() const noexcept -> SampleOffset { auto const prep = i64{mSrcPrepCount} - MaxResamplerEdge; return SampleOffset{(prep< std::unique_ptr; DEF_FAM_NEWDEL(SampleConverter, mChan) }; using SampleConverterPtr = std::unique_ptr; struct ChannelConverter { DevFmtType mSrcType{}; u32 mSrcStep{}; u32 mChanMask{}; DevFmtChannels mDstChans{}; [[nodiscard]] auto is_active() const noexcept -> bool { return mChanMask != 0; } void convert(const void *src, f32 *dst, u32 frames) const; }; #endif /* CORE_CONVERTER_H */ kcat-openal-soft-75c0059/core/cpu_caps.cpp000066400000000000000000000107241512220627100203720ustar00rootroot00000000000000 #include "config.h" #include "config_simd.h" #include "cpu_caps.h" #if defined(_WIN32) && (defined(_M_ARM) || defined(_M_ARM64)) #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 #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 auto get_cpuid(unsigned int f) -> std::array { auto ret = std::array{}; __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 auto get_cpuid(unsigned int f) -> std::array { auto ret = std::array{}; (__cpuid)(ret.data(), f); return ret; } #define CAN_GET_CPUID #endif } // namespace auto GetCPUInfo() -> std::optional { auto ret = CPUInfo{}; #ifdef CAN_GET_CPUID auto cpuregs = get_cpuid(0); if(cpuregs[0] == 0) return std::nullopt; const auto maxfunc = cpuregs[0]; cpuregs = get_cpuid(0x80000000); const auto maxextfunc = cpuregs[0]; const auto as_chars4 = std::array{ std::bit_cast>(cpuregs[1]), std::bit_cast>(cpuregs[3]), std::bit_cast>(cpuregs[2])}; auto all_chars12 = as_chars4 | std::views::join; ret.mVendor.append(all_chars12.begin(), all_chars12.end()); /* Remove null chars and duplicate/unnecessary spaces. */ std::erase(ret.mVendor, '\0'); auto iter_end = std::ranges::unique(ret.mVendor, [](const char c0, const char c1) { return std::isspace(c0) && std::isspace(c1); }); ret.mVendor.erase(iter_end.begin(), iter_end.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) { const auto as_chars16 = std::array{ std::bit_cast>(get_cpuid(0x80000002)), std::bit_cast>(get_cpuid(0x80000003)), std::bit_cast>(get_cpuid(0x80000004))}; const auto all_chars48 = as_chars16 | std::views::join; ret.mName.append(all_chars48.begin(), all_chars48.end()); std::erase(ret.mName, '\0'); iter_end = std::ranges::unique(ret.mName, [](const char c0, const char c1) { return std::isspace(c0) && std::isspace(c1); }); ret.mName.erase(iter_end.begin(), iter_end.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; } kcat-openal-soft-75c0059/core/cpu_caps.h000066400000000000000000000006401512220627100200330ustar00rootroot00000000000000#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 */ kcat-openal-soft-75c0059/core/cubic_defs.h000066400000000000000000000006241512220627100203260ustar00rootroot00000000000000#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 */ kcat-openal-soft-75c0059/core/cubic_tables.cpp000066400000000000000000000130561512220627100212150ustar00rootroot00000000000000 #include "cubic_tables.h" #include #include #include #include #include #include "alnumeric.h" #include "cubic_defs.h" #include "gsl/gsl" /* 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 auto k = 0.5 + idx; if(k > 512.0) return 0.0; const auto s = std::sin(std::numbers::pi*1.280/1024.0 * k); const auto t = (std::cos(std::numbers::pi*2.000/1023.0 * k) - 1.0) * 0.50; const auto u = (std::cos(std::numbers::pi*4.000/1023.0 * k) - 1.0) * 0.08; return s * (t + u + 1.0) / k; } } // namespace GaussianTable::GaussianTable() noexcept { static constexpr auto IndexScale = 512.0 / double{CubicPhaseCount*2}; /* Fill in the main coefficients. */ for(const auto pi : std::views::iota(0_uz, std::size_t{CubicPhaseCount})) { const auto coeff0 = GetCoeff(gsl::narrow_cast(CubicPhaseCount + pi)*IndexScale); const auto coeff1 = GetCoeff(gsl::narrow_cast(pi)*IndexScale); const auto coeff2 = GetCoeff(gsl::narrow_cast(CubicPhaseCount - pi)*IndexScale); const auto coeff3 = GetCoeff(gsl::narrow_cast(CubicPhaseCount*2_uz - pi) *IndexScale); const auto scale = 1.0 / (coeff0 + coeff1 + coeff2 + coeff3); mTable[pi].mCoeffs[0] = gsl::narrow_cast(coeff0 * scale); mTable[pi].mCoeffs[1] = gsl::narrow_cast(coeff1 * scale); mTable[pi].mCoeffs[2] = gsl::narrow_cast(coeff2 * scale); mTable[pi].mCoeffs[3] = gsl::narrow_cast(coeff3 * scale); } /* Fill in the coefficient deltas. */ for(const auto pi : std::views::iota(0_uz, CubicPhaseCount-1_uz)) { 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]; } constexpr auto pi = CubicPhaseCount - 1_uz; 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]; } consteval SplineTable::SplineTable() noexcept { constexpr auto third = 1.0/3.0; constexpr auto sixth = 1.0/6.0; /* 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(const auto pi : std::views::iota(0_uz, std::size_t{CubicPhaseCount})) { const auto mu = gsl::narrow_cast(pi) / double{CubicPhaseCount}; const auto mu2 = mu*mu; const auto mu3 = mu*mu2; mTable[pi].mCoeffs[0] = gsl::narrow_cast( -third*mu + 0.5*mu2 - sixth*mu3); mTable[pi].mCoeffs[1] = gsl::narrow_cast(1.0 - 0.5*mu - mu2 + 0.5*mu3); mTable[pi].mCoeffs[2] = gsl::narrow_cast( mu + 0.5*mu2 - 0.5*mu3); mTable[pi].mCoeffs[3] = gsl::narrow_cast( -sixth*mu + sixth*mu3); } for(const auto pi : std::views::iota(0_uz, CubicPhaseCount-1_uz)) { 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]; } constexpr auto pi = CubicPhaseCount - 1_uz; 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]; } constinit const SplineTable gSplineFilter; CubicFilter::CubicFilter() noexcept { static constexpr auto 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(const auto i : std::views::iota(0_uz, sTableSteps/2_uz + 1_uz)) { const auto coeff0 = GetCoeff(gsl::narrow_cast(sTableSteps + i)*IndexScale); const auto coeff1 = GetCoeff(gsl::narrow_cast(i)*IndexScale); const auto coeff2 = GetCoeff(gsl::narrow_cast(sTableSteps - i)*IndexScale); const auto coeff3 = GetCoeff(gsl::narrow_cast(sTableSteps*2_uz - i)*IndexScale); const auto scale = 1.0 / (coeff0 + coeff1 + coeff2 + coeff3); mFilter[sTableSteps + i] = gsl::narrow_cast(coeff0 * scale); mFilter[i] = gsl::narrow_cast(coeff1 * scale); mFilter[sTableSteps - i] = gsl::narrow_cast(coeff2 * scale); mFilter[sTableSteps*2 - i] = gsl::narrow_cast(coeff3 * scale); } } kcat-openal-soft-75c0059/core/cubic_tables.h000066400000000000000000000024221512220627100206550ustar00rootroot00000000000000#ifndef CORE_CUBIC_TABLES_H #define CORE_CUBIC_TABLES_H #include #include #include "cubic_defs.h" #include "opthelpers.h" struct CubicTable { std::array mTable{}; }; struct GaussianTable : CubicTable { GaussianTable() noexcept; }; inline const GaussianTable gGaussianFilter; struct SplineTable : CubicTable { consteval SplineTable() noexcept; }; DECL_HIDDEN extern constinit 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() noexcept; [[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 */ kcat-openal-soft-75c0059/core/devformat.cpp000066400000000000000000000037571512220627100205740ustar00rootroot00000000000000 #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; } kcat-openal-soft-75c0059/core/devformat.h000066400000000000000000000050211512220627100202230ustar00rootroot00000000000000#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{32}; /* 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 = 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 */ kcat-openal-soft-75c0059/core/device.cpp000066400000000000000000000023221512220627100200270ustar00rootroot00000000000000 #include "config.h" #include "bformatdec.h" #include "bs2b.h" #include "device.h" #include "front_stablizer.h" #include "gsl/gsl" #include "hrtf.h" #include "mastering.h" DeviceBase::DeviceBase(DeviceType type) : Type{type}, mContexts{al::FlexArray::Create(0)} { } DeviceBase::~DeviceBase() = default; auto DeviceBase::removeContext(ContextBase *context) -> size_t { auto oldarray = std::span{*mContexts.load(std::memory_order_acquire)}; if(const auto toremove = std::ranges::count(oldarray, context)) { const auto newsize = oldarray.size() - gsl::narrow_cast(toremove); auto newarray = ContextArray::Create(newsize); /* Copy the current/old context handles to the new array, excluding the * given context. */ std::ranges::copy_if(oldarray, newarray->begin(), [context](const ContextBase *ctx) { return ctx != context; }); /* Store the new context array in the device. Wait for any current mix * to finish before deleting the old array. */ auto prevarray = mContexts.exchange(std::move(newarray)); std::ignore = waitForMix(); return newsize; } return oldarray.size(); } kcat-openal-soft-75c0059/core/device.h000066400000000000000000000302131512220627100174740ustar00rootroot00000000000000#ifndef CORE_DEVICE_H #define CORE_DEVICE_H #include #include #include #include #include #include #include #include #include #include #include #include "alformat.hpp" #include "almalloc.h" #include "alnumeric.h" #include "ambidefs.h" #include "atomic.h" #include "bufferline.h" #include "devformat.h" #include "filters/nfc.h" #include "flexarray.h" #include "gsl/gsl" #include "intrusive_ptr.h" #include "mixer/hrtfdefs.h" #include "resampler_limits.h" #include "uhjfilter.h" #include "vector.h" class BFormatDec; namespace Bs2b { struct bs2b; } // namespace Bs2b class Compressor; struct ContextBase; class DirectHrtfState; class FrontStablizer; struct HrtfStore; inline constexpr auto MinOutputRate = 8000_uz; inline constexpr auto MaxOutputRate = 192000_uz; inline constexpr auto DefaultOutputRate = 48000_uz; inline constexpr auto DefaultUpdateSize = 512_uz; /* ~10.7ms */ inline constexpr auto DefaultNumUpdates = 3_uz; enum class DeviceType : u8 { Playback, Capture, Loopback }; enum class RenderMode : u8 { Normal, Pairwise, Hrtf }; enum class StereoEncoding : u8 { Basic, Uhj, Hrtf, Default = Basic }; struct InputRemixMap { struct TargetMix { Channel channel; f32 mix; }; Channel channel; std::span targets; }; class DistanceComp { explicit DistanceComp(usize const count) : mSamples{count} { } public: /* Maximum delay in samples for speaker distance compensation. */ static constexpr auto MaxDelay = 1024_u32; struct ChanData { std::span Buffer; /* Valid size is [0...MaxDelay). */ f32 Gain{1.0f}; }; std::array mChannels{}; al::FlexArray mSamples; static auto Create(usize const numsamples) -> std::unique_ptr { return std::unique_ptr{new(FamCount{numsamples}) DistanceComp{numsamples}}; } DEF_FAM_NEWDEL(DistanceComp, mSamples) }; constexpr auto InvalidChannelIndex = gsl::narrow_cast(~0u); struct BFChannelConfig { f32 Scale; u32 Index; }; struct MixParams { /* Coefficient channel mapping for mixing to the buffer. */ std::array AmbiMap{}; std::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 F> void setAmbiMixParams(MixParams const &inmix, f32 const gainbase, F func) const { auto const numIn = inmix.Buffer.size(); auto const numOut = Buffer.size(); for(auto const i : std::views::iota(0_uz, numIn)) { auto idx = InvalidChannelIndex; auto gain = 0.0f; for(auto const j : std::views::iota(0_uz, numOut)) { if(AmbiMap[j].Index == inmix.AmbiMap[i].Index) { idx = gsl::narrow_cast(j); gain = AmbiMap[j].Scale * gainbase; break; } } std::invoke(func, i, idx, gain); } } }; struct RealMixParams { std::span RemixMap; std::array ChannelIndex{}; std::span Buffer; }; using AmbiRotateMatrix = std::array, MaxAmbiChannels>; struct AmbiDecPostProcess { std::unique_ptr mAmbiDecoder; }; struct HrtfPostProcess { std::unique_ptr mHrtfState; }; struct UhjPostProcess { std::unique_ptr mUhjEncoder; }; struct StablizerPostProcess { std::unique_ptr mAmbiDecoder; std::unique_ptr mStablizer; }; struct Bs2bPostProcess { std::unique_ptr mAmbiDecoder; std::unique_ptr mBs2b; }; using PostProcess = std::variant; 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 : u8 { Unprepared, Configured, Playing }; /* NOLINTNEXTLINE(clang-analyzer-optin.performance.Padding) */ struct DeviceBase { std::atomic Connected{true}; DeviceType const Type{}; std::string mDeviceName; u32 mSampleRate{}; u32 mUpdateSize{}; u32 mBufferSize{}; DevFmtChannels FmtChans{}; DevFmtType FmtType{}; u32 mAmbiOrder{0_u32}; f32 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}; u32 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. */ f32 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{0_u32}; /* 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 auto MixerLineSize = usize{BufferLineSize + DecoderBase::sMaxPadding}; static constexpr auto MixerChannelsMax = 25_uz; 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 */ al::intrusive_ptr mHrtf; u32 mIrSize{0_u32}; PostProcess mPostProcess; std::unique_ptr Limiter; /* Delay buffers used to compensate for speaker distances. */ std::unique_ptr ChannelDelays; /* Dithering control. */ f32 DitherDepth{0.0f}; u32 DitherSeed{0_u32}; /* 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{0_u32}; // Contexts created on this device using ContextArray = al::FlexArray; al::atomic_unique_ptr mContexts; /** Returns the number of contexts remaining on the device. */ [[nodiscard]] auto removeContext(ContextBase *context) -> usize; [[nodiscard]] auto bytesFromFmt() const noexcept -> u32 { return BytesFromDevFmt(FmtType); } [[nodiscard]] auto channelsFromFmt() const noexcept -> u32 { return ChannelsFromDevFmt(FmtChans, mAmbiOrder); } [[nodiscard]] auto frameSizeFromFmt() const noexcept -> u32 { return bytesFromFmt() * channelsFromFmt(); } struct MixLock { DeviceBase *const self; u32 const mEndVal; MixLock(DeviceBase *device, u32 const 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); } }; [[nodiscard]] auto getWriteMixLock() noexcept -> MixLock { /* Increment the mix count at the start of mixing and writing clock * info (lsb should be 1). */ auto const 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 -> u32 { auto 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 const 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; } static void Process(std::monostate const&, usize const) { } void Process(AmbiDecPostProcess const &proc, usize SamplesToDo) const; void Process(HrtfPostProcess const &proc, usize SamplesToDo); void Process(UhjPostProcess const &proc, usize SamplesToDo); void Process(StablizerPostProcess const &proc, usize SamplesToDo); void Process(Bs2bPostProcess const &proc, usize SamplesToDo); void renderSamples(std::span outBuffers, u32 numSamples); void renderSamples(void *outBuffer, u32 numSamples, usize frameStep); /* Caller must lock the device state, and the mixer must not be running. */ void doDisconnect(std::string&& msg); template void handleDisconnect(al::format_string fmt, Args&& ...args) { doDisconnect(al::format(std::move(fmt), std::forward(args)...)); } private: [[nodiscard]] auto renderSamples(u32 numSamples) -> u32; 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 -> gsl::czstring { return "alsoft-mixer"; } [[nodiscard]] constexpr auto GetRecordThreadName() noexcept -> gsl::czstring { return "alsoft-record"; } #endif /* CORE_DEVICE_H */ kcat-openal-soft-75c0059/core/effects/000077500000000000000000000000001512220627100175045ustar00rootroot00000000000000kcat-openal-soft-75c0059/core/effects/base.h000066400000000000000000000107561512220627100206000ustar00rootroot00000000000000#ifndef CORE_EFFECTS_BASE_H #define CORE_EFFECTS_BASE_H #include #include #include #include #include "core/bufferline.h" #include "intrusive_ptr.h" struct BufferStorage; struct ContextBase; struct DeviceBase; struct EffectSlotBase; 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 EffectState : public al::intrusive_ref { std::span mOutTarget; virtual ~EffectState() = default; virtual void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) = 0; virtual void update(const ContextBase *context, const EffectSlotBase *slot, const EffectProps *props, const EffectTarget target) = 0; virtual void process(const size_t samplesToDo, const std::span samplesIn, const std::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 */ kcat-openal-soft-75c0059/core/effectslot.cpp000066400000000000000000000004341512220627100207300ustar00rootroot00000000000000 #include "config.h" #include "effectslot.h" #include #include "almalloc.h" #include "context.h" std::unique_ptr EffectSlotBase::CreatePtrArray(size_t count) { return std::unique_ptr{new(FamCount{count}) EffectSlotArray(count)}; } kcat-openal-soft-75c0059/core/effectslot.h000066400000000000000000000035111512220627100203740ustar00rootroot00000000000000#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 EffectSlotBase; 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; EffectSlotBase *Target; EffectSlotType Type; EffectProps Props; al::intrusive_ptr State; std::atomic next; }; struct EffectSlotBase { 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}; EffectSlotBase *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 */ kcat-openal-soft-75c0059/core/except.cpp000066400000000000000000000000521512220627100200560ustar00rootroot00000000000000 #include "config.h" #include "except.h" kcat-openal-soft-75c0059/core/except.h000066400000000000000000000016711512220627100175330ustar00rootroot00000000000000#ifndef CORE_EXCEPT_H #define CORE_EXCEPT_H #include #include #include #include "opthelpers.h" namespace al { /* NOLINTNEXTLINE(clazy-copyable-polymorphic) Exceptions must be copyable. */ class base_exception : public std::exception { std::string mMessage; public: base_exception() = default; template requires(std::is_constructible_v) explicit base_exception(T&& msg) : mMessage{std::forward(msg)} { } base_exception(const base_exception&) = default; base_exception(base_exception&&) = default; NOINLINE ~base_exception() override = default; auto operator=(const base_exception&) & -> base_exception& = default; auto operator=(base_exception&&) & -> base_exception& = default; [[nodiscard]] auto what() const noexcept LIFETIMEBOUND -> const char* override { return mMessage.c_str(); } }; } // namespace al #endif /* CORE_EXCEPT_H */ kcat-openal-soft-75c0059/core/filters/000077500000000000000000000000001512220627100175355ustar00rootroot00000000000000kcat-openal-soft-75c0059/core/filters/biquad.cpp000066400000000000000000000255111512220627100215120ustar00rootroot00000000000000 #include "config.h" #include "biquad.h" #include #include #include #include #include #include #include "alnumeric.h" #include "gsl/gsl" #include "opthelpers.h" namespace { /* The number of steps for the filter to transition from the current to target * coefficients. More steps create a smoother transition, but increases the * amount of time to reach the target coefficients. */ constexpr auto InterpSteps = 8; /* The number of sample frames to process for each interpolation step. More * sample frames improve performance, but increases the amount of time to reach * the target coefficients. */ constexpr auto SamplesPerStep = 32; constexpr auto SamplesPerStepMask = SamplesPerStep-1; static_assert(std::popcount(gsl::narrow(SamplesPerStep)) == 1, "SamplesPerStep must be a power of 2"); /* Sets dst to the given value, returns true if it's meaningfully different. */ [[nodiscard]] auto check_set(f32 &dst, f32 const value) noexcept -> bool { const auto is_diff = !(std::abs(value - dst) <= 0.015625f/* 1/64 */); dst = value; return is_diff; } } // namespace auto BiquadFilter::SetParams(BiquadType const type, f32 const f0norm, f32 gain, f32 const rcpQ, Coefficients &coeffs) -> bool { /* 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, 0.00001f); auto const w0 = std::numbers::pi_v*2.0f * f0norm; auto const sin_w0 = std::sin(w0); auto const cos_w0 = std::cos(w0); auto const alpha = sin_w0/2.0f * rcpQ; auto sqrtgain_alpha_2 = f32{}; auto a = std::array{{1.0f, 0.0f, 0.0f}}; auto b = std::array{{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; } auto is_diff = check_set(coeffs.mB0, b[0] / a[0]); is_diff |= check_set(coeffs.mB1, b[1] / a[0]); is_diff |= check_set(coeffs.mB2, b[2] / a[0]); is_diff |= check_set(coeffs.mA1, a[1] / a[0]); is_diff |= check_set(coeffs.mA2, a[2] / a[0]); return is_diff; } void BiquadInterpFilter::setParams(BiquadType const type, f32 const f0norm, f32 const gain, f32 const rcpQ) { if(!SetParams(type, f0norm, gain, rcpQ, mTargetCoeffs)) { if(mCounter <= 0) { mCounter = 0; mCoeffs = mTargetCoeffs; } } else if(mCounter >= 0) mCounter = InterpSteps*SamplesPerStep; else { mCounter = 0; mCoeffs = mTargetCoeffs; } } void BiquadInterpFilter::copyParamsFrom(BiquadInterpFilter const &other) noexcept { auto is_diff = check_set(mTargetCoeffs.mB0, other.mTargetCoeffs.mB0); is_diff |= check_set(mTargetCoeffs.mB1, other.mTargetCoeffs.mB1); is_diff |= check_set(mTargetCoeffs.mB2, other.mTargetCoeffs.mB2); is_diff |= check_set(mTargetCoeffs.mA1, other.mTargetCoeffs.mA1); is_diff |= check_set(mTargetCoeffs.mA2, other.mTargetCoeffs.mA2); if(!is_diff) { if(mCounter <= 0) { mCounter = 0; mCoeffs = mTargetCoeffs; } } else if(mCounter >= 0) mCounter = InterpSteps*SamplesPerStep; else { mCounter = 0; mCoeffs = mTargetCoeffs; } } void BiquadFilter::process(std::span const src, std::span const dst) { auto z1 = mZ1; auto 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/ */ std::ranges::transform(src, dst.begin(), [coeffs=mCoeffs,&z1,&z2](f32 const x) noexcept -> f32 { auto const y = x*coeffs.mB0 + z1; z1 = x*coeffs.mB1 - y*coeffs.mA1 + z2; z2 = x*coeffs.mB2 - y*coeffs.mA2; return y; }); mZ1 = z1; mZ2 = z2; } void BiquadInterpFilter::process(std::span src, std::span dst) { if(mCounter > 0) { auto counter = mCounter / SamplesPerStep; auto steprem = gsl::narrow_cast(SamplesPerStep - (mCounter&SamplesPerStepMask)); while(counter > 0) { auto const td = std::min(steprem, src.size()); BiquadFilter::process(src | std::views::take(td), dst); steprem -= td; if(steprem) { steprem = SamplesPerStep - steprem; mCounter = (counter*SamplesPerStep) | gsl::narrow_cast(steprem); return; } src = src.subspan(td); dst = dst.subspan(td); steprem = SamplesPerStep; --counter; if(!counter) { mCounter = 0; mCoeffs = mTargetCoeffs; break; } auto const a = 1.0f / static_cast(counter+1); mCoeffs.mB0 = lerpf(mCoeffs.mB0, mTargetCoeffs.mB0, a); mCoeffs.mB1 = lerpf(mCoeffs.mB1, mTargetCoeffs.mB1, a); mCoeffs.mB2 = lerpf(mCoeffs.mB2, mTargetCoeffs.mB2, a); mCoeffs.mA1 = lerpf(mCoeffs.mA1, mTargetCoeffs.mA1, a); mCoeffs.mA2 = lerpf(mCoeffs.mA2, mTargetCoeffs.mA2, a); if(src.empty()) { mCounter = counter*SamplesPerStep; return; } } } BiquadFilter::process(src, dst); } void BiquadFilter::dualProcess(BiquadFilter &other, std::span const src, std::span const dst) { ASSUME(this != &other); auto z01 = mZ1; auto z02 = mZ2; auto z11 = other.mZ1; auto z12 = other.mZ2; std::ranges::transform(src, dst.begin(), [coeffs0=mCoeffs,coeffs1=other.mCoeffs,&z01,&z02,&z11,&z12](f32 const x0) noexcept -> f32 { auto const y0 = x0*coeffs0.mB0 + z01; z01 = x0*coeffs0.mB1 - y0*coeffs0.mA1 + z02; z02 = x0*coeffs0.mB2 - y0*coeffs0.mA2; auto const x1 = y0; auto const y1 = x1*coeffs1.mB0 + z11; z11 = x1*coeffs1.mB1 - y1*coeffs1.mA1 + z12; z12 = x1*coeffs1.mB2 - y1*coeffs1.mA2; return y1; }); mZ1 = z01; mZ2 = z02; other.mZ1 = z11; other.mZ2 = z12; } void BiquadInterpFilter::dualProcess(BiquadInterpFilter &other, std::span src, std::span dst) { ASSUME(this != &other); if(auto const maxcounter = std::max(mCounter, other.mCounter); maxcounter > 0) { auto counter = maxcounter / SamplesPerStep; auto steprem = gsl::narrow_cast(SamplesPerStep - (maxcounter&SamplesPerStepMask)); while(counter > 0) { auto const td = std::min(steprem, src.size()); BiquadFilter::dualProcess(other, src | std::views::take(td), dst); steprem -= td; if(steprem) { steprem = SamplesPerStep - steprem; mCounter = (counter*SamplesPerStep) | gsl::narrow_cast(steprem); other.mCounter = mCounter; return; } src = src.subspan(td); dst = dst.subspan(td); steprem = SamplesPerStep; --counter; if(!counter) { mCounter = 0; mCoeffs = mTargetCoeffs; other.mCounter = 0; other.mCoeffs = other.mTargetCoeffs; break; } auto const a = 1.0f / static_cast(counter+1); mCoeffs.mB0 = lerpf(mCoeffs.mB0, mTargetCoeffs.mB0, a); mCoeffs.mB1 = lerpf(mCoeffs.mB1, mTargetCoeffs.mB1, a); mCoeffs.mB2 = lerpf(mCoeffs.mB2, mTargetCoeffs.mB2, a); mCoeffs.mA1 = lerpf(mCoeffs.mA1, mTargetCoeffs.mA1, a); mCoeffs.mA2 = lerpf(mCoeffs.mA2, mTargetCoeffs.mA2, a); other.mCoeffs.mB0 = lerpf(other.mCoeffs.mB0, other.mTargetCoeffs.mB0, a); other.mCoeffs.mB1 = lerpf(other.mCoeffs.mB1, other.mTargetCoeffs.mB1, a); other.mCoeffs.mB2 = lerpf(other.mCoeffs.mB2, other.mTargetCoeffs.mB2, a); other.mCoeffs.mA1 = lerpf(other.mCoeffs.mA1, other.mTargetCoeffs.mA1, a); other.mCoeffs.mA2 = lerpf(other.mCoeffs.mA2, other.mTargetCoeffs.mA2, a); if(src.empty()) { mCounter = counter*SamplesPerStep; other.mCounter = mCounter; return; } } } BiquadFilter::dualProcess(other, src, dst); } kcat-openal-soft-75c0059/core/filters/biquad.h000066400000000000000000000172421512220627100211610ustar00rootroot00000000000000#ifndef CORE_FILTERS_BIQUAD_H #define CORE_FILTERS_BIQUAD_H #include #include #include #include #include #include "alnumeric.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, }; class BiquadFilter { protected: struct Coefficients { /* Transfer function coefficients "b" (numerator) */ f32 mB0{1.0f}, mB1{0.0f}, mB2{0.0f}; /* Transfer function coefficients "a" (denominator; a0 is pre-applied). */ f32 mA1{0.0f}, mA2{0.0f}; }; /* Last two delayed components for direct form II. */ f32 mZ1{0.0f}, mZ2{0.0f}; Coefficients mCoeffs; static auto SetParams(BiquadType type, f32 f0norm, f32 gain, f32 rcpQ, Coefficients &coeffs) -> bool; /** * 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 auto rcpQFromSlope(f32 const gain, f32 const slope) -> f32 { return std::sqrt((gain + 1.0f/gain)*(1.0f/slope - 1.0f) + 2.0f); } /** * 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 auto rcpQFromBandwidth(f32 const f0norm, f32 const bandwidth) -> f32 { const auto w0 = std::numbers::pi_v*2.0f * f0norm; return 2.0f*std::sinh(std::log(2.0f)/2.0f*bandwidth*w0/std::sin(w0)); } public: void clear() noexcept { mZ1 = mZ2 = 0.0f; } /** * 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 const type, f32 const f0norm, f32 gain, f32 const slope) { gain = std::max(gain, 0.001f); /* Limit -60dB */ SetParams(type, f0norm, gain, rcpQFromSlope(gain, slope), mCoeffs); } /** * 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 const type, f32 const f0norm, f32 const gain, f32 const bandwidth) { SetParams(type, f0norm, gain, rcpQFromBandwidth(f0norm, bandwidth), mCoeffs); } void copyParamsFrom(BiquadFilter const &other) noexcept { mCoeffs = other.mCoeffs; } void process(std::span src, std::span dst); /** Processes this filter and the other at the same time. */ void dualProcess(BiquadFilter &other, std::span src, std::span dst); /* Rather hacky. It's just here to support "manual" processing. */ [[nodiscard]] auto getComponents() const noexcept -> std::array { return {mZ1,mZ2}; } void setComponents(f32 const z1, f32 const z2) noexcept { mZ1 = z1; mZ2 = z2; } [[nodiscard]] auto processOne(f32 const in, f32 &z1, f32 &z2) const noexcept -> f32 { const auto out = in*mCoeffs.mB0 + z1; z1 = in*mCoeffs.mB1 - out*mCoeffs.mA1 + z2; z2 = in*mCoeffs.mB2 - out*mCoeffs.mA2; return out; } }; class BiquadInterpFilter : protected BiquadFilter { Coefficients mTargetCoeffs; int mCounter{-1}; void setParams(BiquadType type, f32 f0norm, f32 gain, f32 rcpQ); public: void reset() noexcept { BiquadFilter::clear(); mTargetCoeffs = Coefficients{}; mCoeffs = mTargetCoeffs; mCounter = -1; } void clear() noexcept { BiquadFilter::clear(); mCoeffs = mTargetCoeffs; mCounter = 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 const type, f32 const f0norm, f32 gain, f32 const 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 const type, f32 const f0norm, f32 const gain, f32 const bandwidth) { setParams(type, f0norm, gain, rcpQFromBandwidth(f0norm, bandwidth)); } void copyParamsFrom(const BiquadInterpFilter &other) noexcept; void process(std::span src, std::span dst); /** Processes this filter and the other at the same time. */ void dualProcess(BiquadInterpFilter &other, std::span src, std::span dst); }; struct DualBiquad { BiquadFilter &f0, &f1; void process(std::span const src, std::span const dst) const { f0.dualProcess(f1, src, dst); } }; struct DualBiquadInterp { BiquadInterpFilter &f0, &f1; void process(std::span const src, std::span const dst) const { f0.dualProcess(f1, src, dst); } }; #endif /* CORE_FILTERS_BIQUAD_H */ kcat-openal-soft-75c0059/core/filters/nfc.cpp000066400000000000000000000206631512220627100210160ustar00rootroot00000000000000 #include "config.h" #include "nfc.h" #include #include "alnumeric.h" /* 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 auto B1 = std::array{ 1.0f}; constexpr auto B2 = std::array{ 3.0f, 3.0f}; constexpr auto B3 = std::array{3.6778f, 6.4595f, 2.3222f}; constexpr auto B4 = std::array{4.2076f, 11.4877f, 5.7924f, 9.1401f}; auto NfcFilterCreate1(f32 const w1) noexcept -> NfcFilter1 { auto nfc = NfcFilter1{}; /* Calculate bass-cut coefficients. */ auto const r = 0.5f * w1; auto const b_00 = B1[0] * r; auto const g_0 = 1.0f + b_00; nfc.mBaseGain = 1.0f / g_0; nfc.mCoeffs.a1 = 2.0f * b_00 / g_0; /* Calculate bass-boost coefficients (matches bass-cut for passthrough). */ nfc.mCoeffs.a0 = 1.0f; nfc.mCoeffs.b1 = nfc.mCoeffs.a1; return nfc; } void NfcFilterAdjust1(NfcFilter1 *const nfc, f32 const w0) noexcept { auto const r = 0.5f * w0; auto const b_00 = B1[0] * r; auto const g_0 = 1.0f + b_00; nfc->mCoeffs.a0 = nfc->mBaseGain * g_0; nfc->mCoeffs.b1 = 2.0f * b_00 / g_0; } auto NfcFilterCreate2(f32 const w1) noexcept -> NfcFilter2 { auto nfc = NfcFilter2{}; auto const r = 0.5f * w1; auto const b_10 = B2[0] * r; auto const b_11 = B2[1] * (r*r); auto const g_1 = 1.0f + b_10 + b_11; nfc.mBaseGain = 1.0f / g_1; nfc.mCoeffs.a1 = (2.0f*b_10 + 4.0f*b_11) / g_1; nfc.mCoeffs.a2 = 4.0f * b_11 / g_1; nfc.mCoeffs.a0 = 1.0f; nfc.mCoeffs.b1 = nfc.mCoeffs.a1; nfc.mCoeffs.b2 = nfc.mCoeffs.a2; return nfc; } void NfcFilterAdjust2(NfcFilter2 *const nfc, f32 const w0) noexcept { const auto r = 0.5f * w0; const auto b_10 = B2[0] * r; const auto b_11 = B2[1] * (r*r); const auto g_1 = 1.0f + b_10 + b_11; nfc->mCoeffs.a0 = nfc->mBaseGain * g_1; nfc->mCoeffs.b1 = (2.0f*b_10 + 4.0f*b_11) / g_1; nfc->mCoeffs.b2 = 4.0f * b_11 / g_1; } auto NfcFilterCreate3(f32 const w1) noexcept -> NfcFilter3 { auto nfc = NfcFilter3{}; auto const r = 0.5f * w1; auto const b_10 = B3[0] * r; auto const b_11 = B3[1] * (r*r); auto const b_00 = B3[2] * r; auto const g_1 = 1.0f + b_10 + b_11; auto const g_0 = 1.0f + b_00; nfc.mBaseGain = 1.0f / (g_1 * g_0); nfc.mCoeffs.a1 = (2.0f*b_10 + 4.0f*b_11) / g_1; nfc.mCoeffs.a2 = 4.0f * b_11 / g_1; nfc.mCoeffs.a3 = 2.0f * b_00 / g_0; nfc.mCoeffs.a0 = 1.0f; nfc.mCoeffs.b1 = nfc.mCoeffs.a1; nfc.mCoeffs.b2 = nfc.mCoeffs.a2; nfc.mCoeffs.b3 = nfc.mCoeffs.a3; return nfc; } void NfcFilterAdjust3(NfcFilter3 *const nfc, f32 const w0) noexcept { auto const r = 0.5f * w0; auto const b_10 = B3[0] * r; auto const b_11 = B3[1] * (r*r); auto const b_00 = B3[2] * r; auto const g_1 = 1.0f + b_10 + b_11; auto const g_0 = 1.0f + b_00; nfc->mCoeffs.a0 = nfc->mBaseGain * (g_1 * g_0); nfc->mCoeffs.b1 = (2.0f*b_10 + 4.0f*b_11) / g_1; nfc->mCoeffs.b2 = 4.0f * b_11 / g_1; nfc->mCoeffs.b3 = 2.0f * b_00 / g_0; } auto NfcFilterCreate4(f32 const w1) noexcept -> NfcFilter4 { auto nfc = NfcFilter4{}; auto const r = 0.5f * w1; auto const b_10 = B4[0] * r; auto const b_11 = B4[1] * (r*r); auto const b_00 = B4[2] * r; auto const b_01 = B4[3] * (r*r); auto const g_1 = 1.0f + b_10 + b_11; auto const g_0 = 1.0f + b_00 + b_01; nfc.mBaseGain = 1.0f / (g_1 * g_0); nfc.mCoeffs.a1 = (2.0f*b_10 + 4.0f*b_11) / g_1; nfc.mCoeffs.a2 = 4.0f * b_11 / g_1; nfc.mCoeffs.a3 = (2.0f*b_00 + 4.0f*b_01) / g_0; nfc.mCoeffs.a4 = 4.0f * b_01 / g_0; nfc.mCoeffs.a0 = 1.0f; nfc.mCoeffs.b1 = nfc.mCoeffs.a1; nfc.mCoeffs.b2 = nfc.mCoeffs.a2; nfc.mCoeffs.b3 = nfc.mCoeffs.a3; nfc.mCoeffs.b4 = nfc.mCoeffs.a4; return nfc; } void NfcFilterAdjust4(NfcFilter4 *const nfc, f32 const w0) noexcept { auto const r = 0.5f * w0; auto const b_10 = B4[0] * r; auto const b_11 = B4[1] * (r*r); auto const b_00 = B4[2] * r; auto const b_01 = B4[3] * (r*r); auto const g_1 = 1.0f + b_10 + b_11; auto const g_0 = 1.0f + b_00 + b_01; nfc->mCoeffs.a0 = nfc->mBaseGain * (g_1 * g_0); nfc->mCoeffs.b1 = (2.0f*b_10 + 4.0f*b_11) / g_1; nfc->mCoeffs.b2 = 4.0f * b_11 / g_1; nfc->mCoeffs.b3 = (2.0f*b_00 + 4.0f*b_01) / g_0; nfc->mCoeffs.b4 = 4.0f * b_01 / g_0; } } // namespace void NfcFilter::init(f32 const w1) noexcept { first = NfcFilterCreate1(w1); second = NfcFilterCreate2(w1); third = NfcFilterCreate3(w1); fourth = NfcFilterCreate4(w1); } void NfcFilter::adjust(f32 const w0) noexcept { NfcFilterAdjust1(&first, w0); NfcFilterAdjust2(&second, w0); NfcFilterAdjust3(&third, w0); NfcFilterAdjust4(&fourth, w0); } void NfcFilter1::process(std::span const src, std::span const dst) { auto z = mZ; std::ranges::transform(src, dst.begin(), [coeffs0=mCoeffs, &z](f32 const in) noexcept -> f32 { auto const y = in*coeffs0.a0 - coeffs0.a1*z[0]; auto const out = y + coeffs0.b1*z[0]; z[0] += y; return out; }); mZ = z; } void NfcFilter2::process(std::span const src, std::span const dst) { auto z = mZ; std::ranges::transform(src, dst.begin(), [coeffs=mCoeffs,&z](f32 const in) noexcept -> f32 { auto const y = in*coeffs.a0 - coeffs.a1*z[0] - coeffs.a2*z[1]; auto const out = y + coeffs.b1*z[0] + coeffs.b2*z[1]; z[1] += z[0]; z[0] += y; return out; }); mZ = z; } void NfcFilter3::process(std::span const src, std::span const dst) { auto z = mZ; std::ranges::transform(src, dst.begin(), [coeffs=mCoeffs,&z](f32 const in) noexcept -> f32 { auto const y0 = in*coeffs.a0 - coeffs.a1*z[0] - coeffs.a2*z[1]; auto const out0 = y0 + coeffs.b1*z[0] + coeffs.b2*z[1]; z[1] += z[0]; z[0] += y0; auto const y1 = out0 - coeffs.a3*z[2]; auto const out1 = y1 + coeffs.b3*z[2]; z[2] += y1; return out1; }); mZ = z; } void NfcFilter4::process(std::span const src, std::span const dst) { auto z = mZ; std::ranges::transform(src, dst.begin(), [coeffs=mCoeffs,&z](f32 const in) noexcept -> f32 { auto const y0 = in*coeffs.a0 - coeffs.a1*z[0] - coeffs.a2*z[1]; auto const out0 = y0 + coeffs.b1*z[0] + coeffs.b2*z[1]; z[1] += z[0]; z[0] += y0; auto const y1 = out0 - coeffs.a3*z[2] - coeffs.a4*z[3]; auto const out1 = y1 + coeffs.b3*z[2] + coeffs.b4*z[3]; z[3] += z[2]; z[2] += y1; return out1; }); mZ = z; } kcat-openal-soft-75c0059/core/filters/nfc.h000066400000000000000000000047741512220627100204700ustar00rootroot00000000000000#ifndef CORE_FILTERS_NFC_H #define CORE_FILTERS_NFC_H #include #include #include "alnumeric.h" struct NfcFilter1 { struct Coefficients { f32 a0{1.0f}, a1{}, b1{}; }; f32 mBaseGain{1.0f}; Coefficients mCoeffs; std::array mZ{}; void process(std::span src, std::span dst); }; struct NfcFilter2 { struct Coefficients { f32 a0{1.0f}, a1{}, a2{}, b1{}, b2{}; }; f32 mBaseGain{1.0f}; Coefficients mCoeffs; std::array mZ{}; void process(std::span src, std::span dst); }; struct NfcFilter3 { struct Coefficients { f32 a0{1.0f}, a1{}, a2{}, a3{}, b1{}, b2{}, b3{}; }; f32 mBaseGain{1.0f}; Coefficients mCoeffs; std::array mZ{}; void process(std::span src, std::span dst); }; struct NfcFilter4 { struct Coefficients { f32 a0{1.0f}, a1{}, a2{}, a3{}, a4{}, b1{}, b2{}, b3{}, b4{}; }; f32 mBaseGain{1.0f}; Coefficients mCoeffs; std::array mZ{}; void process(std::span src, std::span dst); }; 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(f32 w1) noexcept; void adjust(f32 w0) noexcept; /* Near-field control filter for first-order ambisonic channels (1-3). */ void process1(std::span const src, std::span const dst) { first.process(src, dst); } /* Near-field control filter for second-order ambisonic channels (4-8). */ void process2(std::span const src, std::span const dst) { second.process(src, dst); } /* Near-field control filter for third-order ambisonic channels (9-15). */ void process3(std::span const src, std::span const dst) { third.process(src, dst); } /* Near-field control filter for fourth-order ambisonic channels (16-24). */ void process4(std::span const src, std::span const dst) { fourth.process(src, dst); } }; #endif /* CORE_FILTERS_NFC_H */ kcat-openal-soft-75c0059/core/filters/splitter.cpp000066400000000000000000000115541512220627100221150ustar00rootroot00000000000000 #include "config.h" #include "splitter.h" #include #include #include #include #include "alnumeric.h" #include "gsl/gsl" void BandSplitter::init(f32 const f0norm) { auto const w = f0norm * (std::numbers::pi_v*2.0f); if(auto const cw = std::cos(w); 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; } void BandSplitter::process(std::span const input, std::span const hpout, std::span const lpout) { auto const ap_coeff = mCoeff; auto const lp_coeff = mCoeff*0.5f + 0.5f; auto lp_z1 = mLpZ1; auto lp_z2 = mLpZ2; auto ap_z1 = mApZ1; Expects(lpout.size() >= input.size()); auto lpiter = lpout.begin(); std::ranges::transform(input, hpout.begin(), [ap_coeff,lp_coeff,&lp_z1,&lp_z2,&ap_z1,&lpiter](f32 const in) noexcept -> f32 { /* Low-pass sample processing. */ auto const d0 = (in - lp_z1) * lp_coeff; auto const lp_y0 = lp_z1 + d0; lp_z1 = lp_y0 + d0; auto const d1 = (lp_y0 - lp_z2) * lp_coeff; auto const lp_y1 = lp_z2 + d1; lp_z2 = lp_y1 + d1; *(lpiter++) = lp_y1; /* All-pass sample processing. */ auto const 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_y1; }); mLpZ1 = lp_z1; mLpZ2 = lp_z2; mApZ1 = ap_z1; } void BandSplitter::processHfScale(std::span const input, std::span const output, f32 const hfscale) { auto const ap_coeff = mCoeff; auto const lp_coeff = mCoeff*0.5f + 0.5f; auto lp_z1 = mLpZ1; auto lp_z2 = mLpZ2; auto ap_z1 = mApZ1; std::ranges::transform(input, output.begin(), [hfscale,ap_coeff,lp_coeff,&lp_z1,&lp_z2,&ap_z1](f32 const in) noexcept -> f32 { /* Low-pass sample processing. */ auto const d0 = (in - lp_z1) * lp_coeff; auto const lp_y0 = lp_z1 + d0; lp_z1 = lp_y0 + d0*lp_coeff; auto const d1 = (lp_y0 - lp_z2) * lp_coeff; auto const lp_y1 = lp_z2 + d1; lp_z2 = lp_y1 + d1; /* All-pass sample processing. */ auto const 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_y1)*hfscale + lp_y1; }); mLpZ1 = lp_z1; mLpZ2 = lp_z2; mApZ1 = ap_z1; } void BandSplitter::processHfScale(std::span const samples, f32 const hfscale) { auto const ap_coeff = mCoeff; auto const lp_coeff = mCoeff*0.5f + 0.5f; auto lp_z1 = mLpZ1; auto lp_z2 = mLpZ2; auto ap_z1 = mApZ1; std::ranges::transform(samples, samples.begin(), [hfscale,ap_coeff,lp_coeff,&lp_z1,&lp_z2,&ap_z1](f32 const in) noexcept -> f32 { /* Low-pass sample processing. */ auto const d0 = (in - lp_z1) * lp_coeff; auto const lp_y0 = lp_z1 + d0; lp_z1 = lp_y0 + d0; auto const d1 = (lp_y0 - lp_z2) * lp_coeff; auto const lp_y1 = lp_z2 + d1; lp_z2 = lp_y1 + d1; /* All-pass sample processing. */ auto const 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_y1)*hfscale + lp_y1; }); mLpZ1 = lp_z1; mLpZ2 = lp_z2; mApZ1 = ap_z1; } void BandSplitter::processScale(std::span const samples, f32 const hfscale, f32 const lfscale) { auto const ap_coeff = mCoeff; auto const lp_coeff = mCoeff*0.5f + 0.5f; auto lp_z1 = mLpZ1; auto lp_z2 = mLpZ2; auto ap_z1 = mApZ1; std::ranges::transform(samples, samples.begin(), [hfscale,lfscale,ap_coeff,lp_coeff,&lp_z1,&lp_z2,&ap_z1](f32 const in) noexcept -> f32 { auto const d0 = (in - lp_z1) * lp_coeff; auto const lp_y0 = lp_z1 + d0; lp_z1 = lp_y0 + d0; auto const d1 = (lp_y0 - lp_z2) * lp_coeff; auto const lp_y1 = lp_z2 + d1; lp_z2 = lp_y1 + d1; auto const 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_y1)*hfscale + lp_y1*lfscale; }); mLpZ1 = lp_z1; mLpZ2 = lp_z2; mApZ1 = ap_z1; } void BandSplitter::processAllPass(std::span const samples) { auto const coeff = mCoeff; auto z1 = mApZ1; std::ranges::transform(samples, samples.begin(), [coeff,&z1](f32 const x) noexcept -> f32 { auto const y = x*coeff + z1; z1 = x - y*coeff; return y; }); mApZ1 = z1; } kcat-openal-soft-75c0059/core/filters/splitter.h000066400000000000000000000021731512220627100215570ustar00rootroot00000000000000#ifndef CORE_FILTERS_SPLITTER_H #define CORE_FILTERS_SPLITTER_H #include #include "alnumeric.h" /* Band splitter. Splits a signal into two phase-matching frequency bands. */ class BandSplitter { f32 mCoeff{0.0f}; f32 mLpZ1{0.0f}; f32 mLpZ2{0.0f}; f32 mApZ1{0.0f}; public: BandSplitter() = default; BandSplitter(BandSplitter const&) = default; explicit BandSplitter(f32 const f0norm) { init(f0norm); } auto operator=(BandSplitter const&) -> BandSplitter& = default; void init(f32 f0norm); void clear() noexcept { mLpZ1 = mLpZ2 = mApZ1 = 0.0f; } void process(std::span input, std::span hpout, std::span lpout); void processHfScale(std::span input, std::span output, f32 hfscale); void processHfScale(std::span samples, f32 hfscale); void processScale(std::span samples, f32 hfscale, f32 lfscale); /** * The all-pass portion of the band splitter. Applies the same phase shift * without splitting or scaling the signal. */ void processAllPass(std::span samples); }; #endif /* CORE_FILTERS_SPLITTER_H */ kcat-openal-soft-75c0059/core/fmt_traits.h000066400000000000000000000157021512220627100204170ustar00rootroot00000000000000#ifndef CORE_FMT_TRAITS_H #define CORE_FMT_TRAITS_H #include #include #include #include "gsl/gsl" #include "storage_formats.h" 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 }}; struct MulawSample { uint8_t value; }; struct AlawSample { uint8_t value; }; struct IMA4Data { std::byte value; }; struct MSADPCMData { std::byte value; }; template struct SampleInfo; template<> struct SampleInfo { static constexpr auto format() noexcept { return FmtUByte; } static constexpr auto silence() noexcept { return uint8_t{128}; } static constexpr auto to_float(const uint8_t sample) noexcept -> float { return gsl::narrow_cast(sample-128) * (1.0f/128.0f); } }; template<> struct SampleInfo { static constexpr auto format() noexcept { return FmtShort; } static constexpr auto silence() noexcept { return int16_t{}; } static constexpr auto to_float(const int16_t sample) noexcept -> float { return gsl::narrow_cast(sample) * (1.0f/32768.0f); } }; template<> struct SampleInfo { static constexpr auto format() noexcept { return FmtInt; } static constexpr auto silence() noexcept { return int32_t{}; } static constexpr auto to_float(const int32_t sample) noexcept -> float { return gsl::narrow_cast(sample)*(1.0f/2147483648.0f); } }; template<> struct SampleInfo { static constexpr auto format() noexcept { return FmtFloat; } static constexpr auto silence() noexcept { return float{}; } static constexpr auto to_float(const float sample) noexcept -> float { return sample; } }; template<> struct SampleInfo { static constexpr auto format() noexcept { return FmtDouble; } static constexpr auto silence() noexcept { return double{}; } static constexpr auto to_float(const double sample) noexcept -> float { return gsl::narrow_cast(sample); } }; template<> struct SampleInfo { static constexpr auto format() noexcept { return FmtMulaw; } static constexpr auto silence() noexcept { return MulawSample{uint8_t{127}}; } static constexpr auto to_float(const MulawSample sample) noexcept -> float { return gsl::narrow_cast(muLawDecompressionTable[sample.value]) * (1.0f/32768.0f); } }; template<> struct SampleInfo { static constexpr auto format() noexcept { return FmtAlaw; } /* Technically not "silence", it's a value of +8 as int16, but +/-8 is the * closest to 0. */ static constexpr auto silence() noexcept { return AlawSample{uint8_t{213}}; } static constexpr auto to_float(const AlawSample sample) noexcept -> float { return gsl::narrow_cast(aLawDecompressionTable[sample.value]) * (1.0f/32768.0f); } }; template<> struct SampleInfo { static constexpr auto format() noexcept { return FmtIMA4; } static constexpr auto silence() noexcept { return IMA4Data{std::byte{}}; } }; template<> struct SampleInfo { static constexpr auto format() noexcept { return FmtMSADPCM; } static constexpr auto silence() noexcept { return MSADPCMData{std::byte{}}; } }; #endif /* CORE_FMT_TRAITS_H */ kcat-openal-soft-75c0059/core/fpu_ctrl.cpp000066400000000000000000000037251512220627100204160ustar00rootroot00000000000000 #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 /* NOLINT(*-reserved-identifier) */ #define _MM_DENORMALS_ZERO_ON 0x0040u /* NOLINT(*-reserved-identifier) */ #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]] auto disable_denormals() -> unsigned int { #if HAVE_SSE_INTRINSICS const auto state = _mm_getcsr(); auto sseState = state; sseState &= ~(_MM_FLUSH_ZERO_MASK | _MM_DENORMALS_ZERO_MASK); sseState |= _MM_FLUSH_ZERO_ON | _MM_DENORMALS_ZERO_ON; _mm_setcsr(sseState); return state; #elif HAVE_SSE const auto state = _mm_getcsr(); auto 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); return state; #else return 0u; #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 auto FPUCtl::Set() noexcept -> unsigned int { #if HAVE_SSE_INTRINSICS return disable_denormals(); #else #if HAVE_SSE if((CPUCapFlags&CPU_CAP_SSE)) return disable_denormals(); #endif return 0u; #endif } 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 } kcat-openal-soft-75c0059/core/fpu_ctrl.h000066400000000000000000000012351512220627100200550ustar00rootroot00000000000000#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 */ kcat-openal-soft-75c0059/core/front_stablizer.h000066400000000000000000000016271512220627100214530ustar00rootroot00000000000000#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" class FrontStablizer { explicit FrontStablizer(const size_t numchans) : ChannelFilters{numchans} { } public: 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 auto Create(size_t numchans) -> std::unique_ptr { return std::unique_ptr{new(FamCount{numchans}) FrontStablizer{numchans}}; } DEF_FAM_NEWDEL(FrontStablizer, ChannelFilters) }; #endif /* CORE_FRONT_STABLIZER_H */ kcat-openal-soft-75c0059/core/helpers.cpp000066400000000000000000000343361512220627100202440ustar00rootroot00000000000000 #include "config.h" #include "helpers.h" #if defined(_WIN32) #include #endif #include #include #include #include #include #include #include #include #include #include #include #include "almalloc.h" #include "alnumeric.h" #include "alstring.h" #include "filesystem.h" #include "gsl/gsl" #include "logging.h" #include "strutils.hpp" namespace { using namespace std::string_view_literals; auto gSearchLock = std::mutex{}; 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 = std::span{*results}.subspan(base); std::ranges::sort(newlist); for(const auto &name : newlist) TRACE(" got {}", name); } } // namespace #ifdef _WIN32 #include #include auto GetProcBinary() -> const PathNamePair& { static const auto procbin = std::invoke([]() -> PathNamePair { #if !ALSOFT_UWP auto pathlen = DWORD{256}; auto fullpath = std::wstring(pathlen, L'\0'); auto 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 if(__argc < 1 || !__wargv) { ERR("Failed to get process name: __argc = {}, __wargv = {}", __argc, static_cast(__wargv)); return PathNamePair{}; } const auto *exePath = __wargv[0]; if(!exePath) { ERR("Failed to get process name: __wargv[0] == nullptr"); return PathNamePair{}; } auto fullpath = std::wstring{exePath}; #endif std::ranges::replace(fullpath, L'/', L'\\'); auto res = PathNamePair{}; 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; }); 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 { const 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 { const auto srchlock = std::lock_guard{gSearchLock}; /* If the path is absolute, use it directly. */ auto results = std::vector{}; const auto path = fs::path(al::char_as_u8(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}) { auto buffer = std::unique_ptr{}; 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 "rtkit.h" #ifndef RLIMIT_RTTIME #define RLIMIT_RTTIME 15 #endif #endif auto GetProcBinary() -> const PathNamePair& { static const auto procbin = std::invoke([]() -> PathNamePair { auto pathname = std::string{}; #ifdef __FreeBSD__ auto pathlen = size_t{}; auto mib = std::array{{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()) { auto procpath = std::array{}; const auto 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()) { auto procpath = std::array{}; 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()) { constexpr auto SelfLinkNames = std::array{ "/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 auto res = PathNamePair{}; if(const 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(R"(Got binary: "{}", "{}")", res.path, res.fname); return res; }); return procbin; } auto SearchDataFiles(const std::string_view ext) -> std::vector { const 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 { const auto srchlock = std::lock_guard{gSearchLock}; auto results = std::vector{}; auto path = fs::path(al::char_as_u8(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 */ const auto datadirs = std::string{al::getenv("XDG_DATA_DIRS") .value_or("/usr/local/share/:/usr/share/")}; auto curpos = 0_uz; while(curpos < datadirs.size()) { auto nextpos = datadirs.find(':', curpos); const auto 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]]) { auto 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. */ const auto rtmin = sched_get_priority_min(SCHED_RR); auto rtmax = sched_get_priority_max(SCHED_RR); rtmax = (rtmax-rtmin)/2 + rtmin; auto param = sched_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 auto const conn = rtkit_get_dbus_connection(); if(!conn) return false; auto nicemin = int{}; auto 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; } auto rtmax = rtkit_get_max_realtime_priority(conn.get()); TRACE("Maximum real-time priority: {}, minimum niceness: {}", rtmax, nicemin); static constexpr auto limit_rttime = [](DBusConnection *c) -> int { using ulonglong = unsigned long long; const auto maxrttime = rtkit_get_rttime_usec_max(c); if(maxrttime <= 0) return gsl::narrow_cast(std::abs(maxrttime)); const auto umaxtime = gsl::narrow_cast(maxrttime); auto rlim = rlimit{}; 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 = al::saturate_cast(umaxtime); 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)) SetRTPriorityRTKit(RTPrioLevel); } #endif kcat-openal-soft-75c0059/core/helpers.h000066400000000000000000000011321512220627100176750ustar00rootroot00000000000000#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 */ kcat-openal-soft-75c0059/core/hrtf.cpp000066400000000000000000000557131512220627100175470ustar00rootroot00000000000000 #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 "alformat.hpp" #include "almalloc.h" #include "alnumeric.h" #include "alstring.h" #include "ambidefs.h" #include "filesystem.h" #include "filters/splitter.h" #include "gsl/gsl" #include "helpers.h" #include "hrtf_loader.hpp" #include "hrtf_resource.hpp" #include "logging.h" #include "mixer/hrtfdefs.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. */ NOINLINE ~HrtfEntry() = default; }; struct LoadedHrtf { std::string mFilename; u32 mSampleRate{}; std::unique_ptr mEntry; template LoadedHrtf(T&& name, u32 const srate, U&& entry) : mFilename{std::forward(name)}, mSampleRate{srate}, mEntry{std::forward(entry)} { } LoadedHrtf(LoadedHrtf&&) = default; /* GCC warns when it tries to inline this. */ NOINLINE ~LoadedHrtf() = default; LoadedHrtf& operator=(LoadedHrtf&&) = default; }; /* First value for pass-through coefficients (remaining are 0), used for omni- * directional sounds. */ constexpr auto PassthruCoeff = gsl::narrow_cast(1.0/std::numbers::sqrt2); auto LoadedHrtfLock = std::mutex{}; auto LoadedHrtfs = std::vector{}; auto EnumeratedHrtfLock = std::mutex{}; auto EnumeratedHrtfs = std::vector{}; /* 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 { auto underflow() -> int_type final { return traits_type::eof(); } auto seekoff(off_type const offset, std::ios_base::seekdir const whence, std::ios_base::openmode const mode) -> pos_type final { 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(); } auto seekpos(pos_type const pos, std::ios_base::openmode const mode) -> pos_type final { // 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()+gsl::narrow_cast(pos), egptr()); return pos; } public: explicit databuf(std::span const data) noexcept { setg(data.data(), data.data(), std::to_address(data.end())); } }; /* NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic) */ class idstream final : public std::istream { databuf mStreamBuf; public: explicit idstream(const std::span data) : std::istream{nullptr}, mStreamBuf{data} { init(&mStreamBuf); } }; struct IdxBlend { u32 idx; f32 blend; }; /* Calculate the elevation index given the polar elevation in radians. This * will return an index between 0 and (evcount - 1). */ auto CalcEvIndex(u32 evcount, f32 ev) -> IdxBlend { ev = (std::numbers::inv_pi_v*ev + 0.5f) * gsl::narrow_cast(evcount-1); const auto idx = float2uint(ev); return IdxBlend{std::min(idx, evcount-1u), ev-gsl::narrow_cast(idx)}; } /* Calculate the azimuth index given the polar azimuth in radians. This will * return an index between 0 and (azcount - 1). */ auto CalcAzIndex(u32 azcount, f32 az) -> IdxBlend { az = (std::numbers::inv_pi_v*0.5f*az + 1.0f) * gsl::narrow_cast(azcount); const auto idx = float2uint(az); return IdxBlend{idx%azcount, az-gsl::narrow_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(f32 const elevation, f32 const azimuth, f32 const distance, f32 const spread, HrirSpan const coeffs, std::span const delays) const { auto const dirfact = 1.0f - (std::numbers::inv_pi_v/2.0f * spread); auto ebase = 0_uz; auto const field = std::ranges::find_if(mFields | std::views::take(mFields.size()-1), [&ebase,distance](Field const &fd) noexcept -> bool { if(distance >= fd.distance) return true; ebase += fd.evCount; return false; }); /* Calculate the elevation indices. */ auto const elev0 = CalcEvIndex(field->evCount, elevation); auto const elev1_idx = usize{std::min(elev0.idx+1u, field->evCount-1u)}; auto const ir0offset = usize{mElev[ebase + elev0.idx].irOffset}; auto const ir1offset = usize{mElev[ebase + elev1_idx].irOffset}; /* Calculate azimuth indices. */ auto const az0 = CalcAzIndex(mElev[ebase + elev0.idx].azCount, azimuth); auto const az1 = CalcAzIndex(mElev[ebase + elev1_idx].azCount, azimuth); /* Calculate the HRIR indices to blend. */ auto const idx = std::array{ 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. */ auto const blend = std::array{ (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. */ auto d = gsl::narrow_cast(mDelays[idx[0]][0])*blend[0] + gsl::narrow_cast(mDelays[idx[1]][0])*blend[1] + gsl::narrow_cast(mDelays[idx[2]][0])*blend[2] + gsl::narrow_cast(mDelays[idx[3]][0])*blend[3]; delays[0] = fastf2u(d * f32{1.0f/HrirDelayFracOne}); d = gsl::narrow_cast(mDelays[idx[0]][1])*blend[0] + gsl::narrow_cast(mDelays[idx[1]][1])*blend[1] + gsl::narrow_cast(mDelays[idx[2]][1])*blend[2] + gsl::narrow_cast(mDelays[idx[3]][1])*blend[3]; delays[1] = fastf2u(d * f32{1.0f/HrirDelayFracOne}); /* Calculate the blended HRIR coefficients. */ coeffs[0][0] = PassthruCoeff * (1.0f-dirfact); coeffs[0][1] = PassthruCoeff * (1.0f-dirfact); std::ranges::fill(coeffs | std::views::drop(1) | std::views::join, 0.0f); for(auto c = 0_uz;c < 4;c++) { const auto joined_coeffs = coeffs | std::views::join; std::ranges::transform(mCoeffs[idx[c]] | std::views::join, joined_coeffs, joined_coeffs.begin(), [mult = blend[c]](f32 const src, f32 const coeff) -> f32 { return src*mult + coeff; }); } } auto DirectHrtfState::Create(usize const num_chans) -> std::unique_ptr { return std::unique_ptr{new(FamCount{num_chans}) DirectHrtfState{num_chans}}; } void DirectHrtfState::build(HrtfStore const *const Hrtf, u32 const irSize, bool const perHrirMin, std::span const AmbiPoints, std::span const> const AmbiMatrix, f32 const XOverFreq, std::span const AmbiOrderHFGain) { using f64x2 = std::array; struct ImpulseResponse { ConstHrirSpan hrir; u32 ldelay, rdelay; }; auto const xover_norm = f64{XOverFreq} / Hrtf->mSampleRate; mChannels[0].mSplitter.init(gsl::narrow_cast(xover_norm)); std::ranges::fill(mChannels | std::views::transform(&HrtfChannelState::mSplitter) | std::views::drop(1), mChannels[0].mSplitter); std::ranges::transform(std::views::iota(0_uz, mChannels.size()), (mChannels | std::views::transform(&HrtfChannelState::mHfScale)).begin(), [AmbiOrderHFGain](usize const idx) { auto const order = AmbiIndex::OrderFromChannel[idx]; return AmbiOrderHFGain[order]; }); auto min_delay = u32{HrtfHistoryLength * HrirDelayFracOne}; auto max_delay = 0_u32; auto impulses = std::vector{}; impulses.reserve(AmbiPoints.size()); std::ranges::transform(AmbiPoints, std::back_inserter(impulses), [Hrtf,&max_delay,&min_delay](AngularPoint const &pt) -> ImpulseResponse { auto const &field = Hrtf->mFields[0]; auto const elev0 = CalcEvIndex(field.evCount, pt.Elev.value); auto const elev1_idx = std::min(elev0.idx+1_uz, field.evCount-1_uz); auto const ir0offset = Hrtf->mElev[elev0.idx].irOffset; auto const ir1offset = Hrtf->mElev[elev1_idx].irOffset; auto const az0 = CalcAzIndex(Hrtf->mElev[elev0.idx].azCount, pt.Azim.value); auto const az1 = CalcAzIndex(Hrtf->mElev[elev1_idx].azCount, pt.Azim.value); auto const idx = std::array{ 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 auto irOffset = idx[(elev0.blend >= 0.5f)*2_uz + (az1.blend >= 0.5f)]; const auto res = ImpulseResponse{.hrir=Hrtf->mCoeffs[irOffset], .ldelay=Hrtf->mDelays[irOffset][0], .rdelay=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; }); TRACE("Min delay: {:.2f}, max delay: {:.2f}, FIR length: {}", min_delay/f64{HrirDelayFracOne}, max_delay/f64{HrirDelayFracOne}, irSize); static constexpr auto hrir_delay_round = [](u32 const d) noexcept -> u32 { return (d+HrirDelayFracHalf) >> HrirDelayFracBits; }; auto tmpres = std::vector>(mChannels.size()); max_delay = 0; std::ignore = std::ranges::mismatch(impulses, AmbiMatrix, [perHrirMin,min_delay,&max_delay,&tmpres](ImpulseResponse const &impulse, std::span const matrixline) { auto const hrir = impulse.hrir; auto const base_delay = perHrirMin ? std::min(impulse.ldelay, impulse.rdelay) : min_delay; auto const ldelay = hrir_delay_round(impulse.ldelay - base_delay); auto const rdelay = hrir_delay_round(impulse.rdelay - base_delay); max_delay = std::max(max_delay, std::max(impulse.ldelay, impulse.rdelay) - base_delay); std::ignore = std::ranges::mismatch(tmpres, matrixline, [hrir,ldelay,rdelay](std::array &result, f64 const mult) { auto const lresult = result | std::views::drop(ldelay) | std::views::elements<0>; std::ranges::transform(hrir | std::views::elements<0>, lresult, lresult.begin(), std::plus{}, [mult](f64 const coeff) noexcept { return coeff*mult; }); auto const rresult = result | std::views::drop(rdelay) | std::views::elements<1>; std::ranges::transform(hrir | std::views::elements<1>, rresult, rresult.begin(), std::plus{}, [mult](f64 const coeff) noexcept { return coeff*mult; }); return true; }); return true; }); impulses.clear(); constexpr auto join_join = std::views::join | std::views::join; std::ignore = std::ranges::transform(tmpres | join_join, (mChannels | std::views::transform(&HrtfChannelState::mCoeffs) | join_join).begin(), [](f64 const in) noexcept { return gsl::narrow_cast(in); }); tmpres.clear(); const auto max_length = std::min(hrir_delay_round(max_delay) + irSize, HrirLength); TRACE("New max delay: {:.2f}, FIR length: {}", max_delay/f64{HrirDelayFracOne}, max_length); mIrSize = max_length; } namespace { auto checkName(std::string_view const name) -> bool { using std::ranges::find; return find(EnumeratedHrtfs, name, &HrtfEntry::mDispName) != EnumeratedHrtfs.end(); } void AddFileEntry(std::string_view const filename) { /* Check if this file has already been enumerated. */ if(std::ranges::find(EnumeratedHrtfs, filename,&HrtfEntry::mFilename) != EnumeratedHrtfs.end()) { TRACE("Skipping duplicate file entry {}", filename); return; } /* TODO: Get a human-readable name from the HRTF data (possibly coming in a * format update). */ auto const namepos = std::max(filename.rfind('/')+1, filename.rfind('\\')+1); auto const extpos = filename.substr(namepos).rfind('.'); auto const basename = (extpos == std::string_view::npos) ? filename.substr(namepos) : filename.substr(namepos, extpos); auto count = 1; auto newname = std::string{basename}; while(checkName(newname)) newname = al::format("{} #{}", basename, ++count); auto const &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(std::string_view const dispname, u32 const residx) { auto filename = al::format("!{}_{}", residx, dispname); if(std::ranges::find(EnumeratedHrtfs, filename,&HrtfEntry::mFilename) != EnumeratedHrtfs.end()) { 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 = al::format("{} #{}", dispname, ++count); auto const &entry = EnumeratedHrtfs.emplace_back(std::move(newname), std::move(filename)); TRACE("Adding built-in entry \"{}\"", entry.mFilename); } } // namespace auto EnumerateHrtf(std::optional const &pathopt) -> std::vector { auto enumlock = std::lock_guard{EnumeratedHrtfLock}; EnumeratedHrtfs.clear(); std::ranges::for_each(SearchDataFiles(".mhr"sv), AddFileEntry); auto usedefaults = true; if(pathopt) { std::ranges::for_each(*pathopt | std::views::split(','), [&usedefaults](auto&& range) { auto entry = std::string_view{range.begin(), range.end()}; constexpr auto wspace_chars = " \t\n\f\r\v"sv; entry.remove_prefix(std::min(entry.find_first_not_of(wspace_chars), entry.size())); entry.remove_suffix(entry.size() - (entry.find_last_not_of(wspace_chars)+1)); if(entry.empty()) { usedefaults = true; return; } usedefaults = false; std::ranges::for_each(SearchDataFiles(".mhr"sv, entry), AddFileEntry); }); } if(usedefaults) { std::ranges::for_each(SearchDataFiles(".mhr"sv, "openal/hrtf"sv), AddFileEntry); if(!GetHrtfResource(DefaultHrtfResourceID).empty()) AddBuiltInEntry("Built-In HRTF", DefaultHrtfResourceID); } auto list = std::vector(EnumeratedHrtfs.size()); std::ranges::transform(EnumeratedHrtfs, list.begin(), &HrtfEntry::mDispName); return list; } auto GetLoadedHrtf(std::string_view const name, u32 const devrate) -> HrtfStorePtr try { if(devrate > MaxHrtfSampleRate) { WARN("Device sample rate too large for HRTF ({}hz > {}hz)", devrate, MaxHrtfSampleRate); return nullptr; } auto const fname = std::invoke([name]() -> std::string { auto const enumlock = std::lock_guard{EnumeratedHrtfLock}; auto const entry_iter = std::ranges::find(EnumeratedHrtfs, name, &HrtfEntry::mDispName); if(entry_iter == EnumeratedHrtfs.cend()) return std::string{}; return entry_iter->mFilename; }); if(fname.empty()) return nullptr; auto const loadlock = std::lock_guard{LoadedHrtfLock}; auto handle = std::lower_bound(LoadedHrtfs.begin(), LoadedHrtfs.end(), fname, [devrate](LoadedHrtf const &hrtf, std::string const &filename) -> bool { return hrtf.mSampleRate < devrate || (hrtf.mSampleRate == devrate && hrtf.mFilename < filename); }); if(handle != LoadedHrtfs.end() && handle->mSampleRate == devrate && handle->mFilename == fname) { if(auto *hrtf = handle->mEntry.get()) { Expects(hrtf->mSampleRate == devrate); hrtf->inc_ref(); return HrtfStorePtr{hrtf}; } } auto stream = std::unique_ptr{}; auto residx = int{}; auto ch = char{}; /* NOLINTNEXTLINE(cert-err34-c,cppcoreguidelines-pro-type-vararg) */ if(sscanf(fname.c_str(), "!%d%c", &residx, &ch) == 2 && ch == '_') { TRACE("Loading built-in HRTF {}...", residx); auto const res = GetHrtfResource(residx); if(res.empty()) { ERR("Could not get resource {}, {}", residx, name); return nullptr; } /* NOLINTNEXTLINE(*-const-cast) */ stream = std::make_unique(std::span{const_cast(res.data()), res.size()}); } else { TRACE("Loading {}...", fname); auto fstr = std::make_unique(fs::path(al::char_as_u8(fname)), std::ios::binary); if(!fstr->is_open()) { ERR("Could not open {}", fname); return nullptr; } stream = std::move(fstr); } auto hrtf = LoadHrtf(*stream); stream.reset(); Expects(hrtf != nullptr); if(hrtf->mSampleRate != devrate) { TRACE("Resampling HRTF {} ({}hz -> {}hz)", name, u32{hrtf->mSampleRate}, devrate); /* Resample all the IRs. */ auto inout = std::array, 2>{}; auto rs = PPhaseResampler{}; rs.init(hrtf->mSampleRate, devrate); std::ranges::for_each(hrtf->mCoeffs, [&inout,&rs](HrirArray const &const_coeffs) { /* NOLINTNEXTLINE(*-const-cast) */ auto coeffs = std::span{const_cast(const_coeffs)}; auto coeffs0 = coeffs | std::views::elements<0>; std::ranges::copy(coeffs0, inout[0].begin()); rs.process(inout[0], inout[1]); std::ranges::transform(inout[1], coeffs0.begin(), [](f64 const d) noexcept { return gsl::narrow_cast(d); }); auto coeffs1 = coeffs | std::views::elements<1>; std::ranges::copy(coeffs1, inout[0].begin()); rs.process(inout[0], inout[1]); std::ranges::transform(inout[1], coeffs1.begin(), [](f64 const d) noexcept { return gsl::narrow_cast(d); }); }); rs = {}; /* Scale the delays for the new sample rate. */ auto max_delay = 0.0f; auto new_delays = std::vector(hrtf->mDelays.size()*2_uz); auto const rate_scale = gsl::narrow_cast(devrate) / gsl::narrow_cast(u32{hrtf->mSampleRate}); std::ranges::transform(hrtf->mDelays | std::views::join, new_delays.begin(), [&max_delay,rate_scale](u8 const oldval) { auto const ret = std::round(gsl::narrow_cast(oldval) * rate_scale) / f32{HrirDelayFracOne}; max_delay = std::max(max_delay, ret); return ret; }); /* 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). */ auto delay_scale = f32{HrirDelayFracOne}; if(max_delay > MaxHrirDelay) { WARN("Resampled delay exceeds max ({:.2f} > {})", max_delay, MaxHrirDelay); delay_scale *= f32{MaxHrirDelay} / max_delay; } /* NOLINTNEXTLINE(*-const-cast) */ auto const delays = std::span{const_cast(hrtf->mDelays.data()), hrtf->mDelays.size()}; std::ranges::transform(new_delays, (delays | std::views::join).begin(), [delay_scale](f32 const fdelay) -> u8 { return gsl::narrow_cast(float2int(fdelay*delay_scale + 0.5f)); }); /* Scale the IR size for the new sample rate and update the stored * sample rate. */ auto const newIrSize = std::round(gsl::narrow_cast(u32{hrtf->mIrSize})*rate_scale); hrtf->mIrSize = gsl::narrow_cast(std::min(f32{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::inc_ref() noexcept { auto const ref = mRef.fetch_add(1, std::memory_order_acq_rel)+1; TRACE("HrtfStore {} increasing refcount to {}", decltype(std::declval()){this}, ref); } void HrtfStore::dec_ref() noexcept { auto const ref = mRef.fetch_sub(1, std::memory_order_acq_rel)-1; TRACE("HrtfStore {} decreasing refcount to {}", decltype(std::declval()){this}, ref); if(ref == 0) { auto const loadlock = std::lock_guard{LoadedHrtfLock}; /* Go through and remove all unused HRTFs. */ auto iter = std::ranges::remove_if(LoadedHrtfs, [](LoadedHrtf &hrtf) -> bool { if(auto const *const entry = hrtf.mEntry.get(); entry && entry->mRef.load() == 0) { TRACE("Unloading unused HRTF {}", hrtf.mFilename); hrtf.mEntry = nullptr; return true; } return false; }); LoadedHrtfs.erase(iter.begin(), iter.end()); } } kcat-openal-soft-75c0059/core/hrtf.h000066400000000000000000000064271512220627100172120ustar00rootroot00000000000000#ifndef CORE_HRTF_H #define CORE_HRTF_H #include #include #include #include #include #include #include #include #include "almalloc.h" #include "ambidefs.h" #include "bufferline.h" #include "flexarray.h" #include "intrusive_ptr.h" #include "mixer/hrtfdefs.h" struct HrtfStore { alignas(16) std::atomic mRef; u32 mSampleRate : 24; u32 mIrSize : 8; struct Field { f32 distance; u8 evCount; }; /* NOTE: Fields are stored *backwards*. mFields.front() is the farthest * field, and mFields.back() is the nearest. */ std::span mFields; struct Elevation { u16 azCount; u16 irOffset; }; std::span mElev; std::span mCoeffs; std::span mDelays; void getCoeffs(f32 elevation, f32 azimuth, f32 distance, f32 spread, HrirSpan coeffs, std::span delays) const; void inc_ref() noexcept; void dec_ref() noexcept; auto operator new(usize) -> void* = delete; auto operator new[](usize) -> void* = 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; /* Data set limits must be the same as or more flexible than those defined in * the makemhr utility. */ constexpr inline auto MaxHrirDelay = u32{HrtfHistoryLength - 1}; constexpr inline auto HrirDelayFracBits = 2_u32; constexpr inline auto HrirDelayFracOne = 1_u32 << HrirDelayFracBits; constexpr inline auto HrirDelayFracHalf = HrirDelayFracOne >> 1_u32; /* The sample rate is stored as a 24-bit integer, so 16MHz is the largest * supported. */ constexpr inline auto MaxHrtfSampleRate = 0xff'ff'ff_u32; struct EvRadians { f32 value; }; struct AzRadians { f32 value; }; struct AngularPoint { EvRadians Elev; AzRadians Azim; }; class DirectHrtfState { explicit DirectHrtfState(usize const numchans) : mChannels{numchans} { } public: std::array mTemp{}; /* HRTF filter state for dry buffer content */ u32 mIrSize{0}; al::FlexArray mChannels; /** * 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(HrtfStore const *Hrtf, u32 irSize, bool perHrirMin, std::span AmbiPoints, std::span const> AmbiMatrix, f32 XOverFreq, std::span AmbiOrderHFGain); static auto Create(usize num_chans) -> std::unique_ptr; DEF_FAM_NEWDEL(DirectHrtfState, mChannels) }; auto EnumerateHrtf(std::optional const &pathopt) -> std::vector; auto GetLoadedHrtf(std::string_view name, u32 devrate) -> HrtfStorePtr; #endif /* CORE_HRTF_H */ kcat-openal-soft-75c0059/core/hrtf_loader.cpp000066400000000000000000000722311512220627100210670ustar00rootroot00000000000000 #include "config.h" #include #include #include #include #include #include #include #include "hrtf_loader.hpp" #include "alformat.hpp" #include "alnumeric.h" #include "fmt/core.h" #include "fmt/ranges.h" #include "gsl/gsl" #include "hrtf.h" #include "logging.h" namespace { using namespace std::string_view_literals; /* Data set limits must be the same as or more flexible than those defined in * the makemhr utility. */ constexpr auto MinFdCount = 1u; constexpr auto MaxFdCount = 16u; constexpr auto MinFdDistance = 50u; constexpr auto MaxFdDistance = 2500u; constexpr auto MinEvCount = 5u; constexpr auto MaxEvCount = 181u; constexpr auto MinAzCount = 1u; constexpr auto MaxAzCount = 255u; static_assert(MaxHrirDelay*HrirDelayFracOne < 256, "MaxHrirDelay or HrirDelayFracOne 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); auto CreateHrtfStore(u32 const rate, u8 const irSize, std::span const fields, std::span const elevs, std::span const coeffs, std::span const delays) -> std::unique_ptr { static_assert(16 <= alignof(HrtfStore)); static_assert(alignof(HrtfStore::Field) <= alignof(HrtfStore)); static_assert(alignof(HrtfStore::Elevation) <= alignof(HrtfStore::Field)); if(rate > MaxHrtfSampleRate) throw std::runtime_error{al::format("Sample rate is too large (max: {}hz)", MaxHrtfSampleRate)}; const auto irCount = size_t{elevs.back().azCount} + elevs.back().irOffset; auto total = sizeof(HrtfStore); total = RoundFromZero(total, alignof(HrtfStore::Field)); /* Align for field infos */ total += fields.size_bytes(); total = RoundFromZero(total, alignof(HrtfStore::Elevation)); /* Align for elevation infos */ total += elevs.size_bytes(); total = RoundFromZero(total, 16); /* Align for coefficients using SIMD */ total += coeffs.size_bytes(); total += delays.size_bytes(); static constexpr auto AlignVal = std::align_val_t{alignof(HrtfStore)}; auto Hrtf = std::unique_ptr{::new(::operator new[](total, AlignVal)) HrtfStore{}}; Hrtf->mRef.store(1u, std::memory_order_relaxed); Hrtf->mSampleRate = rate & 0xff'ff'ff; Hrtf->mIrSize = irSize; /* NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast) * Set up pointers to storage following the main HRTF struct. */ const auto storage = std::span{reinterpret_cast(std::to_address(Hrtf)), total}; const auto base = storage.begin(); auto storeiter = base; std::advance(storeiter, sizeof(HrtfStore)); auto field_ = std::span{reinterpret_cast(std::to_address(storeiter)), fields.size()}; std::advance(storeiter, fields.size_bytes()); static_assert(alignof(HrtfStore::Field) >= alignof(HrtfStore::Elevation)); auto elev_ = std::span{reinterpret_cast(std::to_address(storeiter)), elevs.size()}; std::advance(storeiter, elevs.size_bytes()); storeiter = RoundFromZero(storeiter-base, 16)+base; /* Align for coefficients using SIMD */ auto coeffs_ = std::span{reinterpret_cast(std::to_address(storeiter)), irCount}; std::advance(storeiter, coeffs.size_bytes()); auto delays_ = std::span{reinterpret_cast(std::to_address(storeiter)), irCount}; std::advance(storeiter, delays.size_bytes()); /* NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast) */ if(gsl::narrow_cast(std::distance(base, storeiter)) != total) throw std::runtime_error{"HrtfStore allocation size mismatch"}; /* Copy input data to storage. */ std::ranges::uninitialized_copy(fields, field_); std::ranges::uninitialized_copy(elevs, elev_); std::ranges::uninitialized_copy(coeffs, coeffs_); std::ranges::uninitialized_copy(delays, delays_); /* Finally, assign the storage pointers. */ Hrtf->mFields = field_; Hrtf->mElev = elev_; Hrtf->mCoeffs = coeffs_; Hrtf->mDelays = delays_; return Hrtf; } void MirrorLeftHrirs(std::span const elevs, std::span coeffs, std::span delays) { for(const auto &elev : elevs) { const auto evoffset = size_t{elev.irOffset}; const auto azcount = size_t{elev.azCount}; for(const auto j : std::views::iota(0_uz, azcount)) { const auto lidx = evoffset + j; const auto ridx = evoffset + ((azcount-j) % azcount); std::ranges::copy(coeffs[lidx] | std::views::elements<0>, (coeffs[ridx] | std::views::elements<1>).begin()); delays[ridx][1] = delays[lidx][0]; } } } template constexpr auto fixsign(T value) noexcept -> T { if constexpr(std::is_signed_v && num_bits < sizeof(T)*8) { constexpr auto signbit = gsl::narrow_cast(1u << (num_bits-1)); return gsl::narrow_cast((value^signbit) - signbit); } else return value; } template auto readle(std::istream &data) -> T { 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) auto ret = std::array{}; if(!data.read(ret.data(), num_bits/8)) return gsl::narrow_cast(EOF); if constexpr(std::endian::native == std::endian::big) std::reverse(ret.begin(), ret.end()); return fixsign(std::bit_cast(ret)); } template<> auto readle(std::istream &data) -> uint8_t { return gsl::narrow_cast(data.get()); } auto LoadHrtf00(std::istream &data) -> std::unique_ptr { const auto rate = readle(data); const auto irCount = readle(data); const auto irSize = readle(data); const auto evCount = readle(data); if(!data || data.eof()) throw std::runtime_error{"Premature end of file"}; if(irSize < MinIrLength || irSize > HrirLength) { throw std::runtime_error{al::format("Unsupported HRIR size, irSize={} ({} to {})", irSize, MinIrLength, HrirLength)}; } if(evCount < MinEvCount || evCount > MaxEvCount) { throw std::runtime_error{al::format("Unsupported elevation count: evCount={} ({} to {})", evCount, MinEvCount, MaxEvCount)}; } auto elevs = std::vector(evCount); std::ranges::generate(elevs | std::views::transform(&HrtfStore::Elevation::irOffset), [&data] { return 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) { throw std::runtime_error{al::format("Invalid evOffset: evOffset[{}]={} (last={})", i, elevs[i].irOffset, elevs[i-1].irOffset)}; } } if(irCount <= elevs.back().irOffset) { throw std::runtime_error{al::format("Invalid evOffset: evOffset[{}]={} (irCount={})", elevs.size()-1, elevs.back().irOffset, irCount)}; } for(size_t i{1};i < evCount;i++) { elevs[i-1].azCount = gsl::narrow_cast(elevs[i].irOffset - elevs[i-1].irOffset); if(elevs[i-1].azCount < MinAzCount || elevs[i-1].azCount > MaxAzCount) { throw std::runtime_error{al::format( "Unsupported azimuth count: azCount[{}]={} ({} to {})", i-1, elevs[i-1].azCount, MinAzCount, MaxAzCount)}; } } elevs.back().azCount = gsl::narrow_cast(irCount - elevs.back().irOffset); if(elevs.back().azCount < MinAzCount || elevs.back().azCount > MaxAzCount) { throw std::runtime_error{al::format( "Unsupported azimuth count: azCount[{}]={} ({} to {})", elevs.size()-1, elevs.back().azCount, MinAzCount, MaxAzCount)}; } auto coeffs = std::vector(irCount, HrirArray{}); auto delays = std::vector(irCount, u8x2{}); std::ranges::for_each(coeffs, [&data,irSize](HrirSpan hrir) { std::ranges::generate(hrir | std::views::take(irSize) | std::views::elements<0>, [&data]{ return gsl::narrow_cast(readle(data)) / 32768.0f; }); }); std::ranges::generate(delays|std::views::elements<0>, [&data]{return 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) { throw std::runtime_error{al::format("Invalid delays[{}]: {} ({})", i, delays[i][0], MaxHrirDelay)}; } delays[i][0] = gsl::narrow(delays[i][0] << HrirDelayFracBits); } /* Mirror the left ear responses to the right ear. */ MirrorLeftHrirs(elevs, coeffs, delays); const auto field = std::array{HrtfStore::Field{0.0f, evCount}}; return CreateHrtfStore(rate, gsl::narrow_cast(irSize), field, elevs, coeffs, delays); } auto LoadHrtf01(std::istream &data) -> std::unique_ptr { const auto rate = readle(data); const auto irSize = readle(data); const auto evCount = readle(data); if(!data || data.eof()) throw std::runtime_error{"Premature end of file"}; if(irSize < MinIrLength || irSize > HrirLength) { throw std::runtime_error{al::format("Unsupported HRIR size, irSize={} ({} to {})", irSize, MinIrLength, HrirLength)}; } if(evCount < MinEvCount || evCount > MaxEvCount) { throw std::runtime_error{al::format("Unsupported elevation count: evCount={} ({} to {})", evCount, MinEvCount, MaxEvCount)}; } auto elevs = std::vector(evCount); std::ranges::generate(elevs | std::views::transform(&HrtfStore::Elevation::azCount), [&data] { return 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) { throw std::runtime_error{al::format( "Unsupported azimuth count: azCount[{}]={} ({} to {})", i, elevs[i].azCount, MinAzCount, MaxAzCount)}; } } elevs[0].irOffset = 0; for(size_t i{1};i < evCount;i++) elevs[i].irOffset = gsl::narrow_cast(elevs[i-1].irOffset + elevs[i-1].azCount); auto const irCount = gsl::narrow_cast(elevs.back().irOffset + elevs.back().azCount); auto coeffs = std::vector(irCount, HrirArray{}); auto delays = std::vector(irCount, u8x2{}); std::ranges::for_each(coeffs, [&data,irSize](HrirSpan hrir) { std::ranges::generate(hrir | std::views::take(irSize) | std::views::elements<0>, [&data]{ return gsl::narrow_cast(readle(data)) / 32768.0f; }); }); std::ranges::generate(delays|std::views::elements<0>, [&data]{return 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) { throw std::runtime_error{al::format("Invalid delays[{}]: {} ({})", i, delays[i][0], MaxHrirDelay)}; } delays[i][0] = gsl::narrow(delays[i][0] << HrirDelayFracBits); } /* Mirror the left ear responses to the right ear. */ MirrorLeftHrirs(elevs, coeffs, delays); const auto field = std::array{HrtfStore::Field{0.0f, evCount}}; return CreateHrtfStore(rate, irSize, field, elevs, coeffs, delays); } auto LoadHrtf02(std::istream &data) -> std::unique_ptr { static constexpr auto SampleType_S16 = 0_u8; static constexpr auto SampleType_S24 = 1_u8; static constexpr auto ChanType_LeftOnly = 0_u8; static constexpr auto ChanType_LeftRight = 1_u8; const auto rate = readle(data); const auto sampleType = readle(data); const auto channelType = readle(data); const auto irSize = readle(data); const auto fdCount = readle(data); if(!data || data.eof()) throw std::runtime_error{"Premature end of file"}; if(sampleType > SampleType_S24) throw std::runtime_error{al::format("Unsupported sample type: {}", sampleType)}; if(channelType > ChanType_LeftRight) throw std::runtime_error{al::format("Unsupported channel type: {}", channelType)}; if(irSize < MinIrLength || irSize > HrirLength) { throw std::runtime_error{al::format("Unsupported HRIR size, irSize={} ({} to {})", irSize, MinIrLength, HrirLength)}; } if(fdCount < 1 || fdCount > MaxFdCount) { throw std::runtime_error{al::format( "Unsupported number of field-depths: fdCount={} ({} to {})", fdCount, MinFdCount, MaxFdCount)}; } auto fields = std::vector(fdCount); auto elevs = std::vector{}; for(size_t f{0};f < fdCount;f++) { const auto distance = readle(data); const auto evCount = readle(data); if(!data || data.eof()) throw std::runtime_error{"Premature end of file"}; if(distance < MinFdDistance || distance > MaxFdDistance) { throw std::runtime_error{al::format( "Unsupported field distance[{}]={} ({} to {} millimeters)", f, distance, MinFdDistance, MaxFdDistance)}; } if(evCount < MinEvCount || evCount > MaxEvCount) { throw std::runtime_error{al::format( "Unsupported elevation count: evCount[{}]={} ({} to {})", f, evCount, MinEvCount, MaxEvCount)}; } fields[f].distance = gsl::narrow_cast(distance) / 1000.0f; fields[f].evCount = evCount; if(f > 0 && !(fields[f].distance > fields[f-1].distance)) { throw std::runtime_error{al::format( "Field distance[{}] is not after previous ({} > {})", f, fields[f].distance, fields[f-1].distance)}; } const auto ebase = elevs.size(); elevs.resize(ebase + evCount); const auto new_azs = elevs | std::views::transform(&HrtfStore::Elevation::azCount) | std::views::drop(ebase); std::ranges::generate(new_azs, [&data] { return readle(data); }); if(!data || data.eof()) throw std::runtime_error{"Premature end of file"}; const auto invazi = std::ranges::find_if_not(new_azs, [](const auto &azi) noexcept { return azi >= MinAzCount && azi <= MaxAzCount; }); if(invazi != new_azs.end()) { const auto idx = std::distance(new_azs.begin(), invazi); throw std::runtime_error{al::format( "Unsupported azimuth count: azCount[{}][{}]={} ({} to {})", f, idx, *invazi, MinAzCount, MaxAzCount)}; } } 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, gsl::narrow_cast(last.azCount + last.irOffset)}; }); auto const irTotal = gsl::narrow_cast(elevs.back().azCount + elevs.back().irOffset); auto coeffs = std::vector(irTotal, HrirArray{}); auto delays = std::vector(irTotal, u8x2{}); if(channelType == ChanType_LeftOnly) { if(sampleType == SampleType_S16) { std::ranges::for_each(coeffs, [&data,irSize](HrirSpan hrir) { std::ranges::generate(hrir | std::views::take(irSize) | std::views::elements<0>, [&data]{ return gsl::narrow_cast(readle(data)) / 32768.0f; }); }); } else if(sampleType == SampleType_S24) { std::ranges::for_each(coeffs, [&data,irSize](HrirSpan hrir) { std::ranges::generate(hrir | std::views::take(irSize) | std::views::elements<0>, [&data]{ return gsl::narrow_cast(readle(data)) / 8388608.0f; }); }); } const auto ldelays = delays | std::views::elements<0>; std::ranges::generate(ldelays, [&data]{ return readle(data); }); if(!data || data.eof()) throw std::runtime_error{"Premature end of file"}; auto const invdelay = std::ranges::find_if(ldelays, [](u8 const delay) noexcept { return delay > MaxHrirDelay; }); if(invdelay != ldelays.end()) { const auto idx = std::distance(ldelays.begin(), invdelay); throw std::runtime_error{al::format("Invalid delays[{}][0]: {} > {}", idx, *invdelay, MaxHrirDelay)}; } std::ranges::transform(ldelays, ldelays.begin(), [](u8 const delay) -> u8 { return gsl::narrow_cast(delay << HrirDelayFracBits); }); /* Mirror the left ear responses to the right ear. */ MirrorLeftHrirs(elevs, coeffs, delays); } else if(channelType == ChanType_LeftRight) { if(sampleType == SampleType_S16) { std::ranges::for_each(coeffs, [&data,irSize](HrirSpan hrir) { std::ranges::generate(hrir | std::views::take(irSize) | std::views::join, [&data]{ return gsl::narrow_cast(readle(data)) / 32768.0f; }); }); } else if(sampleType == SampleType_S24) { std::ranges::for_each(coeffs, [&data,irSize](HrirSpan hrir) { std::ranges::generate(hrir | std::views::take(irSize) | std::views::join, [&data]{ return gsl::narrow_cast(readle(data)) / 8388608.0f; }); }); } const auto joined_delays = delays | std::views::join; std::ranges::generate(joined_delays, [&data]{ return readle(data); }); if(!data || data.eof()) throw std::runtime_error{"Premature end of file"}; auto const invdelay = std::ranges::find_if(joined_delays, [](u8 const delay) noexcept { return delay > MaxHrirDelay; }); if(invdelay != joined_delays.end()) { const auto idx = std::distance(joined_delays.begin(), invdelay); throw std::runtime_error{al::format("Invalid delays[{}][{}]: {} > {}", idx>>1, idx&1, *invdelay, MaxHrirDelay)}; } std::ranges::transform(joined_delays, joined_delays.begin(), [](u8 const delay) -> u8 { return gsl::narrow_cast(delay << 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::ranges::reverse_copy(fields, 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(); std::ignore = std::accumulate(fields.cbegin(), fields.cend(), ptrdiff_t{0}, [&elevs,&elevs_end](const ptrdiff_t ebase, const HrtfStore::Field &field) -> ptrdiff_t { elevs_end = std::ranges::copy_backward(elevs | std::views::drop(ebase) | std::views::take(field.evCount), elevs_end).out; return ebase + field.evCount; }); Ensures(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, gsl::narrow_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(); std::ignore = std::accumulate(fields.cbegin(), fields.cend(), ptrdiff_t{0}, [&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::ranges::copy_backward(coeffs | std::views::drop(abase) | std::views::take(num_azs), coeffs_end).out; delays_end = std::ranges::copy_backward(delays | std::views::drop(abase) | std::views::take(num_azs), delays_end).out; return ebase + field.evCount; }); Ensures(coeffs_.begin() == coeffs_end); Ensures(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, delays); } auto LoadHrtf03(std::istream &data) -> std::unique_ptr { static constexpr auto ChanType_LeftOnly = 0_u8; static constexpr auto ChanType_LeftRight = 1_u8; const auto rate = readle(data); const auto channelType = readle(data); const auto irSize = readle(data); const auto fdCount = readle(data); if(!data || data.eof()) throw std::runtime_error{"Premature end of file"}; if(channelType > ChanType_LeftRight) throw std::runtime_error{al::format("Unsupported channel type: {}", channelType)}; if(irSize < MinIrLength || irSize > HrirLength) { throw std::runtime_error{al::format("Unsupported HRIR size, irSize={} ({} to {})", irSize, MinIrLength, HrirLength)}; } if(fdCount < 1 || fdCount > MaxFdCount) { throw std::runtime_error{al::format( "Unsupported number of field-depths: fdCount={} ({} to {})", fdCount, MinFdCount, MaxFdCount)}; } auto fields = std::vector(fdCount); auto elevs = std::vector{}; for(size_t f{0};f < fdCount;f++) { const auto distance = readle(data); const auto evCount = readle(data); if(!data || data.eof()) throw std::runtime_error{"Premature end of file"}; if(distance < MinFdDistance || distance > MaxFdDistance) { throw std::runtime_error{al::format( "Unsupported field distance[{}]={} ({} to {} millimeters)", f, distance, MinFdDistance, MaxFdDistance)}; } if(evCount < MinEvCount || evCount > MaxEvCount) { throw std::runtime_error{al::format( "Unsupported elevation count: evCount[{}]={} ({} to {})", f, evCount, MinEvCount, MaxEvCount)}; } fields[f].distance = gsl::narrow_cast(distance) / 1000.0f; fields[f].evCount = evCount; if(f > 0 && !(fields[f].distance < fields[f-1].distance)) { throw std::runtime_error{al::format( "Field distance[{}] is not before previous ({} < {})", f, fields[f].distance, fields[f-1].distance)}; } const auto ebase = elevs.size(); elevs.resize(ebase + evCount); const auto new_azs = elevs | std::views::transform(&HrtfStore::Elevation::azCount) | std::views::drop(ebase); std::ranges::generate(new_azs, [&data] { return readle(data); }); if(!data || data.eof()) throw std::runtime_error{"Premature end of file"}; const auto invazi = std::ranges::find_if_not(new_azs, [](const auto &azi) noexcept { return azi >= MinAzCount && azi <= MaxAzCount; }); if(invazi != new_azs.end()) { const auto idx = std::distance(new_azs.begin(), invazi); throw std::runtime_error{al::format( "Unsupported azimuth count: azCount[{}][{}]={} ({} to {})", f, idx, *invazi, MinAzCount, MaxAzCount)}; } } 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, gsl::narrow_cast(last.azCount + last.irOffset)}; }); auto const irTotal = gsl::narrow_cast(elevs.back().azCount + elevs.back().irOffset); auto coeffs = std::vector(irTotal, HrirArray{}); auto delays = std::vector(irTotal, u8x2{}); if(channelType == ChanType_LeftOnly) { std::ranges::for_each(coeffs, [&data,irSize](HrirSpan hrir) { std::ranges::generate(hrir | std::views::take(irSize) | std::views::elements<0>, [&data]{ return gsl::narrow_cast(readle(data)) / 8388608.0f; }); }); const auto ldelays = delays | std::views::elements<0>; std::ranges::generate(ldelays, [&data]{ return readle(data); }); if(!data || data.eof()) throw std::runtime_error{"Premature end of file"}; auto const invdelay = std::ranges::find_if(ldelays, [](u8 const delay) noexcept { return delay > MaxHrirDelay< {}", idx, gsl::narrow_cast(*invdelay)/float{HrirDelayFracOne}, MaxHrirDelay)}; } /* Mirror the left ear responses to the right ear. */ MirrorLeftHrirs(elevs, coeffs, delays); } else if(channelType == ChanType_LeftRight) { std::ranges::for_each(coeffs, [&data,irSize](HrirSpan hrir) { std::ranges::generate(hrir | std::views::take(irSize) | std::views::join, [&data]{ return gsl::narrow_cast(readle(data)) / 8388608.0f; }); }); const auto joined_delays = delays | std::views::join; std::ranges::generate(joined_delays, [&data]{ return readle(data); }); if(!data || data.eof()) throw std::runtime_error{"Premature end of file"}; auto const invdelay = std::ranges::find_if(joined_delays, [](u8 const delay) noexcept { return delay > MaxHrirDelay<>1, idx&1, gsl::narrow_cast(*invdelay)/float{HrirDelayFracOne}, MaxHrirDelay)}; } } return CreateHrtfStore(rate, irSize, fields, elevs, coeffs, delays); } } // namespace auto LoadHrtf(std::istream &stream) -> std::unique_ptr { auto magic = std::array{}; stream.read(magic.data(), magic.size()); if(stream.gcount() < std::streamsize{magic.size()}) throw std::runtime_error{al::format("Data is too short ({} bytes)", stream.gcount())}; if(std::ranges::equal(GetMarker03Name(), magic)) { TRACE("Detected data set format v3"); return LoadHrtf03(stream); } if(std::ranges::equal(GetMarker02Name(), magic)) { TRACE("Detected data set format v2"); return LoadHrtf02(stream); } if(std::ranges::equal(GetMarker01Name(), magic)) { TRACE("Detected data set format v1"); return LoadHrtf01(stream); } if(std::ranges::equal(GetMarker00Name(), magic)) { TRACE("Detected data set format v0"); return LoadHrtf00(stream); } throw std::runtime_error{fmt::format("Invalid header: {::#04X}", std::as_bytes(std::span{magic}))}; } kcat-openal-soft-75c0059/core/hrtf_loader.hpp000066400000000000000000000003331512220627100210660ustar00rootroot00000000000000#ifndef CORE_HRTF_LOADER_HPP #define CORE_HRTF_LOADER_HPP #include #include struct HrtfStore; auto LoadHrtf(std::istream &stream) -> std::unique_ptr; #endif /* CORE_HRTF_LOADER_HPP */ kcat-openal-soft-75c0059/core/hrtf_resource.cpp000066400000000000000000000007631512220627100214510ustar00rootroot00000000000000 #include "config.h" #include #include "hrtf_resource.hpp" #ifndef ALSOFT_EMBED_HRTF_DATA auto GetHrtfResource(int name [[maybe_unused]]) noexcept -> std::span { return {}; } #else namespace { /* NOLINTNEXTLINE(*-avoid-c-arrays) */ constexpr char hrtf_default[] = { #include "default_hrtf.txt" }; } // namespace auto GetHrtfResource(int name) noexcept -> std::span { if(name == DefaultHrtfResourceID) return hrtf_default; return {}; } #endif kcat-openal-soft-75c0059/core/hrtf_resource.hpp000066400000000000000000000003521512220627100214500ustar00rootroot00000000000000#ifndef CORE_HRTF_RESOURCE_HPP #define CORE_HRTF_RESOURCE_HPP #include constexpr inline auto DefaultHrtfResourceID = 1; auto GetHrtfResource(int name) noexcept -> std::span; #endif /* CORE_HRTF_RESOURCE_HPP */ kcat-openal-soft-75c0059/core/logging.cpp000066400000000000000000000075741512220627100202340ustar00rootroot00000000000000 #include "config.h" #include "logging.h" #include #include #include #include #include #include #include #include "alnumeric.h" #include "alstring.h" #include "fmt/std.h" #include "strutils.hpp" #if defined(_WIN32) #include #elif defined(__ANDROID__) #include #endif #ifdef _DEBUG LogLevel gLogLevel{LogLevel::Warning}; #else LogLevel gLogLevel{LogLevel::Error}; #endif namespace { using namespace std::string_view_literals; using lpvoid = void*; enum class LogState : u8 { FirstRun, Ready, Disable }; auto LogCallbackMutex = std::mutex{}; auto gLogState = LogState::FirstRun; auto gLogFile = std::ofstream{}; /* NOLINT(cert-err58-cpp) */ auto gLogCallback = LogCallbackFunc{}; auto gLogCallbackPtr = lpvoid{}; constexpr auto GetLevelCode(LogLevel const 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_open_logfile(fs::path const &fname) { gLogFile.open(fname); if(!gLogFile.is_open()) ERR("Failed to open log file '{}'", al::u8_as_char(fname.u8string())); } void al_set_log_callback(LogCallbackFunc const callback, void *const userptr) { auto const cblock = std::lock_guard{LogCallbackMutex}; gLogCallback = callback; gLogCallbackPtr = callback ? userptr : nullptr; if(gLogState == LogState::FirstRun) { if(auto const extlogopt = al::getenv("ALSOFT_DISABLE_LOG_CALLBACK"); !extlogopt || *extlogopt != "1") gLogState = LogState::Ready; else gLogState = LogState::Disable; } } void al_print_impl(LogLevel const level, al::string_view const fmt, al::format_args&& args) { const auto msg = al::vformat(fmt, std::move(args)); auto const prefix = std::invoke([level]() -> std::string_view { switch(level) { case LogLevel::Trace: return "[ALSOFT] (II) "sv; case LogLevel::Warning: return "[ALSOFT] (WW) "sv; case LogLevel::Error: return "[ALSOFT] (EE) "sv; case LogLevel::Disable: break; } return "[ALSOFT] (--) "sv; }); if(gLogLevel >= level) { auto &logfile = gLogFile.is_open() ? gLogFile : std::cerr; /* std::vprint_unicode */ fmt::vprint(logfile, "{}{}\n", fmt::make_format_args(prefix, msg)); logfile.flush(); } #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(al::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::saturate_cast(prefix.size()), prefix.data(), msg.c_str()); #endif auto const cblock = std::lock_guard{LogCallbackMutex}; if(gLogState != LogState::Disable) { if(auto const logcode = GetLevelCode(level)) { if(gLogCallback) gLogCallback(gLogCallbackPtr, *logcode, msg.data(), al::saturate_cast(msg.size())); else if(gLogState == LogState::FirstRun) gLogState = LogState::Disable; } } } kcat-openal-soft-75c0059/core/logging.h000066400000000000000000000025601512220627100176670ustar00rootroot00000000000000#ifndef CORE_LOGGING_H #define CORE_LOGGING_H #include #include "alformat.hpp" #include "alnumeric.h" #include "filesystem.h" #include "gsl/gsl" #include "opthelpers.h" enum class LogLevel : u8 { Disable, Error, Warning, Trace }; DECL_HIDDEN extern LogLevel gLogLevel; using LogCallbackFunc = auto(*)(void *userptr, char level, gsl::czstring message, int length) noexcept -> void; void al_set_log_callback(LogCallbackFunc callback, void *userptr); void al_open_logfile(fs::path const &fname); void al_print_impl(LogLevel level, al::string_view fmt, al::format_args&& args); template void al_print(LogLevel const level, al::format_string const fmt, Args&& ...args) noexcept try { al_print_impl(level, fmt.get(), al::make_format_args(args...)); } catch(...) { /* Swallow all exceptions */ } template void TRACE(al::format_string const fmt, Args&& ...args) noexcept { al_print(LogLevel::Trace, fmt, std::forward(args)...); } template void WARN(al::format_string const fmt, Args&& ...args) noexcept { al_print(LogLevel::Warning, fmt, std::forward(args)...); } template void ERR(al::format_string const fmt, Args&& ...args) noexcept { al_print(LogLevel::Error, fmt, std::forward(args)...); } #endif /* CORE_LOGGING_H */ kcat-openal-soft-75c0059/core/mastering.cpp000066400000000000000000000326011512220627100205640ustar00rootroot00000000000000 #include "config.h" #include "mastering.h" #include #include #include #include #include #include #include #include #include "alnumeric.h" #include "gsl/gsl" #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 SlidingHold { alignas(16) FloatBufferLine mValues; std::array mExpiries; uint mLowerIndex; uint mUpperIndex; uint mLength; }; namespace { template constexpr auto assume_aligned_span(const std::span s) noexcept -> std::span { return std::span{std::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 auto mask = uint{BufferLineSize - 1}; const auto length = Hold->mLength; const auto values = std::span{Hold->mValues}; const auto expiries = std::span{Hold->mExpiries}; auto lowerIndex = Hold->mLowerIndex; auto 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) { if(Hold->mLowerIndex < Hold->mUpperIndex) { auto expiries = std::span{Hold->mExpiries}.first(Hold->mLowerIndex+1); std::ranges::transform(expiries, expiries.begin(), [n](const uint e) { return e - n; }); expiries = std::span{Hold->mExpiries}.subspan(Hold->mUpperIndex); std::ranges::transform(expiries, expiries.begin(), [n](const uint e) { return e - n; }); } else { const auto expiries = std::span{Hold->mExpiries}.first(Hold->mLowerIndex+1) .subspan(Hold->mUpperIndex); std::ranges::transform(expiries, expiries.begin(), [n](const uint e) { return e - n; }); } } } // namespace auto 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) -> std::unique_ptr { const auto lookAhead = gsl::narrow_cast(std::clamp(std::round(LookAheadTime*SampleRate), 0.0f, BufferLineSize-1.0f)); const auto hold = gsl::narrow_cast(std::clamp(std::round(HoldTime*SampleRate), 0.0f, BufferLineSize-1.0f)); auto Comp = std::make_unique(PrivateToken{}); 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(AutoFlags.test(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(PrivateToken) { } Compressor::~Compressor() = default; /* 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 auto autoKnee = mAuto.Knee; const auto autoAttack = mAuto.Attack; const auto autoRelease = mAuto.Release; const auto autoPostGain = mAuto.PostGain; const auto autoDeclip = mAuto.Declip; const auto threshold = mThreshold; const auto slope = mSlope; const auto attack = mAttack; const auto release = mRelease; const auto c_est = mGainEstimate; const auto a_adp = mAdaptCoeff; auto crestFactor = mCrestFactor.cbegin(); auto postGain = mPostGain; auto knee = mKnee; auto t_att = attack; auto t_rel = release - attack; auto a_att = std::exp(-1.0f / t_att); auto a_rel = std::exp(-1.0f / t_rel); auto y_1 = mLastRelease; auto y_L = mLastAttack; auto c_dev = mLastGainDev; ASSUME(SamplesToDo > 0); ASSUME(SamplesToDo <= BufferLineSize); std::ranges::transform(mSideChain | std::views::take(SamplesToDo), mSideChain | std::views::drop(mLookAhead), mSideChain.begin(), [&](const float input, const float lookAhead) -> float { if(autoKnee) knee = std::max(0.0f, 2.5f*(c_dev + c_est)); const auto knee_h = 0.5f * knee; /* This is the gain computer. It applies a static compression curve to * the control signal. */ const auto x_over = lookAhead - threshold; const auto 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 auto 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 auto 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); }); mLastRelease = y_1; mLastAttack = y_L; mLastGainDev = c_dev; } void Compressor::process(const uint SamplesToDo, const std::span InOut) { ASSUME(SamplesToDo > 0); ASSUME(SamplesToDo <= BufferLineSize); if(const auto preGain = mPreGain; preGain != 1.0f) { std::ranges::for_each(InOut, [SamplesToDo,preGain](const FloatBufferSpan input) -> void { std::ranges::transform(input | std::views::take(SamplesToDo), input.begin(), [preGain](const float s) noexcept { return s * preGain; }); }); } /* Multichannel compression is linked via the absolute maximum of all * channels. */ const auto sideChain = std::span{mSideChain}.subspan(mLookAhead, SamplesToDo); std::ranges::fill(sideChain, 0.0f); std::ranges::for_each(InOut, [sideChain](const FloatBufferSpan input) -> void { std::ranges::transform(sideChain, input, sideChain.begin(), [](const float s0, const float s1) noexcept -> float { return std::max(s0, std::fabs(s1)); }); }); if(mAuto.Attack || mAuto.Release) { /* 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. */ const auto a_crest = mCrestCoeff; auto y2_peak = mLastPeakSq; auto y2_rms = mLastRmsSq; std::ranges::transform(sideChain, mCrestFactor.begin(), [&y2_rms,&y2_peak,a_crest](const float x_abs) noexcept -> float { const auto 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; }); mLastPeakSq = y2_peak; mLastRmsSq = y2_rms; } if(auto *hold = mHold.get()) { /* 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. */ auto i = 0u; std::ranges::transform(sideChain, sideChain.begin(), [&i,hold](const float x_abs) -> float { const auto x_G = std::log(std::max(0.000001f, x_abs)); return UpdateSlidingHold(hold, i++, x_G); }); ShiftSlidingHold(hold, SamplesToDo); } else { /* 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. */ std::ranges::transform(sideChain, sideChain.begin(), [](const float s) -> float { return std::log(std::max(0.000001f, s)); }); } gainCompressor(SamplesToDo); if(!mDelay.empty()) { /* 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. */ const auto lookAhead = mLookAhead; ASSUME(lookAhead > 0); ASSUME(lookAhead < BufferLineSize); auto delays = mDelay.begin(); std::ranges::for_each(InOut, [SamplesToDo,lookAhead,&delays](const FloatBufferSpan buffer) { const auto inout = buffer.first(SamplesToDo); const auto delaybuf = std::span{*(delays++)}.first(lookAhead); if(SamplesToDo >= delaybuf.size()) [[likely]] { const auto inout_start = std::prev(inout.end(), std::ssize(delaybuf)); const auto delay_end = std::ranges::rotate(inout, inout_start).begin(); std::ranges::swap_ranges(std::span{inout.begin(), delay_end}, delaybuf); } else { const auto delay_start = std::ranges::swap_ranges(inout, delaybuf).in2; std::ranges::rotate(delaybuf, delay_start); } }); } const auto gains = std::span{mSideChain}.first(SamplesToDo); std::ranges::for_each(InOut, [gains](const FloatBufferSpan inout) -> void { const auto buffer = assume_aligned_span<16>(std::span{inout}); std::ranges::transform(gains, buffer, buffer.begin(), std::multiplies{}); }); std::ranges::copy(mSideChain | std::views::drop(SamplesToDo) | std::views::take(mLookAhead), mSideChain.begin()); } kcat-openal-soft-75c0059/core/mastering.h000066400000000000000000000077451512220627100202440ustar00rootroot00000000000000#ifndef CORE_MASTERING_H #define CORE_MASTERING_H #include #include #include #include #include "alnumeric.h" #include "bufferline.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 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}; void gainCompressor(const uint SamplesToDo); struct PrivateToken { }; public: enum { AutoKnee, AutoAttack, AutoRelease, AutoPostGain, AutoDeclip, FlagsCount }; using FlagBits = std::bitset; Compressor() = delete; Compressor(const Compressor&) = delete; explicit Compressor(PrivateToken); ~Compressor(); auto operator=(const Compressor&) -> Compressor& = delete; void process(const uint SamplesToDo, std::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 AutoFlags Flags to automate specific parameters: * AutoKnee - automate the knee width parameter * AutoAttack - automate the attack time parameter * AutoRelease - automate the release time parameter * AutoPostGain - automate the make-up (post) gain * parameter * AutoDeclip - 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 auto 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) -> std::unique_ptr; }; using CompressorPtr = std::unique_ptr; #endif /* CORE_MASTERING_H */ kcat-openal-soft-75c0059/core/mixer.cpp000066400000000000000000000067141512220627100177250ustar00rootroot00000000000000 #include "config.h" #include "mixer.h" #include #include #include #include #include "core/ambidefs.h" #include "device.h" #include "mixer/defs.h" auto CalcAmbiCoeffs(const float y, const float z, const float x, const float spread) -> std::array { auto 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 auto ca = std::cos(spread * 0.5f); /* Increase the source volume by up to +3dB for a full spread. */ const auto scale = std::sqrt(1.0f + std::numbers::inv_pi_v*0.5f*spread); const auto caca = ca*ca; const auto ZH0_norm = scale; const auto ZH1_norm = scale * 0.5f * (ca+1.0f); const auto ZH2_norm = scale * 0.5f * ((ca+1.0f)*ca); const auto ZH3_norm = scale * 0.125f * ((ca+1.0f)*(5.0f*caca - 1.0f)); const auto ZH4_norm = scale * 0.125f * ((ca+1.0f)*(7.0f*caca - 3.0f)*ca); /* 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; /* Fourth-order */ coeffs[16] *= ZH4_norm; coeffs[17] *= ZH4_norm; coeffs[18] *= ZH4_norm; coeffs[19] *= ZH4_norm; coeffs[20] *= ZH4_norm; coeffs[21] *= ZH4_norm; coeffs[22] *= ZH4_norm; coeffs[23] *= ZH4_norm; coeffs[24] *= ZH4_norm; } return coeffs; } void ComputePanGains(const MixParams *mix, const std::span coeffs, const float ingain, const std::span gains) { auto ambimap = std::span{std::as_const(mix->AmbiMap)}.first(mix->Buffer.size()); const auto iter = std::ranges::transform(ambimap, gains.begin(), [coeffs,ingain](const BFChannelConfig &chanmap) noexcept -> float { return chanmap.Scale * coeffs[chanmap.Index] * ingain; }); std::fill(iter.out, gains.end(), 0.0f); } kcat-openal-soft-75c0059/core/mixer.h000066400000000000000000000101171512220627100173620ustar00rootroot00000000000000#ifndef CORE_MIXER_H #define CORE_MIXER_H #include #include #include #include "alnumeric.h" #include "ambidefs.h" #include "bufferline.h" #include "opthelpers.h" struct MixParams; void Mix_C(std::span InSamples, std::span OutBuffer, std::span CurrentGains, std::span TargetGains, usize Counter, usize OutPos); void Mix_C(std::span InSamples, std::span OutBuffer, f32 &CurrentGain, f32 TargetGain, usize Counter); /* Mixer functions that handle one input and multiple output channels. */ using MixerOutFunc = void(*)(std::span InSamples, std::span OutBuffer, std::span CurrentGains, std::span TargetGains, usize Counter, usize OutPos); inline constinit auto MixSamplesOut = MixerOutFunc{Mix_C}; inline void MixSamples(std::span const InSamples, std::span const OutBuffer, std::span const CurrentGains, std::span const TargetGains, usize const Counter, usize const OutPos) { MixSamplesOut(InSamples, OutBuffer, CurrentGains, TargetGains, Counter, OutPos); } /* Mixer functions that handle one input and one output channel. */ using MixerOneFunc = void(*)(std::span InSamples, std::span OutBuffer, f32 &CurrentGain, f32 TargetGain, usize Counter); inline constinit auto MixSamplesOne = MixerOneFunc{Mix_C}; inline void MixSamples(std::span const InSamples, std::span const OutBuffer, f32 &CurrentGain, f32 const TargetGain, usize const 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 auto CalcDirectionCoeffs(const std::span dir, const float spread) -> std::array { /* 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 auto CalcDirectionCoeffs(const std::span dir) -> std::array { /* 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 auto CalcAngleCoeffs(const float azimuth, const float elevation, const float spread) -> std::array { 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 std::span coeffs, const float ingain, const std::span gains); #endif /* CORE_MIXER_H */ kcat-openal-soft-75c0059/core/mixer/000077500000000000000000000000001512220627100172115ustar00rootroot00000000000000kcat-openal-soft-75c0059/core/mixer/defs.h000066400000000000000000000112701512220627100203040ustar00rootroot00000000000000#ifndef CORE_MIXER_DEFS_H #define CORE_MIXER_DEFS_H #include #include #include #include #include "alnumeric.h" #include "core/bufferline.h" #include "core/cubic_defs.h" struct HrtfChannelState; struct HrtfFilter; struct MixHrtfFilter; using f32x2 = std::array; inline constexpr auto MixerFracBits = 16_i32; inline constexpr auto MixerFracOne = 1_i32 << MixerFracBits; inline constexpr auto MixerFracMask = MixerFracOne - 1_i32; inline constexpr auto MixerFracHalf = MixerFracOne >> 1_i32; inline constexpr auto GainSilenceThreshold = 0.00001_f32; /* -100dB */ enum class Resampler : u8 { Point, Linear, Spline, Gaussian, FastBSinc12, BSinc12, FastBSinc24, BSinc24, FastBSinc48, BSinc48, Max = BSinc48 }; /* 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 { f32 sf; /* Scale interpolation factor. */ u32 m; /* Coefficient count. */ u32 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. */ std::span filter; }; struct CubicState { /* Filter coefficients, and coefficient deltas. Starting at phase index 0, * each subsequent phase index follows contiguously. */ std::span filter; explicit CubicState(std::span const f) : filter{f} { } }; using InterpState = std::variant; using ResamplerFunc = void(*)(InterpState const *state, std::span src, u32 frac, u32 increment, std::span dst); [[nodiscard]] auto PrepareResampler(Resampler resampler, u32 increment, InterpState *state) -> ResamplerFunc; #define DECL_RESAMPLER(T, I) \ void Resample_##T##_##I(InterpState const *state, std::span src, \ u32 frac, u32 increment, std::span dst); #define DECL_MIXER(I) \ void Mix_##I(std::span InSamples, \ std::span OutBuffer, std::span CurrentGains, \ std::span TargetGains, usize Counter, usize OutPos); \ void Mix_##I(std::span InSamples, std::span OutBuffer, \ f32 &CurrentGain, f32 TargetGain, usize Counter); #define DECL_HRTF_MIXER(I) \ void MixHrtf_##I(std::span InSamples, \ std::span AccumSamples, u32 IrSize, \ MixHrtfFilter const *hrtfparams, usize SamplesToDo); \ void MixHrtfBlend_##I(std::span InSamples, \ std::span AccumSamples, u32 IrSize, HrtfFilter const *oldparams, \ MixHrtfFilter const *newparams, usize SamplesToDo); \ void MixDirectHrtf_##I(FloatBufferSpan LeftOut, FloatBufferSpan RightOut, \ std::span InSamples, std::span AccumSamples,\ std::span TempBuf, \ std::span ChanState, usize IrSize, usize SamplesToDo); DECL_RESAMPLER(Point, C) DECL_RESAMPLER(Linear, C) DECL_RESAMPLER(Cubic, C) DECL_RESAMPLER(FastBSinc, C) DECL_RESAMPLER(BSinc, C) DECL_MIXER(C) DECL_HRTF_MIXER(C) #if HAVE_SSE DECL_RESAMPLER(Cubic, SSE) DECL_RESAMPLER(FastBSinc, SSE) DECL_RESAMPLER(BSinc, SSE) DECL_MIXER(SSE) DECL_HRTF_MIXER(SSE) #endif #if HAVE_SSE2 DECL_RESAMPLER(Linear, SSE2) DECL_RESAMPLER(Cubic, SSE2) #endif #if HAVE_SSE4_1 DECL_RESAMPLER(Linear, SSE4) DECL_RESAMPLER(Cubic, SSE4) #endif #if HAVE_NEON DECL_RESAMPLER(Linear, NEON) DECL_RESAMPLER(Cubic, NEON) DECL_RESAMPLER(FastBSinc, NEON) DECL_RESAMPLER(BSinc, NEON) DECL_MIXER(NEON) DECL_HRTF_MIXER(NEON) #endif #undef DECL_HRTF_MIXER #undef DECL_MIXER #undef DECL_RESAMPLER /* Vectorized resampler helpers */ template constexpr void InitPosArrays(u32 const pos, u32 const frac, u32 const increment, std::span const frac_arr, std::span const pos_arr) { static_assert(pos_arr.size() == frac_arr.size()); pos_arr[0] = pos; frac_arr[0] = frac; for(auto const i : std::views::iota(1_uz, pos_arr.size())) { auto const 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 */ kcat-openal-soft-75c0059/core/mixer/hrtfbase.h000066400000000000000000000121451512220627100211630ustar00rootroot00000000000000#ifndef CORE_MIXER_HRTFBASE_H #define CORE_MIXER_HRTFBASE_H #include #include #include #include "alnumeric.h" #include "defs.h" #include "gsl/gsl" #include "hrtfdefs.h" #include "opthelpers.h" using ApplyCoeffsT = void(*)(std::span Values, usize irSize, ConstHrirSpan Coeffs, f32 left, f32 right); template void MixHrtfBase(std::span const InSamples, std::span const AccumSamples, usize const IrSize, MixHrtfFilter const *const hrtfparams, usize const SamplesToDo) { ASSUME(SamplesToDo > 0); ASSUME(SamplesToDo <= BufferLineSize); ASSUME(IrSize <= HrirLength); auto const Coeffs = std::span{hrtfparams->Coeffs}; auto const gainstep = hrtfparams->GainStep; auto const gain = hrtfparams->Gain; auto ldelay = usize{HrtfHistoryLength} - hrtfparams->Delay[0]; auto rdelay = usize{HrtfHistoryLength} - hrtfparams->Delay[1]; auto stepcount = 0.0f; for(auto i = 0_uz;i < SamplesToDo;++i) { auto const g = gain + gainstep*stepcount; auto const left = InSamples[ldelay++] * g; auto const right = InSamples[rdelay++] * g; ApplyCoeffs(AccumSamples.subspan(i), IrSize, Coeffs, left, right); stepcount += 1.0f; } } template void MixHrtfBlendBase(std::span const InSamples, std::span const AccumSamples, usize const IrSize, HrtfFilter const *const oldparams, MixHrtfFilter const *const newparams, usize const SamplesToDo) { ASSUME(SamplesToDo > 0); ASSUME(SamplesToDo <= BufferLineSize); ASSUME(IrSize <= HrirLength); auto const OldCoeffs = ConstHrirSpan{oldparams->Coeffs}; auto const oldGainStep = oldparams->Gain / gsl::narrow_cast(SamplesToDo); auto const NewCoeffs = ConstHrirSpan{newparams->Coeffs}; auto const newGainStep = newparams->GainStep; if(oldparams->Gain > GainSilenceThreshold) [[likely]] { auto ldelay = usize{HrtfHistoryLength} - oldparams->Delay[0]; auto rdelay = usize{HrtfHistoryLength} - oldparams->Delay[1]; auto stepcount = gsl::narrow_cast(SamplesToDo); for(auto i = 0_uz;i < SamplesToDo;++i) { auto const g = oldGainStep*stepcount; auto const left = InSamples[ldelay++] * g; auto const right = InSamples[rdelay++] * g; ApplyCoeffs(AccumSamples.subspan(i), IrSize, OldCoeffs, left, right); stepcount -= 1.0f; } } if(newGainStep*gsl::narrow_cast(SamplesToDo) > GainSilenceThreshold) [[likely]] { auto ldelay = usize{HrtfHistoryLength+1} - newparams->Delay[0]; auto rdelay = usize{HrtfHistoryLength+1} - newparams->Delay[1]; auto stepcount = 1.0f; for(auto i = 1_uz;i < SamplesToDo;++i) { auto const g = newGainStep*stepcount; auto const left = InSamples[ldelay++] * g; auto const right = InSamples[rdelay++] * g; ApplyCoeffs(AccumSamples.subspan(i), IrSize, NewCoeffs, left, right); stepcount += 1.0f; } } } template void MixDirectHrtfBase(FloatBufferSpan const LeftOut, FloatBufferSpan const RightOut, std::span const InSamples, std::span const AccumSamples, std::span const TempBuf, std::span const ChannelState, usize const IrSize, usize const SamplesToDo) { ASSUME(SamplesToDo > 0); ASSUME(SamplesToDo <= BufferLineSize); ASSUME(IrSize <= HrirLength); std::ignore = std::ranges::mismatch(InSamples, ChannelState, [&](FloatConstBufferSpan const input, HrtfChannelState &ChanState) { /* 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(std::span{input}.first(SamplesToDo), TempBuf, ChanState.mHfScale); /* Now apply the HRIR coefficients to this channel. */ auto const Coeffs = ConstHrirSpan{ChanState.mCoeffs}; for(auto i = 0_uz;i < SamplesToDo;++i) { auto const insample = TempBuf[i]; ApplyCoeffs(AccumSamples.subspan(i), IrSize, Coeffs, insample, insample); } return true; }); /* Add the HRTF signal to the existing "direct" signal. */ std::ranges::transform(LeftOut | std::views::take(SamplesToDo), AccumSamples | std::views::elements<0>, LeftOut.begin(), std::plus{}); std::ranges::transform(RightOut | std::views::take(SamplesToDo), AccumSamples | std::views::elements<1>, RightOut.begin(), std::plus{}); /* Copy the new in-progress accumulation values to the front and clear the * following samples for the next mix. */ auto const accum_inprog = AccumSamples.subspan(SamplesToDo, HrirLength); auto const accum_iter = std::ranges::copy(accum_inprog, AccumSamples.begin()).out; std::fill_n(accum_iter, SamplesToDo, f32x2{}); } #endif /* CORE_MIXER_HRTFBASE_H */ kcat-openal-soft-75c0059/core/mixer/hrtfdefs.h000066400000000000000000000020751512220627100211730ustar00rootroot00000000000000#ifndef CORE_MIXER_HRTFDEFS_H #define CORE_MIXER_HRTFDEFS_H #include #include #include "alnumeric.h" #include "core/filters/splitter.h" using u8x2 = std::array; using u32x2 = std::array; using f32x2 = std::array; constexpr auto HrtfHistoryBits = 6_u32; constexpr auto HrtfHistoryLength = 1_u32 << HrtfHistoryBits; constexpr auto HrtfHistoryMask = HrtfHistoryLength - 1_u32; constexpr auto HrirBits = 7_u32; constexpr auto HrirLength = 1_u32 << HrirBits; constexpr auto HrirMask = HrirLength - 1_u32; constexpr auto MinIrLength = 8_u32; using HrirArray = std::array; using HrirSpan = std::span; using ConstHrirSpan = std::span; struct MixHrtfFilter { ConstHrirSpan const Coeffs; u32x2 Delay; f32 Gain; f32 GainStep; }; struct HrtfFilter { alignas(16) HrirArray Coeffs; u32x2 Delay; f32 Gain; }; struct HrtfChannelState { BandSplitter mSplitter; f32 mHfScale{}; alignas(16) HrirArray mCoeffs{}; }; #endif /* CORE_MIXER_HRTFDEFS_H */ kcat-openal-soft-75c0059/core/mixer/mixer_c.cpp000066400000000000000000000232471512220627100213530ustar00rootroot00000000000000#include "config.h" #include #include #include #include #include #include "alnumeric.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 "gsl/gsl" #include "hrtfbase.h" #include "opthelpers.h" namespace { constexpr auto BsincPhaseDiffBits = u32{MixerFracBits - BSincPhaseBits}; constexpr auto BsincPhaseDiffOne = 1_u32 << BsincPhaseDiffBits; constexpr auto BsincPhaseDiffMask = BsincPhaseDiffOne - 1_u32; constexpr auto CubicPhaseDiffBits = u32{MixerFracBits - CubicPhaseBits}; constexpr auto CubicPhaseDiffOne = 1_u32 << CubicPhaseDiffBits; constexpr auto CubicPhaseDiffMask = CubicPhaseDiffOne - 1_u32; using SamplerNST = auto(std::span vals, usize pos, u32 frac) noexcept -> f32; template using SamplerT = auto(T const &istate, std::span vals, usize pos, u32 frac) noexcept -> f32; [[nodiscard]] constexpr auto do_point(std::span const vals, usize const pos, u32) noexcept -> f32 { return vals[pos]; } [[nodiscard]] constexpr auto do_lerp(std::span const vals, usize const pos, u32 const frac) noexcept -> f32 { return lerpf(vals[pos+0], vals[pos+1], gsl::narrow_cast(frac)*(1.0f/MixerFracOne)); } [[nodiscard]] constexpr auto do_cubic(CubicState const &istate, std::span const vals, usize const pos, u32 const frac) noexcept -> f32 { /* Calculate the phase index and factor. */ auto const pi = u32{frac>>CubicPhaseDiffBits}; ASSUME(pi < CubicPhaseCount); auto const pf = gsl::narrow_cast(frac&CubicPhaseDiffMask) * f32{1.0f/CubicPhaseDiffOne}; auto const fil = std::span{istate.filter[pi].mCoeffs}; auto const phd = std::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(BsincState const &bsinc, std::span const vals, usize const pos, u32 const frac) noexcept -> f32 { auto const m = usize{bsinc.m}; ASSUME(m > 0); ASSUME(m <= MaxResamplerPadding); /* Calculate the phase index and factor. */ auto const pi = u32{frac>>BsincPhaseDiffBits}; ASSUME(pi < BSincPhaseCount); auto const pf = gsl::narrow_cast(frac&BsincPhaseDiffMask) * (1.0f/BsincPhaseDiffOne); auto const fil = bsinc.filter.subspan(2_uz*pi*m); auto const phd = fil.subspan(m); /* Apply the phase interpolated filter. */ auto r = 0.0f; for(auto j_f=0_uz;j_f < m;++j_f) r += (fil[j_f] + pf*phd[j_f]) * vals[pos+j_f]; return r; } [[nodiscard]] constexpr auto do_bsinc(BsincState const &bsinc, std::span const vals, usize const pos, u32 const frac) noexcept -> f32 { auto const m = usize{bsinc.m}; ASSUME(m > 0); ASSUME(m <= MaxResamplerPadding); /* Calculate the phase index and factor. */ auto const pi = u32{frac>>BsincPhaseDiffBits}; ASSUME(pi < BSincPhaseCount); auto const pf = gsl::narrow_cast(frac&BsincPhaseDiffMask) * f32{1.0f/BsincPhaseDiffOne}; auto const fil = bsinc.filter.subspan(2_uz*pi*m); auto const phd = fil.subspan(m); auto const scd = fil.subspan(BSincPhaseCount*2_uz*m); auto const spd = scd.subspan(m); /* Apply the scale and phase interpolated filter. */ auto r = 0.0f; for(auto j_f=0_uz;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(std::span const src, u32 frac, u32 const increment, std::span const dst) { ASSUME(frac < MixerFracOne); auto pos = 0_uz; std::generate(dst.begin(), dst.end(), [&pos,&frac,src,increment]() -> f32 { const auto output = Sampler(src, pos, frac); frac += increment; pos += frac>>MixerFracBits; frac &= MixerFracMask; return output; }); } template Sampler> void DoResample(U const istate, std::span const src, u32 frac, u32 const increment, std::span const dst) { ASSUME(frac < MixerFracOne); auto pos = 0_uz; std::generate(dst.begin(), dst.end(), [istate,src,&pos,&frac,increment]() -> f32 { auto const output = Sampler(istate, src, pos, frac); frac += increment; pos += frac>>MixerFracBits; frac &= MixerFracMask; return output; }); } void ApplyCoeffs(std::span const Values, usize const IrSize, ConstHrirSpan const Coeffs, f32 const left, f32 const right) noexcept { ASSUME(IrSize >= MinIrLength); ASSUME(IrSize <= HrirLength); std::ranges::transform(Values | std::views::take(IrSize), Coeffs, Values.begin(), [left,right](f32x2 const &value, f32x2 const &coeff) noexcept -> f32x2 { return f32x2{{value[0] + coeff[0]*left, value[1] + coeff[1]*right}}; }); } force_inline void MixLine(std::span InSamples, std::span const dst, f32 &CurrentGain, f32 const TargetGain, f32 const delta, usize const fade_len, usize Counter) { auto const 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); auto const gain = CurrentGain; auto step_count = 0.0f; output = std::transform(input.begin(), input.end(), output, output, [gain,step,&step_count](f32 const in, f32 out) noexcept -> f32 { 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](f32 const in, f32 const out) noexcept -> f32 { return out + in*TargetGain; }); } } // namespace void Resample_Point_C(InterpState const*, std::span const src, u32 const frac, u32 const increment, std::span const dst) { DoResample(src.subspan(MaxResamplerEdge), frac, increment, dst); } void Resample_Linear_C(InterpState const*, std::span const src, u32 const frac, u32 const increment, std::span const dst) { DoResample(src.subspan(MaxResamplerEdge), frac, increment, dst); } void Resample_Cubic_C(InterpState const *const state, std::span const src, u32 const frac, u32 const increment, std::span const dst) { DoResample(std::get(*state), src.subspan(MaxResamplerEdge-1), frac, increment, dst); } void Resample_FastBSinc_C(InterpState const *const state, std::span const src, u32 const frac, u32 const increment, std::span const dst) { auto const istate = std::get(*state); ASSUME(istate.l <= MaxResamplerEdge); DoResample(istate, src.subspan(MaxResamplerEdge-istate.l), frac, increment, dst); } void Resample_BSinc_C(InterpState const *const state, std::span const src, u32 const frac, u32 const increment, std::span const dst) { auto const istate = std::get(*state); ASSUME(istate.l <= MaxResamplerEdge); DoResample(istate, src.subspan(MaxResamplerEdge-istate.l), frac, increment, dst); } void MixHrtf_C(std::span const InSamples, std::span const AccumSamples, u32 const IrSize, MixHrtfFilter const *const hrtfparams, usize const SamplesToDo) { MixHrtfBase(InSamples, AccumSamples, IrSize, hrtfparams, SamplesToDo); } void MixHrtfBlend_C(std::span const InSamples, std::span const AccumSamples, u32 const IrSize, HrtfFilter const *const oldparams, MixHrtfFilter const *const newparams, usize const SamplesToDo) { MixHrtfBlendBase(InSamples, AccumSamples, IrSize, oldparams, newparams, SamplesToDo); } void MixDirectHrtf_C(FloatBufferSpan const LeftOut, FloatBufferSpan const RightOut, std::span const InSamples, std::span const AccumSamples, std::span const TempBuf, std::span const ChanState, usize const IrSize, usize const SamplesToDo) { MixDirectHrtfBase(LeftOut, RightOut, InSamples, AccumSamples, TempBuf, ChanState, IrSize, SamplesToDo); } void Mix_C(std::span const InSamples, std::span const OutBuffer, std::span const CurrentGains, std::span const TargetGains, usize const Counter, usize const OutPos) { auto const delta = (Counter > 0) ? 1.0f / gsl::narrow_cast(Counter) : 0.0f; auto const fade_len = std::min(Counter, InSamples.size()); auto curgains = CurrentGains.begin(); auto targetgains = TargetGains.begin(); for(FloatBufferLine &output : OutBuffer) MixLine(InSamples, std::span{output}.subspan(OutPos), *curgains++, *targetgains++, delta, fade_len, Counter); } void Mix_C(std::span const InSamples, std::span const OutBuffer, f32 &CurrentGain, f32 const TargetGain, usize const Counter) { auto const delta = (Counter > 0) ? 1.0f / gsl::narrow_cast(Counter) : 0.0f; auto const fade_len = std::min(Counter, InSamples.size()); MixLine(InSamples, OutBuffer, CurrentGain, TargetGain, delta, fade_len, Counter); } kcat-openal-soft-75c0059/core/mixer/mixer_neon.cpp000066400000000000000000000451251512220627100220670ustar00rootroot00000000000000#include "config.h" #include #include #include #include #include #include #include "alnumeric.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 "gsl/gsl" #include "hrtfbase.h" #include "opthelpers.h" #if defined(__GNUC__) && !defined(__clang__) && !defined(__ARM_NEON) #pragma GCC target("fpu=neon") #endif namespace { constexpr auto BSincPhaseDiffBits = u32{MixerFracBits - BSincPhaseBits}; constexpr auto BSincPhaseDiffOne = 1_u32 << BSincPhaseDiffBits; constexpr auto BSincPhaseDiffMask = BSincPhaseDiffOne - 1_u32; constexpr auto CubicPhaseDiffBits = u32{MixerFracBits - CubicPhaseBits}; constexpr auto CubicPhaseDiffOne = 1_u32 << CubicPhaseDiffBits; constexpr auto CubicPhaseDiffMask = CubicPhaseDiffOne - 1_u32; force_inline void vtranspose4(float32x4_t &x0, float32x4_t &x1, float32x4_t &x2, float32x4_t &x3) noexcept { auto t0_ = vzipq_f32(x0, x2); auto t1_ = vzipq_f32(x1, x3); auto u0_ = vzipq_f32(t0_.val[0], t1_.val[0]); auto 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 auto set_f4(f32 const l0, f32 const l1, f32 const l2, f32 const l3) -> float32x4_t { auto 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(std::span const Values, usize const IrSize, ConstHrirSpan const Coeffs, f32 const left, f32 const right) { ASSUME(IrSize >= MinIrLength); ASSUME(IrSize <= HrirLength); auto const leftright2 = vset_lane_f32(right, vmov_n_f32(left), 1); auto const leftright4 = vcombine_f32(leftright2, leftright2); /* 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(auto c = 0_uz;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(std::span const InSamples, std::span const dst, f32 &CurrentGain, f32 const TargetGain, f32 const delta, usize const fade_len, usize const realign_len, usize Counter) { auto const step = f32{(TargetGain-CurrentGain) * delta}; auto pos = 0_uz; if(std::abs(step) > std::numeric_limits::epsilon()) { auto const gain = CurrentGain; auto step_count = 0.0f; /* Mix with applying gain steps in aligned multiples of 4. */ if(auto const todo = fade_len >> 2) { auto const four4 = vdupq_n_f32(4.0f); auto const step4 = vdupq_n_f32(step); auto const gain4 = vdupq_n_f32(gain); auto step_count4 = set_f4(0.0f, 1.0f, 2.0f, 3.0f); /* NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast) */ auto const in4 = std::span{reinterpret_cast(InSamples.data()), InSamples.size()/4}.first(todo); auto const out4 = std::span{reinterpret_cast(dst.data()), dst.size()/4}; /* NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast) */ std::ranges::transform(in4, out4, out4.begin(), [gain4,step4,four4,&step_count4](float32x4_t const 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(auto const leftover = fade_len&3) { auto const in = InSamples.subspan(pos, leftover); auto const out = dst.subspan(pos); std::ranges::transform(in, out, out.begin(), [gain,step,&step_count](f32 const val, f32 dry) noexcept -> f32 { 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(auto const leftover = realign_len&3) { auto const in = InSamples.subspan(pos, leftover); auto const out = dst.subspan(pos); std::ranges::transform(in, out, out.begin(), [TargetGain](f32 const val, f32 const dry) noexcept -> f32 { return dry + val*TargetGain; }); pos += leftover; } } CurrentGain = TargetGain; if(!(std::abs(TargetGain) > GainSilenceThreshold)) return; if(auto const todo = (InSamples.size()-pos) >> 2) { /* NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast) */ auto const in4 = std::span{reinterpret_cast(InSamples.data()), InSamples.size()/4}.last(todo); auto const out = dst.subspan(pos); auto const out4 = std::span{reinterpret_cast(out.data()), out.size()/4}; /* NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast) */ auto const gain4 = vdupq_n_f32(TargetGain); std::ranges::transform(in4, out4, out4.begin(), [gain4](float32x4_t const val4, float32x4_t const dry4) -> float32x4_t { return vmlaq_f32(dry4, val4, gain4); }); pos += in4.size()*4; } if(auto const leftover = (InSamples.size()-pos)&3) { auto const in = InSamples.last(leftover); auto const out = dst.subspan(pos); std::ranges::transform(in, out, out.begin(), [TargetGain](f32 const val, f32 const dry) noexcept -> f32 { return dry + val*TargetGain; }); } } } // namespace void Resample_Linear_NEON(InterpState const*, std::span const src, u32 frac, u32 const increment, std::span const dst) { ASSUME(frac < MixerFracOne); auto const increment4 = vdupq_n_u32(increment*4u); auto const fracMask4 = vdupq_n_u32(MixerFracMask); auto const fracOne4 = vdupq_n_f32(1.0f/MixerFracOne); alignas(16) auto pos_ = std::array{}; alignas(16) auto frac_ = std::array{}; InitPosArrays(MaxResamplerEdge, frac, increment, std::span{frac_}, std::span{pos_}); auto frac4 = vld1q_u32(frac_.data()); auto pos4 = vld1q_u32(pos_.data()); /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) */ std::ranges::generate(std::span{reinterpret_cast(dst.data()), dst.size()/4}, [src,increment4,fracMask4,fracOne4,&pos4,&frac4] { auto const pos0 = vgetq_lane_u32(pos4, 0); auto const pos1 = vgetq_lane_u32(pos4, 1); auto const pos2 = vgetq_lane_u32(pos4, 2); auto const pos3 = vgetq_lane_u32(pos4, 3); ASSUME(pos0 <= pos1); ASSUME(pos1 <= pos2); ASSUME(pos2 <= pos3); auto const val1 = set_f4(src[pos0], src[pos1], src[pos2], src[pos3]); auto const val2 = set_f4(src[pos0+1_uz], src[pos1+1_uz], src[pos2+1_uz], src[pos3+1_uz]); /* val1 + (val2-val1)*mu */ auto const r0 = vsubq_f32(val2, val1); auto const mu = vmulq_f32(vcvtq_f32_u32(frac4), fracOne4); auto const 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(auto const todo = dst.size()&3) { auto pos = usize{vgetq_lane_u32(pos4, 0)}; frac = vgetq_lane_u32(frac4, 0); std::ranges::generate(dst.last(todo), [&pos,&frac,src,increment] { auto const output = lerpf(src[pos+0], src[pos+1], gsl::narrow_cast(frac) * (1.0f/MixerFracOne)); frac += increment; pos += frac>>MixerFracBits; frac &= MixerFracMask; return output; }); } } void Resample_Cubic_NEON(InterpState const *const state, std::span const src, u32 frac, u32 const increment, std::span const dst) { ASSUME(frac < MixerFracOne); auto const filter = std::get(*state).filter; auto const increment4 = vdupq_n_u32(increment*4u); auto const fracMask4 = vdupq_n_u32(MixerFracMask); auto const fracDiffOne4 = vdupq_n_f32(1.0f/CubicPhaseDiffOne); auto const fracDiffMask4 = vdupq_n_u32(CubicPhaseDiffMask); alignas(16) auto pos_ = std::array{}; alignas(16) auto frac_ = std::array{}; InitPosArrays(MaxResamplerEdge-1, frac, increment, std::span{frac_}, std::span{pos_}); auto frac4 = vld1q_u32(frac_.data()); auto pos4 = vld1q_u32(pos_.data()); /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) */ std::ranges::generate(std::span{reinterpret_cast(dst.data()), dst.size()/4}, [src,filter,increment4,fracMask4,fracDiffOne4,fracDiffMask4,&pos4,&frac4] { auto const pos0 = vgetq_lane_u32(pos4, 0); auto const pos1 = vgetq_lane_u32(pos4, 1); auto const pos2 = vgetq_lane_u32(pos4, 2); auto const pos3 = vgetq_lane_u32(pos4, 3); ASSUME(pos0 <= pos1); ASSUME(pos1 <= pos2); ASSUME(pos2 <= pos3); auto const val0 = vld1q_f32(&src[pos0]); auto const val1 = vld1q_f32(&src[pos1]); auto const val2 = vld1q_f32(&src[pos2]); auto const val3 = vld1q_f32(&src[pos3]); auto const pi4 = vshrq_n_u32(frac4, CubicPhaseDiffBits); auto const pi0 = vgetq_lane_u32(pi4, 0); ASSUME(pi0 < CubicPhaseCount); auto const pi1 = vgetq_lane_u32(pi4, 1); ASSUME(pi1 < CubicPhaseCount); auto const pi2 = vgetq_lane_u32(pi4, 2); ASSUME(pi2 < CubicPhaseCount); auto const pi3 = vgetq_lane_u32(pi4, 3); ASSUME(pi3 < CubicPhaseCount); auto const pf4 = vmulq_f32(vcvtq_f32_u32(vandq_u32(frac4, fracDiffMask4)), fracDiffOne4); auto 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()))); auto 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()))); auto 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()))); auto 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(auto const todo = dst.size()&3) { auto pos = usize{vgetq_lane_u32(pos4, 0)}; frac = vgetq_lane_u32(frac4, 0); std::ranges::generate(dst.last(todo), [&pos,&frac,src,increment,filter] { auto const pi = frac >> CubicPhaseDiffBits; ASSUME(pi < CubicPhaseCount); auto const pf = gsl::narrow_cast(frac&CubicPhaseDiffMask) * f32{1.0f/CubicPhaseDiffOne}; auto const pf4 = vdupq_n_f32(pf); auto const f4 = vmlaq_f32(vld1q_f32(filter[pi].mCoeffs.data()), pf4, vld1q_f32(filter[pi].mDeltas.data())); auto r4 = vmulq_f32(f4, vld1q_f32(&src[pos])); r4 = vaddq_f32(r4, vrev64q_f32(r4)); auto const output = vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0); frac += increment; pos += frac>>MixerFracBits; frac &= MixerFracMask; return output; }); } } void Resample_FastBSinc_NEON(InterpState const *const state, std::span const src, u32 frac, u32 const increment, std::span const dst) { auto const &bsinc = std::get(*state); auto const m = usize{bsinc.m}; ASSUME(m > 0); ASSUME(m <= MaxResamplerPadding); ASSUME(frac < MixerFracOne); auto const filter = bsinc.filter.first(2_uz*BSincPhaseCount*m); ASSUME(bsinc.l <= MaxResamplerEdge); auto pos = usize{MaxResamplerEdge-bsinc.l}; std::ranges::generate(dst, [&pos,&frac,src,increment,m,filter]() -> f32 { // Calculate the phase index and factor. auto const pi = frac >> BSincPhaseDiffBits; ASSUME(pi < BSincPhaseCount); auto const pf = static_cast(frac&BSincPhaseDiffMask) * f32{1.0f/BSincPhaseDiffOne}; // Apply the phase interpolated filter. auto r4 = vdupq_n_f32(0.0f); { auto const pf4 = vdupq_n_f32(pf); auto const fil = filter.subspan(2_uz*pi*m); auto const phd = fil.subspan(m); auto td = m >> 2_uz; auto j = 0_uz; do { /* f = fil + pf*phd */ auto const 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)); auto const output = vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0); frac += increment; pos += frac>>MixerFracBits; frac &= MixerFracMask; return output; }); } void Resample_BSinc_NEON(InterpState const *const state, std::span const src, u32 frac, u32 const increment, std::span const dst) { auto const &bsinc = std::get(*state); auto const sf4 = vdupq_n_f32(bsinc.sf); auto const m = usize{bsinc.m}; ASSUME(m > 0); ASSUME(m <= MaxResamplerPadding); ASSUME(frac < MixerFracOne); auto const filter = bsinc.filter.first(4_uz*BSincPhaseCount*m); ASSUME(bsinc.l <= MaxResamplerEdge); auto pos = usize{MaxResamplerEdge-bsinc.l}; std::ranges::generate(dst, [&pos,&frac,src,increment,sf4,m,filter]() -> f32 { // Calculate the phase index and factor. auto const pi = frac >> BSincPhaseDiffBits; ASSUME(pi < BSincPhaseCount); auto const pf = static_cast(frac&BSincPhaseDiffMask) * f32{1.0f/BSincPhaseDiffOne}; // Apply the scale and phase interpolated filter. auto r4 = vdupq_n_f32(0.0f); { auto const pf4 = vdupq_n_f32(pf); auto const fil = filter.subspan(2_uz*pi*m); auto const phd = fil.subspan(m); auto const scd = fil.subspan(2_uz*BSincPhaseCount*m); auto const spd = scd.subspan(m); auto td = m >> 2_uz; auto j = 0_uz; do { /* f = ((fil + sf*scd) + pf*(phd + sf*spd)) */ auto const 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)); auto const output = vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0); frac += increment; pos += frac>>MixerFracBits; frac &= MixerFracMask; return output; }); } void MixHrtf_NEON(std::span const InSamples, std::span const AccumSamples, u32 const IrSize, MixHrtfFilter const *const hrtfparams, usize const SamplesToDo) { MixHrtfBase(InSamples, AccumSamples, IrSize, hrtfparams, SamplesToDo); } void MixHrtfBlend_NEON(std::span const InSamples, std::span const AccumSamples, u32 const IrSize, HrtfFilter const *const oldparams, MixHrtfFilter const *const newparams, usize const SamplesToDo) { MixHrtfBlendBase(InSamples, AccumSamples, IrSize, oldparams, newparams, SamplesToDo); } void MixDirectHrtf_NEON(FloatBufferSpan const LeftOut, FloatBufferSpan const RightOut, std::span const InSamples, std::span const AccumSamples, std::span const TempBuf, std::span const ChanState, usize const IrSize, usize const SamplesToDo) { MixDirectHrtfBase(LeftOut, RightOut, InSamples, AccumSamples, TempBuf, ChanState, IrSize, SamplesToDo); } void Mix_NEON(std::span const InSamples, std::span const OutBuffer, std::span const CurrentGains, std::span const TargetGains, usize const Counter, usize const OutPos) { if((OutPos&3) != 0) [[unlikely]] return Mix_C(InSamples, OutBuffer, CurrentGains, TargetGains, Counter, OutPos); auto const delta = (Counter > 0) ? 1.0f / static_cast(Counter) : 0.0f; auto const fade_len = std::min(Counter, InSamples.size()); auto const realign_len = std::min((fade_len+3_uz) & ~3_uz, InSamples.size()) - fade_len; auto curgains = CurrentGains.begin(); auto targetgains = TargetGains.begin(); for(FloatBufferSpan const output : OutBuffer) MixLine(InSamples, output.subspan(OutPos), *curgains++, *targetgains++, delta, fade_len, realign_len, Counter); } void Mix_NEON(std::span const InSamples, std::span const OutBuffer, f32 &CurrentGain, f32 const TargetGain, usize const Counter) { /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) */ if((reinterpret_cast(OutBuffer.data())&15) != 0) [[unlikely]] return Mix_C(InSamples, OutBuffer, CurrentGain, TargetGain, Counter); auto const delta = (Counter > 0) ? 1.0f / static_cast(Counter) : 0.0f; auto const fade_len = std::min(Counter, InSamples.size()); auto const 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); } kcat-openal-soft-75c0059/core/mixer/mixer_sse.cpp000066400000000000000000000357461512220627100217320ustar00rootroot00000000000000#include "config.h" #include #include #include #include #include #include #include #include "alnumeric.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 "gsl/gsl" #include "hrtfbase.h" #include "opthelpers.h" #if defined(__GNUC__) && !defined(__clang__) && !defined(__SSE__) #pragma GCC target("sse") #endif namespace { constexpr auto BSincPhaseDiffBits = u32{MixerFracBits - BSincPhaseBits}; constexpr auto BSincPhaseDiffOne = 1_u32 << BSincPhaseDiffBits; constexpr auto BSincPhaseDiffMask = BSincPhaseDiffOne - 1_u32; constexpr auto CubicPhaseDiffBits = u32{MixerFracBits - CubicPhaseBits}; constexpr auto CubicPhaseDiffOne = 1_u32 << CubicPhaseDiffBits; constexpr auto CubicPhaseDiffMask = CubicPhaseDiffOne - 1_u32; force_inline auto vmadd(__m128 const x, __m128 const y, __m128 const z) noexcept -> __m128 { return _mm_add_ps(x, _mm_mul_ps(y, z)); } void ApplyCoeffs(std::span const Values, usize const IrSize, ConstHrirSpan const Coeffs, f32 const left, f32 const right) { ASSUME(IrSize >= MinIrLength); ASSUME(IrSize <= HrirLength); auto const 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". */ auto const count4 = usize{(IrSize+1) >> 1}; /* NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast) * 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)) { auto const vals4 = std::span{reinterpret_cast<__m128*>(Values[0].data()), count4}; auto const coeffs4 = std::span{reinterpret_cast(Coeffs[0].data()), count4}; std::ranges::transform(vals4, coeffs4, vals4.begin(), [lrlr](__m128 const &val, __m128 const &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); auto td = count4 - 1_uz; auto i = 1_uz; do { coeffs = _mm_load_ps(Coeffs[i+1].data()); vals = _mm_load_ps(Values[i].data()); auto const 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); } /* NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast) */ } force_inline void MixLine(std::span const InSamples, std::span const dst, f32 &CurrentGain, f32 const TargetGain, f32 const delta, usize const fade_len, usize const realign_len, usize const Counter) { auto const step = f32{(TargetGain-CurrentGain) * delta}; auto pos = 0_uz; if(std::abs(step) > std::numeric_limits::epsilon()) { auto const gain = CurrentGain; auto step_count = 0.0f; /* Mix with applying gain steps in aligned multiples of 4. */ if(auto const todo = fade_len>>2) { auto const four4 = _mm_set1_ps(4.0f); auto const step4 = _mm_set1_ps(step); auto const gain4 = _mm_set1_ps(gain); auto step_count4 = _mm_setr_ps(0.0f, 1.0f, 2.0f, 3.0f); /* NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast) */ auto const in4 = std::span{reinterpret_cast(InSamples.data()), InSamples.size()/4}.first(todo); auto const out4 = std::span{reinterpret_cast<__m128*>(dst.data()), dst.size()/4}; /* NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast) */ std::ranges::transform(in4, out4, out4.begin(), [gain4,step4,four4,&step_count4](__m128 const 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(auto const leftover = fade_len&3) { auto const in = InSamples.subspan(pos, leftover); auto const out = dst.subspan(pos); std::ranges::transform(in, out, out.begin(), [gain,step,&step_count](f32 const val, f32 dry) noexcept -> f32 { 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(auto const leftover = realign_len&3) { auto const in = InSamples.subspan(pos, leftover); auto const out = dst.subspan(pos); std::ranges::transform(in, out, out.begin(), [TargetGain](f32 const val, f32 const dry) noexcept -> f32 { return dry + val*TargetGain; }); pos += leftover; } } CurrentGain = TargetGain; if(!(std::abs(TargetGain) > GainSilenceThreshold)) return; if(auto const todo = (InSamples.size()-pos) >> 2) { /* NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast) */ auto const in4 = std::span{reinterpret_cast(InSamples.data()), InSamples.size()/4}.last(todo); auto const out = dst.subspan(pos); auto const out4 = std::span{reinterpret_cast<__m128*>(out.data()), out.size()/4}; /* NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast) */ auto const gain4 = _mm_set1_ps(TargetGain); std::ranges::transform(in4, out4, out4.begin(), [gain4](__m128 const val4, __m128 const dry4) -> __m128 { return vmadd(dry4, val4, gain4); }); pos += in4.size()*4; } if(auto const leftover = (InSamples.size()-pos)&3) { auto const in = InSamples.last(leftover); auto const out = dst.subspan(pos); std::ranges::transform(in, out, out.begin(), [TargetGain](f32 const val, f32 const dry) noexcept -> f32 { return dry + val*TargetGain; }); } } } // namespace void Resample_Cubic_SSE(InterpState const *const state, std::span const src, u32 frac, u32 const increment, std::span const dst) { ASSUME(frac < MixerFracOne); auto const filter = std::get(*state).filter; auto pos = usize{MaxResamplerEdge-1}; std::ranges::generate(dst, [&pos,&frac,src,increment,filter]() -> f32 { auto const pi = usize{frac >> CubicPhaseDiffBits}; ASSUME(pi < CubicPhaseCount); auto const pf = gsl::narrow_cast(frac&CubicPhaseDiffMask)*(1.0f/CubicPhaseDiffOne); auto const pf4 = _mm_set1_ps(pf); /* Apply the phase interpolated filter. */ /* f = fil + pf*phd */ auto const f4 = vmadd(_mm_load_ps(filter[pi].mCoeffs.data()), pf4, _mm_load_ps(filter[pi].mDeltas.data())); /* r = f*src */ auto 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)); auto const output = _mm_cvtss_f32(r4); frac += increment; pos += frac>>MixerFracBits; frac &= MixerFracMask; return output; }); } void Resample_FastBSinc_SSE(InterpState const *const state, std::span const src, u32 frac, u32 const increment, std::span const dst) { auto const &bsinc = std::get(*state); auto const m = usize{bsinc.m}; ASSUME(m > 0); ASSUME(m <= MaxResamplerPadding); ASSUME(frac < MixerFracOne); auto const filter = bsinc.filter.first(2_uz*m*BSincPhaseCount); ASSUME(bsinc.l <= MaxResamplerEdge); auto pos = usize{MaxResamplerEdge-bsinc.l}; std::ranges::generate(dst, [&pos,&frac,src,increment,filter,m]() -> f32 { // Calculate the phase index and factor. auto const pi = usize{frac >> BSincPhaseDiffBits}; ASSUME(pi < BSincPhaseCount); auto const pf = gsl::narrow_cast(frac&BSincPhaseDiffMask)*(1.0f/BSincPhaseDiffOne); // Apply the phase interpolated filter. auto r4 = _mm_setzero_ps(); { auto const pf4 = _mm_set1_ps(pf); auto const fil = filter.subspan(2_uz*m*pi); auto const phd = fil.subspan(m); auto td = m >> 2; auto j = 0_uz; do { /* f = fil + pf*phd */ auto const 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)); auto const output = _mm_cvtss_f32(r4); frac += increment; pos += frac>>MixerFracBits; frac &= MixerFracMask; return output; }); } void Resample_BSinc_SSE(InterpState const *const state, std::span const src, u32 frac, u32 const increment, std::span const dst) { auto const &bsinc = std::get(*state); auto const sf4 = _mm_set1_ps(bsinc.sf); auto const m = usize{bsinc.m}; ASSUME(m > 0); ASSUME(m <= MaxResamplerPadding); ASSUME(frac < MixerFracOne); auto const filter = bsinc.filter.first(4_uz*BSincPhaseCount*m); ASSUME(bsinc.l <= MaxResamplerEdge); auto pos = usize{MaxResamplerEdge-bsinc.l}; std::ranges::generate(dst, [&pos,&frac,src,increment,sf4,m,filter]() -> f32 { // Calculate the phase index and factor. auto const pi = usize{frac >> BSincPhaseDiffBits}; ASSUME(pi < BSincPhaseCount); auto const pf = gsl::narrow_cast(frac&BSincPhaseDiffMask)*(1.0f/BSincPhaseDiffOne); // Apply the scale and phase interpolated filter. auto r4 = _mm_setzero_ps(); { auto const pf4 = _mm_set1_ps(pf); auto const fil = filter.subspan(2_uz*pi*m); auto const phd = fil.subspan(m); auto const scd = fil.subspan(2_uz*BSincPhaseCount*m); auto const spd = scd.subspan(m); auto td = m >> 2; auto j = 0_uz; do { /* f = ((fil + sf*scd) + pf*(phd + sf*spd)) */ auto const 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)); auto const output = _mm_cvtss_f32(r4); frac += increment; pos += frac>>MixerFracBits; frac &= MixerFracMask; return output; }); } void MixHrtf_SSE(std::span const InSamples, std::span const AccumSamples, u32 const IrSize, MixHrtfFilter const *const hrtfparams, usize const SamplesToDo) { MixHrtfBase(InSamples, AccumSamples, IrSize, hrtfparams, SamplesToDo); } void MixHrtfBlend_SSE(std::span const InSamples, std::span const AccumSamples, u32 const IrSize, HrtfFilter const *const oldparams, MixHrtfFilter const *const newparams, usize const SamplesToDo) { MixHrtfBlendBase(InSamples, AccumSamples, IrSize, oldparams, newparams, SamplesToDo); } void MixDirectHrtf_SSE(FloatBufferSpan const LeftOut, FloatBufferSpan const RightOut, std::span const InSamples, std::span const AccumSamples, std::span const TempBuf, std::span const ChanState, usize const IrSize, usize const SamplesToDo) { MixDirectHrtfBase(LeftOut, RightOut, InSamples, AccumSamples, TempBuf, ChanState, IrSize, SamplesToDo); } void Mix_SSE(std::span const InSamples, std::span const OutBuffer, std::span const CurrentGains, std::span const TargetGains, usize const Counter, usize const OutPos) { if((OutPos&3) != 0) [[unlikely]] return Mix_C(InSamples, OutBuffer, CurrentGains, TargetGains, Counter, OutPos); auto const delta = (Counter > 0) ? 1.0f / gsl::narrow_cast(Counter) : 0.0f; auto const fade_len = std::min(Counter, InSamples.size()); auto const realign_len = std::min((fade_len+3_uz) & ~3_uz, InSamples.size()) - fade_len; auto curgains = CurrentGains.begin(); auto targetgains = TargetGains.begin(); for(FloatBufferSpan const output : OutBuffer) MixLine(InSamples, output.subspan(OutPos), *curgains++, *targetgains++, delta, fade_len, realign_len, Counter); } void Mix_SSE(std::span const InSamples, std::span const OutBuffer, f32 &CurrentGain, f32 const TargetGain, usize const Counter) { /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) */ if((reinterpret_cast(OutBuffer.data())&15) != 0) [[unlikely]] return Mix_C(InSamples, OutBuffer, CurrentGain, TargetGain, Counter); auto const delta = (Counter > 0) ? 1.0f / gsl::narrow_cast(Counter) : 0.0f; auto const fade_len = std::min(Counter, InSamples.size()); auto const 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); } kcat-openal-soft-75c0059/core/mixer/mixer_sse2.cpp000066400000000000000000000212121512220627100217730ustar00rootroot00000000000000/** * 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 "core/cubic_defs.h" #include "core/resampler_limits.h" #include "defs.h" #include "gsl/gsl" #include "opthelpers.h" #if defined(__GNUC__) && !defined(__clang__) && !defined(__SSE2__) #pragma GCC target("sse2") #endif namespace { constexpr auto CubicPhaseDiffBits = u32{MixerFracBits - CubicPhaseBits}; constexpr auto CubicPhaseDiffOne = 1_u32 << CubicPhaseDiffBits; constexpr auto CubicPhaseDiffMask = CubicPhaseDiffOne - 1_u32; force_inline auto vmadd(__m128 const x, __m128 const y, __m128 const z) noexcept -> __m128 { return _mm_add_ps(x, _mm_mul_ps(y, z)); } } // namespace void Resample_Linear_SSE2(InterpState const*, std::span const src, u32 frac, u32 const increment, std::span const dst) { ASSUME(frac < MixerFracOne); auto const increment4 = _mm_set1_epi32(as_signed(increment*4)); auto const fracMask4 = _mm_set1_epi32(MixerFracMask); auto const fracOne4 = _mm_set1_ps(1.0f/MixerFracOne); auto pos_ = std::array{}; auto frac_ = std::array{}; InitPosArrays(MaxResamplerEdge, frac, increment, std::span{frac_}, std::span{pos_}); auto pos4 = _mm_setr_epi32(as_signed(pos_[0]), as_signed(pos_[1]), as_signed(pos_[2]), as_signed(pos_[3])); auto frac4 = _mm_setr_epi32(as_signed(frac_[0]), as_signed(frac_[1]), as_signed(frac_[2]), as_signed(frac_[3])); /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) */ std::ranges::generate(std::span{reinterpret_cast<__m128*>(dst.data()), dst.size()/4}, [src,increment4,fracMask4,fracOne4,&pos4,&frac4] { auto const pos0 = as_unsigned(_mm_cvtsi128_si32(pos4)); auto const pos1 = as_unsigned(_mm_cvtsi128_si32(_mm_srli_si128(pos4, 4))); auto const pos2 = as_unsigned(_mm_cvtsi128_si32(_mm_srli_si128(pos4, 8))); auto const pos3 = as_unsigned(_mm_cvtsi128_si32(_mm_srli_si128(pos4, 12))); ASSUME(pos0 <= pos1); ASSUME(pos1 <= pos2); ASSUME(pos2 <= pos3); auto const val1 = _mm_setr_ps(src[pos0], src[pos1], src[pos2], src[pos3]); auto const val2 = _mm_setr_ps(src[pos0+1_uz], src[pos1+1_uz], src[pos2+1_uz], src[pos3+1_uz]); /* val1 + (val2-val1)*mu */ auto const r0 = _mm_sub_ps(val2, val1); auto const mu = _mm_mul_ps(_mm_cvtepi32_ps(frac4), fracOne4); auto const 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(auto const todo = dst.size()&3) { auto pos = usize{as_unsigned(_mm_cvtsi128_si32(pos4))}; frac = as_unsigned(_mm_cvtsi128_si32(frac4)); std::ranges::generate(dst.last(todo), [src,increment,&pos,&frac] { auto const smp = lerpf(src[pos+0], src[pos+1], gsl::narrow_cast(frac) * (1.0f/MixerFracOne)); frac += increment; pos += frac>>MixerFracBits; frac &= MixerFracMask; return smp; }); } } void Resample_Cubic_SSE2(InterpState const *const state, std::span const src, u32 frac, u32 const increment, std::span const dst) { ASSUME(frac < MixerFracOne); auto const filter = std::get(*state).filter; auto const increment4 = _mm_set1_epi32(as_signed(increment*4)); auto const fracMask4 = _mm_set1_epi32(MixerFracMask); auto const fracDiffOne4 = _mm_set1_ps(1.0f/CubicPhaseDiffOne); auto const fracDiffMask4 = _mm_set1_epi32(CubicPhaseDiffMask); auto pos_ = std::array{}; auto frac_ = std::array{}; InitPosArrays(MaxResamplerEdge-1, frac, increment, std::span{frac_}, std::span{pos_}); auto pos4 = _mm_setr_epi32(as_signed(pos_[0]), as_signed(pos_[1]), as_signed(pos_[2]), as_signed(pos_[3])); auto frac4 = _mm_setr_epi32(as_signed(frac_[0]), as_signed(frac_[1]), as_signed(frac_[2]), as_signed(frac_[3])); /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) */ std::ranges::generate(std::span{reinterpret_cast<__m128*>(dst.data()), dst.size()/4}, [src,filter,increment4,fracMask4,fracDiffOne4,fracDiffMask4,&pos4,&frac4] { auto const pos0 = as_unsigned(_mm_cvtsi128_si32(pos4)); auto const pos1 = as_unsigned(_mm_cvtsi128_si32(_mm_srli_si128(pos4, 4))); auto const pos2 = as_unsigned(_mm_cvtsi128_si32(_mm_srli_si128(pos4, 8))); auto const pos3 = as_unsigned(_mm_cvtsi128_si32(_mm_srli_si128(pos4, 12))); ASSUME(pos0 <= pos1); ASSUME(pos1 <= pos2); ASSUME(pos2 <= pos3); auto const val0 = _mm_loadu_ps(&src[pos0]); auto const val1 = _mm_loadu_ps(&src[pos1]); auto const val2 = _mm_loadu_ps(&src[pos2]); auto const val3 = _mm_loadu_ps(&src[pos3]); auto const pi4 = _mm_srli_epi32(frac4, CubicPhaseDiffBits); auto const pi0 = as_unsigned(_mm_cvtsi128_si32(pi4)); auto const pi1 = as_unsigned(_mm_cvtsi128_si32(_mm_srli_si128(pi4, 4))); auto const pi2 = as_unsigned(_mm_cvtsi128_si32(_mm_srli_si128(pi4, 8))); auto const pi3 = as_unsigned(_mm_cvtsi128_si32(_mm_srli_si128(pi4, 12))); ASSUME(pi0 < CubicPhaseCount); ASSUME(pi1 < CubicPhaseCount); ASSUME(pi2 < CubicPhaseCount); ASSUME(pi3 < CubicPhaseCount); auto const pf4 = _mm_mul_ps(_mm_cvtepi32_ps(_mm_and_si128(frac4, fracDiffMask4)), fracDiffOne4); auto 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()))); auto 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()))); auto 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()))); auto 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(auto const todo = dst.size()&3) { auto pos = usize{as_unsigned(_mm_cvtsi128_si32(pos4))}; frac = as_unsigned(_mm_cvtsi128_si32(frac4)); std::ranges::generate(dst.last(todo), [src,filter,increment,&pos,&frac] { const auto pi = usize{frac >> CubicPhaseDiffBits}; ASSUME(pi < CubicPhaseCount); const auto pf = gsl::narrow_cast(frac&CubicPhaseDiffMask) * (1.0f/CubicPhaseDiffOne); const auto pf4 = _mm_set1_ps(pf); const auto f4 = vmadd(_mm_load_ps(filter[pi].mCoeffs.data()), pf4, _mm_load_ps(filter[pi].mDeltas.data())); auto 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 auto output = _mm_cvtss_f32(r4); frac += increment; pos += frac>>MixerFracBits; frac &= MixerFracMask; return output; }); } } kcat-openal-soft-75c0059/core/mixer/mixer_sse3.cpp000066400000000000000000000000001512220627100217640ustar00rootroot00000000000000kcat-openal-soft-75c0059/core/mixer/mixer_sse41.cpp000066400000000000000000000213531512220627100220640ustar00rootroot00000000000000/** * 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 "core/cubic_defs.h" #include "core/resampler_limits.h" #include "defs.h" #include "opthelpers.h" #if defined(__GNUC__) && !defined(__clang__) && !defined(__SSE4_1__) #pragma GCC target("sse4.1") #endif namespace { constexpr auto CubicPhaseDiffBits = u32{MixerFracBits - CubicPhaseBits}; constexpr auto CubicPhaseDiffOne = 1_u32 << CubicPhaseDiffBits; constexpr auto CubicPhaseDiffMask = CubicPhaseDiffOne - 1_u32; force_inline auto vmadd(__m128 const x, __m128 const y, __m128 const z) noexcept -> __m128 { return _mm_add_ps(x, _mm_mul_ps(y, z)); } } // namespace void Resample_Linear_SSE4(InterpState const*, std::span const src, u32 frac, u32 const increment, std::span const dst) { ASSUME(frac < MixerFracOne); auto const increment4 = _mm_set1_epi32(as_signed(increment*4)); auto const fracMask4 = _mm_set1_epi32(MixerFracMask); auto const fracOne4 = _mm_set1_ps(1.0f/MixerFracOne); auto pos_ = std::array{}; auto frac_ = std::array{}; InitPosArrays(MaxResamplerEdge, frac, increment, std::span{frac_}, std::span{pos_}); auto pos4 = _mm_setr_epi32(as_signed(pos_[0]), as_signed(pos_[1]), as_signed(pos_[2]), as_signed(pos_[3])); auto frac4 = _mm_setr_epi32(as_signed(frac_[0]), as_signed(frac_[1]), as_signed(frac_[2]), as_signed(frac_[3])); /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) */ std::ranges::generate(std::span{reinterpret_cast<__m128*>(dst.data()), dst.size()/4}, [src,increment4,fracMask4,fracOne4,&pos4,&frac4] { auto const pos0 = as_unsigned(_mm_cvtsi128_si32(pos4)); auto const pos1 = as_unsigned(_mm_cvtsi128_si32(_mm_srli_si128(pos4, 4))); auto const pos2 = as_unsigned(_mm_cvtsi128_si32(_mm_srli_si128(pos4, 8))); auto const pos3 = as_unsigned(_mm_cvtsi128_si32(_mm_srli_si128(pos4, 12))); ASSUME(pos0 <= pos1); ASSUME(pos1 <= pos2); ASSUME(pos2 <= pos3); auto const val1 = _mm_setr_ps(src[pos0], src[pos1], src[pos2], src[pos3]); auto const val2 = _mm_setr_ps(src[pos0+1_uz], src[pos1+1_uz], src[pos2+1_uz], src[pos3+1_uz]); /* val1 + (val2-val1)*mu */ auto const r0 = _mm_sub_ps(val2, val1); auto const mu = _mm_mul_ps(_mm_cvtepi32_ps(frac4), fracOne4); auto const 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(auto const 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 = usize{as_unsigned(_mm_cvtsi128_si32(pos4))}; frac = as_unsigned(_mm_cvtsi128_si32(frac4)); std::ranges::generate(dst.last(todo), [&pos,&frac,src,increment] { auto const smp = lerpf(src[pos+0], src[pos+1], gsl::narrow_cast(frac) * (1.0f/MixerFracOne)); frac += increment; pos += frac>>MixerFracBits; frac &= MixerFracMask; return smp; }); } } void Resample_Cubic_SSE4(InterpState const *const state, std::span const src, u32 frac, u32 const increment, std::span const dst) { ASSUME(frac < MixerFracOne); auto const filter = std::get(*state).filter; auto const increment4 = _mm_set1_epi32(as_signed(increment*4)); auto const fracMask4 = _mm_set1_epi32(MixerFracMask); auto const fracDiffOne4 = _mm_set1_ps(1.0f/CubicPhaseDiffOne); auto const fracDiffMask4 = _mm_set1_epi32(CubicPhaseDiffMask); auto pos_ = std::array{}; auto frac_ = std::array{}; InitPosArrays(MaxResamplerEdge-1, frac, increment, std::span{frac_}, std::span{pos_}); auto pos4 = _mm_setr_epi32(as_signed(pos_[0]), as_signed(pos_[1]), as_signed(pos_[2]), as_signed(pos_[3])); auto frac4 = _mm_setr_epi32(as_signed(frac_[0]), as_signed(frac_[1]), as_signed(frac_[2]), as_signed(frac_[3])); /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) */ std::ranges::generate(std::span{reinterpret_cast<__m128*>(dst.data()), dst.size()/4}, [src,filter,increment4,fracMask4,fracDiffOne4,fracDiffMask4,&pos4,&frac4] { auto const pos0 = as_unsigned(_mm_extract_epi32(pos4, 0)); auto const pos1 = as_unsigned(_mm_extract_epi32(pos4, 1)); auto const pos2 = as_unsigned(_mm_extract_epi32(pos4, 2)); auto const pos3 = as_unsigned(_mm_extract_epi32(pos4, 3)); ASSUME(pos0 <= pos1); ASSUME(pos1 <= pos2); ASSUME(pos2 <= pos3); auto const val0 = _mm_loadu_ps(&src[pos0]); auto const val1 = _mm_loadu_ps(&src[pos1]); auto const val2 = _mm_loadu_ps(&src[pos2]); auto const val3 = _mm_loadu_ps(&src[pos3]); auto const pi4 = _mm_srli_epi32(frac4, CubicPhaseDiffBits); auto const pi0 = as_unsigned(_mm_extract_epi32(pi4, 0)); auto const pi1 = as_unsigned(_mm_extract_epi32(pi4, 1)); auto const pi2 = as_unsigned(_mm_extract_epi32(pi4, 2)); auto const pi3 = as_unsigned(_mm_extract_epi32(pi4, 3)); ASSUME(pi0 < CubicPhaseCount); ASSUME(pi1 < CubicPhaseCount); ASSUME(pi2 < CubicPhaseCount); ASSUME(pi3 < CubicPhaseCount); auto const pf4 = _mm_mul_ps(_mm_cvtepi32_ps(_mm_and_si128(frac4, fracDiffMask4)), fracDiffOne4); auto 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()))); auto 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()))); auto 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()))); auto 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(auto const todo = dst.size()&3) { auto pos = usize{as_unsigned(_mm_cvtsi128_si32(pos4))}; frac = as_unsigned(_mm_cvtsi128_si32(frac4)); std::ranges::generate(dst.last(todo), [&pos,&frac,src,increment,filter] { auto const pi = usize{frac >> CubicPhaseDiffBits}; ASSUME(pi < CubicPhaseCount); auto const pf = gsl::narrow_cast(frac&CubicPhaseDiffMask) * (1.0f/CubicPhaseDiffOne); auto const pf4 = _mm_set1_ps(pf); auto const f4 = vmadd(_mm_load_ps(filter[pi].mCoeffs.data()), pf4, _mm_load_ps(filter[pi].mDeltas.data())); auto 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)); auto const output = _mm_cvtss_f32(r4); frac += increment; pos += frac>>MixerFracBits; frac &= MixerFracMask; return output; }); } } kcat-openal-soft-75c0059/core/resampler_limits.h000066400000000000000000000006011512220627100216060ustar00rootroot00000000000000#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 */ kcat-openal-soft-75c0059/core/rtkit.cpp000066400000000000000000000275071512220627100177410ustar00rootroot00000000000000/*-*- 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. ***/ #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include "config.h" #include "rtkit.h" #include #include #include #include #include #include #include #ifdef __linux__ #include #elif defined(__FreeBSD__) #include #endif #include "dynload.h" #include "gsl/gsl" #include "logging.h" #if HAVE_DYNLOAD #include namespace { #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) void *dbus_handle{}; #define DECL_FUNC(x) 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 #define DBUS_LIB "libdbus-1.so.3" OAL_ELF_NOTE_DLOPEN( "core-dbus", "RTKit/D-Bus support", OAL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, DBUS_LIB ); auto HasDBus() -> bool { static constinit auto init_dbus = std::once_flag{}; std::call_once(init_dbus, [] { auto *const dbus_lib = gsl::czstring{DBUS_LIB}; if(auto const libresult = LoadLib(dbus_lib)) dbus_handle = libresult.value(); else { WARN("Failed to load {}: {}", dbus_lib, libresult.error()); return; } static constexpr auto load_func = [](T &func, gsl::czstring const name) -> bool { auto const funcresult = GetSymbol(dbus_handle, name); if(!funcresult) { WARN("Failed to load function {}: {}", name, funcresult.error()); return false; } /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) */ func = reinterpret_cast(funcresult.value()); return true; }; auto ok = true; #define LOAD_FUNC(f) ok &= load_func(p##f, #f); DBUS_FUNCTIONS(LOAD_FUNC) #undef LOAD_FUNC if(!ok) { CloseLib(dbus_handle); dbus_handle = nullptr; } }); return dbus_handle != nullptr; } } /* namespace */ #else namespace { constexpr auto HasDBus() noexcept -> bool { return true; } } /* namespace */ #endif namespace { class dbusError { DBusError mError{}; public: dbusError() { dbus_error_init(&mError); } dbusError(dbusError const&) = delete; dbusError(dbusError&&) = delete; ~dbusError() { dbus_error_free(&mError); } void operator=(dbusError const&) = delete; void operator=(dbusError&&) = delete; auto operator->() noexcept -> DBusError* { return &mError; } auto get() noexcept -> DBusError& { return mError; } }; constexpr auto dbusTypeString = int{'s'}; constexpr auto dbusTypeVariant = int{'v'}; constexpr auto dbusTypeInt32 = int{'i'}; constexpr auto dbusTypeUInt32 = int{'u'}; constexpr auto dbusTypeInt64 = int{'x'}; constexpr auto dbusTypeUInt64 = int{'t'}; constexpr auto dbusTypeInvalid = int{'\0'}; using dbusMessagePtr = std::unique_ptr; auto _gettid() -> pid_t { #ifdef __linux__ /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) */ return gsl::narrow_cast(syscall(SYS_gettid)); #elif defined(__FreeBSD__) auto pid = long{}; thr_self(&pid); return gsl::narrow_cast(pid); #else #warning gettid not available return 0; #endif } auto translate_error(std::string_view const name) -> int { if(name == DBUS_ERROR_NO_MEMORY) return -ENOMEM; if(name == DBUS_ERROR_SERVICE_UNKNOWN || name == DBUS_ERROR_NAME_HAS_NO_OWNER) return -ENOENT; if(name == DBUS_ERROR_ACCESS_DENIED || name == DBUS_ERROR_AUTH_FAILED) return -EACCES; return -EIO; } auto rtkit_get_int_property(DBusConnection *const connection, gsl::czstring const propname, long long *const propval) -> int { auto const m = dbusMessagePtr{dbus_message_new_method_call(RTKIT_SERVICE_NAME, RTKIT_OBJECT_PATH, "org.freedesktop.DBus.Properties", "Get")}; if(!m) return -ENOMEM; auto *const interfacestr = gsl::czstring{RTKIT_SERVICE_NAME}; auto const ready = dbus_message_append_args(std::to_address(m), dbusTypeString, &interfacestr, dbusTypeString, &propname, dbusTypeInvalid); if(!ready) return -ENOMEM; auto error = dbusError{}; auto const r = dbusMessagePtr{dbus_connection_send_with_reply_and_block(connection, std::to_address(m), -1, &error.get())}; if(!r) return translate_error(error->name); if(dbus_set_error_from_message(&error.get(), std::to_address(r))) return translate_error(error->name); auto ret = -EBADMSG; auto iter = DBusMessageIter{}; dbus_message_iter_init(std::to_address(r), &iter); while(auto curtype = dbus_message_iter_get_arg_type(&iter)) { if(curtype == dbusTypeVariant) { auto subiter = DBusMessageIter{}; dbus_message_iter_recurse(&iter, &subiter); curtype = dbus_message_iter_get_arg_type(&subiter); while(curtype != dbusTypeInvalid) { if(curtype == dbusTypeInt32) { auto val32 = dbus_int32_t{}; dbus_message_iter_get_basic(&subiter, &val32); *propval = val32; ret = 0; } if(curtype == dbusTypeInt64) { auto val64 = dbus_int64_t{}; dbus_message_iter_get_basic(&subiter, &val64); *propval = val64; ret = 0; } dbus_message_iter_next(&subiter); curtype = dbus_message_iter_get_arg_type(&subiter); } } dbus_message_iter_next(&iter); } return ret; } } // namespace void dbusConnectionDeleter::operator()(DBusConnection *const conn) const { dbus_connection_unref(conn); } auto rtkit_get_dbus_connection() -> dbusConnectionPtr { if(!HasDBus()) { WARN("D-Bus not available"); return {}; } auto error = dbusError{}; auto conn = dbusConnectionPtr{dbus_bus_get(DBUS_BUS_SYSTEM, &error.get())}; if(!conn) { WARN("D-Bus connection failed with {}: {}", error->name, error->message); return {}; } /* Don't stupidly exit if the connection dies while doing this. */ dbus_connection_set_exit_on_disconnect(std::to_address(conn), false); return conn; } auto rtkit_get_max_realtime_priority(DBusConnection *const system_bus) -> int { long long retval{}; const auto err = rtkit_get_int_property(system_bus, "MaxRealtimePriority", &retval); return err < 0 ? err : gsl::narrow_cast(retval); } auto rtkit_get_min_nice_level(DBusConnection *const system_bus, int *const min_nice_level) -> int { long long retval{}; auto const err = rtkit_get_int_property(system_bus, "MinNiceLevel", &retval); if(err >= 0) *min_nice_level = gsl::narrow_cast(retval); return err; } auto rtkit_get_rttime_usec_max(DBusConnection *const system_bus) -> long long { long long retval{}; auto const err = rtkit_get_int_property(system_bus, "RTTimeUSecMax", &retval); return err < 0 ? err : retval; } auto rtkit_make_realtime(DBusConnection *const system_bus, pid_t thread, int const priority) -> int { if(thread == 0) thread = _gettid(); if(thread == 0) return -ENOTSUP; auto const m = dbusMessagePtr{dbus_message_new_method_call(RTKIT_SERVICE_NAME, RTKIT_OBJECT_PATH, "org.freedesktop.RealtimeKit1", "MakeThreadRealtime")}; if(!m) return -ENOMEM; auto tid64 = gsl::narrow_cast(thread); auto prio32 = gsl::narrow_cast(priority); auto const ready = dbus_message_append_args(std::to_address(m), dbusTypeUInt64, &tid64, dbusTypeUInt32, &prio32, dbusTypeInvalid); if(!ready) return -ENOMEM; auto error = dbusError{}; auto const r = dbusMessagePtr{dbus_connection_send_with_reply_and_block(system_bus, std::to_address(m), -1, &error.get())}; if(!r) return translate_error(error->name); if(dbus_set_error_from_message(&error.get(), std::to_address(r))) return translate_error(error->name); return 0; } auto rtkit_make_high_priority(DBusConnection *const system_bus, pid_t thread, int const nice_level) -> int { if(thread == 0) thread = _gettid(); if(thread == 0) return -ENOTSUP; auto const m = dbusMessagePtr{dbus_message_new_method_call(RTKIT_SERVICE_NAME, RTKIT_OBJECT_PATH, "org.freedesktop.RealtimeKit1", "MakeThreadHighPriority")}; if(!m) return -ENOMEM; auto tid64 = gsl::narrow_cast(thread); auto level32 = gsl::narrow_cast(nice_level); auto const ready = dbus_message_append_args(std::to_address(m), dbusTypeUInt64, &tid64, dbusTypeInt32, &level32, dbusTypeInvalid); if(!ready) return -ENOMEM; auto error = dbusError{}; auto const r = dbusMessagePtr{dbus_connection_send_with_reply_and_block(system_bus, std::to_address(m), -1, &error.get())}; if(!r) return translate_error(error->name); if(dbus_set_error_from_message(&error.get(), std::to_address(r))) return translate_error(error->name); return 0; } kcat-openal-soft-75c0059/core/rtkit.h000066400000000000000000000065741512220627100174070ustar00rootroot00000000000000/*-*- 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 struct DBusConnection; struct dbusConnectionDeleter { void operator()(DBusConnection *conn) const; }; using dbusConnectionPtr = std::unique_ptr; auto rtkit_get_dbus_connection() -> dbusConnectionPtr; /* 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. */ auto rtkit_make_realtime(DBusConnection *system_bus, pid_t thread, int priority) -> int; /* 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.*/ auto rtkit_make_high_priority(DBusConnection *system_bus, pid_t thread, int nice_level) -> int; /* Return the maximum value of realtime priority available. Realtime requests * above this value will fail. A negative value is an errno style error code. */ auto rtkit_get_max_realtime_priority(DBusConnection *system_bus) -> int; /* 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.*/ auto rtkit_get_min_nice_level(DBusConnection *system_bus, int *min_nice_level) -> int; /* Return the maximum value of RLIMIT_RTTIME to set before attempting a * realtime request. A negative value is an errno style error code. */ auto rtkit_get_rttime_usec_max(DBusConnection *system_bus) -> long long; #endif kcat-openal-soft-75c0059/core/storage_formats.cpp000066400000000000000000000044011512220627100217670ustar00rootroot00000000000000 #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; } 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; } return 0; } kcat-openal-soft-75c0059/core/storage_formats.h000066400000000000000000000024361512220627100214420ustar00rootroot00000000000000#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. */ }; 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 */ kcat-openal-soft-75c0059/core/uhjfilter.cpp000066400000000000000000000714001512220627100205670ustar00rootroot00000000000000 #include "config.h" #include "uhjfilter.h" #include #include #include #include #include #include #include #include #include #include #include "alcomplex.h" #include "alnumeric.h" #include "gsl/gsl" #include "pffft.h" #include "phase_shifter.h" #include "vector.h" namespace { template constexpr auto assume_aligned_span(const std::span s) noexcept -> std::span { return std::span{std::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{FilterSize/sSampleLength}; static_assert(FilterSize >= sFftLength); static_assert((FilterSize % sSampleLength) == 0); PFFFTSetup mFft; alignas(16) std::array mFilterData; SegmentedFilter() noexcept : mFft{sFftLength, PFFFT_REAL} { /* To set up the filter, we first need to generate the desired * response (not reversed). */ auto tmpBuffer = std::vector(FilterSize, 0.0); for(const auto i : std::views::iota(0_uz, FilterSize/2)) { const auto k = int{FilterSize/2} - gsl::narrow_cast(i*2 + 1); const auto w = 2.0*std::numbers::pi/double{FilterSize} * gsl::narrow_cast(i*2 + 1); const auto window = 0.3635819 - 0.4891775*std::cos(w) + 0.1365995*std::cos(2.0*w) - 0.0106411*std::cos(3.0*w); const auto pk = std::numbers::pi * gsl::narrow_cast(k); tmpBuffer[i*2 + 1] = window * (1.0-std::cos(pk)) / pk; } /* The response is split into segments that are converted 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(const auto s : std::views::iota(0_uz, sNumSegments)) { const auto tmpspan = std::span{tmpBuffer}.subspan(sSampleLength*s, sSampleLength); auto iter = std::ranges::copy(tmpspan, fftBuffer.begin()).out; std::ranges::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(const auto i : std::views::iota(0_uz, sSampleLength)) { fftTmp[i*2 + 0] = gsl::narrow_cast(fftBuffer[i].real()) / float{sFftLength}; fftTmp[i*2 + 1] = gsl::narrow_cast((i==0) ? fftBuffer[sSampleLength].real() : fftBuffer[i].imag()) / float{sFftLength}; } mFft.zreorder(fftTmp.begin(), filter, PFFFT_BACKWARD); std::advance(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 auto Filter1Coeff = std::array{ 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 auto Filter2Coeff = std::array{ 0.161758498368f, 0.733028932341f, 0.945349700329f, 0.990599156684f }; void processOne(UhjAllPassFilter &self, const std::span coeffs, float x) { auto state = self.mState; static_assert(state.size() == coeffs.size()); for(const auto i : std::views::iota(0_uz, coeffs.size())) { const auto 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 std::span coeffs, const std::span src, const bool updateState, const std::span dst) { auto state = self.mState; static_assert(state.size() == coeffs.size()); std::ranges::transform(src | std::views::take(dst.size()), dst.begin(), [&state,coeffs](float x) noexcept -> float { for(const auto i : std::views::iota(0_uz, coeffs.size())) { const auto 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; }); 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 implemented from a * segmented FFT'd response for the desired shift. */ template void UhjEncoder::encode(const std::span LeftOut, const std::span RightOut, const std::span,3> InSamples) { static_assert(sFftLength == gSegmentedFilter.sFftLength); static_assert(sSegmentSize == gSegmentedFilter.sSampleLength); static_assert(sNumSegments == gSegmentedFilter.sNumSegments); const auto samplesToDo = InSamples[0].size(); const auto winput = assume_aligned_span<16>(InSamples[0]); const auto xinput = assume_aligned_span<16>(InSamples[1].first(samplesToDo)); const auto yinput = assume_aligned_span<16>(InSamples[2].first(samplesToDo)); std::ranges::copy(winput, std::next(mW.begin(), sFilterDelay)); std::ranges::copy(xinput, std::next(mX.begin(), sFilterDelay)); std::ranges::copy(yinput, std::next(mY.begin(), sFilterDelay)); /* S = 0.9396926*W + 0.1855740*X */ std::ranges::transform(mW | std::views::take(samplesToDo), mX, 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(); auto curseg = mCurrentSegment; for(auto base = 0_uz;base < samplesToDo;) { const auto todo = std::min(sSegmentSize-mFifoPos, samplesToDo-base); const auto wseg = winput.subspan(base, todo); const auto xseg = xinput.subspan(base, todo); const auto wxio = std::span{mWXInOut}.subspan(mFifoPos, todo); /* Copy out the samples that were previously processed by the FFT. */ dstore = std::ranges::copy(wxio, dstore).out; /* Transform the non-delayed input and store in the front half of the * filter input. */ std::ranges::transform(wseg, xseg, wxio.begin(), [](const float w, const float x) noexcept { 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; auto initer = std::ranges::copy(mWXInOut | std::views::take(sSegmentSize), input).out; std::ranges::fill(std::views::counted(initer, sSegmentSize), 0.0f); gSegmentedFilter.mFft.transform(input, input, mWorkData.begin(), PFFFT_FORWARD); /* Convolve each input segment with its IR filter counterpart (aligned * in time, from newest to oldest). */ mFftBuffer.fill(0.0f); auto filter = gSegmentedFilter.mFilterData.begin(); for(const auto s [[maybe_unused]] : std::views::iota(curseg, sNumSegments)) { gSegmentedFilter.mFft.zconvolve_accumulate(input, filter, mFftBuffer.begin()); std::advance(input, sFftLength); std::advance(filter, sFftLength); } input = mWXHistory.begin(); for(const auto s [[maybe_unused]] : std::views::iota(0_uz, curseg)) { gSegmentedFilter.mFft.zconvolve_accumulate(input, filter, mFftBuffer.begin()); std::advance(input, sFftLength); std::advance(filter, sFftLength); } /* Convert back to samples, writing to the output and storing the extra * for next time. */ gSegmentedFilter.mFft.transform(mFftBuffer.begin(), mFftBuffer.begin(), mWorkData.begin(), PFFFT_BACKWARD); const auto wxiter = std::ranges::transform(mFftBuffer | std::views::take(sSegmentSize), mWXInOut | std::views::drop(sSegmentSize), mWXInOut.begin(), std::plus{}).out; std::ranges::copy(mFftBuffer | std::views::drop(sSegmentSize), wxiter); /* Shift the input history. */ curseg = curseg ? (curseg-1) : (sNumSegments-1); } mCurrentSegment = curseg; /* D = j(-0.3420201*W + 0.5098604*X) + 0.6554516*Y */ std::ranges::transform(mD | std::views::take(samplesToDo), mY, mD.begin(), [](const float jwx, const float y) noexcept { return jwx + 0.6554516f*y; }); /* Copy the future samples to the front for next time. */ const auto take_end = std::views::drop(samplesToDo) | std::views::take(sFilterDelay); std::ranges::copy(mW | take_end, mW.begin()); std::ranges::copy(mX | take_end, mX.begin()); std::ranges::copy(mY | take_end, mY.begin()); /* Apply a delay to the existing output to align with the input delay. */ std::ignore = std::ranges::mismatch(mDirectDelay, std::array{LeftOut, RightOut}, [](std::span delayBuffer, const std::span buffer) { const auto distbuf = assume_aligned_span<16>(delayBuffer); const auto inout = assume_aligned_span<16>(buffer); if(inout.size() >= sFilterDelay) { const auto inout_start = std::prev(inout.end(), sFilterDelay); const auto delay_end = std::ranges::rotate(inout, inout_start).begin(); std::ranges::swap_ranges(std::span{inout.begin(), delay_end}, distbuf); } else { const auto delay_start = std::ranges::swap_ranges(inout, distbuf).in2; std::ranges::rotate(distbuf, delay_start); } return true; }); /* Combine the direct signal with the produced output. */ /* Left = (S + D)/2.0 */ const auto left = assume_aligned_span<16>(LeftOut); for(auto i = 0_uz;i < samplesToDo;++i) left[i] += (mS[i] + mD[i]) * 0.5f; /* Right = (S - D)/2.0 */ const auto right = assume_aligned_span<16>(RightOut); for(auto i = 0_uz;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(const std::span LeftOut, const std::span RightOut, const std::span,3> InSamples) { const auto samplesToDo = InSamples[0].size(); const auto winput = assume_aligned_span<16>(InSamples[0]); const auto xinput = assume_aligned_span<16>(InSamples[1].first(samplesToDo)); const auto yinput = assume_aligned_span<16>(InSamples[2].first(samplesToDo)); /* S = 0.9396926*W + 0.1855740*X */ std::ranges::transform(winput, xinput, mTemp.begin(), [](const float w, const float x) noexcept { return 0.9396926f*w + 0.1855740f*x; }); process(mFilter1WX, Filter1Coeff, std::span{mTemp}.first(samplesToDo), true, std::span{mS}.subspan(1)); mS[0] = mDelayWX; mDelayWX = mS[samplesToDo]; /* Precompute j(-0.3420201*W + 0.5098604*X) and store in mWX. */ std::ranges::transform(winput, xinput, mTemp.begin(), [](const float w, const float x) noexcept { return -0.3420201f*w + 0.5098604f*x; }); process(mFilter2WX, Filter2Coeff, std::span{mTemp}.first(samplesToDo), true, mWX); /* Apply filter1 to Y and store in mD. */ process(mFilter1Y, Filter1Coeff, yinput, true, std::span{mD}.subspan(1)); mD[0] = mDelayY; mDelayY = mD[samplesToDo]; /* D = j(-0.3420201*W + 0.5098604*X) + 0.6554516*Y */ std::ranges::transform(mWX | std::views::take(samplesToDo), mD, 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 = assume_aligned_span<16>(LeftOut.first(samplesToDo)); process(mFilter1Direct[0], Filter1Coeff, left, true, std::span{mTemp}.subspan(1)); mTemp[0] = mDirectDelay[0]; mDirectDelay[0] = mTemp[samplesToDo]; /* Left = (S + D)/2.0 */ for(auto i = 0_uz;i < samplesToDo;++i) left[i] = (mS[i] + mD[i])*0.5f + mTemp[i]; const auto right = assume_aligned_span<16>(RightOut.first(samplesToDo)); process(mFilter1Direct[1], Filter1Coeff, right, true, std::span{mTemp}.subspan(1)); mTemp[0] = mDirectDelay[1]; mDirectDelay[1] = mTemp[samplesToDo]; /* Right = (S - D)/2.0 */ for(auto i = 0_uz;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 std::span> samples, const bool updateState) { static_assert(sInputPadding <= sMaxPadding, "Filter padding is too large"); { const auto left = assume_aligned_span<16>(samples[0]); const auto right = assume_aligned_span<16>(samples[1]); const auto t = assume_aligned_span<16>(samples[2]); /* S = Left + Right */ std::ranges::transform(left, right, mS.begin(), std::plus{}); /* D = Left - Right */ std::ranges::transform(left, right, mD.begin(), std::minus{}); /* T */ std::ranges::copy(t, mT.begin()); } const auto samplesToDo = samples[0].size() - sInputPadding; const auto woutput = assume_aligned_span<16>(samples[0].first(samplesToDo)); const auto xoutput = assume_aligned_span<16>(samples[1].first(samplesToDo)); const auto youtput = assume_aligned_span<16>(samples[2].first(samplesToDo)); /* Precompute j(0.828331*D + 0.767820*T) and store in xoutput. */ auto tmpiter = std::ranges::copy(mDTHistory, mTemp.begin()).out; std::ranges::transform(mD | std::views::take(samplesToDo+sInputPadding), mT, tmpiter, [](const float d, const float t) noexcept { return 0.828331f*d + 0.767820f*t; }); if(updateState) [[likely]] std::ranges::copy(mTemp|std::views::drop(samplesToDo)|std::views::take(mDTHistory.size()), mDTHistory.begin()); PShifter.process(xoutput, mTemp); /* W = 0.981532*S + 0.197484*j(0.828331*D + 0.767820*T) */ std::ranges::transform(mS | std::views::take(samplesToDo), xoutput, 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::ranges::transform(mS | std::views::take(samplesToDo), xoutput, xoutput.begin(), [](const float s, const float jdt) noexcept { return 0.418496f*s - jdt; }); /* Precompute j*S and store in youtput. */ tmpiter = std::ranges::copy(mSHistory, mTemp.begin()).out; std::ranges::copy(mS | std::views::take(samplesToDo+sInputPadding), tmpiter); if(updateState) [[likely]] std::ranges::copy(mTemp|std::views::drop(samplesToDo)|std::views::take(mSHistory.size()), mSHistory.begin()); PShifter.process(youtput, mTemp); /* Y = 0.795968*D - 0.676392*T + j(0.186633*S) */ for(auto i = 0_uz;i < samplesToDo;++i) youtput[i] = 0.795968f*mD[i] - 0.676392f*mT[i] + 0.186633f*youtput[i]; if(samples.size() > 3) { const auto zoutput = assume_aligned_span<16>(samples[3].first(samplesToDo)); /* Z = 1.023332*Q */ std::ranges::transform(zoutput, zoutput.begin(), [](float q) { return 1.023332f*q; }); } } void UhjDecoderIIR::decode(const std::span> samples, const bool updateState) { static_assert(sInputPadding <= sMaxPadding, "Filter padding is too large"); { const auto left = assume_aligned_span<16>(samples[0]); const auto right = assume_aligned_span<16>(samples[1]); /* S = Left + Right */ std::ranges::transform(left, right, mS.begin(), std::plus{}); /* D = Left - Right */ std::ranges::transform(left, right, mD.begin(), std::minus{}); } const auto samplesToDo = samples[0].size() - sInputPadding; const auto woutput = assume_aligned_span<16>(samples[0].first(samplesToDo)); const auto xoutput = assume_aligned_span<16>(samples[1].first(samplesToDo)); const auto youtput = assume_aligned_span<16>(samples[2].first(samplesToDo)); /* Precompute j(0.828331*D + 0.767820*T) and store in xoutput. */ std::ranges::transform(mD, assume_aligned_span<16>(samples[2]), 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, std::span{mTemp}.subspan(1, samplesToDo), updateState, xoutput); /* Apply filter1 to S and store in mTemp. */ process(mFilter1S, Filter1Coeff, std::span{mS}.first(samplesToDo), updateState, mTemp); /* W = 0.981532*S + 0.197484*j(0.828331*D + 0.767820*T) */ std::ranges::transform(mTemp, xoutput, 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::ranges::transform(mTemp, xoutput, 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::ranges::transform(mD | std::views::take(samplesToDo), youtput, youtput.begin(), [](const float d, const float t) noexcept { return 0.795968f*d - 0.676392f*t; }); process(mFilter1DT, Filter1Coeff, youtput, updateState, mTemp); /* Precompute j*S and store in youtput. */ if(mFirstRun) processOne(mFilter2S, Filter2Coeff, mS[0]); process(mFilter2S, Filter2Coeff, std::span{mS}.subspan(1, samplesToDo), updateState, youtput); /* Y = 0.795968*D - 0.676392*T + j(0.186633*S) */ std::ranges::transform(mTemp | std::views::take(samplesToDo), youtput, youtput.begin(), [](const float dt, const float js) noexcept { return dt + 0.186633f*js; }); if(samples.size() > 3) { const auto zoutput = assume_aligned_span<16>(samples[3].first(samplesToDo)); /* Apply filter1 to Q and store in mTemp. */ process(mFilter1Q, Filter1Coeff, zoutput, updateState, mTemp); /* Z = 1.023332*Q */ std::ranges::transform(mTemp | std::views::take(samplesToDo), 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 std::span> samples, const bool updateState) { static_assert(sInputPadding <= sMaxPadding, "Filter padding is too large"); const auto samplesToDo = samples[0].size() - sInputPadding; { const auto left = assume_aligned_span<16>(samples[0]); const auto right = assume_aligned_span<16>(samples[1]); std::ranges::transform(left, right, mS.begin(), std::plus{}); /* Pre-apply the width factor to the difference signal D. Smoothly * interpolate when it changes. */ const auto wtarget = mWidthControl; const auto wcurrent = (mCurrentWidth < 0.0f) ? wtarget : mCurrentWidth; if(wtarget == wcurrent || !updateState) { std::ranges::transform(left, right, mD.begin(), [wcurrent](float l, float r) noexcept { return (l-r) * wcurrent; }); mCurrentWidth = wcurrent; } else { const auto wstep = (wtarget - wcurrent) / gsl::narrow_cast(samplesToDo); auto fi = 0.0f; const auto lfade = left.first(samplesToDo); auto dstore = std::ranges::transform(lfade, right, 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; }).out; const auto lend = left.last(sInputPadding); const auto rend = right.last(sInputPadding); std::ranges::transform(lend, rend, dstore, [wtarget](float l, float r) noexcept { return (l-r) * wtarget; }); mCurrentWidth = wtarget; } } const auto woutput = assume_aligned_span<16>(samples[0].first(samplesToDo)); const auto xoutput = assume_aligned_span<16>(samples[1].first(samplesToDo)); const auto youtput = assume_aligned_span<16>(samples[2].first(samplesToDo)); /* Precompute j*D and store in xoutput. */ auto tmpiter = std::ranges::copy(mDTHistory, mTemp.begin()).out; std::ranges::copy(mD | std::views::take(samplesToDo+sInputPadding), tmpiter); if(updateState) [[likely]] std::ranges::copy(mTemp|std::views::drop(samplesToDo)|std::views::take(mDTHistory.size()), mDTHistory.begin()); PShifter.process(xoutput, mTemp); /* W = 0.6098637*S + 0.6896511*j*w*D */ std::ranges::transform(mS, xoutput, 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::ranges::transform(mS, xoutput, 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::ranges::copy(mSHistory, mTemp.begin()).out; std::ranges::copy(mS | std::views::take(samplesToDo+sInputPadding), tmpiter); if(updateState) [[likely]] std::ranges::copy(mTemp|std::views::drop(samplesToDo)|std::views::take(mSHistory.size()), mSHistory.begin()); PShifter.process(youtput, mTemp); /* Y = 1.6822415*w*D + 0.2156194*j*S */ std::ranges::transform(mD, youtput, youtput.begin(), [](const float d, const float js) noexcept { return 1.6822415f*d + 0.2156194f*js; }); } void UhjStereoDecoderIIR::decode(const std::span> samples, const bool updateState) { static_assert(sInputPadding <= sMaxPadding, "Filter padding is too large"); const auto samplesToDo = samples[0].size() - sInputPadding; { const auto left = assume_aligned_span<16>(samples[0]); const auto right = assume_aligned_span<16>(samples[1]); std::ranges::transform(left, right, mS.begin(), std::plus{}); /* Pre-apply the width factor to the difference signal D. Smoothly * interpolate when it changes. */ const auto wtarget = mWidthControl; const auto wcurrent = (mCurrentWidth < 0.0f) ? wtarget : mCurrentWidth; if(wtarget == wcurrent || !updateState) { std::ranges::transform(left, right, mD.begin(), [wcurrent](float l, float r) noexcept { return (l-r) * wcurrent; }); mCurrentWidth = wcurrent; } else { const auto wstep = (wtarget - wcurrent) / gsl::narrow_cast(samplesToDo); auto fi = 0.0f; const auto lfade = left.first(samplesToDo); auto dstore = std::ranges::transform(lfade, right, 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; }).out; const auto lend = left.last(sInputPadding); const auto rend = right.last(sInputPadding); std::ranges::transform(lend, rend, dstore, [wtarget](float l, float r) noexcept { return (l-r) * wtarget; }); mCurrentWidth = wtarget; } } const auto woutput = assume_aligned_span<16>(samples[0].first(samplesToDo)); const auto xoutput = assume_aligned_span<16>(samples[1].first(samplesToDo)); const auto youtput = assume_aligned_span<16>(samples[2].first(samplesToDo)); /* Apply filter1 to S and store in mTemp. */ process(mFilter1S, Filter1Coeff, std::span{mS}.first(samplesToDo), updateState, mTemp); /* Precompute j*D and store in xoutput. */ if(mFirstRun) processOne(mFilter2D, Filter2Coeff, mD[0]); process(mFilter2D, Filter2Coeff, std::span{mD}.subspan(1, samplesToDo), updateState, xoutput); /* W = 0.6098637*S + 0.6896511*j*w*D */ std::ranges::transform(mTemp, xoutput, woutput.begin(), [](float s, float jd) noexcept { return 0.6098637f*s + 0.6896511f*jd; }); /* X = 0.8624776*S - 0.7626955*j*w*D */ std::ranges::transform(mTemp, xoutput, xoutput.begin(), [](float s, 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, std::span{mS}.subspan(1, samplesToDo), updateState, youtput); /* Apply filter1 to D and store in mTemp. */ process(mFilter1D, Filter1Coeff, std::span{mD}.first(samplesToDo), updateState, mTemp); /* Y = 1.6822415*w*D + 0.2156194*j*S */ std::ranges::transform(mTemp, youtput, youtput.begin(), [](float d, 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; kcat-openal-soft-75c0059/core/uhjfilter.h000066400000000000000000000224121512220627100202330ustar00rootroot00000000000000#ifndef CORE_UHJFILTER_H #define CORE_UHJFILTER_H #include #include #include #include #include #include "alnumeric.h" #include "bufferline.h" inline constexpr auto UhjLength256 = 256_uz; inline constexpr auto UhjLength512 = 512_uz; enum class UhjQualityType : std::uint8_t { IIR = 0, FIR256, FIR512, Default = IIR }; inline auto UhjDecodeQuality = UhjQualityType::Default; inline auto UhjEncodeQuality = UhjQualityType::Default; struct UhjAllPassFilter { struct AllPassState { /* Last two delayed components for direct form II. */ std::array z{}; }; std::array mState; }; struct 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(const std::span LeftOut, const std::span RightOut, const std::span,3> InSamples) = 0; }; template struct UhjEncoder final : public UhjEncoderBase { struct Tag { using encoder_t = UhjEncoder; }; 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}; static consteval auto TypeName() noexcept -> std::string_view { if constexpr(N == 256) return "FIR-256"; else if constexpr(N == 512) return "FIR-512"; } /* 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(const std::span LeftOut, const std::span RightOut, const std::span,3> InSamples) final; }; struct UhjEncoderIIR final : public UhjEncoderBase { struct Tag { using encoder_t = UhjEncoderIIR; }; static constexpr std::size_t sFilterDelay{1_uz}; static consteval auto TypeName() noexcept -> std::string_view { return "IIR"; } /* 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(const std::span LeftOut, const std::span RightOut, const std::span,3> InSamples) final; }; struct 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 std::span> samples, 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. * * 0.46 seens to produce the least amount of channel bleed when the output * is subsequently UHJ encoded (given a stereo sound with a noise on the * left buffer channel, for instance, when decoded with UhjStereoDecoder * and then encoded with UhjEncoder, the right output channel was at its * quietest). */ float mWidthControl{0.46f}; }; template struct UhjDecoder final : public DecoderBase { struct Tag { using decoder_t = UhjDecoder; }; /* The number of extra sample frames needed for input. */ static constexpr unsigned int sInputPadding{N/2u}; 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 std::span> samples, const bool updateState) final; }; struct UhjDecoderIIR final : public DecoderBase { struct Tag { using decoder_t = UhjDecoderIIR; }; /* 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 unsigned int sInputPadding{1u}; 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 std::span> samples, const bool updateState) final; }; template struct UhjStereoDecoder final : public DecoderBase { struct Tag { using decoder_t = UhjStereoDecoder; }; static constexpr unsigned int sInputPadding{N/2u}; 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 std::span> samples, const bool updateState) final; }; struct UhjStereoDecoderIIR final : public DecoderBase { struct Tag { using decoder_t = UhjStereoDecoderIIR; }; static constexpr unsigned int sInputPadding{1u}; 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 std::span> samples, const bool updateState) final; }; #endif /* CORE_UHJFILTER_H */ kcat-openal-soft-75c0059/core/uiddefs.cpp000066400000000000000000000021441512220627100202150ustar00rootroot00000000000000 #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 */ kcat-openal-soft-75c0059/core/voice.cpp000066400000000000000000001456621512220627100177140ustar00rootroot00000000000000 #include "config.h" #include "config_simd.h" #include "voice.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "alnumeric.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 "gsl/gsl" #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" namespace { static_assert((DeviceBase::MixerLineSize&3) == 0, "MixerLineSize must be a multiple of 4"); static_assert((MaxResamplerEdge&3) == 0, "MaxResamplerEdge is not a multiple of 4"); constexpr auto PitchLimit = (std::numeric_limits::max()-MixerFracMask) / MixerFracOne / BufferLineSize; static_assert(MaxPitch <= PitchLimit, "MaxPitch, BufferLineSize, or MixerFracBits is too large"); static_assert(BufferLineSize > MaxPitch, "MaxPitch must be less then BufferLineSize"); using namespace std::chrono; using namespace std::string_view_literals; using HrtfMixerFunc = void(*)(std::span InSamples, std::span AccumSamples, u32 IrSize, MixHrtfFilter const *hrtfparams, usize SamplesToDo); using HrtfMixerBlendFunc = void(*)(std::span InSamples, std::span AccumSamples, u32 IrSize, HrtfFilter const *oldparams, MixHrtfFilter const *newparams, usize SamplesToDo); constinit auto MixHrtfSamples = HrtfMixerFunc{MixHrtf_C}; constinit auto MixHrtfBlendSamples = HrtfMixerBlendFunc{MixHrtfBlend_C}; [[nodiscard]] auto SelectMixer() -> MixerOutFunc { #if HAVE_NEON if((CPUCapFlags&CPU_CAP_NEON)) return Mix_NEON; #endif #if HAVE_SSE if((CPUCapFlags&CPU_CAP_SSE)) return Mix_SSE; #endif return Mix_C; } [[nodiscard]] auto SelectMixerOne() -> MixerOneFunc { #if HAVE_NEON if((CPUCapFlags&CPU_CAP_NEON)) return Mix_NEON; #endif #if HAVE_SSE if((CPUCapFlags&CPU_CAP_SSE)) return Mix_SSE; #endif return Mix_C; } auto SelectHrtfMixer() -> HrtfMixerFunc { #if HAVE_NEON if((CPUCapFlags&CPU_CAP_NEON)) return MixHrtf_NEON; #endif #if HAVE_SSE if((CPUCapFlags&CPU_CAP_SSE)) return MixHrtf_SSE; #endif return MixHrtf_C; } auto SelectHrtfBlendMixer() -> HrtfMixerBlendFunc { #if HAVE_NEON if((CPUCapFlags&CPU_CAP_NEON)) return MixHrtfBlend_NEON; #endif #if HAVE_SSE if((CPUCapFlags&CPU_CAP_SSE)) return MixHrtfBlend_SSE; #endif return MixHrtfBlend_C; } } // namespace void Voice::InitMixer(std::optional const &resopt) { if(resopt) { struct ResamplerEntry { std::string_view const name; Resampler const resampler; }; constexpr auto ResamplerList = std::array{ 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}, ResamplerEntry{"bsinc48"sv, Resampler::BSinc48}, ResamplerEntry{"fast_bsinc48"sv, Resampler::FastBSinc48}, }; auto resampler = std::string_view{*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 const iter = std::ranges::find_if(ResamplerList, [resampler](ResamplerEntry const &entry) { 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 auto IMAStep_size = std::array{ 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 auto IMA4Codeword = std::array{ 1, 3, 5, 7, 9, 11, 13, 15, -1,-3,-5,-7,-9,-11,-13,-15, }; /* IMA4 ADPCM Step index adjust decode table */ constexpr auto IMA4Index_adjust = std::array{ -1,-1,-1,-1, 2, 4, 6, 8, -1,-1,-1,-1, 2, 4, 6, 8 }; /* MSADPCM Adaption table */ constexpr auto MSADPCMAdaption = std::array{ 230, 230, 230, 230, 307, 409, 512, 614, 768, 614, 512, 409, 307, 230, 230, 230 }; /* MSADPCM Adaption Coefficient tables */ constexpr auto MSADPCMAdaptionCoeff = std::array{ 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 const *const context, u32 const id) { auto *const ring = context->mAsyncEvents.get(); auto const evt_vec = ring->getWriteVector(); if(evt_vec[0].empty()) return; auto &evt = InitAsyncEvent(evt_vec[0].front()); evt.mId = id; evt.mState = AsyncSrcState::Stop; ring->writeAdvance(1); } auto DoFilters(BiquadInterpFilter &lpfilter, BiquadInterpFilter &hpfilter, std::span const dst LIFETIMEBOUND, std::span const src LIFETIMEBOUND, bool const active) -> std::span { if(active) { DualBiquadInterp{lpfilter, hpfilter}.process(src, dst); return dst.first(src.size()); } lpfilter.clear(); hpfilter.clear(); return src; } template void LoadSamples(std::span const dstSamples, std::span const srcData, usize const srcChan, usize const srcOffset, usize const srcStep, usize const samplesPerBlock [[maybe_unused]]) noexcept { using TypeTraits = SampleInfo; Expects(srcChan < srcStep); auto ssrc = srcData.begin(); std::advance(ssrc, srcOffset*srcStep + srcChan); dstSamples.front() = TypeTraits::to_float(*ssrc); std::ranges::generate(dstSamples | std::views::drop(1), [&ssrc,srcStep] { std::advance(ssrc, srcStep); return TypeTraits::to_float(*ssrc); }); } template<> void LoadSamples(std::span dstSamples, std::span src, usize const srcChan, usize const srcOffset, usize const srcStep, usize const samplesPerBlock) noexcept { static constexpr auto MaxStepIndex = gsl::narrow(IMAStep_size.size()) - 1; Expects(srcStep > 0 || srcStep <= 2); Expects(srcChan < srcStep); Expects(samplesPerBlock > 1); auto const blockBytes = ((samplesPerBlock-1_uz)/2_uz + 4_uz)*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. */ auto skip = srcOffset % samplesPerBlock; /* NOTE: This could probably be optimized better. */ while(!dstSamples.empty()) { /* Each IMA4 block starts with a signed 16-bit sample, and a signed(?) * 16-bit table index. The table index needs to be clamped. */ auto sample = std::to_integer(src[srcChan*4 + 0].value) | (std::to_integer(src[srcChan*4 + 1].value) << 8); auto ima_idx = std::to_integer(src[srcChan*4 + 2].value) | (std::to_integer(src[srcChan*4 + 3].value) << 8); auto const nibbleData = src.subspan((srcStep+srcChan)*4); src = src.subspan(blockBytes); /* Sign-extend the 16-bit sample and index values. */ sample = (sample^0x8000) - 32768; ima_idx = std::clamp((ima_idx^0x8000) - 32768, 0, MaxStepIndex); if(skip == 0) { dstSamples[0] = gsl::narrow_cast(sample) / 32768.0f; dstSamples = dstSamples.subspan(1); if(dstSamples.empty()) return; } else --skip; /* 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. */ auto decode_nibble = [&sample,&ima_idx,srcStep,nibbleData](usize const nibbleOffset) noexcept -> i32 { static constexpr auto NibbleMask = std::byte{0xf}; auto const byteShift = (nibbleOffset&1) * 4; auto const wordOffset = (nibbleOffset>>1) & ~3_uz; auto const byteOffset = wordOffset*srcStep + ((nibbleOffset>>1)&3); auto const byteval = nibbleData[byteOffset].value >> byteShift; auto const nibble = std::to_integer(byteval & NibbleMask); sample += IMA4Codeword[nibble] * IMAStep_size[gsl::narrow_cast(ima_idx)] / 8; sample = std::clamp(sample, -32768, 32767); ima_idx = std::clamp(ima_idx + IMA4Index_adjust[nibble], 0, MaxStepIndex); return sample; }; /* 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. */ auto const startOffset = skip + 1_uz; auto nibbleOffset = 0_uz; for(;skip;--skip) { std::ignore = decode_nibble(nibbleOffset); ++nibbleOffset; } /* Second, decode the rest of the block and write to the output, until * the end of the block or the end of output. */ auto const dst = std::ranges::generate(dstSamples | std::views::take(samplesPerBlock - startOffset), [&] { auto const decspl = decode_nibble(nibbleOffset); ++nibbleOffset; return gsl::narrow_cast(decspl) / 32768.0f; }); dstSamples = std::span{dst, dstSamples.end()}; } } template<> void LoadSamples(std::span dstSamples, std::span src, usize const srcChan, usize const srcOffset, usize const srcStep, usize const samplesPerBlock) noexcept { Expects(srcStep > 0 || srcStep <= 2); Expects(srcChan < srcStep); Expects(samplesPerBlock > 2); auto const blockBytes = ((samplesPerBlock-2_uz)/2_uz + 7_uz)*srcStep; src = src.subspan(srcOffset/samplesPerBlock*blockBytes); auto skip = srcOffset % samplesPerBlock; while(!dstSamples.empty()) { /* 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 scaling value which scales the * nibble sample value. This is followed by the two initial 16-bit * sample history values. */ auto const blockpred = std::min(std::to_integer(src[srcChan].value), u8{MSADPCMAdaptionCoeff.size()-1}); auto scale = std::to_integer(src[srcStep + 2*srcChan + 0].value) | (std::to_integer(src[srcStep + 2*srcChan + 1].value) << 8); auto sampleHistory = std::array{ std::to_integer(src[3*srcStep + 2*srcChan + 0].value) | (std::to_integer(src[3*srcStep + 2*srcChan + 1].value)<<8), std::to_integer(src[5*srcStep + 2*srcChan + 0].value) | (std::to_integer(src[5*srcStep + 2*srcChan + 1].value)<<8)}; auto const nibbleData = src.subspan(7*srcStep); src = src.subspan(blockBytes); auto const coeffs = std::span{MSADPCMAdaptionCoeff[blockpred]}; scale = (scale^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) { dstSamples[0] = gsl::narrow_cast(sampleHistory[1]) / 32768.0f; if(dstSamples.size() < 2) return; dstSamples[1] = gsl::narrow_cast(sampleHistory[0]) / 32768.0f; dstSamples = dstSamples.subspan(2); if(dstSamples.empty()) return; } else if(skip == 1) { --skip; dstSamples[0] = gsl::narrow_cast(sampleHistory[0]) / 32768.0f; dstSamples = dstSamples.subspan(1); if(dstSamples.empty()) return; } else skip -= 2; /* The rest of the block is a series of nibbles, interleaved per- * channel. */ auto decode_nibble = [&sampleHistory,&scale,coeffs,nibbleData](usize const nibbleOffset) noexcept -> i32 { static constexpr auto NibbleMask = std::byte{0xf}; auto const byteOffset = nibbleOffset>>1; auto const byteShift = ((nibbleOffset&1)^1) * 4; auto const byteval = nibbleData[byteOffset].value >> byteShift; auto const nibble = std::to_integer(byteval & NibbleMask); auto const pred = ((nibble^0x08) - 0x08) * scale; auto const diff = (sampleHistory[0]*coeffs[0] + sampleHistory[1]*coeffs[1]) / 256; auto const sample = std::clamp(pred + diff, -32768, 32767); sampleHistory[1] = sampleHistory[0]; sampleHistory[0] = sample; scale = MSADPCMAdaption[nibble] * scale / 256; scale = std::max(16, scale); return sample; }; /* First, skip samples. */ auto const startOffset = skip + 2_uz; auto nibbleOffset = srcChan; for(;skip;--skip) { std::ignore = decode_nibble(nibbleOffset); nibbleOffset += srcStep; } /* Now decode the rest of the block, until the end of the block or the * dst buffer is filled. */ auto const dst = std::ranges::generate(dstSamples | std::views::take(samplesPerBlock - startOffset), [&] { auto const sample = decode_nibble(nibbleOffset); nibbleOffset += srcStep; return gsl::narrow_cast(sample) / 32768.0f; }); dstSamples = std::span{dst, dstSamples.end()}; } } void LoadSamples(std::span const dstSamples, SampleVariant const &src, usize const srcChan, usize const srcOffset, usize const srcStep, usize const samplesPerBlock) noexcept { std::visit([&](T&& splvec) { using sample_t = std::remove_cvref_t::value_type; LoadSamples(dstSamples, splvec, srcChan, srcOffset, srcStep, samplesPerBlock); }, src); } void LoadBufferStatic(VoiceBufferItem const *const buffer, VoiceBufferItem const *const bufferLoopItem, usize const dataPosInt, usize const srcChannel, usize const srcStep, std::span voiceSamples) { if(!bufferLoopItem) { auto lastSample = 0.0f; /* Load what's left to play from the buffer */ if(buffer->mSampleLen > dataPosInt) [[likely]] { const auto buffer_remaining = buffer->mSampleLen - dataPosInt; const auto remaining = std::min(voiceSamples.size(), buffer_remaining); LoadSamples(voiceSamples.first(remaining), buffer->mSamples, srcChannel, dataPosInt, srcStep, buffer->mBlockAlign); lastSample = voiceSamples[remaining-1]; voiceSamples = voiceSamples.subspan(remaining); } std::ranges::fill(voiceSamples, lastSample); } else { auto const loopStart = usize{buffer->mLoopStart}; auto const loopEnd = usize{buffer->mLoopEnd}; ASSUME(loopEnd > loopStart); auto const intPos = (dataPosInt < loopEnd) ? dataPosInt : (((dataPosInt-loopStart)%(loopEnd-loopStart)) + loopStart); /* Load what's left of this loop iteration */ auto const remaining = std::min(voiceSamples.size(), loopEnd-intPos); LoadSamples(voiceSamples.first(remaining), buffer->mSamples, srcChannel, intPos, srcStep, buffer->mBlockAlign); voiceSamples = voiceSamples.subspan(remaining); /* Load repeats of the loop to fill the buffer. */ auto const loopSize = loopEnd - loopStart; while(auto const toFill = std::min(voiceSamples.size(), loopSize)) { LoadSamples(voiceSamples.first(toFill), buffer->mSamples, srcChannel, loopStart, srcStep, buffer->mBlockAlign); voiceSamples = voiceSamples.subspan(toFill); } } } void LoadBufferCallback(VoiceBufferItem const *const buffer, usize const dataPosInt, usize const numCallbackSamples, usize const srcChannel, usize const srcStep, std::span voiceSamples) { auto lastSample = 0.0f; if(numCallbackSamples > dataPosInt) [[likely]] { auto const remaining = std::min(voiceSamples.size(), numCallbackSamples-dataPosInt); LoadSamples(voiceSamples.first(remaining), buffer->mSamples, srcChannel, dataPosInt, srcStep, buffer->mBlockAlign); lastSample = voiceSamples[remaining-1]; voiceSamples = voiceSamples.subspan(remaining); } std::ranges::fill(voiceSamples, lastSample); } void LoadBufferQueue(VoiceBufferItem const *buffer, VoiceBufferItem const *const bufferLoopItem, usize dataPosInt, usize const srcChannel, usize const srcStep, std::span voiceSamples) { auto 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; } auto const remaining = std::min(voiceSamples.size(), buffer->mSampleLen-dataPosInt); LoadSamples(voiceSamples.first(remaining), buffer->mSamples, srcChannel, dataPosInt, 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; } std::ranges::fill(voiceSamples, lastSample); } void DoHrtfMix(std::span const samples, DirectParams &parms, f32 const targetGain, usize const counter, usize outPos, bool const isPlaying, DeviceBase *const device) { auto const IrSize = device->mIrSize; auto const HrtfSamples = std::span{device->ExtraSampleData}; auto const AccumSamples = std::span{device->HrtfAccumData}; /* Copy the HRTF history and new input samples into a temp buffer. */ auto const src_iter = std::ranges::copy(parms.Hrtf.History, HrtfSamples.begin()).out; std::ranges::copy(samples, src_iter); /* Copy the last used samples back into the history buffer for later. */ if(isPlaying) [[likely]] { auto const endsamples = HrtfSamples.subspan(samples.size(), parms.Hrtf.History.size()); std::ranges::copy(endsamples, parms.Hrtf.History.begin()); } /* If fading and this is the first mixing pass, fade between the IRs. */ auto fademix = 0_uz; if(counter && outPos == 0) { fademix = std::min(samples.size(), counter); auto 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) { auto const a = gsl::narrow_cast(fademix) / gsl::narrow_cast(counter); gain = lerpf(parms.Hrtf.Old.Gain, targetGain, a); } auto const hrtfparams = MixHrtfFilter{ parms.Hrtf.Target.Coeffs, parms.Hrtf.Target.Delay, 0.0f, gain / gsl::narrow_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()) { auto const todo = samples.size() - fademix; auto gain = targetGain; /* Interpolate the target gain if the gain fading lasts longer than * this mix. */ if(counter > samples.size()) { auto const a = gsl::narrow_cast(todo) / gsl::narrow_cast(counter-fademix); gain = lerpf(parms.Hrtf.Old.Gain, targetGain, a); } auto const hrtfparams = MixHrtfFilter{ parms.Hrtf.Target.Coeffs, parms.Hrtf.Target.Delay, parms.Hrtf.Old.Gain, (gain - parms.Hrtf.Old.Gain) / gsl::narrow_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(std::span const samples, std::span outBuffer, DirectParams &parms, std::span const outGains, u32 const counter, u32 const outPos, DeviceBase *const device) { using FilterProc = void(NfcFilter::*)(std::span src, std::span dst); static constexpr auto NfcProcess = std::array{FilterProc{nullptr}, &NfcFilter::process1, &NfcFilter::process2, &NfcFilter::process3, &NfcFilter::process4}; static_assert(NfcProcess.size() == MaxAmbiOrder+1); MixSamples(samples, std::span{outBuffer[0]}.subspan(outPos), parms.Gains.Current[0], outGains[0], counter); outBuffer = outBuffer.subspan(1); auto CurrentGains = std::span{parms.Gains.Current}.subspan(1); auto TargetGains = outGains.subspan(1); auto const nfcsamples = std::span{device->ExtraSampleData}.first(samples.size()); auto order = 1_uz; while(auto const chancount = usize{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(State const vstate, ContextBase *const context, nanoseconds const deviceTime, u32 const samplesToDo) { static constexpr auto SilentTarget = std::array{}; ASSUME(samplesToDo > 0); auto const device = al::get_not_null(context->mDevice); auto const numSends = device->NumAuxSends; /* Get voice info */ auto bufPosInt = mPosition.load(std::memory_order_relaxed); auto bufPosFrac = mPositionFrac.load(std::memory_order_relaxed); auto *bufferListItem = mCurrentBuffer.load(std::memory_order_relaxed); auto *bufferLoopItem = mLoopBuffer.load(std::memory_order_relaxed); auto const 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(std::cmp_greater_equal(bufPosInt, bufferListItem->mLoopEnd)) bufferLoopItem = nullptr; } auto outPos = 0_u32; /* 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 const 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 = gsl::narrow_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). */ auto const samplesToMix = samplesToDo - outPos; auto const samplesToLoad = samplesToMix + mDecoderPadding; /* Get a span of pointers to hold the floating point, deinterlaced, * resampled buffer data to be mixed. */ auto samplePointers = std::array, DeviceBase::MixerChannelsMax>{}; auto const mixingSamples = std::span{samplePointers} .first((mFmtChannels == FmtMono && !mDuplicateMono) ? 1_uz : mChans.size()); { auto const channelStep = (samplesToLoad+3_u32)&~3_u32; auto base = device->mSampleData.end() - mixingSamples.size()*channelStep; std::ranges::generate(mixingSamples, [&base,samplesToLoad,channelStep] { const auto ret = base; std::advance(base, channelStep); return std::span{ret, samplesToLoad}; }); } /* UHJ2 and SuperStereo only have 2 buffer channels, but 3 mixing channels * (3rd channel is generated from decoding). */ auto const realChannels = (mFmtChannels == FmtMono) ? 1_uz : (mFmtChannels == FmtUHJ2 || mFmtChannels == FmtSuperStereo) ? 2_uz : mixingSamples.size(); for(auto const chan : std::views::iota(0_uz, realChannels)) { static constexpr auto ResBufSize = std::tuple_size_v; static constexpr auto SrcSizeMax = u32{ResBufSize - MaxResamplerEdge}; auto const prevSamples = std::span{mPrevSamples[chan]}; std::ranges::copy(prevSamples, device->mResampleData.begin()); auto const resampleBuffer = std::span{device->mResampleData}.subspan(); auto cbOffset = mCallbackBlockOffset; auto intPos = bufPosInt; auto fracPos = bufPosFrac; /* Load samples for this channel from the available buffer(s), with * resampling. */ for(auto samplesLoaded = 0_u32;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. */ const auto [dstBufferSize, srcBufferSize] = std::invoke( [fracPos,increment,dstRemaining = samplesToLoad-samplesLoaded]() noexcept -> std::array { /* 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 auto ext = increment <= MixerFracOne; auto dataSize64 = u64{dstRemaining - ext}; dataSize64 = (dataSize64*increment + fracPos) >> MixerFracBits; /* Also include resampler padding. */ dataSize64 += ext + MaxResamplerEdge; if(dataSize64 <= SrcSizeMax) return std::array{dstRemaining, gsl::narrow_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)&~3_u32, SrcSizeMax}; } return std::array{dstRemaining, SrcSizeMax}; }); auto srcSampleDelay = 0_uz; if(intPos < 0) [[unlikely]] { /* If the current position is negative, there's that many * silent samples to load before using the buffer. */ srcSampleDelay = gsl::narrow_cast(-intPos); if(srcSampleDelay >= srcBufferSize) { /* If the number of silent source samples exceeds the * number to load, the output will be silent. */ std::ranges::fill(mixingSamples[chan].subspan(samplesLoaded, dstBufferSize), 0.0f); std::ranges::fill(resampleBuffer.first(srcBufferSize), 0.0f); goto skip_resample; } std::ranges::fill(resampleBuffer | std::views::take(srcSampleDelay), 0.0f); } /* Load the necessary samples from the given buffer(s). */ if(!bufferListItem) [[unlikely]] { auto const avail = std::min(srcBufferSize, MaxResamplerEdge); auto const tofill = std::max(srcBufferSize, MaxResamplerEdge); auto const 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 const srciter = std::ranges::min_element(srcbuf.begin(), std::next(srcbuf.begin(), gsl::narrow_cast(avail)), {}, [](f32 const s) { return std::abs(s); }); std::ranges::fill(std::next(srciter), srcbuf.end(), *srciter); } else if(mFlags.test(VoiceIsStatic)) { auto const uintPos = gsl::narrow_cast(std::max(intPos, 0_i32)); auto const bufferSamples = resampleBuffer.first(srcBufferSize) .subspan(srcSampleDelay); LoadBufferStatic(bufferListItem, bufferLoopItem, uintPos, chan, mFrameStep, bufferSamples); } else if(mFlags.test(VoiceIsCallback)) { auto const bufferOffset = usize{cbOffset}; auto const needSamples = bufferOffset + srcBufferSize - srcSampleDelay; auto const needBlocks = (needSamples + mSamplesPerBlock-1) / mSamplesPerBlock; if(!mFlags.test(VoiceCallbackStopped) && needBlocks > mNumCallbackBlocks) { auto const byteOffset = mNumCallbackBlocks * usize{mBytesPerBlock}; auto const needBytes = (needBlocks-mNumCallbackBlocks) * usize{mBytesPerBlock}; auto const samples = std::visit([](auto &splspan) { return std::as_writable_bytes(splspan); }, bufferListItem->mSamples); auto const gotBytes = bufferListItem->mCallback(bufferListItem->mUserData, &samples[byteOffset], gsl::narrow_cast(needBytes)); if(gotBytes < 0) mFlags.set(VoiceCallbackStopped); else if(gsl::narrow_cast(gotBytes) < needBytes) { mFlags.set(VoiceCallbackStopped); mNumCallbackBlocks += gsl::narrow_cast(gotBytes) / mBytesPerBlock; } else mNumCallbackBlocks = gsl::narrow_cast(needBlocks); } auto const numSamples = usize{mNumCallbackBlocks} * mSamplesPerBlock; auto const bufferSamples = resampleBuffer.first(srcBufferSize) .subspan(srcSampleDelay); LoadBufferCallback(bufferListItem, bufferOffset, numSamples, chan, mFrameStep, bufferSamples); } else { auto const uintPos = gsl::narrow_cast(std::max(intPos, 0_i32)); auto const bufferSamples = resampleBuffer.first(srcBufferSize) .subspan(srcSampleDelay); LoadBufferQueue(bufferListItem, bufferLoopItem, uintPos, 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::ranges::copy(resampleBuffer.first(dstBufferSize), mixingSamples[chan].subspan(samplesLoaded).begin()); else mResampler(&mResampleState, device->mResampleData, fracPos, increment, mixingSamples[chan].subspan(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. */ auto const loadEnd = samplesLoaded + dstBufferSize; if(samplesToMix > samplesLoaded && samplesToMix <= loadEnd) [[likely]] { auto const dstOffset = usize{samplesToMix - samplesLoaded}; auto const srcOffset = usize{(dstOffset*increment + fracPos) >> MixerFracBits}; std::ranges::copy(device->mResampleData | std::views::drop(srcOffset) | std::views::take(prevSamples.size()), prevSamples.begin()); } } skip_resample: samplesLoaded += dstBufferSize; if(samplesLoaded < samplesToLoad) { fracPos += dstBufferSize*increment; auto const srcOffset = fracPos >> MixerFracBits; fracPos &= MixerFracMask; intPos = al::add_sat(intPos, gsl::narrow_cast(srcOffset)); cbOffset += 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::ranges::copy(device->mResampleData | std::views::drop(srcOffset) | std::views::take(MaxResamplerPadding), device->mResampleData.begin()); } } } if(mDuplicateMono) { /* 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::ranges::fill(samples, 0.0f); if(mDecoder) { mDecoder->decode(mixingSamples, (vstate==Playing)); std::ranges::transform(mixingSamples, mixingSamples.begin(), [samplesToMix](std::span const samples) { return samples.first(samplesToMix); }); } if(mFlags.test(VoiceIsAmbisonic)) { auto chandata = mChans.begin(); for(auto const samplespan : mixingSamples) { chandata->mAmbiSplitter.processScale(samplespan, chandata->mAmbiHFScale, chandata->mAmbiLFScale); ++chandata; } } auto const counter = mFlags.test(VoiceIsFading) ? std::min(samplesToMix, 64_u32) : 0_u32; if(!counter) { /* No fading, just overwrite the old/current params. */ for(auto &chandata : mChans) { if(auto &parms = chandata.mDryParams; !mFlags.test(VoiceHasHrtf)) parms.Gains.Current = parms.Gains.Target; else parms.Hrtf.Old = parms.Hrtf.Target; std::ignore = std::ranges::mismatch(mSend | std::views::take(numSends), chandata.mWetParams, [](TargetData const &send, SendParams &parms) { if(!send.Buffer.empty()) parms.Gains.Current = parms.Gains.Target; return true; }); } } auto chandata = mChans.begin(); for(auto const samplespan : mixingSamples) { /* Now filter and mix to the appropriate outputs. */ auto const FilterBuf = std::span{device->FilteredData}; { auto &parms = chandata->mDryParams; auto const samples = DoFilters(parms.LowPass, parms.HighPass, FilterBuf, samplespan, mDirect.FilterActive); if(mFlags.test(VoiceHasHrtf)) { auto const targetGain = parms.Hrtf.Target.Gain * gsl::narrow_cast(vstate == Playing); DoHrtfMix(samples, parms, targetGain, counter, outPos, (vstate == Playing), device); } else { auto const targetGains = (vstate == Playing) ? std::span{parms.Gains.Target} : std::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(auto const send : std::views::iota(0_u32, numSends)) { if(mSend[send].Buffer.empty()) continue; auto &parms = chandata->mWetParams[send]; auto const samples = DoFilters(parms.LowPass, parms.HighPass, FilterBuf, samplespan, mSend[send].FilterActive); auto const targetGains = (vstate == Playing) ? std::span{parms.Gains.Target} : std::span{SilentTarget}.first(); MixSamples(samples, mSend[send].Buffer, parms.Gains.Current, targetGains, counter, outPos); } ++chandata; } 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. */ bufPosFrac += increment*samplesToMix; auto const samplesDone = bufPosFrac >> MixerFracBits; bufPosInt = al::add_sat(bufPosInt, gsl::narrow_cast(samplesDone)); bufPosFrac &= MixerFracMask; auto buffers_done = 0_u32; if(bufferListItem && bufPosInt > 0) [[likely]] { if(mFlags.test(VoiceIsStatic)) { if(bufferLoopItem) { /* Handle looping static source */ auto const LoopStart = bufferListItem->mLoopStart; auto const LoopEnd = bufferListItem->mLoopEnd; if(auto DataPosUInt = gsl::narrow_cast(bufPosInt); DataPosUInt >= LoopEnd) { Expects(LoopEnd > LoopStart); DataPosUInt = ((DataPosUInt-LoopStart)%(LoopEnd-LoopStart)) + LoopStart; bufPosInt = gsl::narrow_cast(DataPosUInt); } } else { /* Handle non-looping static source */ if(gsl::narrow_cast(bufPosInt) >= bufferListItem->mSampleLen) bufferListItem = nullptr; } } else if(mFlags.test(VoiceIsCallback)) { /* Handle callback buffer source */ auto const endOffset = mCallbackBlockOffset + std::min(samplesDone, gsl::narrow_cast(bufPosInt)); auto const blocksDone = endOffset / mSamplesPerBlock; if(blocksDone == 0) mCallbackBlockOffset = endOffset; else if(blocksDone < mNumCallbackBlocks) { auto const byteOffset = blocksDone * usize{mBytesPerBlock}; auto const byteEnd = mNumCallbackBlocks * usize{mBytesPerBlock}; auto const data = std::visit([](auto &splspan) { return std::as_writable_bytes(splspan); }, bufferListItem->mSamples); std::ranges::copy(data | std::views::take(byteEnd) | std::views::drop(byteOffset), data.begin()); mNumCallbackBlocks -= blocksDone; mCallbackBlockOffset = endOffset - blocksDone*mSamplesPerBlock; } else { bufferListItem = nullptr; mNumCallbackBlocks = 0; mCallbackBlockOffset = 0; } } else { /* Handle streaming source */ do { if(bufferListItem->mSampleLen > gsl::narrow_cast(bufPosInt)) break; bufPosInt -= gsl::narrow_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. */ auto const sourceID = mSourceID.load(std::memory_order_relaxed); /* Update voice info */ mPosition.store(bufPosInt, std::memory_order_relaxed); mPositionFrac.store(bufPosFrac, std::memory_order_relaxed); mCurrentBuffer.store(bufferListItem, std::memory_order_release); if(!bufferListItem) { mLoopBuffer.store(nullptr, std::memory_order_relaxed); mSourceID.store(0_u32, std::memory_order_release); } /* Send any events now, after the position/buffer info was updated. */ auto const enabledevt = context->mEnabledEvts.load(std::memory_order_acquire); if(buffers_done > 0 && enabledevt.test(al::to_underlying(AsyncEnableBits::BufferCompleted))) { auto *ring = context->mAsyncEvents.get(); if(auto const evt_vec = ring->getWriteVector(); !evt_vec[0].empty()) { auto &evt = InitAsyncEvent(evt_vec[0].front()); 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) { /* Mono can need 2 mixing channels when panning is enabled, which can be * done dynamically. * * UHJ2 and SuperStereo need 3 mixing channels, despite having only 2 * buffer channels. * * Even if storing really high order ambisonics, we only mix channels for * orders up to the device order. The rest are simply dropped. */ auto num_channels = (mFmtChannels == FmtMono) ? 2_u32 : (mFmtChannels == FmtUHJ2 || mFmtChannels == FmtSuperStereo) ? 3_u32 : 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.resize(num_channels); mPrevSamples.resize(num_channels); mDecoder = nullptr; mDecoderPadding = 0; static constexpr auto init_decoder = [](T arg [[maybe_unused]]) -> std::pair, u32> { using decoder_t = T::decoder_t; return {std::make_unique(), decoder_t::sInputPadding}; }; if(mFmtChannels == FmtSuperStereo) { switch(UhjDecodeQuality) { case UhjQualityType::IIR: std::tie(mDecoder, mDecoderPadding) = init_decoder(UhjStereoDecoderIIR::Tag{}); break; case UhjQualityType::FIR256: std::tie(mDecoder,mDecoderPadding)=init_decoder(UhjStereoDecoder::Tag{}); break; case UhjQualityType::FIR512: std::tie(mDecoder,mDecoderPadding)=init_decoder(UhjStereoDecoder::Tag{}); break; } } else if(IsUHJ(mFmtChannels)) { switch(UhjDecodeQuality) { case UhjQualityType::IIR: std::tie(mDecoder, mDecoderPadding) = init_decoder(UhjDecoderIIR::Tag{}); break; case UhjQualityType::FIR256: std::tie(mDecoder, mDecoderPadding) = init_decoder(UhjDecoder::Tag{}); break; case UhjQualityType::FIR512: std::tie(mDecoder, mDecoderPadding) = init_decoder(UhjDecoder::Tag{}); 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::ranges::fill(mPrevSamples | std::views::join, 0.0f); if(mFmtChannels == FmtUHJ2 && !std::holds_alternative(device->mPostProcess)) { /* 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 auto splitter = BandSplitter{device->mXOverFreq / gsl::narrow_cast(device->mSampleRate)}; std::ranges::for_each(mChans, [splitter,device](ChannelData &chandata) { chandata.mAmbiHFScale = 1.0f; chandata.mAmbiLFScale = 1.0f; chandata.mAmbiSplitter = splitter; chandata.mDryParams = DirectParams{}; chandata.mDryParams.NFCtrlFilter = device->mNFCtrlFilter; std::ranges::fill(chandata.mWetParams | std::views::take(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) { const auto ordersSpan = Is2DAmbisonic(mFmtChannels) ? std::span{AmbiIndex::OrderFrom2DChannel} : std::span{AmbiIndex::OrderFromChannel}; const auto scales = AmbiScale::GetHFOrderScales(mAmbiOrder, device->mAmbiOrder, device->m2DMixing); const auto splitter = BandSplitter{device->mXOverFreq / gsl::narrow_cast(device->mSampleRate)}; std::ignore = std::ranges::mismatch(mChans, ordersSpan, [&scales,splitter,device](ChannelData &chandata, usize const scaleidx) { chandata.mAmbiHFScale = scales[scaleidx]; chandata.mAmbiLFScale = 1.0f; chandata.mAmbiSplitter = splitter; chandata.mDryParams = DirectParams{}; chandata.mDryParams.NFCtrlFilter = device->mNFCtrlFilter; std::ranges::fill(chandata.mWetParams | std::views::take(device->NumAuxSends), SendParams{}); return true; }); mFlags.set(VoiceIsAmbisonic); } else { std::ranges::for_each(mChans, [device](ChannelData &chandata) { chandata.mDryParams = DirectParams{}; chandata.mDryParams.NFCtrlFilter = device->mNFCtrlFilter; std::ranges::fill(chandata.mWetParams | std::views::take(device->NumAuxSends), SendParams{}); }); mFlags.reset(VoiceIsAmbisonic); } } kcat-openal-soft-75c0059/core/voice.h000066400000000000000000000141561512220627100173520ustar00rootroot00000000000000#ifndef CORE_VOICE_H #define CORE_VOICE_H #include #include #include #include #include #include #include #include #include "alnumeric.h" #include "ambidefs.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 "resampler_limits.h" #include "uhjfilter.h" #include "vector.h" struct ContextBase; struct DeviceBase; struct EffectSlotBase; enum class DistanceModel : u8; inline constexpr auto MaxSendCount = 6_uz; inline constexpr auto MaxPitch = 10_u32; inline constinit auto ResamplerDefault = Resampler::Spline; enum class SpatializeMode : u8 { Off, On, Auto }; enum class DirectMode : u8 { Off, DropMismatch, RemixMismatch }; struct DirectParams { BiquadInterpFilter LowPass; BiquadInterpFilter 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 { BiquadInterpFilter LowPass; BiquadInterpFilter HighPass; struct GainParams { std::array Current{}; std::array Target{}; }; GainParams Gains; }; struct VoiceBufferItem { std::atomic mNext{nullptr}; CallbackType mCallback{nullptr}; void *mUserData{nullptr}; u32 mBlockAlign{0_u32}; u32 mSampleLen{0_u32}; u32 mLoopStart{0_u32}; u32 mLoopEnd{0_u32}; SampleVariant mSamples; protected: ~VoiceBufferItem() = default; }; using LPVoiceBufferItem = VoiceBufferItem*; struct VoiceProps { f32 Pitch; f32 Gain; f32 OuterGain; f32 MinGain; f32 MaxGain; f32 InnerAngle; f32 OuterAngle; f32 RefDistance; f32 MaxDistance; f32 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 mPanningEnabled; bool DryGainHFAuto; bool WetGainAuto; bool WetGainHFAuto; f32 OuterGainHF; f32 AirAbsorptionFactor; f32 RoomRolloffFactor; f32 DopplerFactor; std::array StereoPan; f32 Radius; f32 EnhWidth; f32 Panning; /** Direct filter and auxiliary send info. */ struct DirectData { f32 Gain; f32 GainHF; f32 HFReference; f32 GainLF; f32 LFReference; }; DirectData Direct; struct SendData { EffectSlotBase *Slot; f32 Gain; f32 GainHF; f32 HFReference; f32 GainLF; f32 LFReference; }; std::array Send; }; struct VoicePropsItem : VoiceProps { std::atomic next{nullptr}; }; enum : u32 { VoiceIsStatic, VoiceIsCallback, VoiceIsAmbisonic, VoiceCallbackStopped, VoiceIsFading, VoiceHasHrtf, VoiceHasNfc, VoiceFlagCount }; struct Voice { enum State { Stopped, Playing, Stopping, Pending }; std::atomic mUpdate{nullptr}; VoiceProps mProps{}; std::atomic mSourceID{0_u32}; 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{}; u32 mFrequency{}; u32 mFrameStep{}; /**< In steps of the sample type size. */ u32 mBytesPerBlock{}; /**< Or for PCM formats, BytesPerFrame. */ u32 mSamplesPerBlock{}; /**< Always 1 for PCM formats. */ bool mDuplicateMono{}; AmbiLayout mAmbiLayout{}; AmbiScaling mAmbiScaling{}; u32 mAmbiOrder{}; std::unique_ptr mDecoder; u32 mDecoderPadding{}; /** Current target parameters used for mixing. */ u32 mStep{0_u32}; ResamplerFunc mResampler{}; InterpState mResampleState; std::bitset mFlags; u32 mNumCallbackBlocks{0_u32}; u32 mCallbackBlockOffset{0_u32}; struct TargetData { bool FilterActive{}; std::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 { f32 mAmbiHFScale{}, mAmbiLFScale{}; BandSplitter mAmbiSplitter; DirectParams mDryParams; std::array mWetParams; }; al::vector mChans{2}; Voice() = default; ~Voice() = default; Voice(const Voice&) = delete; Voice& operator=(Voice const&) = delete; void mix(State vstate, ContextBase *context, std::chrono::nanoseconds deviceTime, u32 samplesToDo); void prepare(DeviceBase *device); static void InitMixer(std::optional const &resopt); }; #endif /* CORE_VOICE_H */ kcat-openal-soft-75c0059/core/voice_change.h000066400000000000000000000006661512220627100206600ustar00rootroot00000000000000#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}; }; using LPVoiceChange = VoiceChange*; #endif /* VOICE_CHANGE_H */ kcat-openal-soft-75c0059/docs/000077500000000000000000000000001512220627100160655ustar00rootroot00000000000000kcat-openal-soft-75c0059/docs/3D7.1.txt000066400000000000000000000075431512220627100173330ustar00rootroot00000000000000Overview ======== 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 multichannel 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. kcat-openal-soft-75c0059/docs/ambdec.txt000066400000000000000000000210371512220627100200440ustar00rootroot00000000000000AmbDec 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 semiregular speaker setups is somewhat of a black art, so may take some time. kcat-openal-soft-75c0059/docs/ambisonics.txt000066400000000000000000000144111512220627100207560ustar00rootroot00000000000000OpenAL 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 to 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 arrangement 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 wideband 90-degree phase shift filter. This is generated by taking the ambisonic mix and deriving a front-stereo mix 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. kcat-openal-soft-75c0059/docs/env-vars.txt000066400000000000000000000115411512220627100203710ustar00rootroot00000000000000Useful 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. ALSOFT_EAX_TRACE_COMMITS Overrides the EAX trace-commits config option. This specifies whether EAX property commits are logged with trace messages. *** 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. kcat-openal-soft-75c0059/docs/hrtf.txt000066400000000000000000000072421512220627100175760ustar00rootroot00000000000000HRTF Support ============ Starting with OpenAL Soft 1.14, HRTFs can be used to enable enhanced spatialization for both 3D (mono) and multichannel sources, when used with headphones. This can be enabled using the 'hrtf' config option. For multichannel 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 (i.e. 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. kcat-openal-soft-75c0059/examples/000077500000000000000000000000001512220627100167535ustar00rootroot00000000000000kcat-openal-soft-75c0059/examples/alconvolve.c000066400000000000000000000443451512220627100213010ustar00rootroot00000000000000/* * 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; ALint 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; ALenum 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) { StreamPlayer *player; ALuint ir_buffer; ALuint filter; ALuint effect; ALuint slot; 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; } kcat-openal-soft-75c0059/examples/aldebug.cpp000066400000000000000000000270261512220627100210710ustar00rootroot00000000000000/* * 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 "config.h" #include #include #include #include #include #include #include #include #include #include #include "alnumeric.h" #include "fmt/base.h" #include "fmt/ostream.h" #include "fmt/std.h" #include "win_main_utf8.h" #if HAVE_CXXMODULES import gsl; import openal; #else #include "AL/alc.h" #include "AL/al.h" #include "AL/alext.h" #include "gsl/gsl" #endif namespace { using namespace std::string_view_literals; using DevicePtr = std::unique_ptr; 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{}; auto main(std::span args) -> int { /* Print out usage if -h was specified */ if(args.size() > 1 && (args[1] == "-h" || args[1] == "--help")) { fmt::println(std::cerr, "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(std::cerr, "Failed to open \"{}\", trying default", args[1]); args = args.subspan(2); } if(!device) device = DevicePtr{alcOpenDevice(nullptr)}; if(!device) { fmt::println(std::cerr, "Could not open a device!"); return 1; } if(!alcIsExtensionPresent(device.get(), "ALC_EXT_debug")) { fmt::println(std::cerr, "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)) /* NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast) */ 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); /* NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast) */ #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::to_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(std::cerr, "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(gsl::narrow(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(std::cerr, "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(), gsl::narrow(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, gsl::narrow(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(std::cerr, "{} 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)..."); std::ignore = 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()); fmt::println(""); 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) { auto args = std::vector(gsl::narrow(argc)); std::ranges::copy(std::views::counted(argv, argc), args.begin()); return main(std::span{args}); } kcat-openal-soft-75c0059/examples/aldirect.cpp000066400000000000000000000442661512220627100212620ustar00rootroot00000000000000/* * 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 "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "sndfile.h" #include "common/alhelpers.h" #include "fmt/base.h" #include "fmt/ostream.h" #include "win_main_utf8.h" #if HAVE_CXXMODULES import gsl; import openal; #else #include "AL/al.h" #include "AL/alc.h" #include "AL/alext.h" #include "gsl/gsl" #endif 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{}; using SndFilePtr = std::unique_ptr; /* LoadBuffer loads the named audio file into an OpenAL buffer object, and * returns the new buffer ID. */ auto LoadSound(ALCcontext *context, const std::string_view filename) -> ALuint { /* Open the audio file and check that it's usable. */ auto sfinfo = SF_INFO{}; auto sndfile = SndFilePtr{sf_open(std::string{filename}.c_str(), SFM_READ, &sfinfo)}; if(!sndfile) { fmt::println(std::cerr, "Could not open audio in {}: {}", filename, sf_strerror(sndfile.get())); return 0u; } if(sfinfo.frames < 1) { fmt::println(std::cerr, "Bad sample count in {} ({})", filename, sfinfo.frames); return 0u; } /* 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. */ enum class FormatType { Int16, Float, IMA4, MSADPCM }; auto 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; } auto byteblockalign = 0; auto 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. */ auto inf = SF_CHUNK_INFO{.id="fmt ", .id_size=4, .datalen=0, .data=nullptr}; auto *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. */ auto format = ALenum{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(std::cerr, "Unsupported channel count: {}", sfinfo.channels); return 0u; } if(sfinfo.frames/splblockalign > sf_count_t{std::numeric_limits::max()}/byteblockalign) { fmt::println(std::cerr, "Too many sample frames in {} ({})", filename, sfinfo.frames); return 0u; } /* Decode the whole audio file to a buffer. */ auto memstore = std::variant,std::vector,std::vector>{}; auto membuf = std::span{}; if(sample_format == FormatType::Int16) { auto &vec = memstore.emplace>(gsl::narrow(sfinfo.frames / splblockalign * sfinfo.channels)); const auto num_frames = sf_readf_short(sndfile.get(), vec.data(), sfinfo.frames); if(num_frames > 0) { const auto num_samples = gsl::narrow(num_frames * sfinfo.channels); membuf = std::as_writable_bytes(std::span{vec}.first(num_samples)); } } else if(sample_format == FormatType::Float) { auto &vec = memstore.emplace>(gsl::narrow(sfinfo.frames / splblockalign * sfinfo.channels)); const auto num_frames = sf_readf_float(sndfile.get(), vec.data(), sfinfo.frames); if(num_frames > 0) { const auto num_samples = gsl::narrow(num_frames * sfinfo.channels); membuf = std::as_writable_bytes(std::span{vec}.first(num_samples)); } } else { const auto count = sfinfo.frames / splblockalign * byteblockalign; auto &vec = memstore.emplace>(gsl::narrow(count)); const auto num_bytes = sf_read_raw(sndfile.get(), membuf.data(), count); if(num_bytes > 0) membuf = std::as_writable_bytes(std::span{vec}.first(gsl::narrow(num_bytes))); } if(membuf.empty()) { fmt::println(std::cerr, "Failed to read samples in {}", filename); return 0u; } fmt::println("Loading: {} ({}, {}hz)", filename, FormatName(format), sfinfo.samplerate); auto buffer = ALuint{}; alGenBuffersDirect(context, 1, &buffer); if(splblockalign > 1) alBufferiDirect(context, buffer, AL_UNPACK_BLOCK_ALIGNMENT_SOFT, splblockalign); alBufferDataDirect(context, buffer, format, membuf.data(), gsl::narrow(membuf.size()), sfinfo.samplerate); /* Check if an error occurred, and clean up if so. */ if(const auto err = alGetErrorDirect(context); err != AL_NO_ERROR) { fmt::println(std::cerr, "OpenAL Error: {}", alGetStringDirect(context, err)); if(buffer && alIsBufferDirect(context, buffer)) alDeleteBuffersDirect(context, 1, &buffer); return 0u; } return buffer; } auto main(std::span args) -> int { /* Print out usage if no arguments were specified */ if(args.size() < 2) { fmt::println(std::cerr, "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(std::cerr, "Failed to open \"{}\", trying default", args[1]); args = args.subspan(2); } if(!device) device = p_alcOpenDevice(nullptr); if(!device) { fmt::println(std::cerr, "Could not open a device!"); return 1; } if(!p_alcIsExtensionPresent(device, "ALC_EXT_direct_context")) { fmt::println(std::cerr, "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 auto devname = std::string{alcGetString(device, ALC_ALL_DEVICES_SPECIFIER)}; /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) */ 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)) /* NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast) */ LOAD_PROC(alcOpenDevice); LOAD_PROC(alcCloseDevice); LOAD_PROC(alcIsExtensionPresent); LOAD_PROC(alcGetProcAddress); LOAD_PROC(alcCreateContext); LOAD_PROC(alcDestroyContext); LOAD_PROC(alcGetProcAddress); /* NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast) */ #undef LOAD_PROC device = gsl::make_not_null(p_alcOpenDevice(devname.c_str())); } /* Load the Direct API functions we're using. */ #define LOAD_PROC(N) N = reinterpret_cast(p_alcGetProcAddress(device, #N)) /* NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast) */ 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); /* NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast) */ #undef LOAD_PROC /* Create the context. It doesn't need to be set as current to use with the * Direct API functions. */ auto *context = p_alcCreateContext(device, nullptr); if(!context) { p_alcCloseDevice(device); fmt::println(std::cerr, "Could not create a context!"); return 1; } /* Load the sound into a buffer. */ const auto buffer = LoadSound(context, args[0]); if(!buffer) { p_alcDestroyContext(context); p_alcCloseDevice(device); return 1; } /* Create the source to play the sound with. */ auto source = ALuint{0u}; alGenSourcesDirect(context, 1, &source); alSourceiDirect(context, source, AL_BUFFER, static_cast(buffer)); if(alGetErrorDirect(context) != AL_NO_ERROR) throw std::runtime_error{"Failed to setup sound source"}; /* Play the sound until it finishes. */ alSourcePlayDirect(context, source); auto state = ALenum{}; do { al_nssleep(10000000); alGetSourceiDirect(context, source, AL_SOURCE_STATE, &state); /* Get the source offset. */ auto offset = ALfloat{}; alGetSourcefDirect(context, source, AL_SEC_OFFSET, &offset); fmt::print(" \rOffset: {:.02f}", offset); std::cout.flush(); } 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 auto main(int argc, char **argv) -> int { auto args = std::vector(gsl::narrow(argc)); std::ranges::copy(std::views::counted(argv, argc), args.begin()); return main(std::span{args}); } kcat-openal-soft-75c0059/examples/alffplay.cpp000066400000000000000000002541011512220627100212600ustar00rootroot00000000000000/* * An example showing how to play a stream sync'd to video, using ffmpeg. */ #include "config.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 #include #include "almalloc.h" #include "alnumeric.h" #include "alstring.h" #include "common/alhelpers.hpp" #include "fmt/base.h" #include "fmt/ostream.h" #include "opthelpers.h" #include "pragmadefs.h" DIAGNOSTIC_PUSH std_pragma("GCC diagnostic ignored \"-Wconversion\"") std_pragma("GCC diagnostic ignored \"-Wold-style-cast\"") 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; } /* extern "C" */ #define SDL_MAIN_HANDLED #include "SDL3/SDL_events.h" #include "SDL3/SDL_main.h" #include "SDL3/SDL_render.h" #include "SDL3/SDL_video.h" #if HAVE_CXXMODULES import gsl; import openal; /* AL_APIENTRY is needed, but not exported from the module. */ #ifdef _WIN32 #define AL_APIENTRY __cdecl #else #define AL_APIENTRY #endif #else #include "AL/al.h" #include "AL/alc.h" #include "AL/alext.h" #include "gsl/gsl" #endif namespace { [[nodiscard]] constexpr auto DefineSDLColorspace(SDL_ColorType type, SDL_ColorRange range, SDL_ColorPrimaries primaries, SDL_TransferCharacteristics transfer, SDL_MatrixCoefficients matrix, SDL_ChromaLocation chromaloc) noexcept { return SDL_DEFINE_COLORSPACE(type, range, primaries, transfer, matrix, chromaloc); } constexpr auto AVNoPtsValue = AV_NOPTS_VALUE; constexpr auto AVErrorEOF = AVERROR_EOF; } /* namespace */ DIAGNOSTIC_POP 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; constexpr auto AppName = std::to_array("alffplay"); auto PlaybackGain = 1.0f; auto DirectOutMode = ALenum{AL_FALSE}; auto EnableWideStereo = false; auto EnableUhj = false; auto EnableSuperStereo = false; auto DisableVideo = false; auto alGetSourcei64vSOFT = LPALGETSOURCEI64VSOFT{}; auto alEventControlSOFT = LPALEVENTCONTROLSOFT{}; auto alEventCallbackSOFT = LPALEVENTCALLBACKSOFT{}; auto alBufferCallbackSOFT = LPALBUFFERCALLBACKSOFT{}; constexpr auto AVNoSyncThreshold = seconds{10}; constexpr auto VideoPictureQueueSize = 24; constexpr auto AudioSyncThreshold = seconds_d64{0.03}; constexpr auto AudioSampleCorrectionMax = milliseconds{50}; /* Averaging filter coefficient for audio sync. */ constexpr auto AudioDiffAvgNB = 20.0; const auto AudioAvgFilterCoeff = std::pow(0.01, 1.0/AudioDiffAvgNB); /* NOLINT(cert-err58-cpp) */ /* Per-buffer size, in time */ constexpr auto AudioBufferTime = milliseconds{20}; /* Buffer total size, in time (should be divisible by the buffer time) */ constexpr auto AudioBufferTotalTime = milliseconds{800}; constexpr auto AudioBufferCount = AudioBufferTotalTime / AudioBufferTime; enum { FF_MOVIE_DONE_EVENT = SDL_EVENT_USER }; enum class SyncMaster { Audio, Video, External, Default = Audio }; inline auto get_avtime() -> microseconds { return microseconds{av_gettime()}; } /* Define unique_ptrs to auto-cleanup associated ffmpeg objects. */ using AVIOContextPtr = std::unique_ptr; using AVFormatCtxPtr = std::unique_ptr; using AVCodecCtxPtr = std::unique_ptr; using AVPacketPtr = std::unique_ptr; using AVFramePtr = std::unique_ptr; using SwrContextPtr = std::unique_ptr; using SwsContextPtr = std::unique_ptr; struct SDLProps { SDL_PropertiesID mProperties{}; SDLProps() : mProperties{SDL_CreateProperties()} { } ~SDLProps() { SDL_DestroyProperties(mProperties); } SDLProps(const SDLProps&) = delete; auto operator=(const SDLProps&) -> SDLProps& = delete; [[nodiscard]] auto getid() const noexcept -> SDL_PropertiesID { return mProperties; } [[nodiscard]] auto setPointer(gsl::czstring const name, void *value) const { return SDL_SetPointerProperty(mProperties, name, value); } [[nodiscard]] auto setString(gsl::czstring const name, gsl::czstring const value) const { return SDL_SetStringProperty(mProperties, name, value); } [[nodiscard]] auto setInt(gsl::czstring const name, Sint64 const value) const { return SDL_SetNumberProperty(mProperties, name, value); } }; struct TextureFormatEntry { AVPixelFormat avformat; SDL_PixelFormat sdlformat; }; constexpr auto TextureFormatMap = std::array{ TextureFormatEntry{AV_PIX_FMT_RGB8, SDL_PIXELFORMAT_RGB332}, TextureFormatEntry{AV_PIX_FMT_RGB444, SDL_PIXELFORMAT_XRGB4444}, TextureFormatEntry{AV_PIX_FMT_RGB555, SDL_PIXELFORMAT_XRGB1555}, TextureFormatEntry{AV_PIX_FMT_BGR555, SDL_PIXELFORMAT_XBGR1555}, TextureFormatEntry{AV_PIX_FMT_RGB565, SDL_PIXELFORMAT_RGB565}, TextureFormatEntry{AV_PIX_FMT_BGR565, SDL_PIXELFORMAT_BGR565}, TextureFormatEntry{AV_PIX_FMT_RGB24, SDL_PIXELFORMAT_RGB24}, TextureFormatEntry{AV_PIX_FMT_BGR24, SDL_PIXELFORMAT_BGR24}, TextureFormatEntry{AV_PIX_FMT_0RGB32, SDL_PIXELFORMAT_XRGB8888}, TextureFormatEntry{AV_PIX_FMT_0BGR32, SDL_PIXELFORMAT_XBGR8888}, TextureFormatEntry{AV_PIX_FMT_NE(RGB0, 0BGR), SDL_PIXELFORMAT_RGBX8888}, TextureFormatEntry{AV_PIX_FMT_NE(BGR0, 0RGB), SDL_PIXELFORMAT_BGRX8888}, TextureFormatEntry{AV_PIX_FMT_RGB32, SDL_PIXELFORMAT_ARGB8888}, TextureFormatEntry{AV_PIX_FMT_RGB32_1, SDL_PIXELFORMAT_RGBA8888}, TextureFormatEntry{AV_PIX_FMT_BGR32, SDL_PIXELFORMAT_ABGR8888}, TextureFormatEntry{AV_PIX_FMT_BGR32_1, SDL_PIXELFORMAT_BGRA8888}, TextureFormatEntry{AV_PIX_FMT_YUV420P, SDL_PIXELFORMAT_IYUV}, TextureFormatEntry{AV_PIX_FMT_YUYV422, SDL_PIXELFORMAT_YUY2}, TextureFormatEntry{AV_PIX_FMT_UYVY422, SDL_PIXELFORMAT_UYVY}, TextureFormatEntry{AV_PIX_FMT_NV12, SDL_PIXELFORMAT_NV12}, TextureFormatEntry{AV_PIX_FMT_NV21, SDL_PIXELFORMAT_NV21}, }; using ChannelData = std::variant>; struct ChannelLayout : public AVChannelLayout { ChannelLayout() noexcept : AVChannelLayout{} { } ChannelLayout(const ChannelLayout &rhs) : AVChannelLayout{} { av_channel_layout_copy(this, &rhs); } explicit ChannelLayout(const AVChannelLayout &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; } [[nodiscard]] auto getChannels() const noexcept LIFETIMEBOUND -> ChannelData { /* NOLINTBEGIN(*-union-access) */ if(this->order == AV_CHANNEL_ORDER_CUSTOM) { if(this->u.map && this->nb_channels > 0) return std::span{this->u.map, gsl::narrow_cast(this->nb_channels)}; return std::span{}; } return this->u.mask; /* NOLINTEND(*-union-access) */ } }; 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}; auto getPacket() -> AVPacketPtr { auto plock = std::unique_lock{mPacketMutex}; mPacketCond.wait(plock, [this] { return !mPackets.empty() || mFinished; }); if(mPackets.empty()) return nullptr; auto ret = std::move(mPackets.front()); mPackets.pop_front(); mTotalSize -= gsl::narrow_cast(ret->size); return ret; } public: explicit DataQueue(size_t size_limit) : mSizeLimit{size_limit} { } int sendPacket(AVCodecContext *codecctx) { auto packet = getPacket(); auto ret = int{}; { auto flock = std::unique_lock{mFrameMutex}; mInFrameCond.wait(flock, [this,codecctx,pkt=packet.get(),&ret] { ret = avcodec_send_packet(codecctx, pkt); if(ret != AVERROR(EAGAIN)) return true; mOutFrameCond.notify_all(); return false; }); } mOutFrameCond.notify_all(); if(!packet) { if(!ret) return AVErrorEOF; fmt::println(std::cerr, "Failed to send flush packet: {}", ret); return ret; } if(ret < 0) fmt::println(std::cerr, "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, [this,codecctx,frame,&ret] { ret = avcodec_receive_frame(codecctx, frame); if(ret != AVERROR(EAGAIN)) return true; mInFrameCond.notify_all(); return false; }); } mInFrameCond.notify_all(); return ret; } void setFinished() { { auto plock = std::lock_guard{mPacketMutex}; mFinished = true; } mPacketCond.notify_all(); } void flush() { { auto plock = std::lock_guard{mPacketMutex}; mFinished = true; mPackets.clear(); mTotalSize = 0; } mPacketCond.notify_all(); } auto put(const AVPacket *pkt) -> bool { { auto plock = std::lock_guard{mPacketMutex}; if(mTotalSize >= mSizeLimit || mFinished) return false; auto *newpkt = mPackets.emplace_back(AVPacketPtr{av_packet_alloc()}).get(); if(av_packet_ref(newpkt, pkt) == 0) mTotalSize += gsl::narrow_cast(newpkt->size); else mPackets.pop_back(); } mPacketCond.notify_all(); 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}; /* The PTS of the start of the source playback. */ nanoseconds mStartPts{nanoseconds::min()}; /* The steady_clock time point the audio stream stopped at. */ nanoseconds mEndTime{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{}; std::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 LIFETIMEBOUND) : mMovie(movie) { mConnected.test_and_set(std::memory_order_relaxed); } ~AudioState() { if(mSource) alDeleteSources(1, &mSource); if(mBuffers[0]) alDeleteBuffers(gsl::narrow_cast(mBuffers.size()), mBuffers.data()); av_freep(static_cast(mSamples.data())); } auto eventCallback(ALenum eventType, ALuint object, ALuint param, std::string_view message) noexcept -> void; auto bufferCallback(const std::span data) noexcept -> ALsizei; [[nodiscard]] auto getClockNoLock() const -> nanoseconds; [[nodiscard]] auto getClock() -> nanoseconds { const auto lock = std::lock_guard{mSrcMutex}; return getClockNoLock(); } [[nodiscard]] auto startPlayback() -> bool; [[nodiscard]] auto getSync() -> int; [[nodiscard]] auto decodeFrame() -> int; [[nodiscard]] auto readAudio(std::span samples, int &sample_skip) -> bool; auto readAudio(int sample_skip) -> void; void 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 */ unsigned int mSDLFormat{SDL_PIXELFORMAT_UNKNOWN}; int mAVFormat{AV_PIX_FMT_NONE}; bool mFirstUpdate{true}; std::atomic mEOS{false}; std::atomic mFinalUpdate{false}; explicit VideoState(MovieState &movie LIFETIMEBOUND) : mMovie(movie) { } ~VideoState() { if(mImage) SDL_DestroyTexture(mImage); mImage = nullptr; } auto getClock() -> nanoseconds; void display(SDL_Renderer *renderer, AVFrame *frame) const; void updateVideo(SDL_Window *screen, SDL_Renderer *renderer, bool redraw); void handler(); }; struct MovieState { AVIOContextPtr mIOContext; AVFormatCtxPtr mFormatCtx; SyncMaster mAVSyncType{SyncMaster::Default}; microseconds mClockBase{microseconds::min()}; std::atomic mQuit{false}; AudioState mAudio; VideoState mVideo; std::atomic 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 auto decode_interrupt_cb(void *ctx) -> int; auto prepare() -> bool; void setTitle(SDL_Window *window) const; void stop(); [[nodiscard]] auto getClock() const -> nanoseconds; [[nodiscard]] auto getMasterClock() -> nanoseconds; [[nodiscard]] auto getDuration() const -> nanoseconds; auto streamComponentOpen(AVStream *stream) -> bool; void parse_handler(); }; auto AudioState::getClockNoLock() const -> nanoseconds { /* The audio clock is the timestamp of the sample currently being heard. */ if(mStartPts == nanoseconds::min()) return nanoseconds::zero(); /* If the stream ended, count from the ending time to ensure any video can * keep going. */ if(mEndTime > nanoseconds::min()) return std::chrono::steady_clock::now().time_since_epoch() - mEndTime + mCurrentPts; /* This more safely converts fixed32 to nanoseconds, avoiding overflow * unlike a normal duration_cast call. */ static constexpr auto sec32_to_nanoseconds = [](const fixed32 s) -> nanoseconds { static constexpr auto one32 = fixed32{seconds{1}}; return seconds{s/one32} + duration_cast(s%one32); }; if(!mBufferData.empty()) { /* With a callback buffer, mStartPts is the timestamp of the first * sample frame played. The audio clock, then, is that plus the current * source offset. */ auto offset = std::array{}; if(alGetSourcei64vSOFT) alGetSourcei64vSOFT(mSource, AL_SAMPLE_OFFSET_LATENCY_SOFT, offset.data()); else { auto ioffset = ALint{}; 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. */ auto status = ALint{}; alGetSourcei(mSource, AL_SOURCE_STATE, &status); auto pts = nanoseconds{}; if(status == AL_PLAYING || status == AL_PAUSED) { const auto sec_fixed32 = fixed32{offset[0] / mCodecCtx->sample_rate}; pts = mStartPts + sec32_to_nanoseconds(sec_fixed32) - nanoseconds{offset[1]}; } 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 auto woffset = mWritePos.load(std::memory_order_acquire); const auto roffset = mReadPos.load(std::memory_order_relaxed); /* Account for the write offset wrapping behind the read offset. */ const auto readable = (woffset < roffset)*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. */ auto pts = mCurrentPts; if(mSource) { auto offset = std::array{}; if(alGetSourcei64vSOFT) alGetSourcei64vSOFT(mSource, AL_SAMPLE_OFFSET_LATENCY_SOFT, offset.data()); else { auto ioffset = ALint{}; alGetSourcei(mSource, AL_SAMPLE_OFFSET, &ioffset); offset[0] = ALint64SOFT{ioffset} << 32; } auto queued = ALint{}; auto status = ALint{}; 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 += sec32_to_nanoseconds(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 pts; } auto AudioState::startPlayback() -> bool { if(!mBufferData.empty()) { const auto woffset = mWritePos.load(std::memory_order_acquire); const auto roffset = mReadPos.load(std::memory_order_relaxed); /* Account for the write offset wrapping behind the read offset. */ const auto readable = (woffset < roffset)*mBufferData.size() + woffset - roffset; if(readable == 0) return false; const auto nanosamples = nanoseconds{seconds{readable / mFrameSize}}; mStartPts = mCurrentPts - nanosamples/mCodecCtx->sample_rate; } else { auto queued = ALint{}; alGetSourcei(mSource, AL_BUFFERS_QUEUED, &queued); if(queued == 0) return false; /* Subtract the total buffer queue time from the current pts to get the * pts of the start of the queue. */ mStartPts = mCurrentPts - AudioBufferTime*queued; } alSourcePlay(mSource); return true; } auto AudioState::getSync() -> int { 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 gsl::narrow_cast(duration_cast(diff*mCodecCtx->sample_rate).count()); } auto AudioState::decodeFrame() -> int { do { while(const auto ret = mQueue.receiveFrame(mCodecCtx.get(), mDecodedFrame.get())) { if(ret == AVErrorEOF) return 0; fmt::println(std::cerr, "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) * gsl::narrow_cast(mDecodedFrame->best_effort_timestamp)}); if(mDecodedFrame->nb_samples > mSamplesMax) { av_freep(static_cast(mSamples.data())); if(av_samples_alloc(mSamples.data(), nullptr, mCodecCtx->ch_layout.nb_channels, mDecodedFrame->nb_samples, mDstSampleFmt, 0) < 0) { mSamplesMax = 0; mSamplesSpan = {}; return 0; } mSamplesMax = mDecodedFrame->nb_samples; mSamplesSpan = {mSamples[0], gsl::narrow_cast(mSamplesMax)*mFrameSize}; } /* Return the amount of sample frames converted */ const auto 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. */ void sample_dup(std::span out, std::span in, size_t count) { auto dstiter = out.begin(); std::ranges::for_each(std::views::iota(0_uz, count), [in,&dstiter](size_t) { dstiter = std::ranges::copy(in, dstiter).out; }); } auto AudioState::readAudio(std::span samples, int &sample_skip) -> bool { auto audio_size = 0u; /* Read the next chunk of data, refill the buffer, and queue it * on the source. */ const auto length = samples.size() / mFrameSize; while(mSamplesLen > 0 && audio_size < length) { auto rem = length - audio_size; if(mSamplesPos >= 0) { rem = std::min(rem, gsl::narrow_cast(mSamplesLen - mSamplesPos)); const auto boffset = gsl::narrow_cast(mSamplesPos) * size_t{mFrameSize}; std::ranges::copy(mSamplesSpan | std::views::drop(boffset) | std::views::take(rem*mFrameSize), samples.begin()); } else { rem = std::min(rem, gsl::narrow_cast(-mSamplesPos)); /* Add samples by copying the first sample */ sample_dup(samples, mSamplesSpan.first(mFrameSize), rem); } mSamplesPos += gsl::narrow_cast(rem); mCurrentPts += nanoseconds{seconds{rem}} / mCodecCtx->sample_rate; samples = samples.subspan(rem * mFrameSize); audio_size += rem; while(mSamplesPos >= mSamplesLen) { mSamplesLen = decodeFrame(); mSamplesPos = std::min(mSamplesLen, sample_skip); if(mSamplesLen <= 0) break; sample_skip -= mSamplesPos; /* Adjust the start time and current pts by the amount we're * skipping/duplicating, so that the clock remains correct for the * current stream position. */ const auto skip = nanoseconds{seconds{mSamplesPos}} / mCodecCtx->sample_rate; mStartPts -= skip; mCurrentPts += skip; } } if(audio_size <= 0) return false; if(audio_size < length) { const auto rem = length - audio_size; const auto audio_data = std::array{samples.data()}; av_samples_set_silence(audio_data.data(), gsl::narrow_cast(audio_size), gsl::narrow_cast(rem), mCodecCtx->ch_layout.nb_channels, mDstSampleFmt); mCurrentPts += nanoseconds{seconds{rem}} / mCodecCtx->sample_rate; } return true; } auto AudioState::readAudio(int sample_skip) -> void { auto woffset = mWritePos.load(std::memory_order_acquire); const auto roffset = mReadPos.load(std::memory_order_relaxed); while(mSamplesLen > 0) { const auto nsamples = ((roffset > woffset) ? roffset-woffset-1 : (roffset == 0) ? (mBufferData.size()-woffset-1) : (mBufferData.size()-woffset)) / mFrameSize; if(!nsamples) break; if(mSamplesPos < 0) { const auto rem = std::min(nsamples, gsl::narrow_cast(-mSamplesPos)); sample_dup(mBufferData|std::views::drop(woffset), mSamplesSpan.first(mFrameSize), rem); woffset += rem * mFrameSize; if(woffset == mBufferData.size()) woffset = 0; mWritePos.store(woffset, std::memory_order_release); mCurrentPts += nanoseconds{seconds{rem}} / mCodecCtx->sample_rate; mSamplesPos += gsl::narrow_cast(rem); continue; } if(const auto rem = std::min(nsamples, gsl::narrow_cast(mSamplesLen-mSamplesPos))) { const auto boffset = gsl::narrow_cast(mSamplesPos) * size_t{mFrameSize}; const auto nbytes = rem * mFrameSize; std::ranges::copy(mSamplesSpan | std::views::drop(boffset) | std::views::take(nbytes), (mBufferData | std::views::drop(woffset)).begin()); woffset += nbytes; if(woffset == mBufferData.size()) woffset = 0; mWritePos.store(woffset, std::memory_order_release); mCurrentPts += nanoseconds{seconds{rem}} / mCodecCtx->sample_rate; mSamplesPos += gsl::narrow_cast(rem); } while(mSamplesPos >= mSamplesLen) { mSamplesLen = decodeFrame(); mSamplesPos = std::min(mSamplesLen, sample_skip); if(mSamplesLen <= 0) return; sample_skip -= mSamplesPos; const auto skip = nanoseconds{seconds{mSamplesPos}} / mCodecCtx->sample_rate; mStartPts -= skip; mCurrentPts += skip; } } } auto AL_APIENTRY AudioState::eventCallback(ALenum eventType, ALuint object, ALuint param, std::string_view message) noexcept -> void { 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_all(); 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, message); if(eventType == AL_EVENT_TYPE_DISCONNECTED_SOFT) { { auto lock = std::lock_guard{mSrcMutex}; mConnected.clear(std::memory_order_release); } mSrcCond.notify_all(); } } auto AudioState::bufferCallback(const std::span data) noexcept -> ALsizei { auto output = data.begin(); auto roffset = mReadPos.load(std::memory_order_acquire); while(const auto rem = gsl::narrow_cast(std::distance(output, data.end()))) { const auto woffset = mWritePos.load(std::memory_order_relaxed); if(woffset == roffset) break; auto todo = ((woffset < roffset) ? mBufferData.size() : woffset) - roffset; todo = std::min(todo, rem); output = std::ranges::copy(mBufferData | std::views::drop(roffset) | std::views::take(todo), output).out; roffset += todo; if(roffset == mBufferData.size()) roffset = 0; } mReadPos.store(roffset, std::memory_order_release); return gsl::narrow_cast(std::distance(data.begin(), output)); } void AudioState::handler() { static constexpr auto evt_types = std::array{{ AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT, AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT, AL_EVENT_TYPE_DISCONNECTED_SOFT}}; auto srclock = std::unique_lock{mSrcMutex, std::defer_lock}; auto sleep_time = milliseconds{AudioBufferTime / 2}; if(alEventControlSOFT) { static constexpr auto callback = [](ALenum eventType, ALuint object, ALuint param, ALsizei length, const ALchar *message, void *userParam) noexcept -> void { static_cast(userParam)->eventCallback(eventType, object, param, std::string_view{message, gsl::narrow_cast(length)}); }; alEventControlSOFT(evt_types.size(), evt_types.data(), AL_TRUE); alEventCallbackSOFT(callback, this); sleep_time = AudioBufferTotalTime; } const auto _ = gsl::finally([] { if(alEventControlSOFT) { alEventControlSOFT(evt_types.size(), evt_types.data(), AL_FALSE); alEventCallbackSOFT(nullptr, nullptr); } }); /* 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), otherwise * only 1st order is supported with AL_EXT_BFORMAT. */ const auto max_ambi_order = has_bfmt_hoa ? 14u : 1u; auto ambi_order = 0u; /* Find a suitable format for OpenAL. */ const auto layoutmask = std::invoke([layout=ChannelLayout{mCodecCtx->ch_layout}] { auto chansvar = layout.getChannels(); if(auto *mask = std::get_if(&chansvar)) return *mask; return 0_u64; }); 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_SURROUND /* a.k.a. 3.0 */ && EnableUhj) { mDstChanLayout = layoutmask; mFrameSize *= 3; mFormat = AL_FORMAT_UHJ3CHN_FLOAT32_SOFT; } 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. */ const auto order = gsl::narrow_cast( std::sqrt(mCodecCtx->ch_layout.nb_channels)) - 1u; if(const auto channels = (order+1u) * (order+1u); std::cmp_equal(channels, mCodecCtx->ch_layout.nb_channels) || std::cmp_equal(channels+2u, mCodecCtx->ch_layout.nb_channels)) { ambi_order = std::min(order, max_ambi_order); mFrameSize *= (ambi_order+1u) * (ambi_order+1u); 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_SURROUND && EnableUhj) { mDstChanLayout = layoutmask; mFrameSize *= 3; mFormat = AL_FORMAT_UHJ3CHN8_SOFT; } 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) { const auto order = gsl::narrow_cast( std::sqrt(mCodecCtx->ch_layout.nb_channels)) - 1u; if(const auto channels = (order+1u) * (order+1u); std::cmp_equal(channels, mCodecCtx->ch_layout.nb_channels) || std::cmp_equal(channels+2u, mCodecCtx->ch_layout.nb_channels)) { ambi_order = std::min(order, max_ambi_order); mFrameSize *= (ambi_order+1u) * (ambi_order+1u); 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_SURROUND && EnableUhj) { mDstChanLayout = layoutmask; mFrameSize *= 3; mFormat = AL_FORMAT_UHJ3CHN16_SOFT; } 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) { const auto order = gsl::narrow_cast( std::sqrt(mCodecCtx->ch_layout.nb_channels)) - 1u; if(const auto channels = (order+1u) * (order+1u); std::cmp_equal(channels, mCodecCtx->ch_layout.nb_channels) || std::cmp_equal(channels+2u, mCodecCtx->ch_layout.nb_channels)) { ambi_order = std::min(order, max_ambi_order); mFrameSize *= (ambi_order+1u) * (ambi_order+1u); 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(std::cerr, "Failed to allocate audio frame"); return; } 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) { auto errstr = std::array{}; fmt::println(std::cerr, "Failed to allocate SwrContext: {}", av_make_error_string(errstr.data(), errstr.size(), err)); return; } 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. */ auto mtx = std::vector(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 { auto layout = ChannelLayout{}; av_channel_layout_from_mask(&layout, mDstChanLayout); 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) { auto errstr = std::array{}; fmt::println(std::cerr, "Failed to allocate SwrContext: {}", av_make_error_string(errstr.data(), errstr.size(), err)); return; } } if(const auto err = swr_init(mSwresCtx.get())) { auto errstr = std::array{}; fmt::println(std::cerr, "Failed to initialize audio converter: {}", av_make_error_string(errstr.data(), errstr.size(), err)); return; } alGenBuffers(gsl::narrow_cast(mBuffers.size()), mBuffers.data()); alGenSources(1, &mSource); /* The gain limit is the internal max that the calculated source gain is * clamped to after cone and distance attenuation, the filter gain, and * listener gain are applied. Since none of those apply here, there's no * need to raise the source's max gain beyond that limit. */ const auto maxgain = alIsExtensionPresent("AL_SOFT_gain_clamp_ex") ? alGetFloat(AL_GAIN_LIMIT_SOFT) : 1.0f; alSourcef(mSource, AL_MAX_GAIN, maxgain); /* The source's AL_GAIN can really be set to any non-negative finite value, * but without cone and distance attenuation, there's no real point to * setting it greater than the max gain. */ auto gain = PlaybackGain; if(gain > maxgain) { fmt::println(std::cerr, "Limiting requested gain {:+}dB ({}) to max {:+}dB ({})", std::round(std::log10(gain)*2000.0f) / 100.0f, gain, std::round(std::log10(maxgain)*2000.0f) / 100.0f, maxgain); gain = maxgain; } else fmt::println("Setting gain {:+}dB ({})", std::round(std::log10(gain)*2000.0f) / 100.0f, gain); alSourcef(mSource, AL_GAIN, gain); if(DirectOutMode) alSourcei(mSource, AL_DIRECT_CHANNELS_SOFT, DirectOutMode); if(EnableWideStereo) { static constexpr auto angles = std::array{gsl::narrow_cast(std::numbers::pi / 3.0), gsl::narrow_cast(-std::numbers::pi / 3.0)}; alSourcefv(mSource, AL_STEREO_ANGLES, angles.data()); } if(has_bfmt_ex) { std::ranges::for_each(mBuffers, [](const ALuint bufid) { alBufferi(bufid, AL_AMBISONIC_LAYOUT_SOFT, AL_ACN_SOFT); alBufferi(bufid, AL_AMBISONIC_SCALING_SOFT, AL_SN3D_SOFT); }); } if(ambi_order > 1) { std::ranges::for_each(mBuffers, [ambi_order](const ALuint bufid) { alBufferi(bufid, AL_UNPACK_AMBISONIC_ORDER_SOFT, gsl::narrow_cast(ambi_order)); }); } if(EnableSuperStereo) alSourcei(mSource, AL_STEREO_MODE_SOFT, AL_SUPER_STEREO_SOFT); if(alGetError() != AL_NO_ERROR) return; auto samples = std::vector{}; auto callback_ok = false; if(alBufferCallbackSOFT) { static constexpr auto callback = [](void *userptr, void *data, ALsizei size) noexcept -> ALsizei { return static_cast(userptr)->bufferCallback( std::views::counted(static_cast(data), size)); }; alBufferCallbackSOFT(mBuffers[0], mFormat, mCodecCtx->sample_rate, callback, this); alSourcei(mSource, AL_BUFFER, as_signed(mBuffers[0])); if(alGetError() != AL_NO_ERROR) { fmt::println(std::cerr, "Failed to set buffer callback"); alSourcei(mSource, AL_BUFFER, 0); } else { const auto numsamples = duration_cast(mCodecCtx->sample_rate * AudioBufferTotalTime).count(); mBufferData.resize(gsl::narrow_cast(numsamples) * mFrameSize); std::ranges::fill(mBufferData, uint8_t{}); mReadPos.store(0, std::memory_order_relaxed); mWritePos.store(0, std::memory_order_relaxed); auto refresh = ALCint{}; alcGetIntegerv(alcGetContextsDevice(alcGetCurrentContext()), ALC_REFRESH, 1, &refresh); sleep_time = milliseconds{seconds{1}} / refresh; callback_ok = true; } } if(!callback_ok) { auto buffer_len = duration_cast(mCodecCtx->sample_rate * AudioBufferTime).count(); if(buffer_len > 0) samples.resize(gsl::narrow_cast(buffer_len) * mFrameSize); } /* Prefill the codec buffer. */ auto sender [[maybe_unused]] = std::async(std::launch::async, [this] { while(true) { const auto ret = mQueue.sendPacket(mCodecCtx.get()); if(ret == AVErrorEOF) break; } }); if(alIsExtensionPresent("AL_SOFT_source_start_delay")) { /* Start after a short delay, to give other threads a chance to get * buffered. Prerolling would be better here, but short of that, this * will do. */ const auto start_delay = round(AudioBufferTotalTime/2 * mCodecCtx->sample_rate).count(); alSourcei(mSource, AL_SAMPLE_OFFSET, -gsl::narrow_cast(start_delay)); } srclock.lock(); mSamplesLen = decodeFrame(); mSamplesPos = 0; 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; } auto state = ALenum{}; 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 { auto processed = ALint{}; auto queued = ALint{}; /* First remove any processed buffers. */ alGetSourcei(mSource, AL_BUFFERS_PROCESSED, &processed); while(processed > 0) { auto bid = ALuint{}; alSourceUnqueueBuffers(mSource, 1, &bid); --processed; } /* Refill the buffer queue. */ auto sync_skip = getSync(); alGetSourcei(mSource, AL_BUFFERS_QUEUED, &queued); while(gsl::narrow_cast(queued) < mBuffers.size()) { /* Read the next chunk of data, filling the buffer, and queue * it on the source. */ if(!readAudio(samples, sync_skip)) break; const auto bufid = mBuffers[mBufferIdx]; mBufferIdx = gsl::narrow_cast((mBufferIdx+1_uz) % mBuffers.size()); alBufferData(bufid, mFormat, samples.data(), gsl::narrow_cast(samples.size()), 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); 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(const auto err = alGetError()) fmt::println(std::cerr, "Got AL error: {:#x} ({})", as_unsigned(err), alGetString(err)); mSrcCond.wait_for(srclock, sleep_time); } mEndTime = std::chrono::steady_clock::now().time_since_epoch(); alSourceRewind(mSource); alSourcei(mSource, AL_BUFFER, 0); } auto VideoState::getClock() -> nanoseconds { /* NOTE: This returns incorrect times while not playing. */ auto displock = std::lock_guard{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 - gsl::narrow_cast(frame->crop_left+frame->crop_right); auto frame_height = frame->height - gsl::narrow_cast(frame->crop_top+frame->crop_bottom); const auto src_rect = SDL_FRect{gsl::narrow_cast(frame->crop_left), gsl::narrow_cast(frame->crop_top), gsl::narrow_cast(frame_width), gsl::narrow_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) { auto read_idx = mPictQRead.load(std::memory_order_relaxed); auto *vp = &mPictQ[read_idx]; auto clocktime = mMovie.getMasterClock(); auto updated = false; while(true) { auto next_idx = (read_idx+1) % mPictQ.size(); if(next_idx == mPictQWrite.load(std::memory_order_acquire)) break; auto *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_all(); return; } auto *frame = vp->mFrame.get(); if(updated) { mPictQRead.store(read_idx, std::memory_order_release); std::unique_lock{mPictQMutex}.unlock(); mPictQCond.notify_all(); /* allocate or resize the buffer! */ if(!mImage || mWidth != frame->width || mHeight != frame->height || frame->format != mAVFormat) { if(mImage) SDL_DestroyTexture(mImage); mImage = nullptr; mSwscaleCtx = nullptr; const auto fmtiter = std::ranges::find(TextureFormatMap, frame->format, &TextureFormatEntry::avformat); if(fmtiter != TextureFormatMap.end()) { auto const props = SDLProps{}; std::ignore = props.setInt(SDL_PROP_TEXTURE_CREATE_FORMAT_NUMBER, fmtiter->sdlformat); std::ignore = props.setInt(SDL_PROP_TEXTURE_CREATE_ACCESS_NUMBER, SDL_TEXTUREACCESS_STREAMING); std::ignore = props.setInt(SDL_PROP_TEXTURE_CREATE_WIDTH_NUMBER, frame->width); std::ignore = props.setInt(SDL_PROP_TEXTURE_CREATE_HEIGHT_NUMBER, frame->height); /* Should be a better way to check YCbCr vs RGB. */ const auto ctype = (frame->format == AV_PIX_FMT_YUV420P || frame->format == AV_PIX_FMT_YUYV422 || frame->format == AV_PIX_FMT_UYVY422 || frame->format == AV_PIX_FMT_NV12 || frame->format == AV_PIX_FMT_NV21) ? SDL_COLOR_TYPE_YCBCR : SDL_COLOR_TYPE_RGB; const auto crange = std::invoke([frame] { switch(frame->color_range) { case AVCOL_RANGE_UNSPECIFIED: return SDL_COLOR_RANGE_UNKNOWN; case AVCOL_RANGE_MPEG: return SDL_COLOR_RANGE_LIMITED; case AVCOL_RANGE_JPEG: return SDL_COLOR_RANGE_FULL; case AVCOL_RANGE_NB: break; } return SDL_COLOR_RANGE_UNKNOWN; }); const auto cprims = std::invoke([frame] { switch(frame->color_primaries) { case AVCOL_PRI_RESERVED0: break; case AVCOL_PRI_BT709: return SDL_COLOR_PRIMARIES_BT709; case AVCOL_PRI_UNSPECIFIED: return SDL_COLOR_PRIMARIES_UNSPECIFIED; case AVCOL_PRI_RESERVED: break; case AVCOL_PRI_BT470M: return SDL_COLOR_PRIMARIES_BT470M; case AVCOL_PRI_BT470BG: return SDL_COLOR_PRIMARIES_BT470BG; case AVCOL_PRI_SMPTE170M: return SDL_COLOR_PRIMARIES_BT601; case AVCOL_PRI_SMPTE240M: return SDL_COLOR_PRIMARIES_SMPTE240; case AVCOL_PRI_FILM: return SDL_COLOR_PRIMARIES_GENERIC_FILM; case AVCOL_PRI_BT2020: return SDL_COLOR_PRIMARIES_BT2020; case AVCOL_PRI_SMPTE428: return SDL_COLOR_PRIMARIES_XYZ; case AVCOL_PRI_SMPTE431: return SDL_COLOR_PRIMARIES_SMPTE431; case AVCOL_PRI_SMPTE432: return SDL_COLOR_PRIMARIES_SMPTE432; case AVCOL_PRI_EBU3213: return SDL_COLOR_PRIMARIES_EBU3213; case AVCOL_PRI_NB: break; } return SDL_COLOR_PRIMARIES_UNKNOWN; }); const auto ctransfer = std::invoke([frame] { switch(frame->color_trc) { case AVCOL_TRC_RESERVED0: break; case AVCOL_TRC_BT709: return SDL_TRANSFER_CHARACTERISTICS_BT709; case AVCOL_TRC_UNSPECIFIED: return SDL_TRANSFER_CHARACTERISTICS_UNSPECIFIED; case AVCOL_TRC_RESERVED: break; case AVCOL_TRC_GAMMA22: return SDL_TRANSFER_CHARACTERISTICS_GAMMA22; case AVCOL_TRC_GAMMA28: return SDL_TRANSFER_CHARACTERISTICS_GAMMA28; case AVCOL_TRC_SMPTE170M: return SDL_TRANSFER_CHARACTERISTICS_BT601; case AVCOL_TRC_SMPTE240M: return SDL_TRANSFER_CHARACTERISTICS_SMPTE240; case AVCOL_TRC_LINEAR: return SDL_TRANSFER_CHARACTERISTICS_LINEAR; case AVCOL_TRC_LOG: return SDL_TRANSFER_CHARACTERISTICS_LOG100; case AVCOL_TRC_LOG_SQRT: return SDL_TRANSFER_CHARACTERISTICS_LOG100_SQRT10; case AVCOL_TRC_IEC61966_2_4: return SDL_TRANSFER_CHARACTERISTICS_IEC61966; case AVCOL_TRC_BT1361_ECG: return SDL_TRANSFER_CHARACTERISTICS_BT1361; case AVCOL_TRC_IEC61966_2_1: return SDL_TRANSFER_CHARACTERISTICS_SRGB; case AVCOL_TRC_BT2020_10: return SDL_TRANSFER_CHARACTERISTICS_BT2020_10BIT; case AVCOL_TRC_BT2020_12: return SDL_TRANSFER_CHARACTERISTICS_BT2020_12BIT; case AVCOL_TRC_SMPTE2084: return SDL_TRANSFER_CHARACTERISTICS_PQ; case AVCOL_TRC_SMPTE428: return SDL_TRANSFER_CHARACTERISTICS_SMPTE428; case AVCOL_TRC_ARIB_STD_B67: return SDL_TRANSFER_CHARACTERISTICS_HLG; case AVCOL_TRC_NB: break; } return SDL_TRANSFER_CHARACTERISTICS_UNKNOWN; }); const auto cmatrix = std::invoke([frame] { switch(frame->colorspace) { case AVCOL_SPC_RGB: return SDL_MATRIX_COEFFICIENTS_IDENTITY; case AVCOL_SPC_BT709: return SDL_MATRIX_COEFFICIENTS_BT709; case AVCOL_SPC_UNSPECIFIED: return SDL_MATRIX_COEFFICIENTS_UNSPECIFIED; case AVCOL_SPC_RESERVED: break; case AVCOL_SPC_FCC: return SDL_MATRIX_COEFFICIENTS_FCC; case AVCOL_SPC_BT470BG: return SDL_MATRIX_COEFFICIENTS_BT470BG; case AVCOL_SPC_SMPTE170M: return SDL_MATRIX_COEFFICIENTS_BT601; case AVCOL_SPC_SMPTE240M: return SDL_MATRIX_COEFFICIENTS_SMPTE240; case AVCOL_SPC_YCGCO: return SDL_MATRIX_COEFFICIENTS_YCGCO; case AVCOL_SPC_BT2020_NCL: return SDL_MATRIX_COEFFICIENTS_BT2020_NCL; case AVCOL_SPC_BT2020_CL: return SDL_MATRIX_COEFFICIENTS_BT2020_CL; case AVCOL_SPC_SMPTE2085: return SDL_MATRIX_COEFFICIENTS_SMPTE2085; case AVCOL_SPC_CHROMA_DERIVED_NCL: return SDL_MATRIX_COEFFICIENTS_CHROMA_DERIVED_NCL; case AVCOL_SPC_CHROMA_DERIVED_CL: return SDL_MATRIX_COEFFICIENTS_CHROMA_DERIVED_CL; case AVCOL_SPC_ICTCP: return SDL_MATRIX_COEFFICIENTS_ICTCP; case AVCOL_SPC_IPT_C2: break; // ??? case AVCOL_SPC_YCGCO_RE: return SDL_MATRIX_COEFFICIENTS_YCGCO; // ??? case AVCOL_SPC_YCGCO_RO: return SDL_MATRIX_COEFFICIENTS_YCGCO; // ??? case AVCOL_SPC_NB: break; } return SDL_MATRIX_COEFFICIENTS_UNSPECIFIED; }); const auto cchromaloc = std::invoke([frame] { switch(frame->chroma_location) { case AVCHROMA_LOC_UNSPECIFIED: return SDL_CHROMA_LOCATION_NONE; case AVCHROMA_LOC_LEFT: return SDL_CHROMA_LOCATION_LEFT; case AVCHROMA_LOC_CENTER: return SDL_CHROMA_LOCATION_CENTER; case AVCHROMA_LOC_TOPLEFT: return SDL_CHROMA_LOCATION_TOPLEFT; case AVCHROMA_LOC_TOP: return SDL_CHROMA_LOCATION_TOPLEFT; // ??? case AVCHROMA_LOC_BOTTOMLEFT: return SDL_CHROMA_LOCATION_LEFT; // ??? case AVCHROMA_LOC_BOTTOM: return SDL_CHROMA_LOCATION_CENTER; // ??? case AVCHROMA_LOC_NB: break; } return SDL_CHROMA_LOCATION_NONE; }); const auto colorspace = DefineSDLColorspace(ctype, crange, cprims, ctransfer, cmatrix, cchromaloc); std::ignore = props.setInt(SDL_PROP_TEXTURE_CREATE_COLORSPACE_NUMBER, colorspace); mImage = SDL_CreateTextureWithProperties(renderer, props.getid()); if(!mImage) fmt::println(std::cerr, "Failed to create texture!"); mWidth = frame->width; mHeight = frame->height; mSDLFormat = fmtiter->sdlformat; mAVFormat = fmtiter->avformat; } else { /* If there's no matching format, convert to RGB24. */ fmt::println(std::cerr, "Could not find SDL format for pix_fmt {0:#x} ({0})", as_unsigned(frame->format)); auto const props = SDLProps{}; std::ignore = props.setInt(SDL_PROP_TEXTURE_CREATE_FORMAT_NUMBER, SDL_PIXELFORMAT_RGB24); std::ignore = props.setInt(SDL_PROP_TEXTURE_CREATE_ACCESS_NUMBER, SDL_TEXTUREACCESS_STREAMING); std::ignore = props.setInt(SDL_PROP_TEXTURE_CREATE_WIDTH_NUMBER, frame->width); std::ignore = props.setInt(SDL_PROP_TEXTURE_CREATE_HEIGHT_NUMBER, frame->height); mImage = SDL_CreateTextureWithProperties(renderer, props.getid()); if(!mImage) fmt::println(std::cerr, "Failed to create texture!"); mWidth = frame->width; mHeight = frame->height; mSDLFormat = SDL_PIXELFORMAT_RGB24; mAVFormat = frame->format; mSwscaleCtx = SwsContextPtr{sws_getContext( frame->width, frame->height, gsl::narrow_cast(frame->format), frame->width, frame->height, AV_PIX_FMT_RGB24, 0, nullptr, nullptr, nullptr)}; sws_setColorspaceDetails(mSwscaleCtx.get(), sws_getCoefficients(frame->colorspace), (frame->color_range==AVCOL_RANGE_JPEG), sws_getCoefficients(SWS_CS_DEFAULT), 1, 0<<16, 1<<16, 1<<16); } } auto frame_width = frame->width - gsl::narrow_cast(frame->crop_left + frame->crop_right); auto frame_height = frame->height - gsl::narrow_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) { const auto aspect_ratio = av_q2d(frame->sample_aspect_ratio); if(aspect_ratio >= 1.0) frame_width = gsl::narrow_cast(std::lround(frame_width * aspect_ratio)); else if(aspect_ratio > 0.0) frame_height = gsl::narrow_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) { if(mSDLFormat == SDL_PIXELFORMAT_IYUV || mSDLFormat == SDL_PIXELFORMAT_YV12) SDL_UpdateYUVTexture(mImage, nullptr, frame->data[0], frame->linesize[0], frame->data[1], frame->linesize[1], frame->data[2], frame->linesize[2]); else if(mSDLFormat == SDL_PIXELFORMAT_NV12 || mSDLFormat == SDL_PIXELFORMAT_NV21) SDL_UpdateNVTexture(mImage, nullptr, frame->data[0], frame->linesize[0], frame->data[1], frame->linesize[1]); else if(mSwscaleCtx) { auto pixels = voidp{}; auto pitch = int{}; if(!SDL_LockTexture(mImage, nullptr, &pixels, &pitch)) fmt::println(std::cerr, "Failed to lock texture: {}", SDL_GetError()); else { /* Formats passing through mSwscaleCtx are converted to * 24-bit RGB, which is interleaved/non-planar. */ const auto pict_data = std::array{static_cast(pixels)}; const auto pict_linesize = std::array{pitch}; sws_scale(mSwscaleCtx.get(), std::data(frame->data), std::data(frame->linesize), 0, frame->height, pict_data.data(), pict_linesize.data()); SDL_UnlockTexture(mImage); } } else SDL_UpdateTexture(mImage, nullptr, frame->data[0], frame->linesize[0]); redraw = true; } } if(redraw) { /* Show the picture! */ display(renderer, frame); } if(updated) { auto disp_time = get_avtime(); auto displock = std::lock_guard{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_all(); } } } void VideoState::handler() { std::ranges::for_each(mPictQ, [](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 auto ret = mQueue.sendPacket(mCodecCtx.get()); if(ret == AVErrorEOF) break; } }); { auto displock = std::lock_guard{mDispPtsMutex}; mDisplayPtsTime = get_avtime(); } auto current_pts = nanoseconds::zero(); while(true) { auto write_idx = mPictQWrite.load(std::memory_order_relaxed); auto *vp = &mPictQ[write_idx]; /* Retrieve video frame. */ auto *decoded_frame = std::invoke([this](AVFrame *frame) -> AVFrame* { while(const auto ret = mQueue.receiveFrame(mCodecCtx.get(), frame)) { if(ret == AVErrorEOF) return nullptr; fmt::println(std::cerr, "Failed to receive frame: {}", ret); } return 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) * gsl::narrow_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] { 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(); }); } int MovieState::decode_interrupt_cb(void *ctx) { return static_cast(ctx)->mQuit.load(std::memory_order_relaxed); } bool MovieState::prepare() { auto intcb = AVIOInterruptCB{decode_interrupt_cb, this}; if(avio_open2(al::out_ptr(mIOContext), mFilename.c_str(), AVIO_FLAG_READ, &intcb, nullptr) < 0) { fmt::println(std::cerr, "Failed to open {}", mFilename); return false; } /* Open movie file. If avformat_open_input fails it will automatically free * this context. */ mFormatCtx.reset(avformat_alloc_context()); mFormatCtx->pb = mIOContext.get(); mFormatCtx->interrupt_callback = intcb; if(avformat_open_input(al::inout_ptr(mFormatCtx), mFilename.c_str(), nullptr, nullptr) < 0) { fmt::println(std::cerr, "Failed to open {}", mFilename); return false; } /* Retrieve stream information */ if(avformat_find_stream_info(mFormatCtx.get(), nullptr) < 0) { fmt::println(std::cerr, "{}: 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}; mStartupDone.wait(false, std::memory_order_acquire); 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.data()); SDL_SetWindowTitle(window, title.c_str()); } auto MovieState::getClock() const -> nanoseconds { if(mClockBase == microseconds::min()) return nanoseconds::zero(); return get_avtime() - mClockBase; } auto MovieState::getMasterClock() -> nanoseconds { if(mAVSyncType == SyncMaster::Video && mVideo.mStream) return mVideo.getClock(); if(mAVSyncType == SyncMaster::Audio && mAudio.mStream) return mAudio.getClock(); return getClock(); } auto MovieState::getDuration() const -> nanoseconds { return std::chrono::duration>(mFormatCtx->duration); } auto MovieState::streamComponentOpen(AVStream *stream) -> bool { /* Get a pointer to the codec context for the stream, and open the * associated codec. */ auto avctx = AVCodecCtxPtr{avcodec_alloc_context3(nullptr)}; if(!avctx) return false; if(avcodec_parameters_to_context(avctx.get(), stream->codecpar)) return false; const auto *codec = avcodec_find_decoder(avctx->codec_id); if(!codec || avcodec_open2(avctx.get(), codec, nullptr) < 0) { fmt::println(std::cerr, "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; } void MovieState::parse_handler() { auto &audio_queue = mAudio.mQueue; auto &video_queue = mVideo.mQueue; auto video_index = -1; auto audio_index = -1; /* Find the first video and audio streams */ const auto ctxstreams = std::span{mFormatCtx->streams, mFormatCtx->nb_streams}; for(const auto i : std::views::iota(0_uz, ctxstreams.size())) { auto codecpar = ctxstreams[i]->codecpar; if(codecpar->codec_type == AVMEDIA_TYPE_VIDEO && !DisableVideo && video_index < 0 && streamComponentOpen(ctxstreams[i])) video_index = gsl::narrow_cast(i); else if(codecpar->codec_type == AVMEDIA_TYPE_AUDIO && audio_index < 0 && streamComponentOpen(ctxstreams[i])) audio_index = gsl::narrow_cast(i); } mStartupDone.store(true, std::memory_order_release); mStartupDone.notify_all(); if(video_index < 0 && audio_index < 0) { fmt::println(std::cerr, "{}: 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 */ auto packet = AVPacketPtr{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; { auto lock = std::unique_lock{mVideo.mPictQMutex}; while(!mVideo.mFinalUpdate) mVideo.mPictQCond.wait(lock); } auto evt = SDL_Event{}; evt.user.type = FF_MOVIE_DONE_EVENT; SDL_PushEvent(&evt); } 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); } auto main(std::span args) -> int { SDL_SetMainReady(); if(args.size() < 2) { fmt::println(std::cerr, "Usage: {} [-device ] [options] ", args[0]); fmt::println(std::cerr, "\n Options:\n" " -gain Set audio playback gain (prepend +/- or append \"dB\" to \n" " indicate decibels, otherwise it's linear amplitude)\n" " -novideo Disable video playback\n" " -direct Play audio directly on the output, bypassing virtualization\n" " -superstereo Apply Super Stereo processing to stereo tracks\n" " -uhj Decode as UHJ (stereo = UHJ2, 3.0 = UHJ3, quad = UHJ4)"); return 1; } args = args.subspan(1); /* Initialize networking protocols */ avformat_network_init(); if(!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS)) { fmt::println(std::cerr, "Could not initialize SDL - {}", SDL_GetError()); return 1; } /* Make a window to put our video */ auto *screen = SDL_CreateWindow(AppName.data(), 640, 480, SDL_WINDOW_RESIZABLE); if(!screen) { fmt::println(std::cerr, "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(std::cerr, "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 */ auto almgr = InitAL(args); almgr.printName(); /* NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast) */ 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")); } /* NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast) */ auto curarg = args.begin(); for(auto args_end=args.end();curarg != args_end;++curarg) { const auto argval = *curarg; if(argval == "-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(std::cerr, "AL_SOFT_direct_channels not supported for direct output"); continue; } if(argval == "-wide") { if(!alIsExtensionPresent("AL_EXT_STEREO_ANGLES")) fmt::println(std::cerr, "AL_EXT_STEREO_ANGLES not supported for wide stereo"); else { fmt::println("Found AL_EXT_STEREO_ANGLES"); EnableWideStereo = true; } continue; } if(argval == "-uhj") { if(!alIsExtensionPresent("AL_SOFT_UHJ")) fmt::println(std::cerr, "AL_SOFT_UHJ not supported for UHJ decoding"); else { fmt::println("Found AL_SOFT_UHJ"); EnableUhj = true; } continue; } if(argval == "-superstereo") { if(!alIsExtensionPresent("AL_SOFT_UHJ")) fmt::println(std::cerr, "AL_SOFT_UHJ not supported for Super Stereo decoding"); else { fmt::println("Found AL_SOFT_UHJ (Super Stereo)"); EnableSuperStereo = true; } continue; } if(argval == "-novideo") { DisableVideo = true; continue; } if(argval == "-gain") { if(curarg+1 == args_end) fmt::println(std::cerr, "Missing argument for -gain"); else { const auto optarg = *++curarg; auto endpos = size_t{}; const auto gainval = std::invoke([optarg,&endpos] { try { return std::stof(std::string{optarg}, &endpos); } catch(std::exception &e) { fmt::println(std::cerr, "Exception reading gain value: {}", e.what()); } return std::numeric_limits::quiet_NaN(); }); if(optarg.starts_with("+") || optarg.starts_with("-") || al::case_compare(optarg.substr(endpos), "db") == 0) { if(!std::isfinite(gainval) || (endpos != optarg.size() && al::case_compare(optarg.substr(endpos), "db") != 0)) fmt::println(std::cerr, "Invalid dB gain value: {}", optarg); else PlaybackGain = std::pow(10.0f, gainval/20.0f); } else { if(endpos != optarg.size() || !(gainval >= 0.0f) || !std::isfinite(gainval)) fmt::println(std::cerr, "Invalid linear gain value: {}", optarg); else PlaybackGain = gainval; } } continue; } break; } auto movState = std::unique_ptr{}; curarg = std::ranges::find_if(curarg, args.end(), [&movState](const std::string_view argval) { auto movie = std::make_unique(argval); if(!movie->prepare()) return false; movState = std::move(movie); return true; }); if(curarg == args.end()) { fmt::println(std::cerr, "Could not start a video"); return 1; } ++curarg; movState->setTitle(screen); /* Default to going to the next movie at the end of one. */ enum class EomAction { Next, Quit } eom_action{EomAction::Next}; auto 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)); std::cout.flush(); 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; curarg = std::ranges::find_if(curarg, args.end(), [&movState](const std::string_view argval) { auto movie = std::make_unique(argval); if(!movie->prepare()) return false; movState = std::move(movie); return true; }); if(curarg != args.end()) { ++curarg; movState->setTitle(screen); break; } } /* Nothing more to play. Shut everything down and quit. */ movState = nullptr; almgr.close(); 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(std::cerr, "SDL_WaitEvent error - {}", SDL_GetError()); return 1; } } // namespace auto main(int argc, char *argv[]) -> int { auto args = std::vector(gsl::narrow(argc)); std::ranges::copy(std::views::counted(argv, argc), args.begin()); return main(std::span{args}); } kcat-openal-soft-75c0059/examples/alhrtf.c000066400000000000000000000217641512220627100204110ustar00rootroot00000000000000/* * 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; ALenum 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; ALuint 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; } kcat-openal-soft-75c0059/examples/allafplay.cpp000066400000000000000000001563651512220627100214440ustar00rootroot00000000000000/* * 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. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef _WIN32 #include #include #endif #include "alnumeric.h" #include "alstring.h" #include "common/alhelpers.hpp" #include "filesystem.h" #include "fmt/base.h" #include "fmt/ostream.h" #include "fmt/std.h" #include "win_main_utf8.h" #if HAVE_CXXMODULES import gsl; import openal; #else #include "AL/alc.h" #include "AL/al.h" #include "AL/alext.h" #include "gsl/gsl" #endif namespace { using ALCdevicePtr = std::unique_ptr; using ALCcontextPtr = std::unique_ptr; /* 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 alcRenderSamplesSOFT = LPALCRENDERSAMPLESSOFT{}; auto MuteFilterID = ALuint{}; auto LowFrequencyEffectID = ALuint{}; auto LfeSlotID = ALuint{}; auto RenderChannels = ALCenum{}; auto RenderOutMode = ALCenum{}; auto RenderSamples = ALCenum{}; auto RenderSampleRate = ALCsizei{}; auto RenderAmbiOrder = ALCint{}; void fwrite16be(u16 const value, std::ostream &f) { auto data = std::bit_cast>(value); if constexpr(std::endian::native != std::endian::big) std::ranges::reverse(data); f.write(data.data(), std::ssize(data)); } void fwrite32be(u32 const value, std::ostream &f) { auto data = std::bit_cast>(value); if constexpr(std::endian::native != std::endian::big) std::ranges::reverse(data); f.write(data.data(), std::ssize(data)); } void fwrite64be(u64 const value, std::ostream &f) { auto data = std::bit_cast>(value); if constexpr(std::endian::native != std::endian::big) std::ranges::reverse(data); f.write(data.data(), std::ssize(data)); } using namespace std::string_view_literals; [[noreturn]] void do_assert(const char *message, const std::source_location loc=std::source_location::current()) { auto errstr = fmt::format("{}:{}: {}", loc.file_name(), loc.line(), message); throw std::runtime_error{errstr}; } #define MyAssert(cond) do { \ if(!(cond)) [[unlikely]] \ do_assert("Assertion '" #cond "' failed"); \ } while(0) template struct overloaded : Ts... { using Ts::operator()...; }; enum class Quality : u8 { s8, s16, f32, s24 }; enum class Mode : bool { Channels, Objects }; auto GetQualityName(Quality const 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 const mode) noexcept -> std::string_view { switch(mode) { case Mode::Channels: return "channels"sv; case Mode::Objects: return "objects"sv; } return ""sv; } auto BytesFromQuality(Quality const quality) noexcept -> usize { switch(quality) { case Quality::s8: return 1; case Quality::s16: return 2; case Quality::f32: return 4; case Quality::s24: return 3; } return 4; } /* Helper class for reading little-endian samples on big-endian targets, and * convert 24-bit samples. */ template struct SampleInfo; template<> struct SampleInfo { static constexpr auto SrcSize = 4_uz; [[nodiscard]] static auto read(std::input_iterator auto input) -> f32 { auto src = std::array{}; if constexpr(std::endian::native == std::endian::little) std::ranges::copy(std::views::counted(input, 4), src.begin()); else std::ranges::copy(std::views::counted(input, 4), src.rbegin()); return std::bit_cast(src); } }; template<> struct SampleInfo { static constexpr auto SrcSize = 3_uz; [[nodiscard]] static auto read(std::input_iterator auto input) -> i32 { auto src = std::array{}; if constexpr(std::endian::native == std::endian::little) std::ranges::copy(std::views::counted(input, 3), src.begin()+1); else std::ranges::copy(std::views::counted(input, 3), src.rbegin()+1); return std::bit_cast(src); } }; template<> struct SampleInfo { static constexpr auto SrcSize = 2_uz; [[nodiscard]] static auto read(std::input_iterator auto input) -> i16 { auto src = std::array{}; if constexpr(std::endian::native == std::endian::little) std::ranges::copy(std::views::counted(input, 2), src.begin()); else std::ranges::copy(std::views::counted(input, 2), src.rbegin()); return std::bit_cast(src); } }; template<> struct SampleInfo { static constexpr auto SrcSize = 1_uz; [[nodiscard]] static auto read(std::input_iterator auto input) -> i8 { return std::bit_cast(*input); } }; /* 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{}; f32 mAzimuth{}; f32 mElevation{}; bool mIsLfe{}; Channel() = default; Channel(const Channel&) = delete; Channel(Channel&& rhs) noexcept : 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(gsl::narrow(mBuffers.size()), mBuffers.data()); } auto operator=(const Channel&) -> Channel& = delete; auto operator=(Channel&& rhs) noexcept -> 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::ifstream mInFile; Quality mQuality{}; Mode mMode{}; u32 mNumTracks{}; u32 mSampleRate{}; ALenum mALFormat{}; u64 mSampleCount{}; u64 mCurrentSample{}; std::array mEnabledTracks{}; u32 mNumEnabled{}; std::vector mSampleChunk; template using vector = std::vector; std::variant,vector,vector,vector> mSampleLine; std::vector mChannels; std::vector> mPosTracks; LafStream() = default; LafStream(const LafStream&) = delete; ~LafStream() = default; auto operator=(const LafStream&) -> LafStream& = delete; [[nodiscard]] auto isAtEnd() const noexcept -> bool { return mCurrentSample >= mSampleCount; } [[nodiscard]] auto readChunk() -> u32; [[nodiscard]] auto prepareTrack(usize trackidx, usize count) -> std::span; void convertSamples(std::span samples) const; void convertPositions(std::span dst) const; }; auto LafStream::readChunk() -> u32 { auto enableTrackBits = std::array>{}; auto &infile = mInFile.is_open() ? mInFile : std::cin; if(!infile.read(enableTrackBits.data(), gsl::narrow((mNumTracks+7u)>>3u))) [[unlikely]] { /* Only print an error when expecting more samples. A sample count of * ~0_u64 indicates unbounded input, which will end when it has nothing * more to give. */ if(mSampleCount < ~0_u64 || infile.gcount() != 0) fmt::println(std::cerr, "Premature end of file ({} of {} samples)", mCurrentSample, mSampleCount); mSampleCount = mCurrentSample; return 0_u32; } mEnabledTracks = std::bit_cast(enableTrackBits); mNumEnabled = gsl::narrow(std::accumulate(mEnabledTracks.cbegin(), mEnabledTracks.cend(), 0, [](int const val, u8 const in) -> int { return val + std::popcount(in); })); /* Make sure enable bits aren't set for non-existent tracks. */ if(mNumEnabled > 0 && 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. */ auto const numsamples = gsl::narrow(std::min(u64{mSampleRate}, mSampleCount-mCurrentSample)); /* Choose the smaller of std::streamsize or isize, to ensure neither the * read size or range drop size get truncated. */ using readsize_t = std::conditional_t<(sizeof(std::streamsize) > sizeof(isize)), isize, std::streamsize>; const auto toread = gsl::narrow(numsamples * BytesFromQuality(mQuality) * mNumEnabled); if(!infile.read(mSampleChunk.data(), toread)) [[unlikely]] { const auto framesize = BytesFromQuality(mQuality) * mNumEnabled; const auto samplesread = al::saturate_cast(infile.gcount()) / framesize; mCurrentSample += samplesread; if(mSampleCount < ~0_u64) fmt::println(std::cerr, "Premature end of file ({} of {} samples)", mCurrentSample, mSampleCount); mSampleCount = mCurrentSample; std::ranges::fill(mSampleChunk | std::views::drop(numsamples*framesize), char{}); return gsl::narrow(samplesread); } std::ranges::fill(mSampleChunk | std::views::drop(toread), char{}); mCurrentSample += numsamples; return gsl::narrow(numsamples); } auto LafStream::prepareTrack(usize const trackidx, usize const count) -> std::span { auto const todo = std::min(usize{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. */ auto const idx = std::invoke([this,trackidx]() -> u32 { auto const bits = std::span{mEnabledTracks}.first(trackidx>>3); auto const res = std::accumulate(bits.begin(), bits.end(), 0_i32, [](int const val, u8 const in) -> int { return val + std::popcount(in); }) + std::popcount(mEnabledTracks[trackidx>>3] & ((1u<<(trackidx&7))-1)); return gsl::narrow_cast(res); }); auto const step = usize{mNumEnabled}; Expects(idx < step); return std::visit([count,idx,step,src=std::span{mSampleChunk}](T &dst) { using sample_t = T::value_type; auto inptr = src.begin(); std::advance(inptr, idx*SampleInfo::SrcSize); auto output = std::span{dst}.first(count); output.front() = SampleInfo::read(inptr); std::ranges::generate(output | std::views::drop(1), [&inptr,step] { std::advance(inptr, step*SampleInfo::SrcSize); return SampleInfo::read(inptr); }); return std::as_writable_bytes(output); }, mSampleLine); } /* If the track is disabled, provide silence. */ return std::visit([todo](T &dst) { using sample_t = T::value_type; std::ranges::fill(dst, sample_t{}); return std::as_writable_bytes(std::span{dst}.first(todo)); }, mSampleLine); } void LafStream::convertSamples(std::span const 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::ranges::transform(samples, samples.begin(), [](std::byte const sample) noexcept -> std::byte { return sample^std::byte{0x80}; }); } } void LafStream::convertPositions(std::span const dst) const { std::visit(overloaded { [dst](vector const &src) { std::ranges::transform(src, dst.begin(), [](i8 const in) noexcept -> f32 { return gsl::narrow_cast(in) / 127.0f; }); }, [dst](vector const &src) { std::ranges::transform(src, dst.begin(), [](i16 const in) noexcept -> f32 { return gsl::narrow_cast(in) / 32767.0f; }); }, [dst](vector const &src) { std::ranges::copy(src, dst.begin()); }, [dst](vector const &src) { /* 24-bit samples are converted to 32-bit in copySamples. */ std::ranges::transform(src, dst.begin(), [](i32 const in) noexcept -> f32 { return gsl::narrow_cast(in>>8) / 8388607.0f; }); }, }, mSampleLine); } auto LoadLAF(const fs::path &fname) -> std::unique_ptr { auto laf = std::make_unique(); auto &infile = std::invoke([&fname,&laf]() -> std::istream& { if(fname == "-") { #ifdef _WIN32 /* Set stdin to binary mode, so cin's rdbuf will read the file * correctly. */ if(_setmode(_fileno(stdin), _O_BINARY) == -1) throw std::runtime_error{"Failed to set stdin to binary mode"}; #endif return std::cin; } laf->mInFile.open(fname, std::ios_base::binary); if(!laf->mInFile.is_open()) throw std::runtime_error{"Could not open file"}; return laf->mInFile; }); /* Throw exceptions if we fail reading the header, so it will skip the file * and go to the next. */ infile.exceptions(std::ios_base::eofbit | std::ios_base::failbit | std::ios_base::badbit); auto marker = std::array{}; infile.read(marker.data(), marker.size()); if(std::string_view{marker.data(), marker.size()} != "LIMITLESS"sv) throw std::runtime_error{"Not an LAF file"}; auto header = std::array{}; infile.read(header.data(), header.size()); 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 = headview.find("HEAD"sv); 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::ranges::copy(header | std::views::drop(hpos), hiter).out; } else if(headview.ends_with("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::ranges::copy_n(header.end()-3, 3, hiter).out; } else if(headview.ends_with("HE"sv)) hiter = std::ranges::copy_n(header.end()-2, 2, hiter).out; else if(headview.back() == 'H') hiter = std::ranges::copy_n(header.end()-1, 1, hiter).out; infile.read(std::to_address(hiter), std::distance(hiter, header.end())); } laf->mQuality = std::invoke([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 = std::invoke([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 = std::invoke([input=std::span{header}.subspan<6,4>()] { return u32{as_unsigned(input[0])} | (u32{as_unsigned(input[1])}<<8) | (u32{as_unsigned(input[2])}<<16) | (u32{as_unsigned(input[3])}<<24); }); fmt::println("Filename: {}", al::u8_as_char(fname.u8string())); 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); infile.read(chandata.data(), std::ssize(chandata)); if(laf->mMode == Mode::Channels) laf->mChannels.resize(laf->mNumTracks); else { if(laf->mNumTracks < 2) throw std::runtime_error{"Not enough tracks"}; auto numchans = usize{laf->mNumTracks - 1}; auto numpostracks = 1_uz; while(numpostracks*16 < numchans) { --numchans; ++numpostracks; } laf->mChannels.resize(numchans); laf->mPosTracks.resize(numpostracks); } static constexpr auto read_float = [](std::span const input) { const auto value = u32{as_unsigned(input[0])} | (u32{as_unsigned(input[1])}<<8) | (u32{as_unsigned(input[2])}<<16) | (u32{as_unsigned(input[3])}<<24); return std::bit_cast(value); }; /* C++23 can use chandata | std::views::chunk(9) | std::views::enumerate to * get a range of ~std::pair chunk>. */ auto chanspan = std::span{chandata}.first(laf->mChannels.size()*9_uz); std::ranges::generate(laf->mChannels, [&chandata,&chanspan] { const auto idx = (chanspan.data()-chandata.data()) / 9_z; auto x_axis = read_float(chanspan.first<4>()); auto y_axis = read_float(chanspan.subspan<4,4>()); auto lfe_flag = int{chanspan[8]}; chanspan = chanspan.subspan(9); fmt::println("Track {}: E={:f}, A={:f} (LFE: {})", idx, x_axis, y_axis, lfe_flag); MyAssert(std::isfinite(x_axis) && std::isfinite(y_axis)); auto channel = Channel{}; channel.mAzimuth = y_axis; channel.mElevation = x_axis; channel.mIsLfe = lfe_flag != 0; return channel; }); chanspan = std::span{chandata}.last(laf->mPosTracks.size()*9_uz); std::ranges::for_each(laf->mPosTracks, [&chandata,&chanspan](auto&&) { const auto idx = (chanspan.data()-chandata.data()) / 9_z; auto x_axis = read_float(chanspan.first<4>()); auto y_axis = read_float(chanspan.subspan<4,4>()); auto lfe_flag = int{chanspan[8]}; chanspan = chanspan.subspan(9); fmt::println("Track {}: E={:f}, A={:f} (LFE: {})", idx, x_axis, y_axis, lfe_flag); MyAssert(std::isnan(x_axis) && y_axis == 0.0f); MyAssert(idx != 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{}; infile.read(footer.data(), footer.size()); laf->mSampleRate = std::invoke([input=std::span{footer}.first<4>()] { return u32{as_unsigned(input[0])} | (u32{as_unsigned(input[1])}<<8) | (u32{as_unsigned(input[2])}<<16) | (u32{as_unsigned(input[3])}<<24); }); laf->mSampleCount = std::invoke([input=std::span{footer}.last<8>()] { return u64{as_unsigned(input[0])} | (u64{as_unsigned(input[1])}<<8) | (u64{as_unsigned(input[2])}<<16) | (u64{as_unsigned(input[3])}<<24) | (u64{as_unsigned(input[4])}<<32) | (u64{as_unsigned(input[5])}<<40) | (u64{as_unsigned(input[6])}<<48) | (u64{as_unsigned(input[7])}<<56); }); fmt::println("Sample rate: {}", laf->mSampleRate); if(laf->mSampleCount < ~0_u64) fmt::println("Length: {} samples ({:.2f} sec)", laf->mSampleCount, static_cast(laf->mSampleCount)/static_cast(laf->mSampleRate)); else fmt::println("Length: unbounded"); /* 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, resulting 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); std::ranges::generate(laf->mPosTracks, [length=laf->mSampleRate*2_uz] { return std::vector(length, 0.0f); }); laf->mSampleChunk.resize(laf->mSampleRate*BytesFromQuality(laf->mQuality)*laf->mNumTracks); switch(laf->mQuality) { case Quality::s8: laf->mSampleLine.emplace>(laf->mSampleRate); break; case Quality::s16: laf->mSampleLine.emplace>(laf->mSampleRate); break; case Quality::f32: laf->mSampleLine.emplace>(laf->mSampleRate); break; case Quality::s24: laf->mSampleLine.emplace>(laf->mSampleRate); break; } /* Re-disable exceptions since we'll manually check each read. */ infile.exceptions(std::ios_base::goodbit); return laf; } void PlayLAF(std::string_view const fname) try { const auto laf = LoadLAF(fs::path(al::char_as_u8(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))}; std::ranges::for_each(laf->mChannels, [](Channel &channel) { alGenSources(1, &channel.mSource); alGenBuffers(gsl::narrow(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); /* Convert degrees to radians, wrapping between -pi...+pi. */ auto azi = channel.mAzimuth / 180.0f; /* At this magnitude, the result is always 0. */ if(!(std::abs(azi) < 16777216.0f)) azi = 0.0f; else { auto const tmp = gsl::narrow_cast(azi); azi -= gsl::narrow_cast(tmp + (tmp%2)); azi *= std::numbers::pi_v; } auto elev = channel.mElevation / 180.0f; if(!(std::abs(elev) < 16777216.0f)) elev = 0.0f; else { auto const tmp = gsl::narrow_cast(elev); elev -= gsl::narrow_cast(tmp + (tmp%2)); elev *= std::numbers::pi_v; } auto const x = std::sin(azi) * std::cos(elev); auto const y = std::sin(elev); auto const z = -std::cos(azi) * std::cos(elev); 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, as_signed(MuteFilterID)); alSource3i(channel.mSource, AL_AUXILIARY_SEND_FILTER, as_signed(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(const auto err = alGetError()) throw std::runtime_error{fmt::format("OpenAL error: {}", alGetString(err))}; }); auto renderFile = std::ofstream{}; auto renderStart = std::streamoff{}; auto leadIn = 0_z; auto leadOut = 0_z; auto renderbuf = std::vector{}; if(alcRenderSamplesSOFT) { auto *device = alcGetContextsDevice(alcGetCurrentContext()); auto const chancount = std::invoke([]() -> u32 { switch(RenderChannels) { case ALC_MONO_SOFT: return 1_u32; case ALC_STEREO_SOFT: return 2_u32; case ALC_QUAD_SOFT: return 4_u32; case ALC_SURROUND_5_1_SOFT: return 6_u32; case ALC_SURROUND_6_1_SOFT: return 7_u32; case ALC_SURROUND_7_1_SOFT: return 8_u32; case ALC_BFORMAT3D_SOFT: return gsl::narrow((RenderAmbiOrder+1)*(RenderAmbiOrder+1)); default: throw std::runtime_error{fmt::format("Unexpected channel enum: {:#x}", RenderChannels)}; } }); auto const samplesize = std::invoke([]() -> u32 { switch(RenderSamples) { case ALC_UNSIGNED_BYTE_SOFT: return 1_u32; case ALC_BYTE_SOFT: return 1_u32; case ALC_UNSIGNED_SHORT_SOFT: return 2_u32; case ALC_SHORT_SOFT: return 2_u32; case ALC_UNSIGNED_INT_SOFT: return 4_u32; case ALC_INT_SOFT: return 4_u32; case ALC_FLOAT_SOFT: return 4_u32; default: throw std::runtime_error{fmt::format("Unexpected sample type enum: {:#x}", RenderSamples)}; } }); auto const framesize = usize{chancount} * samplesize; renderbuf.resize(framesize * FramesPerPos); if(std::cmp_not_equal(RenderSampleRate, laf->mSampleRate)) { /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) */ auto const alcResetDeviceSOFT = reinterpret_cast( alcGetProcAddress(nullptr, "alcResetDeviceSOFT")); auto const attribs = std::to_array({ ALC_FREQUENCY, gsl::narrow(laf->mSampleRate), ALC_FORMAT_CHANNELS_SOFT, RenderChannels, ALC_FORMAT_TYPE_SOFT, RenderSamples, ALC_OUTPUT_MODE_SOFT, RenderOutMode, ALC_AMBISONIC_LAYOUT_SOFT, ALC_ACN_SOFT, ALC_AMBISONIC_SCALING_SOFT, ALC_SN3D_SOFT, ALC_AMBISONIC_ORDER_SOFT, RenderAmbiOrder, 0}); if(!alcResetDeviceSOFT(device, attribs.data())) throw std::runtime_error{fmt::format( "Failed to reset loopback device for {}hz rendering", RenderSampleRate)}; RenderSampleRate = gsl::narrow_cast(laf->mSampleRate); } if(alcIsExtensionPresent(device, "ALC_SOFT_device_clock")) { /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) */ const auto alcGetInteger64vSOFT = reinterpret_cast( alcGetProcAddress(device, "alcGetInteger64vSOFT")); auto latency = ALCint64SOFT{}; alcGetInteger64vSOFT(device, ALC_DEVICE_LATENCY_SOFT, 1, &latency); std::ignore = alcGetError(device); leadIn = gsl::narrow(latency * RenderSampleRate / 1'000'000'000) * gsl::narrow_cast(framesize); leadOut = gsl::narrow((latency*RenderSampleRate + 999'999'999) / 1'000'000'000) * gsl::narrow_cast(framesize); } auto outname = fs::path(al::char_as_u8(fname)).stem(); outname += u8".caf"; if(fs::exists(outname) && !fs::is_fifo(outname)) throw std::runtime_error{fmt::format("Output file {} exists", al::u8_as_char(outname.u8string()))}; renderFile.open(outname, std::ios_base::binary | std::ios_base::out); if(!renderFile.is_open()) throw std::runtime_error{fmt::format("Failed to create {}", al::u8_as_char(outname.u8string()))}; renderFile.write("caff", 4); fwrite16be(1, renderFile); fwrite16be(0, renderFile); renderFile.write("desc", 4); fwrite64be(32, renderFile); fwrite64be(std::bit_cast(gsl::narrow_cast(RenderSampleRate)),renderFile); renderFile.write("lpcm", 4); auto const flags = std::invoke([] { switch(RenderSamples) { case ALC_UNSIGNED_BYTE_SOFT: case ALC_BYTE_SOFT: break; case ALC_UNSIGNED_SHORT_SOFT: case ALC_SHORT_SOFT: case ALC_UNSIGNED_INT_SOFT: case ALC_INT_SOFT: if constexpr(std::endian::native == std::endian::little) return 2_u32; /* kCAFLinearPCMFormatFlagIsLittleEndian */ else break; case ALC_FLOAT_SOFT: if constexpr(std::endian::native == std::endian::little) return 3_u32; /* kCAFLinearPCMFormatFlagIsFloat | kCAFLinearPCMFormatFlagIsLittleEndian */ else return 1_u32; /* kCAFLinearPCMFormatFlagIsFloat */ } return 0_u32; }); fwrite32be(flags, renderFile); fwrite32be(samplesize*chancount, renderFile); fwrite32be(1, renderFile); fwrite32be(chancount, renderFile); fwrite32be(samplesize*8, renderFile); auto const chanmask = std::invoke([]() -> u32 { switch(RenderChannels) { case ALC_MONO_SOFT: return 0x4u; case ALC_STEREO_SOFT: return 0x1u | 0x2u; case ALC_QUAD_SOFT: return 0x1u | 0x2u | 0x10u | 0x20u; case ALC_SURROUND_5_1_SOFT: return 0x1u | 0x2u | 0x4u | 0x8u | 0x200u | 0x400u; case ALC_SURROUND_6_1_SOFT: return 0x1u | 0x2u | 0x4u | 0x8u | 0x100u | 0x200u | 0x400u; case ALC_SURROUND_7_1_SOFT: return 0x1u | 0x2u | 0x4u | 0x8u | 0x10u | 0x20u | 0x200u | 0x400u; case ALC_BFORMAT3D_SOFT: return 0u; default: throw std::runtime_error{fmt::format("Unexpected channel enum: {:#x}", RenderChannels)}; } }); if(chanmask) { renderFile.write("chan", 4); fwrite64be(12, renderFile); fwrite32be(0x10000, renderFile); /* kCAFChannelLayoutTag_UseChannelBitmap */ fwrite32be(chanmask, renderFile); fwrite32be(0, renderFile); } renderFile.write("data", 4); fwrite64be(~0_u64, renderFile); /* filled in at stop */ renderStart = renderFile.tellp(); fwrite32be(0, renderFile); fmt::println("Rendering to {}...", al::u8_as_char(outname.u8string())); } 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) { /* Playing normally. Update the source positions for the current * playback offset, for dynamic objects. */ if(!laf->mPosTracks.empty()) { alcSuspendContext(alcGetCurrentContext()); for(auto const i : std::views::iota(0_uz, laf->mChannels.size())) { auto const trackidx = i>>4; auto const posoffset = gsl::narrow(offset)/FramesPerPos*16_uz + (i&15); auto const x = laf->mPosTracks[trackidx][posoffset*3 + 0]; auto const y = laf->mPosTracks[trackidx][posoffset*3 + 1]; auto const z = laf->mPosTracks[trackidx][posoffset*3 + 2]; /* Convert left-handed coords to right-handed. */ alSource3f(laf->mChannels[i].mSource, AL_POSITION, x, y, -z); } alcProcessContext(alcGetCurrentContext()); } /* Unqueue processed buffers and refill with the next chunk, or * sleep for ~10ms before updating again. */ if(processed > 0) { auto const numsamples = laf->readChunk(); for(auto const i : std::views::iota(0_uz, laf->mChannels.size())) { auto const samples = laf->prepareTrack(i, numsamples); laf->convertSamples(samples); auto bufid = ALuint{}; alSourceUnqueueBuffers(laf->mChannels[i].mSource, 1, &bufid); alBufferData(bufid, laf->mALFormat, samples.data(), gsl::narrow(samples.size()), gsl::narrow(laf->mSampleRate)); alSourceQueueBuffers(laf->mChannels[i].mSource, 1, &bufid); } for(auto const i : std::views::iota(0_uz, laf->mPosTracks.size())) { std::ranges::copy(laf->mPosTracks[i] | std::views::drop(laf->mSampleRate), laf->mPosTracks[i].begin()); std::ignore = laf->prepareTrack(laf->mChannels.size()+i, numsamples); laf->convertPositions(std::span{laf->mPosTracks[i]}.last(laf->mSampleRate)); } } else if(alcRenderSamplesSOFT) { alcRenderSamplesSOFT(alcGetContextsDevice(alcGetCurrentContext()), renderbuf.data(), FramesPerPos); if(leadIn >= std::ssize(renderbuf)) leadIn -= std::ssize(renderbuf); else if(leadIn > 0) { auto const out = renderbuf | std::views::drop(leadIn); renderFile.write(out.data(), std::ssize(out)); leadIn = 0; } else renderFile.write(renderbuf.data(), std::ssize(renderbuf)); } else std::this_thread::sleep_for(std::chrono::milliseconds{10}); } else if(state == AL_STOPPED) { /* Underrun. Restart all sources in sync from the beginning of the * currently buffered chunks. This will cause some old audio to * replay, but all the channels will agree on where they are in the * stream and ensure nothing is skipped. */ auto sources = std::array{}; std::ranges::transform(laf->mChannels, sources.begin(), &Channel::mSource); alSourcePlayv(gsl::narrow(laf->mChannels.size()), sources.data()); } else if(state == AL_INITIAL) { /* Starting playback. Read and prepare the two second-long chunks * per track (buffering audio samples to OpenAL, and storing the * position vectors). */ auto numsamples = laf->readChunk(); for(auto const i : std::views::iota(0_uz, laf->mChannels.size())) { auto const samples = laf->prepareTrack(i, numsamples); laf->convertSamples(samples); alBufferData(laf->mChannels[i].mBuffers[0], laf->mALFormat, samples.data(), gsl::narrow(samples.size()), gsl::narrow(laf->mSampleRate)); } for(auto const i : std::views::iota(0_uz, laf->mPosTracks.size())) { std::ignore = laf->prepareTrack(laf->mChannels.size()+i, numsamples); laf->convertPositions(std::span{laf->mPosTracks[i]}.first(laf->mSampleRate)); } numsamples = laf->readChunk(); for(auto const i : std::views::iota(0_uz, laf->mChannels.size())) { auto const samples = laf->prepareTrack(i, numsamples); laf->convertSamples(samples); alBufferData(laf->mChannels[i].mBuffers[1], laf->mALFormat, samples.data(), gsl::narrow(samples.size()), gsl::narrow(laf->mSampleRate)); alSourceQueueBuffers(laf->mChannels[i].mSource, gsl::narrow(laf->mChannels[i].mBuffers.size()), laf->mChannels[i].mBuffers.data()); } for(auto const i : std::views::iota(0_uz, laf->mPosTracks.size())) { std::ignore = laf->prepareTrack(laf->mChannels.size()+i, numsamples); laf->convertPositions(std::span{laf->mPosTracks[i]}.last(laf->mSampleRate)); } /* Set the initial source positions for dynamic objects, then start * all sources in sync. */ if(!laf->mPosTracks.empty()) { for(auto const i : std::views::iota(0_uz, laf->mChannels.size())) { auto const trackidx = i>>4; auto const x = laf->mPosTracks[trackidx][(i&15)*3 + 0]; auto const y = laf->mPosTracks[trackidx][(i&15)*3 + 1]; auto const z = laf->mPosTracks[trackidx][(i&15)*3 + 2]; alSource3f(laf->mChannels[i].mSource, AL_POSITION, x, y, -z); } } auto sources = std::array{}; std::ranges::transform(laf->mChannels, sources.begin(), &Channel::mSource); alSourcePlayv(gsl::narrow(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(auto const i : std::views::iota(0_uz, laf->mChannels.size())) { auto const trackidx = i>>4; auto const posoffset = gsl::narrow(offset)/FramesPerPos*16_uz + (i&15); auto const x = laf->mPosTracks[trackidx][posoffset*3 + 0]; auto const y = laf->mPosTracks[trackidx][posoffset*3 + 1]; auto const z = laf->mPosTracks[trackidx][posoffset*3 + 2]; alSource3f(laf->mChannels[i].mSource, AL_POSITION, x, y, -z); } alcProcessContext(alcGetCurrentContext()); } if(alcRenderSamplesSOFT) { alcRenderSamplesSOFT(alcGetContextsDevice(alcGetCurrentContext()), renderbuf.data(), FramesPerPos); if(leadIn > std::ssize(renderbuf)) leadIn -= std::ssize(renderbuf); else if(leadIn > 0) { auto const out = renderbuf | std::views::drop(leadIn); renderFile.write(out.data(), std::ssize(out)); leadIn = 0; } else renderFile.write(renderbuf.data(), std::ssize(renderbuf)); } else 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); } while(leadOut > 0) { alcRenderSamplesSOFT(alcGetContextsDevice(alcGetCurrentContext()), renderbuf.data(), FramesPerPos); auto const todo = std::min(std::ssize(renderbuf), leadOut); renderFile.write(renderbuf.data(), todo); leadOut -= todo; } if(renderStart > 0) { auto const renderEnd = std::streamoff{renderFile.tellp()}; if(renderEnd > renderStart) { auto const dataLen = renderEnd - renderStart; if(renderFile.seekp(renderStart-8)) { fwrite64be(gsl::narrow(dataLen), renderFile); renderFile.seekp(0, std::ios_base::end); } } } } catch(std::exception& e) { fmt::println(std::cerr, "Error playing {}:\n {}", fname, e.what()); } auto main(std::span args) -> int { /* Print out usage if no arguments were specified */ if(args.size() < 2) { fmt::println(std::cerr, "Usage: {} [-device ] [-render ] \n" "\n" " -render Renders samples to an output file instead of real-time playback.\n" " Outputs a CAF file with the same name as the input, but with the\n" " \"caf\" extension.\n" " Available channels: mono, stereo, hrtf, uhj, quad, surround51,\n" " surround61, surround71, ambi1, ambi2, ambi3,\n" " ambi4\n" " Available samples: s16, f32", args[0]); return 1; } args = args.subspan(1); auto almgr = InitAL(args); almgr.printName(); if(!args.empty() && args[0] == "-render") { if(args.size() < 2) { fmt::println(std::cerr, "Missing -render format"); return 1; } auto params = std::vector{}; std::ranges::transform(args[1] | std::views::split(','), std::back_inserter(params), [](auto prange) { return std::string(prange.begin(), prange.end()); }); if(params.size() != 2) { fmt::println(std::cerr, "Invalid -render argument: {}", args[1]); return 1; } args = args.subspan(2); RenderOutMode = ALC_ANY_SOFT; RenderAmbiOrder = 0; if(al::case_compare(params[0], "mono") == 0) RenderChannels = ALC_MONO_SOFT; else if(al::case_compare(params[0], "stereo") == 0) { RenderChannels = ALC_STEREO_SOFT; RenderOutMode = ALC_STEREO_BASIC_SOFT; } else if(al::case_compare(params[0], "hrtf") == 0) { RenderChannels = ALC_STEREO_SOFT; RenderOutMode = ALC_STEREO_HRTF_SOFT; } else if(al::case_compare(params[0], "uhj") == 0) { RenderChannels = ALC_STEREO_SOFT; RenderOutMode = ALC_STEREO_UHJ_SOFT; } else if(al::case_compare(params[0], "quad") == 0) RenderChannels = ALC_QUAD_SOFT; else if(al::case_compare(params[0], "surround51") == 0) RenderChannels = ALC_SURROUND_5_1_SOFT; else if(al::case_compare(params[0], "surround61") == 0) RenderChannels = ALC_SURROUND_6_1_SOFT; else if(al::case_compare(params[0], "surround71") == 0) RenderChannels = ALC_SURROUND_7_1_SOFT; else if(al::case_compare(params[0], "ambi1") == 0) { RenderChannels = ALC_BFORMAT3D_SOFT; RenderAmbiOrder = 1; } else if(al::case_compare(params[0], "ambi2") == 0) { RenderChannels = ALC_BFORMAT3D_SOFT; RenderAmbiOrder = 2; } else if(al::case_compare(params[0], "ambi3") == 0) { RenderChannels = ALC_BFORMAT3D_SOFT; RenderAmbiOrder = 3; } else if(al::case_compare(params[0], "ambi4") == 0) { RenderChannels = ALC_BFORMAT3D_SOFT; RenderAmbiOrder = 4; } else { fmt::println(std::cerr, "Unsupported channel configuration: {}", params[0]); return 1; } if(al::case_compare(params[1], "f32") == 0) RenderSamples = ALC_FLOAT_SOFT; else if(al::case_compare(params[1], "s16") == 0) RenderSamples = ALC_SHORT_SOFT; else { fmt::println(std::cerr, "Unsupported sample type: {}", params[1]); return 1; } RenderSampleRate = 48'000; if(!alcIsExtensionPresent(nullptr, "ALC_SOFT_loopback")) { fmt::println(std::cerr, "Loopback rendering not supported"); return 1; } /* NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast) */ auto const alcLoopbackOpenDevice = reinterpret_cast( alcGetProcAddress(nullptr, "alcLoopbackOpenDeviceSOFT")); auto const alcIsRenderFormatSupported = reinterpret_cast( alcGetProcAddress(nullptr, "alcIsRenderFormatSupportedSOFT")); alcRenderSamplesSOFT = reinterpret_cast( alcGetProcAddress(nullptr, "alcRenderSamplesSOFT")); /* NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast) */ auto loopbackDev = ALCdevicePtr{alcLoopbackOpenDevice(nullptr)}; if(!loopbackDev) { fmt::println(std::cerr, "Failed to open loopback device: {:x}", alcGetError(nullptr)); return 1; } if(!alcIsRenderFormatSupported(loopbackDev.get(), RenderSampleRate, RenderChannels, RenderSamples)) { fmt::println(std::cerr, "Format {},{} @ {}hz not supported", params[0], params[1], RenderSampleRate); return 1; } if(RenderAmbiOrder > 0) { auto maxorder = ALCint{}; if(alcIsExtensionPresent(loopbackDev.get(), "ALC_SOFT_loopback_bformat")) alcGetIntegerv(loopbackDev.get(), ALC_MAX_AMBISONIC_ORDER_SOFT, 1, &maxorder); if(RenderAmbiOrder > maxorder) { fmt::println(std::cerr, "Unsupported ambisonic order: {} (max: {})", RenderAmbiOrder, maxorder); return 1; } } auto const attribs = std::to_array({ ALC_FREQUENCY, RenderSampleRate, ALC_FORMAT_CHANNELS_SOFT, RenderChannels, ALC_FORMAT_TYPE_SOFT, RenderSamples, ALC_OUTPUT_MODE_SOFT, RenderOutMode, ALC_AMBISONIC_LAYOUT_SOFT, ALC_ACN_SOFT, ALC_AMBISONIC_SCALING_SOFT, ALC_SN3D_SOFT, ALC_AMBISONIC_ORDER_SOFT, RenderAmbiOrder, 0}); auto loopbackCtx = ALCcontextPtr{alcCreateContext(loopbackDev.get(), attribs.data())}; if(!loopbackCtx || alcMakeContextCurrent(loopbackCtx.get()) == ALC_FALSE) { fmt::println(std::cerr, "Failed to create loopback device context: {:x}", alcGetError(loopbackDev.get())); return 1; } almgr.close(); almgr.mDevice = loopbackDev.release(); almgr.mContext = loopbackCtx.release(); } /* Automate effect cleanup at end of scope (before almgr destructs). */ const auto _ = gsl::finally([] { if(LfeSlotID) { alDeleteAuxiliaryEffectSlots(1, &LfeSlotID); alDeleteEffects(1, &LowFrequencyEffectID); alDeleteFilters(1, &MuteFilterID); } }); if(alcIsExtensionPresent(almgr.mDevice, "ALC_EXT_EFX") && alcIsExtensionPresent(almgr.mDevice, "ALC_EXT_DEDICATED")) { static constexpr auto load_proc = [](T &func, gsl::czstring const funcname) { /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) */ func = reinterpret_cast(alGetProcAddress(funcname)); if(!func) fmt::println(std::cerr, "Failed to find function '{}'", funcname); }; #define LOAD_PROC(x) load_proc(x, #x) 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, as_signed(LowFrequencyEffectID)); MyAssert(alGetError() == AL_NO_ERROR); } std::ranges::for_each(args, PlayLAF); return 0; } } // namespace auto main(int const argc, char **const argv) -> int { auto args = std::vector(gsl::narrow(argc)); std::ranges::copy(std::views::counted(argv, argc), args.begin()); return main(std::span{args}); } kcat-openal-soft-75c0059/examples/allatency.c000066400000000000000000000160551512220627100211020ustar00rootroot00000000000000/* * 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; ALenum 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; ALuint 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; } kcat-openal-soft-75c0059/examples/alloopback.c000066400000000000000000000225231512220627100212320ustar00rootroot00000000000000/* * 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; ALuint 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; } kcat-openal-soft-75c0059/examples/almultireverb.c000066400000000000000000000644121512220627100220030ustar00rootroot00000000000000/* * 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; ALenum 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]; ALfloat this_dir[3]; ALfloat listener_pos[3]; ALfloat local_norm[3]; ALfloat local_dir[3]; ALfloat near_edge[3]; ALfloat far_edge[3]; ALfloat edist; ALfloat dist; /* 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; const EFXEAXREVERBPROPERTIES *this_reverb; ALuint other_effect; ALuint this_effect; ALfloat magnitude; ALfloat 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; } kcat-openal-soft-75c0059/examples/alplay.c000066400000000000000000000252771512220627100204160ustar00rootroot00000000000000/* * 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; ALenum 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; ALuint 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; } kcat-openal-soft-75c0059/examples/alrecord.c000066400000000000000000000316711512220627100207220ustar00rootroot00000000000000/* * 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(const Locale&, format_string, T&&...) ::: format_to(OutputIt, const Locale&, format_string, T&&...) ::: formatted_size(const Locale&, 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() { auto now = std::chrono::system_clock::now(); fmt::print("The date is {:%Y-%m-%d}.\n", now); // 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 } ::: 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(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. - The default floating-point representation in {fmt} uses the smallest precision that provides round-trip guarantees similarly to other languages like Java and Python. `std::format` is currently specified in terms of `std::to_chars` which tries to generate the smallest number of characters (ignoring redundant digits and sign in exponent) and may procude more decimal digits than necessary. kcat-openal-soft-75c0059/fmt-11.2.0/doc/fmt.css000066400000000000000000000023631512220627100205110ustar00rootroot00000000000000: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 */ } } kcat-openal-soft-75c0059/fmt-11.2.0/doc/fmt.js000066400000000000000000000001141512220627100203250ustar00rootroot00000000000000document$.subscribe(() => { hljs.highlightAll(), { language: 'c++' } }) kcat-openal-soft-75c0059/fmt-11.2.0/doc/get-started.md000066400000000000000000000153151512220627100217570ustar00rootroot00000000000000# 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', default_options: ['header-only=true']) 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. kcat-openal-soft-75c0059/fmt-11.2.0/doc/index.md000066400000000000000000000111311512220627100206330ustar00rootroot00000000000000--- 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.6, 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.

kcat-openal-soft-75c0059/fmt-11.2.0/doc/perf.svg000066400000000000000000000104011512220627100206560ustar00rootroot00000000000000double to string02505007501,0001,2501,500ostringstreamostrstreamsprintfdoubleconvfmtTime (ns), smaller is betterkcat-openal-soft-75c0059/fmt-11.2.0/doc/python-license.txt000066400000000000000000000350671512220627100227220ustar00rootroot00000000000000A. 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. kcat-openal-soft-75c0059/fmt-11.2.0/doc/syntax.md000066400000000000000000000665041512220627100210700ustar00rootroot00000000000000# 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" ``` kcat-openal-soft-75c0059/fmt-11.2.0/include/000077500000000000000000000000001512220627100200635ustar00rootroot00000000000000kcat-openal-soft-75c0059/fmt-11.2.0/include/fmt/000077500000000000000000000000001512220627100206515ustar00rootroot00000000000000kcat-openal-soft-75c0059/fmt-11.2.0/include/fmt/args.h000066400000000000000000000160141512220627100217600ustar00rootroot00000000000000// 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_ kcat-openal-soft-75c0059/fmt-11.2.0/include/fmt/base.h000066400000000000000000003130751512220627100217450ustar00rootroot00000000000000// 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 110200 // 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 >= 702 && FMT_CPLUSPLUS >= 201402L // GCC only allows constexpr member functions in non-literal types since 7.2: // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66297. # 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 #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 // Enable minimal optimizations for more compact code in debug mode. FMT_PRAGMA_GCC(push_options) #if !defined(__OPTIMIZE__) && !defined(__CUDACC__) && !defined(FMT_MODULE) FMT_PRAGMA_GCC(optimize("Og")) # define FMT_GCC_OPTIMIZED #endif FMT_PRAGMA_CLANG(diagnostic push) #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. #if defined(NDEBUG) || defined(FMT_GCC_OPTIMIZED) # define FMT_INLINE FMT_ALWAYS_INLINE #else # define FMT_INLINE inline #endif #ifndef FMT_BEGIN_NAMESPACE # define FMT_BEGIN_NAMESPACE \ namespace fmt { \ inline namespace alsoft_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 unused = int[]; \ (void)unused { 0, (expr, 0)... } 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) || FMT_MSC_VERSION // A workaround for gcc 4.9 & MSVC v141 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 view 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 view 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(__builtin_strlen) || FMT_GCC_VERSION || FMT_CLANG_VERSION if (std::is_same::value && !detail::is_constant_evaluated()) { size_ = __builtin_strlen(detail::narrow(s)); // strlen is not costexpr. return; } #endif size_t len = 0; while (*s++) ++len; size_ = len; } /// Constructs a string view 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)); } 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; // DEPRECATED! Will be merged with is_char and moved to detail. 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 // Specifies if `T` is a character (code unit) type. 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 }; unsigned data_ = 1 << fill_size_shift; static_assert(sizeof(basic_specs::data_) * CHAR_BIT >= 18, ""); // 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) | (static_cast(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]); } FMT_CONSTEXPR void copy_fill_from(const basic_specs& specs) { set_fill_size(specs.fill_size()); for (size_t i = 0; i < max_fill_size; ++i) fill_data_[i] = specs.fill_data_[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 is_view : std::false_type {}; template struct is_view> : std::is_base_of {}; 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; }; // named_args is non-const to suppress a bogus -Wmaybe-uninitalized in gcc 13. template FMT_CONSTEXPR void check_for_duplicate(named_arg_info* named_args, int named_arg_index, basic_string_view arg_name) { for (int i = 0; i < named_arg_index; ++i) { if (named_args[i].name == arg_name) report_error("duplicate named arg"); } } 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) { check_for_duplicate(named_args, named_arg_index, arg.name); 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) { check_for_duplicate(named_args, named_arg_index, T::name); 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) && FMT_BUILTIN_TYPES }; 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_); // If id is out of range, it means we do not know the type and cannot parse // the format at compile time. Instead, skip over content until we finish // the format spec, accounting for any nested replacements. for (int bracket_count = 0; begin != end && (bracket_count > 0 || *begin != '}'); ++begin) { if (*begin == '{') ++bracket_count; else if (*begin == '}') --bracket_count; } 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, enable_if_t&>()++), T>::value>> : 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. class 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 public: 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); } auto args() const -> const format_args& { return args_; } // 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<(is_view>::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 unused = (parse_format_string(sv, checker(sv, arg_pack())), 0); detail::ignore_unused(unused); } 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_ kcat-openal-soft-75c0059/fmt-11.2.0/include/fmt/chrono.h000066400000000000000000002335461512220627100223270ustar00rootroot00000000000000// 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" 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 { template void to_sys(T); }; #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 (const_check(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 using is_similar_arithmetic_type = bool_constant<(std::is_integral::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 ::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(); } namespace tz { // DEPRECATED! struct time_zone { template auto to_sys(LocalTime) -> sys_time { return {}; } }; template auto current_zone(T...) -> time_zone* { return nullptr; } template void _tzset(T...) {} } // namespace tz // DEPRECATED! inline void tzset_once() { static bool init = []() { using namespace tz; _tzset(); return false; }(); ignore_unused(init); } } // 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. */ FMT_DEPRECATED 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 FMT_DEPRECATED auto localtime(std::chrono::local_time time) -> std::tm { using namespace std::chrono; using namespace detail::tz; 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(); } }; class tm_format_checker : public null_chrono_spec_handler { private: bool has_timezone_ = false; public: constexpr explicit tm_format_checker(bool has_timezone) : has_timezone_(has_timezone) {} 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) { if (!has_timezone_) FMT_THROW(format_error("no timezone")); } FMT_CONSTEXPR void on_tz_name() { if (!has_timezone_) FMT_THROW(format_error("no timezone")); } }; 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_tm_gmtoff : std::false_type {}; template struct has_tm_gmtoff> : std::true_type {}; template struct has_tm_zone : std::false_type {}; template struct has_tm_zone> : std::true_type {}; template ::value)> bool set_tm_zone(T& time, char* tz) { time.tm_zone = tz; return true; } template ::value)> bool set_tm_zone(T&, char*) { return false; } inline char* utc() { static char tz[] = "UTC"; return tz; } // 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(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 duration_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_; 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 { auto h = tm_hour(); 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 { auto prev_year = curr_year - 1; auto curr_p = (curr_year + curr_year / 4 - curr_year / 100 + curr_year / 400) % days_per_week; 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 { auto year = tm_year(); 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 { auto year = tm_year(); 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(const T& tm, numeric_system ns) { write_utc_offset(tm.tm_gmtoff, ns); } template ::value)> void format_utc_offset(const T&, numeric_system ns) { write_utc_offset(0, ns); } template ::value)> void format_tz_name(const T& tm) { out_ = write_tm_str(out_, tm.tm_zone, loc_); } template ::value)> void format_tz_name(const T&) { out_ = std::copy_n(utc(), 3, out_); } 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(tm_, ns); } void on_tz_name() { format_tz_name(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 common_seconds_type = typename std::common_type::type; auto d_as_common = detail::duration_cast(d); auto d_as_whole_seconds = detail::duration_cast(d_as_common); // This conversion should be nonproblematic. auto diff = d_as_common - d_as_whole_seconds; 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 duration_formatter { using iterator = basic_appender; iterator out; // rep is unsigned to avoid overflow. using rep = conditional_t::value && sizeof(Rep) < sizeof(int), unsigned, typename make_unsigned_or_unchanged::type>; rep val; int precision; locale_ref locale; bool localized = false; using seconds = std::chrono::duration; seconds s; using milliseconds = std::chrono::duration; bool negative; using tm_writer_type = tm_writer; duration_formatter(iterator o, std::chrono::duration d, locale_ref loc) : out(o), val(static_cast(d.count())), locale(loc), 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) std::copy_n("inf", 3, out); else std::copy_n("-inf", 4, out); 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) return; *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); } template void format_tm(const tm& time, Callback cb, Args... args) { if (isnan(val)) return write_nan(); get_locale loc(localized, locale); auto w = tm_writer_type(loc, out, time); (w.*cb)(args...); out = w.out(); } void on_text(const Char* begin, const Char* 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 // __cpp_lib_chrono >= 201907 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(); if (it != end && *it == 'L') { ++it; this->set_localized(); } 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(this->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 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; this->set_localized(); } 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(this->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_; 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') { specs_.set_localized(); ++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 { auto f = detail::duration_formatter(out, d, ctx.locale()); f.precision = precision; f.localized = specs_.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_; basic_string_view fmt_ = detail::string_literal(); protected: auto localized() const -> bool { return specs_.localized(); } FMT_CONSTEXPR void set_localized() { specs_.set_localized(); } FMT_CONSTEXPR auto do_parse(parse_context& ctx, bool has_timezone) -> 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; } if (*it == 'L') { specs_.set_localized(); ++it; } end = detail::parse_chrono_format(it, end, detail::tm_format_checker(has_timezone)); // 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 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 = specs.localized() ? ctx.locale() : detail::locale_ref(); detail::get_locale loc(static_cast(loc_ref), loc_ref); auto w = detail::tm_writer, Char, Duration>( 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* { return do_parse(ctx, detail::has_tm_gmtoff::value); } template auto format(const std::tm& tm, FormatContext& ctx) const -> decltype(ctx.out()) { return do_format(tm, ctx, nullptr); } }; // DEPRECATED! Reversed order of template parameters. template struct formatter, Char> : private formatter { FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { return this->do_parse(ctx, true); } 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)) { detail::set_tm_zone(tm, detail::utc()); 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); detail::set_tm_zone(tm, detail::utc()); } subsecs += second; } 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> : private formatter { FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { return this->do_parse(ctx, false); } template auto format(local_time val, FormatContext& ctx) const -> decltype(ctx.out()) { auto time_since_epoch = val.time_since_epoch(); auto seconds_since_epoch = detail::duration_cast(time_since_epoch); // Use gmtime to prevent time zone conversion since local_time has an // unspecified time zone. std::tm t = gmtime(seconds_since_epoch.count()); using period = typename Duration::period; if (period::num == 1 && period::den == 1 && !std::is_floating_point::value) { return formatter::format(t, ctx); } auto subsecs = detail::duration_cast(time_since_epoch - seconds_since_epoch); return formatter::do_format(t, ctx, &subsecs); } }; FMT_END_EXPORT FMT_END_NAMESPACE #endif // FMT_CHRONO_H_ kcat-openal-soft-75c0059/fmt-11.2.0/include/fmt/color.h000066400000000000000000000573421512220627100221530ustar00rootroot00000000000000// 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 { constexpr rgb() : r(0), g(0), b(0) {} constexpr rgb(uint8_t r_, uint8_t g_, uint8_t b_) : r(r_), g(g_), b(b_) {} constexpr rgb(uint32_t hex) : r((hex >> 16) & 0xFF), g((hex >> 8) & 0xFF), b(hex & 0xFF) {} 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 { // A bit-packed variant of an RGB color, a terminal color, or unset color. // see text_style for the bit-packing scheme. struct color_type { constexpr color_type() noexcept = default; constexpr color_type(color rgb_color) noexcept : value_(static_cast(rgb_color) | (1 << 24)) {} constexpr color_type(rgb rgb_color) noexcept : color_type(static_cast( (static_cast(rgb_color.r) << 16) | (static_cast(rgb_color.g) << 8) | rgb_color.b)) {} constexpr color_type(terminal_color term_color) noexcept : value_(static_cast(term_color) | (3 << 24)) {} constexpr auto is_terminal_color() const noexcept -> bool { return (value_ & (1 << 25)) != 0; } constexpr auto value() const noexcept -> uint32_t { return value_ & 0xFFFFFF; } constexpr color_type(uint32_t value) noexcept : value_(value) {} uint32_t value_ = 0; }; } // namespace detail /// A text style consisting of foreground and background colors and emphasis. class text_style { // The information is packed as follows: // ┌──┠// │ 0│─┠// │..│ ├── foreground color value // │23│─┘ // ├──┤ // │24│─┬── discriminator for the above value. 00 if unset, 01 if it's // │25│─┘ an RGB color, or 11 if it's a terminal color (10 is unused) // ├──┤ // │26│──── overflow bit, always zero (see below) // ├──┤ // │27│─┠// │..│ │ // │50│ │ // ├──┤ │ // │51│ ├── background color (same format as the foreground color) // │52│ │ // ├──┤ │ // │53│─┘ // ├──┤ // │54│─┠// │..│ ├── emphases // │61│─┘ // ├──┤ // │62│─┬── unused // │63│─┘ // └──┘ // The overflow bits are there to make operator|= efficient. // When ORing, we must throw if, for either the foreground or background, // one style specifies a terminal color and the other specifies any color // (terminal or RGB); in other words, if one discriminator is 11 and the // other is 11 or 01. // // We do that check by adding the styles. Consider what adding does to each // possible pair of discriminators: // 00 + 00 = 000 // 01 + 00 = 001 // 11 + 00 = 011 // 01 + 01 = 010 // 11 + 01 = 100 (!!) // 11 + 11 = 110 (!!) // In the last two cases, the ones we want to catch, the third bit——the // overflow bit——is set. Bingo. // // We must take into account the possible carry bit from the bits // before the discriminator. The only potentially problematic case is // 11 + 00 = 011 (a carry bit would make it 100, not good!), but a carry // bit is impossible in that case, because 00 (unset color) means the // 24 bits that precede the discriminator are all zero. // // This test can be applied to both colors simultaneously. public: FMT_CONSTEXPR text_style(emphasis em = emphasis()) noexcept : style_(static_cast(em) << 54) {} FMT_CONSTEXPR auto operator|=(text_style rhs) -> text_style& { if (((style_ + rhs.style_) & ((1ULL << 26) | (1ULL << 53))) != 0) report_error("can't OR a terminal color"); style_ |= rhs.style_; return *this; } friend FMT_CONSTEXPR auto operator|(text_style lhs, text_style rhs) -> text_style { return lhs |= rhs; } FMT_CONSTEXPR auto operator==(text_style rhs) const noexcept -> bool { return style_ == rhs.style_; } FMT_CONSTEXPR auto operator!=(text_style rhs) const noexcept -> bool { return !(*this == rhs); } FMT_CONSTEXPR auto has_foreground() const noexcept -> bool { return (style_ & (1 << 24)) != 0; } FMT_CONSTEXPR auto has_background() const noexcept -> bool { return (style_ & (1ULL << 51)) != 0; } FMT_CONSTEXPR auto has_emphasis() const noexcept -> bool { return (style_ >> 54) != 0; } FMT_CONSTEXPR auto get_foreground() const noexcept -> detail::color_type { FMT_ASSERT(has_foreground(), "no foreground specified for this style"); return style_ & 0x3FFFFFF; } FMT_CONSTEXPR auto get_background() const noexcept -> detail::color_type { FMT_ASSERT(has_background(), "no background specified for this style"); return (style_ >> 27) & 0x3FFFFFF; } FMT_CONSTEXPR auto get_emphasis() const noexcept -> emphasis { FMT_ASSERT(has_emphasis(), "no emphasis specified for this style"); return static_cast(style_ >> 54); } private: FMT_CONSTEXPR text_style(uint64_t style) noexcept : style_(style) {} friend FMT_CONSTEXPR auto fg(detail::color_type foreground) noexcept -> text_style; friend FMT_CONSTEXPR auto bg(detail::color_type background) noexcept -> text_style; uint64_t style_ = 0; }; /// Creates a text style from the foreground (text) color. FMT_CONSTEXPR inline auto fg(detail::color_type foreground) noexcept -> text_style { return foreground.value_; } /// Creates a text style from the background color. FMT_CONSTEXPR inline auto bg(detail::color_type background) noexcept -> text_style { return static_cast(background.value_) << 27; } 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_terminal_color()) { bool is_background = esc == string_view("\x1b[48;2;"); uint32_t value = text_color.value(); // 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()); 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, text_style ts, basic_string_view fmt, basic_format_args> args) { if (ts.has_emphasis()) { auto emphasis = make_emphasis(ts.get_emphasis()); buf.append(emphasis.begin(), emphasis.end()); } if (ts.has_foreground()) { auto foreground = make_foreground_color(ts.get_foreground()); buf.append(foreground.begin(), foreground.end()); } if (ts.has_background()) { auto background = make_background_color(ts.get_background()); buf.append(background.begin(), background.end()); } vformat_to(buf, fmt, args); if (ts != text_style()) reset_color(buf); } } // namespace detail inline void vprint(FILE* f, 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, 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(text_style ts, format_string fmt, T&&... args) { return print(stdout, ts, fmt, std::forward(args)...); } inline auto vformat(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(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, 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, 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_ kcat-openal-soft-75c0059/fmt-11.2.0/include/fmt/compile.h000066400000000000000000000445501512220627100224620ustar00rootroot00000000000000// 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 {}; template struct is_compiled_string : std::is_base_of {}; namespace detail { /** * 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 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() { return FMT_COMPILE(Str.data); } } // namespace literals #endif FMT_END_EXPORT FMT_END_NAMESPACE #endif // FMT_COMPILE_H_ kcat-openal-soft-75c0059/fmt-11.2.0/include/fmt/core.h000066400000000000000000000002731512220627100217540ustar00rootroot00000000000000// 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" kcat-openal-soft-75c0059/fmt-11.2.0/include/fmt/format-inl.h000066400000000000000000002361311512220627100231000ustar00rootroot00000000000000// 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 // Use FMT_THROW instead of throw to avoid bogus unreachable code warnings // from MSVC. FMT_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 div_small_pow10_infos_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(const cache_entry_type& 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 < this->file_->_IO_write_end) return; // Force buffer initialization by placing and removing a char in a buffer. 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_ kcat-openal-soft-75c0059/fmt-11.2.0/include/fmt/format.h000066400000000000000000004642161512220627100223270ustar00rootroot00000000000000/* 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::free # 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 // GCC 4.9 doesn't support qualified names in specializations. 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 { operator basic_string_view() const; }; #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 >= 309 && 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 struct is_floating_point : std::is_floating_point {}; template <> struct is_floating_point : std::true_type {}; 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(std::malloc(n * sizeof(T))); if (!p) FMT_THROW(std::bad_alloc()); return p; } void deallocate(T* p, size_t) { std::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_CONSTEXPR FMT_NOINLINE 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: template ::value)> explicit digit_grouping(Locale 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, int exp_upper, 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; 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, int exp_upper, locale_ref loc) -> OutputIt { if (is_constant_evaluated()) { return do_write_float>(out, f, specs, s, exp_upper, loc); } else { return do_write_float(out, f, specs, s, exp_upper, 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; } // Numbers with exponents greater or equal to the returned value will use // the exponential notation. template constexpr auto exp_upper() -> int { return std::numeric_limits::digits10 != 0 ? min_of(16, std::numeric_limits::digits10 + 1) : 16; } 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; } constexpr int exp_upper = detail::exp_upper(); 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, exp_upper, 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, exp_upper, 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, exp_upper(), {}); } 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! https://github.com/fmtlib/fmt/issues/4292. template struct is_locale : std::false_type {}; template struct is_locale> : std::true_type {}; // 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.copy_fill_from(specs_); 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; template ::value)> inline auto vformat(const Locale& loc, string_view fmt, format_args args) -> std::string { auto buf = memory_buffer(); detail::vformat_to(buf, fmt, args, detail::locale_ref(loc)); return {buf.data(), buf.size()}; } template ::value)> FMT_INLINE auto format(const Locale& loc, format_string fmt, T&&... args) -> std::string { return vformat(loc, fmt.str, vargs{{args...}}); } template ::value)> auto vformat_to(OutputIt out, const Locale& loc, string_view fmt, format_args args) -> OutputIt { auto&& buf = detail::get_buffer(out); detail::vformat_to(buf, fmt, args, detail::locale_ref(loc)); return detail::get_iterator(buf, out); } template ::value&& detail::is_locale::value)> FMT_INLINE auto format_to(OutputIt out, const Locale& loc, format_string fmt, T&&... args) -> OutputIt { return fmt::vformat_to(out, loc, fmt.str, vargs{{args...}}); } template ::value)> FMT_NODISCARD FMT_INLINE auto formatted_size(const Locale& loc, format_string fmt, T&&... args) -> size_t { auto buf = detail::counting_buffer<>(); detail::vformat_to(buf, fmt.str, vargs{{args...}}, detail::locale_ref(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_ kcat-openal-soft-75c0059/fmt-11.2.0/include/fmt/os.h000066400000000000000000000307621512220627100214530ustar00rootroot00000000000000// 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_ kcat-openal-soft-75c0059/fmt-11.2.0/include/fmt/ostream.h000066400000000000000000000116401512220627100224760ustar00rootroot00000000000000// 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::const_check(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, FMT_STRING("{}\n"), fmt::format(fmt, std::forward(args)...)); } FMT_END_NAMESPACE #endif // FMT_OSTREAM_H_ kcat-openal-soft-75c0059/fmt-11.2.0/include/fmt/printf.h000066400000000000000000000477301512220627100223370ustar00rootroot00000000000000// 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_ kcat-openal-soft-75c0059/fmt-11.2.0/include/fmt/ranges.h000066400000000000000000000670631512220627100223150ustar00rootroot00000000000000// 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>, detail::is_formattable_delayed>::value>> { 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& value, FormatContext& ctx) const -> decltype(ctx.out()) { struct getter : T { static auto get(const T& v) -> all { return {v.*(&getter::c)}; // Access c through the derived class. } }; return formatter::format(getter::get(value), 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_ kcat-openal-soft-75c0059/fmt-11.2.0/include/fmt/std.h000066400000000000000000000534051512220627100216230ustar00rootroot00000000000000// 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 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 template struct formatter, Char> : nested_formatter, Char> { 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 this->write_padded(ctx, writer{bs}); } }; template struct formatter : basic_ostream_formatter {}; FMT_END_NAMESPACE #ifdef __cpp_lib_optional FMT_BEGIN_NAMESPACE 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 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 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; }; 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"); } }; 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 template <> struct formatter { private: format_specs specs_; detail::arg_ref width_ref_; bool debug_ = false; 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_); char c = *it; if (it != end && ((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 == 's') { specs_.set_type(presentation_type::string); ++it; } 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); auto buf = memory_buffer(); if (specs_.type() == presentation_type::string) { buf.append(ec.message()); } else { buf.append(string_view(ec.category().name())); buf.push_back(':'); detail::write(appender(buf), ec.value()); } auto quoted = memory_buffer(); auto str = string_view(buf.data(), buf.size()); if (debug_) { detail::write_escaped_string(std::back_inserter(quoted), str); str = string_view(quoted.data(), quoted.size()); } return detail::write(ctx.out(), str, 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 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 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. 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(); } 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 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 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; outer_specs.copy_fill_from(specs); 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); } }; 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_ kcat-openal-soft-75c0059/fmt-11.2.0/include/fmt/xchar.h000066400000000000000000000325041512220627100221330ustar00rootroot00000000000000// 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}}; } #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_locale::value&& detail::is_exotic_char::value)> inline auto vformat(const Locale& 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_locale::value&& detail::is_exotic_char::value)> inline auto format(const Locale& 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_locale::value&& detail::is_exotic_char::value)> inline auto vformat_to(OutputIt out, const Locale& 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_locale::value && detail::is_exotic_char::value> inline auto format_to(OutputIt out, const Locale& 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(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(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, text_style ts, wformat_string fmt, const T&... args) { vprint(f, ts, fmt, fmt::make_wformat_args(args...)); } template FMT_DEPRECATED void print(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_ kcat-openal-soft-75c0059/fmt-11.2.0/src/000077500000000000000000000000001512220627100172275ustar00rootroot00000000000000kcat-openal-soft-75c0059/fmt-11.2.0/src/fmt.cc000066400000000000000000000063271512220627100203340ustar00rootroot00000000000000module; #define FMT_MODULE #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 kcat-openal-soft-75c0059/fmt-11.2.0/src/format.cc000066400000000000000000000025611512220627100210320ustar00rootroot00000000000000// 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 kcat-openal-soft-75c0059/fmt-11.2.0/src/os.cc000066400000000000000000000261501512220627100201630ustar00rootroot00000000000000// 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 kcat-openal-soft-75c0059/fmt-11.2.0/support/000077500000000000000000000000001512220627100201545ustar00rootroot00000000000000kcat-openal-soft-75c0059/fmt-11.2.0/support/Android.mk000066400000000000000000000004531512220627100220670ustar00rootroot00000000000000LOCAL_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) kcat-openal-soft-75c0059/fmt-11.2.0/support/AndroidManifest.xml000066400000000000000000000000371512220627100237450ustar00rootroot00000000000000 kcat-openal-soft-75c0059/fmt-11.2.0/support/C++.sublime-syntax000066400000000000000000002152041512220627100233760ustar00rootroot00000000000000%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 kcat-openal-soft-75c0059/fmt-11.2.0/support/README000066400000000000000000000001251512220627100210320ustar00rootroot00000000000000This directory contains build support files such as * CMake modules * Build scripts kcat-openal-soft-75c0059/fmt-11.2.0/support/Vagrantfile000066400000000000000000000011071512220627100223400ustar00rootroot00000000000000# -*- 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 kcat-openal-soft-75c0059/fmt-11.2.0/support/bazel/000077500000000000000000000000001512220627100212515ustar00rootroot00000000000000kcat-openal-soft-75c0059/fmt-11.2.0/support/bazel/.bazelversion000066400000000000000000000000061512220627100237510ustar00rootroot000000000000008.1.1 kcat-openal-soft-75c0059/fmt-11.2.0/support/bazel/BUILD.bazel000066400000000000000000000010351512220627100231260ustar00rootroot00000000000000load("@rules_cc//cc:defs.bzl", "cc_library") cc_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"], ) kcat-openal-soft-75c0059/fmt-11.2.0/support/bazel/MODULE.bazel000066400000000000000000000002331512220627100232530ustar00rootroot00000000000000module( name = "fmt", compatibility_level = 10, ) bazel_dep(name = "platforms", version = "0.0.11") bazel_dep(name = "rules_cc", version = "0.1.1") kcat-openal-soft-75c0059/fmt-11.2.0/support/bazel/README.md000066400000000000000000000017011512220627100225270ustar00rootroot00000000000000# 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 = "11.1.4") ``` ### 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", ) ``` kcat-openal-soft-75c0059/fmt-11.2.0/support/bazel/WORKSPACE.bazel000066400000000000000000000000511512220627100236220ustar00rootroot00000000000000# WORKSPACE marker file needed by Bazel kcat-openal-soft-75c0059/fmt-11.2.0/support/build.gradle000066400000000000000000000076111512220627100224400ustar00rootroot00000000000000import 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" } } kcat-openal-soft-75c0059/fmt-11.2.0/support/check-commits000077500000000000000000000025551512220627100226370ustar00rootroot00000000000000#!/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() kcat-openal-soft-75c0059/fmt-11.2.0/support/cmake/000077500000000000000000000000001512220627100212345ustar00rootroot00000000000000kcat-openal-soft-75c0059/fmt-11.2.0/support/cmake/FindSetEnv.cmake000066400000000000000000000004531512220627100242450ustar00rootroot00000000000000# 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 () kcat-openal-soft-75c0059/fmt-11.2.0/support/cmake/JoinPaths.cmake000066400000000000000000000016771512220627100241500ustar00rootroot00000000000000# 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() kcat-openal-soft-75c0059/fmt-11.2.0/support/cmake/fmt-config.cmake.in000066400000000000000000000002231512220627100246710ustar00rootroot00000000000000@PACKAGE_INIT@ if (NOT TARGET fmt::fmt) include(${CMAKE_CURRENT_LIST_DIR}/@targets_export_name@.cmake) endif () check_required_components(fmt) kcat-openal-soft-75c0059/fmt-11.2.0/support/cmake/fmt.pc.in000066400000000000000000000004101512220627100227460ustar00rootroot00000000000000prefix=@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} kcat-openal-soft-75c0059/fmt-11.2.0/support/docopt.py000066400000000000000000000465101512220627100220240ustar00rootroot00000000000000"""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() kcat-openal-soft-75c0059/fmt-11.2.0/support/mkdocs000077500000000000000000000044211512220627100213630ustar00rootroot00000000000000#!/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)) kcat-openal-soft-75c0059/fmt-11.2.0/support/mkdocs.yml000066400000000000000000000017451512220627100221660ustar00rootroot00000000000000site_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 kcat-openal-soft-75c0059/fmt-11.2.0/support/printable.py000077500000000000000000000137351512220627100225220ustar00rootroot00000000000000#!/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() kcat-openal-soft-75c0059/fmt-11.2.0/support/python/000077500000000000000000000000001512220627100214755ustar00rootroot00000000000000kcat-openal-soft-75c0059/fmt-11.2.0/support/python/mkdocstrings_handlers/000077500000000000000000000000001512220627100260645ustar00rootroot00000000000000kcat-openal-soft-75c0059/fmt-11.2.0/support/python/mkdocstrings_handlers/cxx/000077500000000000000000000000001512220627100266665ustar00rootroot00000000000000kcat-openal-soft-75c0059/fmt-11.2.0/support/python/mkdocstrings_handlers/cxx/__init__.py000066400000000000000000000274731512220627100310140ustar00rootroot00000000000000# 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) kcat-openal-soft-75c0059/fmt-11.2.0/support/python/mkdocstrings_handlers/cxx/templates/000077500000000000000000000000001512220627100306645ustar00rootroot00000000000000kcat-openal-soft-75c0059/fmt-11.2.0/support/python/mkdocstrings_handlers/cxx/templates/README000066400000000000000000000001001512220627100315330ustar00rootroot00000000000000mkdocsstrings requires a handler to have a templates directory. kcat-openal-soft-75c0059/fmt-11.2.0/support/release.py000077500000000000000000000134241512220627100221550ustar00rootroot00000000000000#!/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]) kcat-openal-soft-75c0059/gsl/000077500000000000000000000000001512220627100157225ustar00rootroot00000000000000kcat-openal-soft-75c0059/gsl/.clang-format000066400000000000000000000014101512220627100202710ustar00rootroot00000000000000ColumnLimit: 100 UseTab: Never IndentWidth: 4 AccessModifierOffset: -4 NamespaceIndentation: Inner BreakBeforeBraces: Custom BraceWrapping: AfterNamespace: true AfterEnum: true AfterStruct: true AfterClass: true SplitEmptyFunction: false AfterControlStatement: true AfterFunction: true AfterUnion: true BeforeElse: true AlwaysBreakTemplateDeclarations: true BreakConstructorInitializersBeforeComma: true ConstructorInitializerAllOnOneLineOrOnePerLine: true AllowShortBlocksOnASingleLine: true AllowShortFunctionsOnASingleLine: All AllowShortIfStatementsOnASingleLine: true AllowShortLoopsOnASingleLine: true PointerAlignment: Left AlignConsecutiveAssignments: false AlignTrailingComments: true SpaceAfterCStyleCast: true CommentPragmas: '^ NO-FORMAT:' kcat-openal-soft-75c0059/gsl/CMakeLists.txt000066400000000000000000000033631512220627100204670ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.14...3.16) project(GSL VERSION 4.2.0 LANGUAGES CXX) add_library(GSL INTERFACE) add_library(Microsoft.GSL::GSL ALIAS GSL) # https://cmake.org/cmake/help/latest/variable/PROJECT_IS_TOP_LEVEL.html string(COMPARE EQUAL ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_SOURCE_DIR} PROJECT_IS_TOP_LEVEL) option(GSL_INSTALL "Generate and install GSL target" ${PROJECT_IS_TOP_LEVEL}) option(GSL_TEST "Build and perform GSL tests" ${PROJECT_IS_TOP_LEVEL}) # The implementation generally assumes a platform that implements C++14 support target_compile_features(GSL INTERFACE "cxx_std_14") # Setup include directory add_subdirectory(include) target_sources(GSL INTERFACE $) if (GSL_TEST) enable_testing() add_subdirectory(tests) endif() if (GSL_INSTALL) include(GNUInstallDirs) include(CMakePackageConfigHelpers) install(DIRECTORY "${PROJECT_SOURCE_DIR}/include/gsl" DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) set(export_name "Microsoft.GSLConfig") set(namespace "Microsoft.GSL::") set(cmake_files_install_dir ${CMAKE_INSTALL_DATADIR}/cmake/Microsoft.GSL) install(TARGETS GSL EXPORT ${export_name} INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) install(EXPORT ${export_name} NAMESPACE ${namespace} DESTINATION ${cmake_files_install_dir}) export(TARGETS GSL NAMESPACE ${namespace} FILE ${export_name}.cmake) set(gls_config_version "${CMAKE_CURRENT_BINARY_DIR}/Microsoft.GSLConfigVersion.cmake") write_basic_package_version_file(${gls_config_version} COMPATIBILITY SameMajorVersion ARCH_INDEPENDENT) install(FILES ${gls_config_version} DESTINATION ${cmake_files_install_dir}) install(FILES GSL.natvis DESTINATION ${cmake_files_install_dir}) endif() kcat-openal-soft-75c0059/gsl/CMakeSettings.json000066400000000000000000000010411512220627100213120ustar00rootroot00000000000000{ "configurations": [ { "name": "x64-Debug", "generator": "Ninja", "configurationType": "Debug", "inheritEnvironments": [ "msvc_x64_x64" ], "buildRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\build\\${name}", "installRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\install\\${name}", "cmakeCommandArgs": "-DGSL_CXX_STANDARD=17", "buildCommandArgs": "-v", "ctestCommandArgs": "", "codeAnalysisRuleset": "CppCoreCheckRules.ruleset" } ] }kcat-openal-soft-75c0059/gsl/CONTRIBUTING.md000066400000000000000000000037221512220627100201570ustar00rootroot00000000000000## Contributing to the Guidelines Support Library The Guidelines Support Library (GSL) contains functions and types that are suggested for use by the [C++ Core Guidelines](https://github.com/isocpp/CppCoreGuidelines). GSL design changes are made only as a result of modifications to the Guidelines. GSL is accepting contributions that improve or refine any of the types in this library as well as ports to other platforms. Changes should have an issue tracking the suggestion that has been approved by the maintainers. Your pull request should include a link to the bug that you are fixing. If you've submitted a PR, please post a comment in the associated issue to avoid duplication of effort. ## Legal You will need to complete a Contributor License Agreement (CLA). Briefly, this agreement testifies that you are granting us and the community permission to use the submitted change according to the terms of the project's license, and that the work being submitted is under appropriate copyright. Please submit a Contributor License Agreement (CLA) before submitting a pull request. You may visit https://cla.microsoft.com to sign digitally. ## Housekeeping Your pull request should: * Include a description of what your change intends to do * Be a child commit of a reasonably recent commit in the **main** branch * Requests need not be a single commit, but should be a linear sequence of commits (i.e. no merge commits in your PR) * It is desirable, but not necessary, for the tests to pass at each commit. Please see [README.md](./README.md) for instructions to build the test suite. * Have clear commit messages * e.g. "Fix issue", "Add tests for type", etc. * Include appropriate tests * Tests should include reasonable permutations of the target fix/change * Include baseline changes with your change * All changed code must have 100% code coverage * To avoid line ending issues, set `autocrlf = input` and `whitespace = cr-at-eol` in your git configuration kcat-openal-soft-75c0059/gsl/GSL.natvis000066400000000000000000000022071512220627100175760ustar00rootroot00000000000000 {{ invoke = {invoke_}, action = {f_} }} invoke_ f_ {{ extent = {storage_.size_} }} storage_.size_ storage_.data_ value = {*ptr_} kcat-openal-soft-75c0059/gsl/LICENSE000066400000000000000000000022051512220627100167260ustar00rootroot00000000000000Copyright (c) 2015 Microsoft Corporation. All rights reserved. This code is licensed under the MIT License (MIT). 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. kcat-openal-soft-75c0059/gsl/README.md000066400000000000000000000350561512220627100172120ustar00rootroot00000000000000# GSL: Guidelines Support Library [![CI](https://github.com/Microsoft/GSL/actions/workflows/compilers.yml/badge.svg)](https://github.com/microsoft/GSL/actions/workflows/compilers.yml?query=branch%3Amain) [![vcpkg](https://img.shields.io/vcpkg/v/ms-gsl)](https://vcpkg.io/en/package/ms-gsl) The Guidelines Support Library (GSL) contains functions and types that are suggested for use by the [C++ Core Guidelines](https://github.com/isocpp/CppCoreGuidelines) maintained by the [Standard C++ Foundation](https://isocpp.org). This repo contains Microsoft's implementation of GSL. The entire implementation is provided inline in the headers under the [gsl](./include/gsl) directory. The implementation generally assumes a platform that implements C++14 support. While some types have been broken out into their own headers (e.g. [gsl/span](./include/gsl/span)), it is simplest to just include [gsl/gsl](./include/gsl/gsl) and gain access to the entire library. > NOTE: We encourage contributions that improve or refine any of the types in this library as well as ports to other platforms. Please see [CONTRIBUTING.md](./CONTRIBUTING.md) for more information about contributing. # Project Code of Conduct This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. # Usage of Third Party Libraries This project makes use of the [Google Test](https://github.com/google/googletest) testing library. Please see the [ThirdPartyNotices.txt](./ThirdPartyNotices.txt) file for details regarding the licensing of Google Test. # Supported features ## Microsoft GSL implements the following from the C++ Core Guidelines: Feature | Supported? | Description -------------------------------------------------------------------------|:----------:|------------- [**1. Views**][cg-views] | | [owner](docs/headers.md#user-content-H-pointers-owner) | ☑ | An alias for a raw pointer [not_null](docs/headers.md#user-content-H-pointers-not_null) | ☑ | Restricts a pointer/smart pointer to hold non-null values [span](docs/headers.md#user-content-H-span-span) | ☑ | A view over a contiguous sequence of memory. Based on the standardized version of `std::span`, however `gsl::span` enforces bounds checking. span_p | ☐ | Spans a range starting from a pointer to the first place for which the predicate is true [basic_zstring](docs/headers.md#user-content-H-zstring) | ☑ | A pointer to a C-string (zero-terminated array) with a templated char type [zstring](docs/headers.md#user-content-H-zstring) | ☑ | An alias to `basic_zstring` with dynamic extent and a char type of `char` [czstring](docs/headers.md#user-content-H-zstring) | ☑ | An alias to `basic_zstring` with dynamic extent and a char type of `const char` [wzstring](docs/headers.md#user-content-H-zstring) | ☑ | An alias to `basic_zstring` with dynamic extent and a char type of `wchar_t` [cwzstring](docs/headers.md#user-content-H-zstring) | ☑ | An alias to `basic_zstring` with dynamic extent and a char type of `const wchar_t` [u16zstring](docs/headers.md#user-content-H-zstring) | ☑ | An alias to `basic_zstring` with dynamic extent and a char type of `char16_t` [cu16zstring](docs/headers.md#user-content-H-zstring) | ☑ | An alias to `basic_zstring` with dynamic extent and a char type of `const char16_t` [u32zstring](docs/headers.md#user-content-H-zstring) | ☑ | An alias to `basic_zstring` with dynamic extent and a char type of `char32_t` [cu32zstring](docs/headers.md#user-content-H-zstring) | ☑ | An alias to `basic_zstring` with dynamic extent and a char type of `const char32_t` [**2. Owners**][cg-owners] | | stack_array | ☐ | A stack-allocated array dyn_array | ☐ | A heap-allocated array [**3. Assertions**][cg-assertions] | | [Expects](docs/headers.md#user-content-H-assert-expects) | ☑ | A precondition assertion; on failure it terminates [Ensures](docs/headers.md#user-content-H-assert-ensures) | ☑ | A postcondition assertion; on failure it terminates [**4. Utilities**][cg-utilities] | | move_owner | ☐ | A helper function that moves one `owner` to the other [final_action](docs/headers.md#user-content-H-util-final_action) | ☑ | A RAII style class that invokes a functor on its destruction [finally](docs/headers.md#user-content-H-util-finally) | ☑ | A helper function instantiating [final_action](docs/headers.md#user-content-H-util-final_action) [GSL_SUPPRESS](docs/headers.md#user-content-H-assert-gsl_suppress) | ☑ | A macro that takes an argument and turns it into `[[gsl::suppress(x)]]` or `[[gsl::suppress("x")]]` [[implicit]] | ☐ | A "marker" to put on single-argument constructors to explicitly make them non-explicit [index](docs/headers.md#user-content-H-util-index) | ☑ | A type to use for all container and array indexing (currently an alias for `std::ptrdiff_t`) [narrow](docs/headers.md#user-content-H-narrow-narrow) | ☑ | A checked version of `narrow_cast`; it can throw [narrowing_error](docs/headers.md#user-content-H-narrow-narrowing_error) [narrow_cast](docs/headers.md#user-content-H-util-narrow_cast) | ☑ | A narrowing cast for values and a synonym for `static_cast` [narrowing_error](docs/headers.md#user-content-H-narrow-narrowing_error) | ☑ | A custom exception type thrown by [narrow](docs/headers.md#user-content-H-narrow-narrow) [**5. Concepts**][cg-concepts] | ☐ | ## The following features do not exist in or have been removed from the C++ Core Guidelines: Feature | Supported? | Description -----------------------------------|:----------:|------------- [strict_not_null](docs/headers.md#user-content-H-pointers-strict_not_null) | ☑ | A stricter version of [not_null](docs/headers.md#user-content-H-pointers-not_null) with explicit constructors multi_span | ☐ | Deprecated. Multi-dimensional span. strided_span | ☐ | Deprecated. Support for this type has been discontinued. basic_string_span | ☐ | Deprecated. Like `span` but for strings with a templated char type string_span | ☐ | Deprecated. An alias to `basic_string_span` with a char type of `char` cstring_span | ☐ | Deprecated. An alias to `basic_string_span` with a char type of `const char` wstring_span | ☐ | Deprecated. An alias to `basic_string_span` with a char type of `wchar_t` cwstring_span | ☐ | Deprecated. An alias to `basic_string_span` with a char type of `const wchar_t` u16string_span | ☐ | Deprecated. An alias to `basic_string_span` with a char type of `char16_t` cu16string_span | ☐ | Deprecated. An alias to `basic_string_span` with a char type of `const char16_t` u32string_span | ☐ | Deprecated. An alias to `basic_string_span` with a char type of `char32_t` cu32string_span | ☐ | Deprecated. An alias to `basic_string_span` with a char type of `const char32_t` ## The following features have been adopted by WG21. They are deprecated in GSL. Feature | Deprecated Since | Notes ------------------------------------------------------------------|------------------|------ [unique_ptr](docs/headers.md#user-content-H-pointers-unique_ptr) | C++11 | Use std::unique_ptr instead. [shared_ptr](docs/headers.md#user-content-H-pointers-shared_ptr) | C++11 | Use std::shared_ptr instead. [byte](docs/headers.md#user-content-H-byte-byte) | C++17 | Use std::byte instead. joining_thread | C++20 (Note: Not yet implemented in GSL) | Use std::jthread instead. This is based on [CppCoreGuidelines semi-specification](https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#gsl-guidelines-support-library). [cg-views]: https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#gslview-views [cg-owners]: https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#gslowner-ownership-pointers [cg-assertions]: https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#gslassert-assertions [cg-utilities]: https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#gslutil-utilities [cg-concepts]: https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#gslconcept-concepts # Quick Start ## Supported Compilers / Toolsets The GSL officially supports recent major versions of Visual Studio with both MSVC and LLVM, GCC, Clang, and XCode with Apple-Clang. For each of these major versions, the GSL officially supports C++14, C++17, C++20, and C++23 (when supported by the compiler). Below is a table showing the versions currently being tested (also see [.github/workflows/compilers.yml](the workflow).) Compiler |Toolset Versions Currently Tested :------- |--: GCC | 12, 13, 14 XCode | 14.3.1, 15.4 Clang | 16, 17, 18 Visual Studio with MSVC | VS2019, VS2022 Visual Studio with LLVM | VS2019, VS2022 --- If you successfully port GSL to another platform, we would love to hear from you! - Submit an issue specifying the platform and target. - Consider contributing your changes by filing a pull request with any necessary changes. - If at all possible, add a CI/CD step and add the button to the table below! Target | CI/CD Status :------- | -----------: iOS | [![CI_iOS](https://github.com/microsoft/GSL/workflows/CI_iOS/badge.svg?branch=main)](https://github.com/microsoft/GSL/actions/workflows/ios.yml?query=branch%3Amain) Android | [![CI_Android](https://github.com/microsoft/GSL/workflows/CI_Android/badge.svg?branch=main)](https://github.com/microsoft/GSL/actions/workflows/android.yml?query=branch%3Amain) Note: These CI/CD steps are run with each pull request, however failures in them are non-blocking. ## Building the tests To build the tests, you will require the following: * [CMake](http://cmake.org), version 3.14 or later to be installed and in your PATH. These steps assume the source code of this repository has been cloned into a directory named `c:\GSL`. 1. Create a directory to contain the build outputs for a particular architecture (we name it `c:\GSL\build-x86` in this example). cd GSL md build-x86 cd build-x86 2. Configure CMake to use the compiler of your choice (you can see a list by running `cmake --help`). cmake -G "Visual Studio 15 2017" c:\GSL 3. Build the test suite (in this case, in the Debug configuration, Release is another good choice). cmake --build . --config Debug 4. Run the test suite. ctest -C Debug All tests should pass - indicating your platform is fully supported and you are ready to use the GSL types! ## Building GSL - Using vcpkg You can download and install GSL 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 ms-gsl The GSL 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. ## Using the libraries As the types are entirely implemented inline in headers, there are no linking requirements. You can copy the [gsl](./include/gsl) directory into your source tree so it is available to your compiler, then include the appropriate headers in your program. Alternatively set your compiler's *include path* flag to point to the GSL development folder (`c:\GSL\include` in the example above) or installation folder (after running the install). Eg. MSVC++ /I c:\GSL\include GCC/clang -I$HOME/dev/GSL/include Include the library using: #include ## Usage in CMake The library provides a Config file for CMake, once installed it can be found via `find_package`. Which, when successful, will add library target called `Microsoft.GSL::GSL` which you can use via the usual `target_link_libraries` mechanism. ```cmake find_package(Microsoft.GSL CONFIG REQUIRED) target_link_libraries(foobar PRIVATE Microsoft.GSL::GSL) ``` ### FetchContent If you are using CMake version 3.11+ you can use the official [FetchContent module](https://cmake.org/cmake/help/latest/module/FetchContent.html). This allows you to easily incorporate GSL into your project. ```cmake # NOTE: This example uses CMake version 3.14 (FetchContent_MakeAvailable). # Since it streamlines the FetchContent process cmake_minimum_required(VERSION 3.14) include(FetchContent) FetchContent_Declare(GSL GIT_REPOSITORY "https://github.com/microsoft/GSL" GIT_TAG "v4.2.0" GIT_SHALLOW ON ) FetchContent_MakeAvailable(GSL) target_link_libraries(foobar PRIVATE Microsoft.GSL::GSL) ``` ## Debugging visualization support For Visual Studio users, the file [GSL.natvis](./GSL.natvis) in the root directory of the repository can be added to your project if you would like more helpful visualization of GSL types in the Visual Studio debugger than would be offered by default. ## See Also For information on [Microsoft Gray Systems Lab (GSL)](https://aka.ms/gsl) of applied data management and system research see . kcat-openal-soft-75c0059/gsl/SECURITY.md000066400000000000000000000053051512220627100175160ustar00rootroot00000000000000 ## Security Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below. ## Reporting Security Issues **Please do not report security vulnerabilities through public GitHub issues.** Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report). If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey). You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc). Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) * Full paths of source file(s) related to the manifestation of the issue * The location of the affected source code (tag/branch/commit or direct URL) * Any special configuration required to reproduce the issue * Step-by-step instructions to reproduce the issue * Proof-of-concept or exploit code (if possible) * Impact of the issue, including how an attacker might exploit the issue This information will help us triage your report more quickly. If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs. ## Preferred Languages We prefer all communications to be in English. ## Policy Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd). kcat-openal-soft-75c0059/gsl/ThirdPartyNotices.txt000066400000000000000000000037461512220627100221140ustar00rootroot00000000000000 THIRD-PARTY SOFTWARE NOTICES AND INFORMATION Do Not Translate or Localize GSL: Guidelines Support Library incorporates third party material from the projects listed below. ------------------------------------------------------------------------------- Software: Google Test Owner: Google Inc. Source URL: github.com/google/googletest License: BSD 3 - Clause Text: Copyright 2008, Google Inc. 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 Google Inc. 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 OWNER 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. ------------------------------------------------------------------------------- kcat-openal-soft-75c0059/gsl/docs/000077500000000000000000000000001512220627100166525ustar00rootroot00000000000000kcat-openal-soft-75c0059/gsl/docs/headers.md000066400000000000000000001103241512220627100206100ustar00rootroot00000000000000The Guidelines Support Library (GSL) interface is very lightweight and exposed via a header-only library. This document attempts to document all of the headers and their exposed classes and functions. Types and functions are exported in the namespace `gsl`. See [GSL: Guidelines support library](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#S-gsl) # Headers - [``](#user-content-H-algorithms) - [``](#user-content-H-assert) - [``](#user-content-H-byte) - [``](#user-content-H-gsl) - [``](#user-content-H-narrow) - [``](#user-content-H-pointers) - [``](#user-content-H-span) - [``](#user-content-H-span_ext) - [``](#user-content-H-zstring) - [``](#user-content-H-util) ## `` This header contains some common algorithms that have been wrapped in GSL safety features. - [`gsl::copy`](#user-content-H-algorithms-copy) ### `gsl::copy` ```cpp template void copy(span src, span dest); ``` This function copies the content from the `src` [`span`](#user-content-H-span-span) to the `dest` [`span`](#user-content-H-span-span). It [`Expects`](#user-content-H-assert-expects) that the destination `span` is at least as large as the source `span`. ## `` This header contains some macros used for contract checking and suppressing code analysis warnings. See [GSL.assert: Assertions](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#SS-assertions) - [`GSL_SUPPRESS`](#user-content-H-assert-gsl_suppress) - [`Expects`](#user-content-H-assert-expects) - [`Ensures`](#user-content-H-assert-ensures) ### `GSL_SUPPRESS` This macro can be used to suppress a code analysis warning. The core guidelines request tools that check for the rules to respect suppressing a rule by writing `[[gsl::suppress(tag)]]` or `[[gsl::suppress(tag, justification: "message")]]`. Clang does not use exactly that syntax, but requires `tag` to be put in double quotes `[[gsl::suppress("tag")]]`. For portable code you can use `GSL_SUPPRESS(tag)`. See [In.force: Enforcement](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#inforce-enforcement). ### `Expects` This macro can be used for expressing a precondition. If the precondition is not held, then `std::terminate` will be called. See [I.6: Prefer `Expects()` for expressing preconditions](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#i6-prefer-expects-for-expressing-preconditions) ### `Ensures` This macro can be used for expressing a postcondition. If the postcondition is not held, then `std::terminate` will be called. See [I.8: Prefer `Ensures()` for expressing postconditions](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#i8-prefer-ensures-for-expressing-postconditions) ## `` This header contains the definition of a byte type, implementing `std::byte` before it was standardized into C++17. - [`gsl::byte`](#user-content-H-byte-byte) ### `gsl::byte` If `GSL_USE_STD_BYTE` is defined to be `1`, then `gsl::byte` will be an alias to `std::byte`. If `GSL_USE_STD_BYTE` is defined to be `0`, then `gsl::byte` will be a distinct type that implements the concept of byte. If `GSL_USE_STD_BYTE` is not defined, then the header file will check if `std::byte` is available (C\+\+17 or higher). If yes, `gsl::byte` will be an alias to `std::byte`, otherwise `gsl::byte` will be a distinct type that implements the concept of byte. ⚠ Take care when linking projects that were compiled with different language standards (before C\+\+17 and C\+\+17 or higher). If you do so, you might want to `#define GSL_USE_STD_BYTE 0` to a fixed value to be sure that both projects use exactly the same type. Otherwise you might get linker errors. See [SL.str.5: Use `std::byte` to refer to byte values that do not necessarily represent characters](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rstr-byte) ### Non-member functions ```cpp template ::value, bool> = true> constexpr byte& operator<<=(byte& b, IntegerType shift) noexcept; template ::value, bool> = true> constexpr byte operator<<(byte b, IntegerType shift) noexcept; template ::value, bool> = true> constexpr byte& operator>>=(byte& b, IntegerType shift) noexcept; template ::value, bool> = true> constexpr byte operator>>(byte b, IntegerType shift) noexcept; ``` Left or right shift a `byte` by a given number of bits. ```cpp constexpr byte& operator|=(byte& l, byte r) noexcept; constexpr byte operator|(byte l, byte r) noexcept; ``` Bitwise "or" of two `byte`s. ```cpp constexpr byte& operator&=(byte& l, byte r) noexcept; constexpr byte operator&(byte l, byte r) noexcept; ``` Bitwise "and" of two `byte`s. ```cpp constexpr byte& operator^=(byte& l, byte r) noexcept; constexpr byte operator^(byte l, byte r) noexcept; ``` Bitwise xor of two `byte`s. ```cpp constexpr byte operator~(byte b) noexcept; ``` Bitwise negation of a `byte`. Flips all bits. Zeroes become ones, ones become zeroes. ```cpp template ::value, bool> = true> constexpr IntegerType to_integer(byte b) noexcept; ``` Convert the given `byte` value to an integral type. ```cpp template constexpr byte to_byte(T t) noexcept; ``` Convert the given value to a `byte`. The template requires `T` to be an `unsigned char` so that no data loss can occur. If you want to convert an integer constant to a `byte` you probably want to call `to_byte()`. ```cpp template constexpr byte to_byte() noexcept; ``` Convert the given value `I` to a `byte`. The template requires `I` to be in the valid range 0..255 for a `gsl::byte`. ## `` This header is a convenience header that includes all other [GSL headers](#user-content-H). Since `` requires exceptions, it will only be included if exceptions are enabled. ## `` This header contains utility functions and classes, for narrowing casts, which require exceptions. The narrowing-related utilities that don't require exceptions are found inside [util](#user-content-H-util). See [GSL.util: Utilities](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#SS-utilities) - [`gsl::narrowing_error`](#user-content-H-narrow-narrowing_error) - [`gsl::narrow`](#user-content-H-narrow-narrow) ### `gsl::narrowing_error` `gsl::narrowing_error` is the exception thrown by [`gsl::narrow`](#user-content-H-narrow-narrow) when a narrowing conversion fails. It is derived from `std::exception`. ### `gsl::narrow` `gsl::narrow(x)` is a named cast that does a `static_cast(x)` for narrowing conversions with no signedness promotions. If the argument `x` cannot be represented in the target type `T`, then the function throws a [`gsl::narrowing_error`](#user-content-H-narrow-narrowing_error) (e.g., `narrow(-42)` and `narrow(300)` throw). Note: compare [`gsl::narrow_cast`](#user-content-H-util-narrow_cast) in header [util](#user-content-H-util). See [ES.46: Avoid lossy (narrowing, truncating) arithmetic conversions](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Res-narrowing) and [ES.49: If you must use a cast, use a named cast](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Res-casts-named) ## `` This header contains some pointer types. See [GSL.view](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#SS-views) - [`gsl::unique_ptr`](#user-content-H-pointers-unique_ptr) - [`gsl::shared_ptr`](#user-content-H-pointers-shared_ptr) - [`gsl::owner`](#user-content-H-pointers-owner) - [`gsl::not_null`](#user-content-H-pointers-not_null) - [`gsl::strict_not_null`](#user-content-H-pointers-strict_not_null) ### `gsl::unique_ptr` `gsl::unique_ptr` is an alias to `std::unique_ptr`. See [GSL.owner: Ownership pointers](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#SS-ownership) ### `gsl::shared_ptr` `gsl::shared_ptr` is an alias to `std::shared_ptr`. See [GSL.owner: Ownership pointers](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#SS-ownership) ### `gsl::owner` `gsl::owner` is designed as a safety mechanism for code that must deal directly with raw pointers that own memory. Ideally such code should be restricted to the implementation of low-level abstractions. `gsl::owner` can also be used as a stepping point in converting legacy code to use more modern RAII constructs such as smart pointers. `T` must be a pointer type (`std::is_pointer`). A `gsl::owner` is a typedef to `T`. It adds no runtime overhead whatsoever, as it is purely syntactic and does not add any runtime checks. Instead, it serves as an annotation for static analysis tools which check for memory safety, and as a code comprehension guide for human readers. See Enforcement section of [C.31: All resources acquired by a class must be released by the class’s destructor](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rc-dtor-release). ### `gsl::not_null` `gsl::not_null` restricts a pointer or smart pointer to only hold non-null values. It has no size overhead over `T`. The checks for ensuring that the pointer is not null are done in the constructor. There is no overhead when retrieving or dereferencing the checked pointer. When a nullptr check fails, `std::terminate` is called. See [F.23: Use a `not_null` to indicate that “null†is not a valid value](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rf-nullptr) #### Member Types ```cpp using element_type = T; ``` The type of the pointer or smart pointer that is managed by this object. #### Member functions ##### Construct/Copy ```cpp template ::value>> constexpr not_null(U&& u); template ::value>> constexpr not_null(T u); ``` Constructs a `gsl_owner` from a pointer that is convertible to `T` or that is a `T`. It [`Expects`](#user-content-H-assert-expects) that the provided pointer is not `== nullptr`. ```cpp template ::value>> constexpr not_null(const not_null& other); ``` Constructs a `gsl_owner` from another `gsl_owner` where the other pointer is convertible to `T`. It [`Expects`](#user-content-H-assert-expects) that the provided pointer is not `== nullptr`. ```cpp not_null(const not_null& other) = default; not_null& operator=(const not_null& other) = default; ``` Copy construction and assignment. ```cpp not_null(std::nullptr_t) = delete; not_null& operator=(std::nullptr_t) = delete; ``` Construction from `std::nullptr_t` and assignment of `std::nullptr_t` are explicitly deleted. ##### Modifiers ```cpp not_null& operator++() = delete; not_null& operator--() = delete; not_null operator++(int) = delete; not_null operator--(int) = delete; not_null& operator+=(std::ptrdiff_t) = delete; not_null& operator-=(std::ptrdiff_t) = delete; ``` Explicitly deleted operators. Pointers point to single objects ([I.13: Do not pass an array as a single pointer](http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Ri-array)), so don't allow these operators. ##### Observers ```cpp constexpr details::value_or_reference_return_t get() const; constexpr operator T() const { return get(); } ``` Get the underlying pointer. ```cpp constexpr decltype(auto) operator->() const { return get(); } constexpr decltype(auto) operator*() const { return *get(); } ``` Dereference the underlying pointer. ```cpp void operator[](std::ptrdiff_t) const = delete; ``` Array index operator is explicitly deleted. Pointers point to single objects ([I.13: Do not pass an array as a single pointer](http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Ri-array)), so don't allow treating them as an array. ```cpp void swap(not_null& other) { std::swap(ptr_, other.ptr_); } ``` Swaps contents with another `gsl::not_null` object. #### Non-member functions ```cpp template auto make_not_null(T&& t) noexcept; ``` Creates a `gsl::not_null` object, deducing the target type from the type of the argument. ```cpp template ::value && std::is_move_constructible::value, bool> = true> void swap(not_null& a, not_null& b); ``` Swaps the contents of two `gsl::not_null` objects. ```cpp template auto operator==(const not_null& lhs, const not_null& rhs) noexcept(noexcept(lhs.get() == rhs.get())) -> decltype(lhs.get() == rhs.get()); template auto operator!=(const not_null& lhs, const not_null& rhs) noexcept(noexcept(lhs.get() != rhs.get())) -> decltype(lhs.get() != rhs.get()); template auto operator<(const not_null& lhs, const not_null& rhs) noexcept(noexcept(lhs.get() < rhs.get())) -> decltype(lhs.get() < rhs.get()); template auto operator<=(const not_null& lhs, const not_null& rhs) noexcept(noexcept(lhs.get() <= rhs.get())) -> decltype(lhs.get() <= rhs.get()); template auto operator>(const not_null& lhs, const not_null& rhs) noexcept(noexcept(lhs.get() > rhs.get())) -> decltype(lhs.get() > rhs.get()); template auto operator>=(const not_null& lhs, const not_null& rhs) noexcept(noexcept(lhs.get() >= rhs.get())) -> decltype(lhs.get() >= rhs.get()); ``` Comparison of pointers that are convertible to each other. ##### Input/Output ```cpp template std::ostream& operator<<(std::ostream& os, const not_null& val); ``` Performs stream output on a `not_null` pointer, invoking `os << val.get()`. This function is only available when `GSL_NO_IOSTREAMS` is not defined. ##### Modifiers ```cpp template std::ptrdiff_t operator-(const not_null&, const not_null&) = delete; template not_null operator-(const not_null&, std::ptrdiff_t) = delete; template not_null operator+(const not_null&, std::ptrdiff_t) = delete; template not_null operator+(std::ptrdiff_t, const not_null&) = delete; ``` Addition and subtraction are explicitly deleted. Pointers point to single objects ([I.13: Do not pass an array as a single pointer](http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Ri-array)), so don't allow these operators. ##### STL integration ```cpp template struct std::hash> { ... }; ``` Specialization of `std::hash` for `gsl::not_null`. ### `gsl::strict_not_null` `strict_not_null` is the same as [`not_null`](#user-content-H-pointers-not_null) except that the constructors are `explicit`. The free function that deduces the target type from the type of the argument and creates a `gsl::strict_not_null` object is `gsl::make_strict_not_null`. ## `` This header file exports the class `gsl::span`, a bounds-checked implementation of `std::span`. - [`gsl::span`](#user-content-H-span-span) ### `gsl::span` ```cpp template class span; ``` `gsl::span` is a view over memory. It does not own the memory and is only a way to access contiguous sequences of objects. The extent can be either a fixed size or [`gsl::dynamic_extent`](#user-content-H-span_ext-dynamic_extent). The `gsl::span` is based on the standardized version of `std::span` which was added to C++20. Originally, the plan was to deprecate `gsl::span` when `std::span` finished standardization, however that plan changed when the runtime bounds checking was removed from `std::span`'s design. The key differences between `gsl::span` and `std::span` are: - `gsl::span` strictly enforces runtime bounds checking for all access operations - Any violations of the bounds check results in termination of the program - `gsl::span`'s iterators also perform bounds checking, unlike `std::span`'s iterators #### Which version of span should I use? The following table compares the different span implementations to help you choose which one is best for your project: | Feature/Version | `std::span` (C++20/23) | Hardened `std::span` (C++26) | `gsl::span` | |-----------------|------------------------|------------------------------|-------------| | **C++ Standard** | Requires C++20 or later | Requires C++26 or backported implementation | Works with C++14 or later | | **Element Access** | No bounds checking | Bounds checking | Bounds checking | | **Iterator Safety** | No bounds checking | Implementation-defined, may depend on vendor | Full bounds checking | | **Error Behavior** | Undefined behavior on invalid access | Implementation-defined, may be configurable | Always calls [`std::terminate()`](https://en.cppreference.com/w/cpp/error/terminate) via [gsl::details::terminate()](https://github.com/microsoft/GSL/blob/main/include/gsl/assert#L111-L118) | | **Performance** | Fastest (no checking) | Varies by implementation and configuration | May have performance impact from bounds checking | **Recommendations:** - **C++14 & C++17 projects**: Use `gsl::span` as `std::span` is not available. - **C++20 & C++23 projects**: - Use `gsl::span` if safety is your priority. - Use `std::span` if performance is critical and you're confident in your index calculations. - **C++26 projects**: - Use `gsl::span` if you need guaranteed iterator safety across all platforms. - Use hardened `std::span` if you want standard library compliance and acceptable safety. **Implementation notes for hardened `std::span` in C++26:** - For MSVC: See [Microsoft STL Hardening](https://github.com/microsoft/STL/wiki/STL-Hardening) - For Clang/LLVM: See [libc++ Hardening](https://libcxx.llvm.org/Hardening.html) #### Types ```cpp using element_type = ElementType; using value_type = std::remove_cv_t; using size_type = std::size_t; using pointer = element_type*; using const_pointer = const element_type*; using reference = element_type&; using const_reference = const element_type&; using difference_type = std::ptrdiff_t; using iterator = details::span_iterator; using reverse_iterator = std::reverse_iterator; ``` #### Member functions ```cpp constexpr span() noexcept; ``` Constructs an empty `span`. This constructor is only available if `Extent` is 0 or [`gsl::dynamic_extent`](#user-content-H-span_ext-dynamic_extent). `span::data()` will return `nullptr`. ```cpp constexpr explicit(Extent != gsl::dynamic_extent) span(pointer ptr, size_type count) noexcept; ``` Constructs a `span` from a pointer and a size. If `Extent` is not [`gsl::dynamic_extent`](#user-content-H-span_ext-dynamic_extent), then the constructor [`Expects`](#user-content-H-assert-expects) that `count == Extent`. ```cpp constexpr explicit(Extent != gsl::dynamic_extent) span(pointer firstElem, pointer lastElem) noexcept; ``` Constructs a `span` from a pointer to the begin and the end of the data. If `Extent` is not [`gsl::dynamic_extent`](#user-content-H-span_ext-dynamic_extent), then the constructor [`Expects`](#user-content-H-assert-expects) that `lastElem - firstElem == Extent`. ```cpp template constexpr span(element_type (&arr)[N]) noexcept; ``` Constructs a `span` from a C style array. This overload is available if `Extent ==`[`gsl::dynamic_extent`](#user-content-H-span_ext-dynamic_extent) or `N == Extent`. ```cpp template constexpr span(std::array& arr) noexcept; template constexpr span(const std::array& arr) noexcept; ``` Constructs a `span` from a `std::array`. These overloads are available if `Extent ==`[`gsl::dynamic_extent`](#user-content-H-span_ext-dynamic_extent) or `N == Extent`, and if the array can be interpreted as a `ElementType` array. ```cpp template constexpr explicit(Extent != gsl::dynamic_extent) span(Container& cont) noexcept; template constexpr explicit(Extent != gsl::dynamic_extent) span(const Container& cont) noexcept; ``` Constructs a `span` from a container. These overloads are available if `Extent ==`[`gsl::dynamic_extent`](#user-content-H-span_ext-dynamic_extent) or `N == Extent`, and if the container can be interpreted as a contiguous `ElementType` array. ```cpp constexpr span(const span& other) noexcept = default; ``` Copy constructor. ```cpp template explicit(Extent != gsl::dynamic_extent && OtherExtent == dynamic_extent) constexpr span(const span& other) noexcept; ``` Constructs a `span` from another `span`. This constructor is available if `OtherExtent == Extent || Extent ==`[`gsl::dynamic_extent`](#user-content-H-span_ext-dynamic_extent)` || OtherExtent ==`[`gsl::dynamic_extent`](#user-content-H-span_ext-dynamic_extent) and if `ElementType` and `OtherElementType` are compatible. If `Extent !=`[`gsl::dynamic_extent`](#user-content-H-span_ext-dynamic_extent) and `OtherExtent ==`[`gsl::dynamic_extent`](#user-content-H-span_ext-dynamic_extent), then the constructor [`Expects`](#user-content-H-assert-expects) that `other.size() == Extent`. ```cpp constexpr span& operator=(const span& other) noexcept = default; ``` Copy assignment ```cpp template constexpr span first() const noexcept; constexpr span first(size_type count) const noexcept; template constexpr span last() const noexcept; constexpr span last(size_type count) const noexcept; ``` Return a subspan of the first/last `Count` elements. [`Expects`](#user-content-H-assert-expects) that `Count` does not exceed the `span`'s size. ```cpp template constexpr auto subspan() const noexcept; constexpr span subspan(size_type offset, size_type count = dynamic_extent) const noexcept; ``` Return a subspan starting at `offset` and having size `count`. [`Expects`](#user-content-H-assert-expects) that `offset` does not exceed the `span`'s size, and that `offset == `[`gsl::dynamic_extent`](#user-content-H-span_ext-dynamic_extent) or `offset + count` does not exceed the `span`'s size. If `count` is `gsl::dynamic_extent`, the number of elements in the subspan is `size() - offset`. ```cpp constexpr size_type size() const noexcept; constexpr size_type size_bytes() const noexcept; ``` Returns the size respective the size in bytes of the `span`. ```cpp constexpr bool empty() const noexcept; ``` Is the `span` empty? ```cpp constexpr reference operator[](size_type idx) const noexcept; ``` Returns a reference to the element at the given index. [`Expects`](#user-content-H-assert-expects) that `idx` is less than the `span`'s size. ```cpp constexpr reference front() const noexcept; constexpr reference back() const noexcept; ``` Returns a reference to the first/last element in the `span`. [`Expects`](#user-content-H-assert-expects) that the `span` is not empty. ```cpp constexpr pointer data() const noexcept; ``` Returns a pointer to the beginning of the contained data. ```cpp constexpr iterator begin() const noexcept; constexpr iterator end() const noexcept; constexpr reverse_iterator rbegin() const noexcept; constexpr reverse_iterator rend() const noexcept; ``` Returns an iterator to the first/last normal/reverse iterator. ```cpp template span(Type (&)[Extent]) -> span; template span(std::array&) -> span; template span(const std::array&) -> span; template ().data())>> span(Container&) -> span; template ().data())>> span(const Container&) -> span; ``` Deduction guides. ```cpp template span::value> as_bytes(span s) noexcept; template span::value> as_writable_bytes(span s) noexcept; ``` Converts a `span` into a `span` of `byte`s. `as_writable_bytes` will only be available for non-const `ElementType`s. ## `` This file is a companion for and included by [``](#user-content-H-span), and should not be used on its own. It contains useful features that aren't part of the `std::span` API as found inside the STL `` header (with the exception of [`gsl::dynamic_extent`](#user-content-H-span_ext-dynamic_extent), which is included here due to implementation constraints). - [`gsl::dynamic_extent`](#user-content-H-span_ext-dynamic_extent) - [`gsl::span`](#user-content-H-span_ext-span) - [`gsl::span` comparison operators](#user-content-H-span_ext-span_comparison_operators) - [`gsl::make_span`](#user-content-H-span_ext-make_span) - [`gsl::at`](#user-content-H-span_ext-at) - [`gsl::ssize`](#user-content-H-span_ext-ssize) - [`gsl::span` iterator functions](#user-content-H-span_ext-span_iterator_functions) ### `gsl::dynamic_extent` Defines the extent value to be used by all `gsl::span` with dynamic extent. Note: `std::dynamic_extent` is exposed by the STL `` header and so ideally `gsl::dynamic_extent` would be under [``](#user-content-H-span), but to avoid cyclic dependency issues it is under `` instead. ### `gsl::span` ```cpp template class span; ``` Forward declaration of `gsl::span`. ### `gsl::span` comparison operators ```cpp template constexpr bool operator==(span l, span r); template constexpr bool operator!=(span l, span r); template constexpr bool operator<(span l, span r); template constexpr bool operator<=(span l, span r); template constexpr bool operator>(span l, span r); template constexpr bool operator>=(span l, span r); ``` The comparison operators for two `span`s lexicographically compare the elements in the `span`s. ### `gsl::make_span` ```cpp template constexpr span make_span(ElementType* ptr, typename span::size_type count); template constexpr span make_span(ElementType* firstElem, ElementType* lastElem); template constexpr span make_span(ElementType (&arr)[N]) noexcept; template constexpr span make_span(Container& cont); template constexpr span make_span(const Container& cont); ``` Utility function for creating a `span` with [`gsl::dynamic_extent`](#user-content-H-span_ext-dynamic_extent) from - pointer and length, - pointer to start and pointer to end, - a C style array, or - a container. ### `gsl::at` ```cpp template constexpr ElementType& at(span s, index i); ``` The function `gsl::at` offers a safe way to access data with index bounds checking. This is the specialization of [`gsl::at`](#user-content-H-util-at) for [`span`](#user-content-H-span-span). It returns a reference to the `i`th element and [`Expects`](#user-content-H-assert-expects) that the provided index is within the bounds of the `span`. Note: `gsl::at` supports indexes up to `PTRDIFF_MAX`. ### `gsl::ssize` ```cpp template constexpr std::ptrdiff_t ssize(const span& s) noexcept; ``` Return the size of a [`span`](#user-content-H-span-span) as a `ptrdiff_t`. ### `gsl::span` iterator functions ```cpp template constexpr typename span::iterator begin(const span& s) noexcept; template constexpr typename span::iterator end(const span& s) noexcept; template constexpr typename span::reverse_iterator rbegin(const span& s) noexcept; template constexpr typename span::reverse_iterator rend(const span& s) noexcept; template constexpr typename span::iterator cbegin(const span& s) noexcept; template constexpr typename span::iterator cend(const span& s) noexcept; template constexpr typename span::reverse_iterator crbegin(const span& s) noexcept; template constexpr typename span::reverse_iterator crend(const span& s) noexcept; ``` Free functions for getting a non-const/const begin/end normal/reverse iterator for a [`span`](#user-content-H-span-span). ## `` This header exports a family of `*zstring` types. A `gsl::XXzstring` is a typedef to `T`. It adds no checks whatsoever, it is just for having a syntax to describe that a pointer points to a zero terminated C style string. This helps static code analysis, and it helps human readers. `basic_zstring` is a pointer to a C-string (zero-terminated array) with a templated char type. Used to implement the rest of the `*zstring` family. `zstring` is a zero terminated `char` string. `czstring` is a const zero terminated `char` string. `wzstring` is a zero terminated `wchar_t` string. `cwzstring` is a const zero terminated `wchar_t` string. `u16zstring` is a zero terminated `char16_t` string. `cu16zstring` is a const zero terminated `char16_t` string. `u32zstring` is a zero terminated `char32_t` string. `cu32zstring` is a const zero terminated `char32_t` string. See [GSL.view](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#SS-views) and [SL.str.3: Use zstring or czstring to refer to a C-style, zero-terminated, sequence of characters](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rstr-zstring). ## `` This header contains utility functions and classes. This header works without exceptions being available. The parts that require exceptions being available are in their own header file [narrow](#user-content-H-narrow). See [GSL.util: Utilities](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#SS-utilities) - [`gsl::narrow_cast`](#user-content-H-util-narrow_cast) - [`gsl::final_action`](#user-content-H-util-final_action) - [`gsl::at`](#user-content-H-util-at) ### `gsl::index` An alias to `std::ptrdiff_t`. It serves as the index type for all container indexes/subscripts/sizes. ### `gsl::narrow_cast` `gsl::narrow_cast(x)` is a named cast that is identical to a `static_cast(x)`. It exists to make clear to static code analysis tools and to human readers that a lossy conversion is acceptable. Note: compare the throwing version [`gsl::narrow`](#user-content-H-narrow-narrow) in header [narrow](#user-content-H-narrow). See [ES.46: Avoid lossy (narrowing, truncating) arithmetic conversions](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Res-narrowing) and [ES.49: If you must use a cast, use a named cast](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Res-casts-named) ### `gsl::final_action` ```cpp template class final_action { ... }; ``` `final_action` allows you to ensure something gets run at the end of a scope. See [E.19: Use a final_action object to express cleanup if no suitable resource handle is available](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Re-finally) #### Member functions ```cpp explicit final_action(const F& ff) noexcept; explicit final_action(F&& ff) noexcept; ``` Construct an object with the action to invoke in the destructor. ```cpp ~final_action() noexcept; ``` The destructor will call the action that was passed in the constructor. ```cpp final_action(final_action&& other) noexcept; final_action(const final_action&) = delete; void operator=(const final_action&) = delete; void operator=(final_action&&) = delete; ``` Move construction is allowed. Copy construction is deleted. Copy and move assignment are also explicitly deleted. #### Non-member functions ```cpp template auto finally(F&& f) noexcept; ``` Creates a `gsl::final_action` object, deducing the template argument type from the type of the argument. ### `gsl::at` The function `gsl::at` offers a safe way to access data with index bounds checking. Note: `gsl::at` supports indexes up to `PTRDIFF_MAX`. See [ES.42: Keep use of pointers simple and straightforward](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Res-ptr) ```cpp template constexpr T& at(T (&arr)[N], const index i); ``` This overload returns a reference to the `i`s element of a C style array `arr`. It [`Expects`](#user-content-H-assert-expects) that the provided index is within the bounds of the array. ```cpp template constexpr auto at(Cont& cont, const index i) -> decltype(cont[cont.size()]); ``` This overload returns a reference to the `i`s element of the container `cont`. It [`Expects`](#user-content-H-assert-expects) that the provided index is within the bounds of the array. ```cpp template constexpr T at(const std::initializer_list cont, const index i); ``` This overload returns a reference to the `i`s element of the initializer list `cont`. It [`Expects`](#user-content-H-assert-expects) that the provided index is within the bounds of the array. ```cpp template constexpr auto at(std::span sp, const index i) -> decltype(sp[sp.size()]); ``` This overload returns a reference to the `i`s element of the `std::span` `sp`. It [`Expects`](#user-content-H-assert-expects) that the provided index is within the bounds of the array. For [`gsl::at`](#user-content-H-span_ext-at) for [`gsl::span`](#user-content-H-span-span) see header [`span_ext`](#user-content-H-span_ext). ```cpp template ::value && std::is_move_constructible::value>> void swap(T& a, T& b); ``` Swaps the contents of two objects. Exists only to specialize `gsl::swap(gsl::not_null&, gsl::not_null&)`. kcat-openal-soft-75c0059/gsl/include/000077500000000000000000000000001512220627100173455ustar00rootroot00000000000000kcat-openal-soft-75c0059/gsl/include/CMakeLists.txt000066400000000000000000000007441512220627100221120ustar00rootroot00000000000000# Add include folders to the library and targets that consume it # the SYSTEM keyword suppresses warnings for users of the library # # By adding this directory as an include directory the user gets a # namespace effect. # # IE: # #include if(PROJECT_IS_TOP_LEVEL) target_include_directories(GSL INTERFACE $) else() target_include_directories(GSL SYSTEM INTERFACE $) endif() kcat-openal-soft-75c0059/gsl/include/gsl/000077500000000000000000000000001512220627100201325ustar00rootroot00000000000000kcat-openal-soft-75c0059/gsl/include/gsl/algorithm000066400000000000000000000044471512220627100220540ustar00rootroot00000000000000/////////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2015 Microsoft Corporation. All rights reserved. // // This code is licensed under the MIT License (MIT). // // 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 GSL_ALGORITHM_H #define GSL_ALGORITHM_H #include "./assert" // for Expects #include "./span" // for dynamic_extent, span #include // for copy_n #include // for ptrdiff_t #include // for is_assignable #ifdef _MSC_VER #pragma warning(push) // turn off some warnings that are noisy about our Expects statements #pragma warning(disable : 4127) // conditional expression is constant #pragma warning(disable : 4996) // unsafe use of std::copy_n #endif // _MSC_VER namespace gsl { // Note: this will generate faster code than std::copy using span iterator in older msvc+stl // not necessary for msvc since VS2017 15.8 (_MSC_VER >= 1915) template void copy(span src, span dest) { static_assert(std::is_assignable::value, "Elements of source span can not be assigned to elements of destination span"); static_assert(SrcExtent == dynamic_extent || DestExtent == dynamic_extent || (SrcExtent <= DestExtent), "Source range is longer than target range"); Expects(dest.size() >= src.size()); // clang-format off GSL_SUPPRESS(stl.1) // NO-FORMAT: attribute // clang-format on std::copy_n(src.data(), src.size(), dest.data()); } } // namespace gsl #ifdef _MSC_VER #pragma warning(pop) #endif // _MSC_VER #endif // GSL_ALGORITHM_H kcat-openal-soft-75c0059/gsl/include/gsl/assert000066400000000000000000000076241512220627100213670ustar00rootroot00000000000000/////////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2015 Microsoft Corporation. All rights reserved. // // This code is licensed under the MIT License (MIT). // // 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 GSL_ASSERT_H #define GSL_ASSERT_H // // Temporary until MSVC STL supports no-exceptions mode. // Currently terminate is a no-op in this mode, so we add termination behavior back // #if defined(_MSC_VER) && (defined(_KERNEL_MODE) || (defined(_HAS_EXCEPTIONS) && !_HAS_EXCEPTIONS)) #define GSL_KERNEL_MODE #define GSL_MSVC_USE_STL_NOEXCEPTION_WORKAROUND #include #define RANGE_CHECKS_FAILURE 0 #if defined(__clang__) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Winvalid-noreturn" #endif // defined(__clang__) #else // defined(_MSC_VER) && (defined(_KERNEL_MODE) || (defined(_HAS_EXCEPTIONS) && // !_HAS_EXCEPTIONS)) #include #endif // defined(_MSC_VER) && (defined(_KERNEL_MODE) || (defined(_HAS_EXCEPTIONS) && // !_HAS_EXCEPTIONS)) // // make suppress attributes parse for some compilers // Hopefully temporary until suppression standardization occurs // #if defined(__clang__) #define GSL_SUPPRESS(x) [[gsl::suppress(#x)]] #else #if defined(_MSC_VER) && !defined(__INTEL_COMPILER) && !defined(__NVCC__) #define GSL_SUPPRESS(x) [[gsl::suppress(x)]] #else #define GSL_SUPPRESS(x) #endif // _MSC_VER #endif // __clang__ #if defined(__clang__) || defined(__GNUC__) #define GSL_LIKELY(x) __builtin_expect(!!(x), 1) #define GSL_UNLIKELY(x) __builtin_expect(!!(x), 0) #else #define GSL_LIKELY(x) (!!(x)) #define GSL_UNLIKELY(x) (!!(x)) #endif // defined(__clang__) || defined(__GNUC__) // // GSL_ASSUME(cond) // // Tell the optimizer that the predicate cond must hold. It is unspecified // whether or not cond is actually evaluated. // #ifdef _MSC_VER #define GSL_ASSUME(cond) __assume(cond) #elif defined(__GNUC__) #define GSL_ASSUME(cond) ((cond) ? static_cast(0) : __builtin_unreachable()) #else #define GSL_ASSUME(cond) static_cast((cond) ? 0 : 0) #endif // // GSL.assert: assertions // namespace gsl { namespace details { #if defined(GSL_MSVC_USE_STL_NOEXCEPTION_WORKAROUND) typedef void(__cdecl* terminate_handler)(); // clang-format off GSL_SUPPRESS(f.6) // NO-FORMAT: attribute // clang-format on [[noreturn]] inline void __cdecl default_terminate_handler() { __fastfail(RANGE_CHECKS_FAILURE); } inline gsl::details::terminate_handler& get_terminate_handler() noexcept { static terminate_handler handler = &default_terminate_handler; return handler; } #endif // defined(GSL_MSVC_USE_STL_NOEXCEPTION_WORKAROUND) [[noreturn]] inline void terminate() noexcept { #if defined(GSL_MSVC_USE_STL_NOEXCEPTION_WORKAROUND) (*gsl::details::get_terminate_handler())(); #else std::terminate(); #endif // defined(GSL_MSVC_USE_STL_NOEXCEPTION_WORKAROUND) } } // namespace details } // namespace gsl #define GSL_CONTRACT_CHECK(type, cond) \ (GSL_LIKELY(cond) ? static_cast(0) : gsl::details::terminate()) #define Expects(cond) GSL_CONTRACT_CHECK("Precondition", cond) #define Ensures(cond) GSL_CONTRACT_CHECK("Postcondition", cond) #if defined(GSL_MSVC_USE_STL_NOEXCEPTION_WORKAROUND) && defined(__clang__) #pragma clang diagnostic pop #endif #endif // GSL_ASSERT_H kcat-openal-soft-75c0059/gsl/include/gsl/byte000066400000000000000000000147301512220627100210250ustar00rootroot00000000000000/////////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2015 Microsoft Corporation. All rights reserved. // // This code is licensed under the MIT License (MIT). // // 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 GSL_BYTE_H #define GSL_BYTE_H #include "./util" // for GSL_DEPRECATED #include #ifdef _MSC_VER #pragma warning(push) // Turn MSVC /analyze rules that generate too much noise. TODO: fix in the tool. #pragma warning(disable : 26493) // don't use c-style casts // TODO: MSVC suppression in templates // does not always work #ifndef GSL_USE_STD_BYTE // this tests if we are under MSVC and the standard lib has std::byte and it is enabled #if defined(__cpp_lib_byte) && __cpp_lib_byte >= 201603 #define GSL_USE_STD_BYTE 1 #else // defined(__cpp_lib_byte) && __cpp_lib_byte >= 201603 #define GSL_USE_STD_BYTE 0 #endif // defined(__cpp_lib_byte) && __cpp_lib_byte >= 201603 #endif // GSL_USE_STD_BYTE #else // _MSC_VER #ifndef GSL_USE_STD_BYTE #include /* __cpp_lib_byte */ // this tests if we are under GCC or Clang with enough -std=c++1z power to get us std::byte // also check if libc++ version is sufficient (> 5.0) or libstdc++ actually contains std::byte #if defined(__cplusplus) && (__cplusplus >= 201703L) && \ (defined(__cpp_lib_byte) && (__cpp_lib_byte >= 201603) || \ defined(_LIBCPP_VERSION) && (_LIBCPP_VERSION >= 5000)) #define GSL_USE_STD_BYTE 1 #else // defined(__cplusplus) && (__cplusplus >= 201703L) && // (defined(__cpp_lib_byte) && (__cpp_lib_byte >= 201603) || // defined(_LIBCPP_VERSION) && (_LIBCPP_VERSION >= 5000)) #define GSL_USE_STD_BYTE 0 #endif // defined(__cplusplus) && (__cplusplus >= 201703L) && // (defined(__cpp_lib_byte) && (__cpp_lib_byte >= 201603) || // defined(_LIBCPP_VERSION) && (_LIBCPP_VERSION >= 5000)) #endif // GSL_USE_STD_BYTE #endif // _MSC_VER // Use __may_alias__ attribute on gcc and clang #if defined __clang__ || (defined(__GNUC__) && __GNUC__ > 5) #define byte_may_alias __attribute__((__may_alias__)) #else // defined __clang__ || defined __GNUC__ #define byte_may_alias #endif // defined __clang__ || defined __GNUC__ #if GSL_USE_STD_BYTE #include #endif namespace gsl { #if GSL_USE_STD_BYTE namespace impl { // impl::byte is used by gsl::as_bytes so our own code does not trigger a deprecation warning as would be the case when we used gsl::byte. // Users of GSL should only use gsl::byte, not gsl::impl::byte. using byte = std::byte; } using byte GSL_DEPRECATED("Use std::byte instead.") = std::byte; using std::to_integer; #else // GSL_USE_STD_BYTE // This is a simple definition for now that allows // use of byte within span<> to be standards-compliant enum class byte_may_alias byte : unsigned char { }; namespace impl { // impl::byte is used by gsl::as_bytes so our own code does not trigger a deprecation warning as would be the case when we used gsl::byte. // Users of GSL should only use gsl::byte, not gsl::impl::byte. using byte = gsl::byte; } template ::value, bool> = true> constexpr byte& operator<<=(byte& b, IntegerType shift) noexcept { return b = byte(static_cast(b) << shift); } template ::value, bool> = true> constexpr byte operator<<(byte b, IntegerType shift) noexcept { return byte(static_cast(b) << shift); } template ::value, bool> = true> constexpr byte& operator>>=(byte& b, IntegerType shift) noexcept { return b = byte(static_cast(b) >> shift); } template ::value, bool> = true> constexpr byte operator>>(byte b, IntegerType shift) noexcept { return byte(static_cast(b) >> shift); } constexpr byte& operator|=(byte& l, byte r) noexcept { return l = byte(static_cast(l) | static_cast(r)); } constexpr byte operator|(byte l, byte r) noexcept { return byte(static_cast(l) | static_cast(r)); } constexpr byte& operator&=(byte& l, byte r) noexcept { return l = byte(static_cast(l) & static_cast(r)); } constexpr byte operator&(byte l, byte r) noexcept { return byte(static_cast(l) & static_cast(r)); } constexpr byte& operator^=(byte& l, byte r) noexcept { return l = byte(static_cast(l) ^ static_cast(r)); } constexpr byte operator^(byte l, byte r) noexcept { return byte(static_cast(l) ^ static_cast(r)); } constexpr byte operator~(byte b) noexcept { return byte(~static_cast(b)); } template ::value, bool> = true> constexpr IntegerType to_integer(byte b) noexcept { return static_cast(b); } #endif // GSL_USE_STD_BYTE template // NOTE: need suppression since c++14 does not allow "return {t}" // GSL_SUPPRESS(type.4) // NO-FORMAT: attribute // TODO: suppression does not work constexpr gsl::impl::byte to_byte(T t) noexcept { static_assert(std::is_same::value, "gsl::to_byte(t) must be provided an unsigned char, otherwise data loss may occur. " "If you are calling to_byte with an integer constant use: gsl::to_byte() version."); return gsl::impl::byte(t); } template constexpr gsl::impl::byte to_byte() noexcept { static_assert(I >= 0 && I <= 255, "gsl::byte only has 8 bits of storage, values must be in range 0-255"); return static_cast(I); } } // namespace gsl #ifdef _MSC_VER #pragma warning(pop) #endif // _MSC_VER #endif // GSL_BYTE_H kcat-openal-soft-75c0059/gsl/include/gsl/gsl000066400000000000000000000023211512220627100206400ustar00rootroot00000000000000/////////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2015 Microsoft Corporation. All rights reserved. // // This code is licensed under the MIT License (MIT). // // 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 GSL_GSL_H #define GSL_GSL_H // IWYU pragma: begin_exports #include "./algorithm" // copy #include "./assert" // Ensures/Expects #include "./byte" // byte #include "./pointers" // owner, not_null #include "./span" // span #include "./zstring" // zstring #include "./util" // finally()/narrow_cast()... #ifdef __cpp_exceptions #include "./narrow" // narrow() #endif // IWYU pragma: end_exports #endif // GSL_GSL_H kcat-openal-soft-75c0059/gsl/include/gsl/narrow000066400000000000000000000065241512220627100213740ustar00rootroot00000000000000/////////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2015 Microsoft Corporation. All rights reserved. // // This code is licensed under the MIT License (MIT). // // 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 GSL_NARROW_H #define GSL_NARROW_H #include "./assert" // for GSL_SUPPRESS #include "./util" // for narrow_cast #include // for std::exception namespace gsl { struct narrowing_error : public std::exception { const char* what() const noexcept override { return "narrowing_error"; } }; // narrow() : a checked version of narrow_cast() that throws if the cast changed the value template ::value>::type* = nullptr> // clang-format off GSL_SUPPRESS(type.1) // NO-FORMAT: attribute GSL_SUPPRESS(es.46) // NO-FORMAT: attribute // The warning suggests that a floating->unsigned conversion can occur // in the static_cast below, and that gsl::narrow should be used instead. // Suppress this warning, since gsl::narrow is defined in terms of // static_cast // clang-format on constexpr T narrow(U u) { constexpr const bool is_different_signedness = (std::is_signed::value != std::is_signed::value); GSL_SUPPRESS(es.103) // NO-FORMAT: attribute // don't overflow GSL_SUPPRESS(es.104) // NO-FORMAT: attribute // don't underflow GSL_SUPPRESS(p.2) // NO-FORMAT: attribute // don't rely on undefined behavior const T t = narrow_cast(u); // While this is technically undefined behavior in some cases (i.e., if the source value is of floating-point type // and cannot fit into the destination integral type), the resultant behavior is benign on the platforms // that we target (i.e., no hardware trap representations are hit). #if defined(__clang__) || defined(__GNUC__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wfloat-equal" #endif // Note: NaN will always throw, since NaN != NaN if (static_cast(t) != u || (is_different_signedness && ((t < T{}) != (u < U{})))) { throw narrowing_error{}; } #if defined(__clang__) || defined(__GNUC__) #pragma GCC diagnostic pop #endif return t; } template ::value>::type* = nullptr> // clang-format off GSL_SUPPRESS(type.1) // NO-FORMAT: attribute // clang-format on constexpr T narrow(U u) { const T t = narrow_cast(u); if (static_cast(t) != u) { throw narrowing_error{}; } return t; } } // namespace gsl #endif // GSL_NARROW_H kcat-openal-soft-75c0059/gsl/include/gsl/pointers000066400000000000000000000305711512220627100217260ustar00rootroot00000000000000/////////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2015 Microsoft Corporation. All rights reserved. // // This code is licensed under the MIT License (MIT). // // 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 GSL_POINTERS_H #define GSL_POINTERS_H #include "./assert" // for Ensures, Expects #include "./util" // for GSL_DEPRECATED #include // for ptrdiff_t, nullptr_t, size_t #include // for less, greater #include // for shared_ptr, unique_ptr, hash #include // for enable_if_t, is_convertible, is_assignable #include // for declval, forward #if !defined(GSL_NO_IOSTREAMS) #include // for ostream #endif // !defined(GSL_NO_IOSTREAMS) namespace gsl { namespace details { template struct is_comparable_to_nullptr : std::false_type { }; template struct is_comparable_to_nullptr< T, std::enable_if_t() != nullptr), bool>::value>> : std::true_type { }; // Resolves to the more efficient of `const T` or `const T&`, in the context of returning a const-qualified value // of type T. // // Copied from cppfront's implementation of the CppCoreGuidelines F.16 (https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rf-in) template using value_or_reference_return_t = std::conditional_t< sizeof(T) <= 2*sizeof(void*) && std::is_trivially_copy_constructible::value, const T, const T&>; } // namespace details // // GSL.owner: ownership pointers // template using shared_ptr GSL_DEPRECATED("Use std::shared_ptr instead") = std::shared_ptr; template using unique_ptr GSL_DEPRECATED("Use std::unique_ptr instead") = std::unique_ptr; // // owner // // `gsl::owner` is designed as a safety mechanism for code that must deal directly with raw pointers that own memory. // Ideally such code should be restricted to the implementation of low-level abstractions. `gsl::owner` can also be used // as a stepping point in converting legacy code to use more modern RAII constructs, such as smart pointers. // // T must be a pointer type // - disallow construction from any type other than pointer type // template ::value, bool> = true> using owner = T; // // not_null // // Restricts a pointer or smart pointer to only hold non-null values. // // Has zero size overhead over T. // // If T is a pointer (i.e. T == U*) then // - allow construction from U* // - disallow construction from nullptr_t // - disallow default construction // - ensure construction from null U* fails // - allow implicit conversion to U* // template class not_null { public: static_assert(details::is_comparable_to_nullptr::value, "T cannot be compared to nullptr."); using element_type = T; template ::value>> constexpr not_null(U&& u) noexcept(std::is_nothrow_move_constructible::value) : ptr_(std::forward(u)) { Expects(ptr_ != nullptr); } template ::value>> constexpr not_null(T u) noexcept(std::is_nothrow_move_constructible::value) : ptr_(std::move(u)) { Expects(ptr_ != nullptr); } template ::value>> constexpr not_null(const not_null& other) noexcept(std::is_nothrow_move_constructible::value) : not_null(other.get()) {} not_null(const not_null& other) = default; not_null& operator=(const not_null& other) = default; constexpr details::value_or_reference_return_t get() const noexcept(noexcept(details::value_or_reference_return_t(std::declval()))) { return ptr_; } constexpr operator T() const { return get(); } constexpr decltype(auto) operator->() const { return get(); } constexpr decltype(auto) operator*() const { return *get(); } // prevents compilation when someone attempts to assign a null pointer constant not_null(std::nullptr_t) = delete; not_null& operator=(std::nullptr_t) = delete; // unwanted operators...pointers only point to single objects! not_null& operator++() = delete; not_null& operator--() = delete; not_null operator++(int) = delete; not_null operator--(int) = delete; not_null& operator+=(std::ptrdiff_t) = delete; not_null& operator-=(std::ptrdiff_t) = delete; void operator[](std::ptrdiff_t) const = delete; void swap(not_null& other) { std::swap(ptr_, other.ptr_); } private: T ptr_; }; template ::value && std::is_move_constructible::value, bool> = true> void swap(not_null& a, not_null& b) { a.swap(b); } template auto make_not_null(T&& t) noexcept { return not_null>>{std::forward(t)}; } #if !defined(GSL_NO_IOSTREAMS) template std::ostream& operator<<(std::ostream& os, const not_null& val) { os << val.get(); return os; } #endif // !defined(GSL_NO_IOSTREAMS) template constexpr auto operator==(const not_null& lhs, const not_null& rhs) noexcept(noexcept(lhs.get() == rhs.get())) -> decltype(lhs.get() == rhs.get()) { return lhs.get() == rhs.get(); } template constexpr auto operator!=(const not_null& lhs, const not_null& rhs) noexcept(noexcept(lhs.get() != rhs.get())) -> decltype(lhs.get() != rhs.get()) { return lhs.get() != rhs.get(); } template constexpr auto operator<(const not_null& lhs, const not_null& rhs) noexcept(noexcept(std::less<>{}(lhs.get(), rhs.get()))) -> decltype(std::less<>{}(lhs.get(), rhs.get())) { return std::less<>{}(lhs.get(), rhs.get()); } template constexpr auto operator<=(const not_null& lhs, const not_null& rhs) noexcept(noexcept(std::less_equal<>{}(lhs.get(), rhs.get()))) -> decltype(std::less_equal<>{}(lhs.get(), rhs.get())) { return std::less_equal<>{}(lhs.get(), rhs.get()); } template constexpr auto operator>(const not_null& lhs, const not_null& rhs) noexcept(noexcept(std::greater<>{}(lhs.get(), rhs.get()))) -> decltype(std::greater<>{}(lhs.get(), rhs.get())) { return std::greater<>{}(lhs.get(), rhs.get()); } template constexpr auto operator>=(const not_null& lhs, const not_null& rhs) noexcept(noexcept(std::greater_equal<>{}(lhs.get(), rhs.get()))) -> decltype(std::greater_equal<>{}(lhs.get(), rhs.get())) { return std::greater_equal<>{}(lhs.get(), rhs.get()); } // more unwanted operators template std::ptrdiff_t operator-(const not_null&, const not_null&) = delete; template not_null operator-(const not_null&, std::ptrdiff_t) = delete; template not_null operator+(const not_null&, std::ptrdiff_t) = delete; template not_null operator+(std::ptrdiff_t, const not_null&) = delete; template ().get()), bool = std::is_default_constructible>::value> struct not_null_hash { std::size_t operator()(const T& value) const { return std::hash{}(value.get()); } }; template struct not_null_hash { not_null_hash() = delete; not_null_hash(const not_null_hash&) = delete; not_null_hash& operator=(const not_null_hash&) = delete; }; } // namespace gsl namespace std { template struct hash> : gsl::not_null_hash> { }; } // namespace std namespace gsl { // // strict_not_null // // Restricts a pointer or smart pointer to only hold non-null values, // // - provides a strict (i.e. explicit constructor from T) wrapper of not_null // - to be used for new code that wishes the design to be cleaner and make not_null // checks intentional, or in old code that would like to make the transition. // // To make the transition from not_null, incrementally replace not_null // by strict_not_null and fix compilation errors // // Expect to // - remove all unneeded conversions from raw pointer to not_null and back // - make API clear by specifying not_null in parameters where needed // - remove unnecessary asserts // template class strict_not_null : public not_null { public: template ::value>> constexpr explicit strict_not_null(U&& u) noexcept(std::is_nothrow_move_constructible::value) : not_null(std::forward(u)) {} template ::value>> constexpr explicit strict_not_null(T u) noexcept(std::is_nothrow_move_constructible::value) : not_null(std::move(u)) {} template ::value>> constexpr strict_not_null(const not_null& other) noexcept(std::is_nothrow_move_constructible::value) : not_null(other) {} template ::value>> constexpr strict_not_null(const strict_not_null& other) noexcept(std::is_nothrow_move_constructible::value) : not_null(other) {} // To avoid invalidating the "not null" invariant, the contained pointer is actually copied // instead of moved. If it is a custom pointer, its constructor could in theory throw exceptions. strict_not_null(strict_not_null&& other) noexcept(std::is_nothrow_copy_constructible::value) = default; strict_not_null(const strict_not_null& other) = default; strict_not_null& operator=(const strict_not_null& other) = default; strict_not_null& operator=(const not_null& other) { not_null::operator=(other); return *this; } // prevents compilation when someone attempts to assign a null pointer constant strict_not_null(std::nullptr_t) = delete; strict_not_null& operator=(std::nullptr_t) = delete; // unwanted operators...pointers only point to single objects! strict_not_null& operator++() = delete; strict_not_null& operator--() = delete; strict_not_null operator++(int) = delete; strict_not_null operator--(int) = delete; strict_not_null& operator+=(std::ptrdiff_t) = delete; strict_not_null& operator-=(std::ptrdiff_t) = delete; void operator[](std::ptrdiff_t) const = delete; }; // more unwanted operators template std::ptrdiff_t operator-(const strict_not_null&, const strict_not_null&) = delete; template strict_not_null operator-(const strict_not_null&, std::ptrdiff_t) = delete; template strict_not_null operator+(const strict_not_null&, std::ptrdiff_t) = delete; template strict_not_null operator+(std::ptrdiff_t, const strict_not_null&) = delete; template auto make_strict_not_null(T&& t) noexcept { return strict_not_null>>{std::forward(t)}; } #if (defined(__cpp_deduction_guides) && (__cpp_deduction_guides >= 201611L)) // deduction guides to prevent the ctad-maybe-unsupported warning template not_null(T) -> not_null; template strict_not_null(T) -> strict_not_null; #endif // ( defined(__cpp_deduction_guides) && (__cpp_deduction_guides >= 201611L) ) } // namespace gsl namespace std { template struct hash> : gsl::not_null_hash> { }; } // namespace std #endif // GSL_POINTERS_H kcat-openal-soft-75c0059/gsl/include/gsl/span000066400000000000000000000771311512220627100210270ustar00rootroot00000000000000/////////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2015 Microsoft Corporation. All rights reserved. // // This code is licensed under the MIT License (MIT). // // 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 GSL_SPAN_H #define GSL_SPAN_H #include "./assert" // for Expects #include "./byte" // for gsl::impl::byte #include "./span_ext" // for span specialization of gsl::at and other span-related extensions #include "./util" // for narrow_cast #include // for array #include // for ptrdiff_t, size_t, nullptr_t #include // for reverse_iterator, distance, random_access_... #include // for pointer_traits #include // for enable_if_t, declval, is_convertible, inte... #if defined(__has_include) && __has_include() #include #endif #if defined(_MSC_VER) && !defined(__clang__) #pragma warning(push) // turn off some warnings that are noisy about our Expects statements #pragma warning(disable : 4127) // conditional expression is constant #pragma warning( \ disable : 4146) // unary minus operator applied to unsigned type, result still unsigned #pragma warning(disable : 4702) // unreachable code // Turn MSVC /analyze rules that generate too much noise. TODO: fix in the tool. #pragma warning(disable : 26495) // uninitialized member when constructor calls constructor #pragma warning(disable : 26446) // parser bug does not allow attributes on some templates #endif // _MSC_VER // See if we have enough C++17 power to use a static constexpr data member // without needing an out-of-line definition #if !(defined(__cplusplus) && (__cplusplus >= 201703L)) #define GSL_USE_STATIC_CONSTEXPR_WORKAROUND #endif // !(defined(__cplusplus) && (__cplusplus >= 201703L)) // GCC 7 does not like the signed unsigned mismatch (size_t ptrdiff_t) // While there is a conversion from signed to unsigned, it happens at // compiletime, so the compiler wouldn't have to warn indiscriminately, but // could check if the source value actually doesn't fit into the target type // and only warn in those cases. #if defined(__GNUC__) && __GNUC__ > 6 #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wsign-conversion" #endif // Turn off clang unsafe buffer warnings as all accessed are guarded by runtime checks #if defined(__clang__) #if __has_warning("-Wunsafe-buffer-usage") #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunsafe-buffer-usage" #endif // __has_warning("-Wunsafe-buffer-usage") #endif // defined(__clang__) namespace gsl { // implementation details namespace details { template struct is_span_oracle : std::false_type { }; template struct is_span_oracle> : std::true_type { }; template struct is_span : public is_span_oracle> { }; template struct is_std_array_oracle : std::false_type { }; template struct is_std_array_oracle> : std::true_type { }; template struct is_std_array : is_std_array_oracle> { }; template struct is_allowed_extent_conversion : std::integral_constant { }; template struct is_allowed_element_type_conversion : std::integral_constant::value> { }; template class span_iterator { public: #if defined(__cpp_lib_ranges) || (defined(_MSVC_STL_VERSION) && defined(__cpp_lib_concepts)) using iterator_concept = std::contiguous_iterator_tag; #endif // __cpp_lib_ranges using iterator_category = std::random_access_iterator_tag; using value_type = std::remove_cv_t; using difference_type = std::ptrdiff_t; using pointer = Type*; using reference = Type&; #ifdef _MSC_VER using _Unchecked_type = pointer; using _Prevent_inheriting_unwrap = span_iterator; #endif // _MSC_VER constexpr span_iterator() = default; constexpr span_iterator(pointer begin, pointer end, pointer current) : begin_(begin), end_(end), current_(current) { Expects(begin_ <= current_ && current <= end_); } constexpr operator span_iterator() const noexcept { return {begin_, end_, current_}; } constexpr reference operator*() const noexcept { Expects(current_ != end_); return *current_; } constexpr pointer operator->() const noexcept { Expects(current_ != end_); return current_; } constexpr span_iterator& operator++() noexcept { Expects(current_ != end_); // clang-format off GSL_SUPPRESS(bounds.1) // NO-FORMAT: attribute // clang-format on ++current_; return *this; } constexpr span_iterator operator++(int) noexcept { span_iterator ret = *this; ++*this; return ret; } constexpr span_iterator& operator--() noexcept { Expects(begin_ != current_); --current_; return *this; } constexpr span_iterator operator--(int) noexcept { span_iterator ret = *this; --*this; return ret; } constexpr span_iterator& operator+=(const difference_type n) noexcept { if (n != 0) Expects(begin_ && current_ && end_); if (n > 0) Expects(end_ - current_ >= n); if (n < 0) Expects(current_ - begin_ >= -n); // clang-format off GSL_SUPPRESS(bounds.1) // NO-FORMAT: attribute // clang-format on current_ += n; return *this; } constexpr span_iterator operator+(const difference_type n) const noexcept { span_iterator ret = *this; ret += n; return ret; } friend constexpr span_iterator operator+(const difference_type n, const span_iterator& rhs) noexcept { return rhs + n; } constexpr span_iterator& operator-=(const difference_type n) noexcept { if (n != 0) Expects(begin_ && current_ && end_); if (n > 0) Expects(current_ - begin_ >= n); if (n < 0) Expects(end_ - current_ >= -n); GSL_SUPPRESS(bounds .1) current_ -= n; return *this; } constexpr span_iterator operator-(const difference_type n) const noexcept { span_iterator ret = *this; ret -= n; return ret; } template < class Type2, std::enable_if_t, value_type>::value, int> = 0> constexpr difference_type operator-(const span_iterator& rhs) const noexcept { Expects(begin_ == rhs.begin_ && end_ == rhs.end_); return current_ - rhs.current_; } constexpr reference operator[](const difference_type n) const noexcept { return *(*this + n); } template < class Type2, std::enable_if_t, value_type>::value, int> = 0> constexpr bool operator==(const span_iterator& rhs) const noexcept { Expects(begin_ == rhs.begin_ && end_ == rhs.end_); return current_ == rhs.current_; } template < class Type2, std::enable_if_t, value_type>::value, int> = 0> constexpr bool operator!=(const span_iterator& rhs) const noexcept { return !(*this == rhs); } template < class Type2, std::enable_if_t, value_type>::value, int> = 0> constexpr bool operator<(const span_iterator& rhs) const noexcept { Expects(begin_ == rhs.begin_ && end_ == rhs.end_); return current_ < rhs.current_; } template < class Type2, std::enable_if_t, value_type>::value, int> = 0> constexpr bool operator>(const span_iterator& rhs) const noexcept { return rhs < *this; } template < class Type2, std::enable_if_t, value_type>::value, int> = 0> constexpr bool operator<=(const span_iterator& rhs) const noexcept { return !(rhs < *this); } template < class Type2, std::enable_if_t, value_type>::value, int> = 0> constexpr bool operator>=(const span_iterator& rhs) const noexcept { return !(*this < rhs); } #ifdef _MSC_VER // MSVC++ iterator debugging support; allows STL algorithms in 15.8+ // to unwrap span_iterator to a pointer type after a range check in STL // algorithm calls friend constexpr void _Verify_range(span_iterator lhs, span_iterator rhs) noexcept { // test that [lhs, rhs) forms a valid range inside an STL algorithm Expects(lhs.begin_ == rhs.begin_ // range spans have to match && lhs.end_ == rhs.end_ && lhs.current_ <= rhs.current_); // range must not be transposed } constexpr void _Verify_offset(const difference_type n) const noexcept { // test that *this + n is within the range of this call if (n != 0) Expects(begin_ && current_ && end_); if (n > 0) Expects(end_ - current_ >= n); if (n < 0) Expects(current_ - begin_ >= -n); } // clang-format off GSL_SUPPRESS(bounds.1) // NO-FORMAT: attribute // clang-format on constexpr pointer _Unwrapped() const noexcept { // after seeking *this to a high water mark, or using one of the // _Verify_xxx functions above, unwrap this span_iterator to a raw // pointer return current_; } // Tell the STL that span_iterator should not be unwrapped if it can't // validate in advance, even in release / optimized builds: #if defined(GSL_USE_STATIC_CONSTEXPR_WORKAROUND) static constexpr const bool _Unwrap_when_unverified = false; #else static constexpr bool _Unwrap_when_unverified = false; #endif // clang-format off GSL_SUPPRESS(con.3) // NO-FORMAT: attribute // TODO: false positive // clang-format on constexpr void _Seek_to(const pointer p) noexcept { // adjust the position of *this to previously verified location p // after _Unwrapped current_ = p; } #endif pointer begin_ = nullptr; pointer end_ = nullptr; pointer current_ = nullptr; template friend struct std::pointer_traits; }; }} // namespace gsl::details namespace std { template struct pointer_traits<::gsl::details::span_iterator> { using pointer = ::gsl::details::span_iterator; using element_type = Type; using difference_type = ptrdiff_t; static constexpr element_type* to_address(const pointer i) noexcept { return i.current_; } }; } // namespace std namespace gsl { namespace details { template class extent_type { public: using size_type = std::size_t; constexpr extent_type() noexcept = default; constexpr explicit extent_type(extent_type); constexpr explicit extent_type(size_type size) { Expects(size == Ext); } constexpr size_type size() const noexcept { return Ext; } private: #if defined(GSL_USE_STATIC_CONSTEXPR_WORKAROUND) static constexpr const size_type size_ = Ext; // static size equal to Ext #else static constexpr size_type size_ = Ext; // static size equal to Ext #endif }; template <> class extent_type { public: using size_type = std::size_t; template constexpr explicit extent_type(extent_type ext) : size_(ext.size()) {} constexpr explicit extent_type(size_type size) : size_(size) { Expects(size != dynamic_extent); } constexpr size_type size() const noexcept { return size_; } private: size_type size_; }; template constexpr extent_type::extent_type(extent_type ext) { Expects(ext.size() == Ext); } template struct calculate_subspan_type { using type = span; }; } // namespace details // [span], class template span template class span { public: // constants and types using element_type = ElementType; using value_type = std::remove_cv_t; using size_type = std::size_t; using pointer = element_type*; using const_pointer = const element_type*; using reference = element_type&; using const_reference = const element_type&; using difference_type = std::ptrdiff_t; using iterator = details::span_iterator; using reverse_iterator = std::reverse_iterator; #if defined(GSL_USE_STATIC_CONSTEXPR_WORKAROUND) static constexpr const size_type extent{Extent}; #else static constexpr size_type extent{Extent}; #endif // [span.cons], span constructors, copy, assignment, and destructor template " SFINAE, since "std::enable_if_t" is ill-formed when Extent is greater than 0. class = std::enable_if_t<(Dependent || details::is_allowed_extent_conversion<0, Extent>::value)>> constexpr span() noexcept : storage_(nullptr, details::extent_type<0>()) {} template = 0> constexpr explicit span(pointer ptr, size_type count) noexcept : storage_(ptr, count) { Expects(count == Extent); } template = 0> constexpr span(pointer ptr, size_type count) noexcept : storage_(ptr, count) {} template = 0> constexpr explicit span(pointer firstElem, pointer lastElem) noexcept : storage_(firstElem, narrow_cast(lastElem - firstElem)) { Expects(lastElem - firstElem == static_cast(Extent)); } template = 0> constexpr span(pointer firstElem, pointer lastElem) noexcept : storage_(firstElem, narrow_cast(lastElem - firstElem)) {} template ::value, int> = 0> constexpr span(element_type (&arr)[N]) noexcept : storage_(KnownNotNull{arr}, details::extent_type()) {} template < class T, std::size_t N, std::enable_if_t<(details::is_allowed_extent_conversion::value && details::is_allowed_element_type_conversion::value), int> = 0> constexpr span(std::array& arr) noexcept : storage_(KnownNotNull{arr.data()}, details::extent_type()) {} template ::value && details::is_allowed_element_type_conversion::value), int> = 0> constexpr span(const std::array& arr) noexcept : storage_(KnownNotNull{arr.data()}, details::extent_type()) {} // NB: the SFINAE on these constructors uses .data() as an incomplete/imperfect proxy for the // requirement on Container to be a contiguous sequence container. template ::value && !details::is_std_array::value && std::is_pointer().data())>::value && std::is_convertible< std::remove_pointer_t().data())> (*)[], element_type (*)[]>::value, int> = 0> constexpr explicit span(Container& cont) noexcept : span(cont.data(), cont.size()) {} template ::value && !details::is_std_array::value && std::is_pointer().data())>::value && std::is_convertible< std::remove_pointer_t().data())> (*)[], element_type (*)[]>::value, int> = 0> constexpr span(Container& cont) noexcept : span(cont.data(), cont.size()) {} template < std::size_t MyExtent = Extent, class Container, std::enable_if_t< MyExtent != dynamic_extent && std::is_const::value && !details::is_span::value && !details::is_std_array::value && std::is_pointer().data())>::value && std::is_convertible< std::remove_pointer_t().data())> (*)[], element_type (*)[]>::value, int> = 0> constexpr explicit span(const Container& cont) noexcept : span(cont.data(), cont.size()) {} template < std::size_t MyExtent = Extent, class Container, std::enable_if_t< MyExtent == dynamic_extent && std::is_const::value && !details::is_span::value && !details::is_std_array::value && std::is_pointer().data())>::value && std::is_convertible< std::remove_pointer_t().data())> (*)[], element_type (*)[]>::value, int> = 0> constexpr span(const Container& cont) noexcept : span(cont.data(), cont.size()) {} constexpr span(const span& other) noexcept = default; template ::value, int> = 0> constexpr span(const span& other) noexcept : storage_(other.data(), details::extent_type(other.size())) {} template ::value, int> = 0> constexpr explicit span(const span& other) noexcept : storage_(other.data(), details::extent_type(other.size())) {} ~span() noexcept = default; constexpr span& operator=(const span& other) noexcept = default; // [span.sub], span subviews template constexpr span first() const noexcept { static_assert(Extent == dynamic_extent || Count <= Extent, "first() cannot extract more elements from a span than it contains."); Expects(Count <= size()); return span{data(), Count}; } template // clang-format off GSL_SUPPRESS(bounds.1) // NO-FORMAT: attribute // clang-format on constexpr span last() const noexcept { static_assert(Extent == dynamic_extent || Count <= Extent, "last() cannot extract more elements from a span than it contains."); Expects(Count <= size()); return span{data() + (size() - Count), Count}; } template // clang-format off GSL_SUPPRESS(bounds.1) // NO-FORMAT: attribute // clang-format on constexpr auto subspan() const noexcept -> typename details::calculate_subspan_type::type { static_assert(Extent == dynamic_extent || (Extent >= Offset && (Count == dynamic_extent || Count <= Extent - Offset)), "subspan() cannot extract more elements from a span than it contains."); Expects((size() >= Offset) && (Count == dynamic_extent || (Count <= size() - Offset))); using type = typename details::calculate_subspan_type::type; return type{data() + Offset, Count == dynamic_extent ? size() - Offset : Count}; } constexpr span first(size_type count) const noexcept { Expects(count <= size()); return {data(), count}; } constexpr span last(size_type count) const noexcept { Expects(count <= size()); return make_subspan(size() - count, dynamic_extent, subspan_selector{}); } constexpr span subspan(size_type offset, size_type count = dynamic_extent) const noexcept { return make_subspan(offset, count, subspan_selector{}); } // [span.obs], span observers constexpr size_type size() const noexcept { return storage_.size(); } constexpr size_type size_bytes() const noexcept { return size() * sizeof(element_type); } constexpr bool empty() const noexcept { return size() == 0; } // [span.elem], span element access // clang-format off GSL_SUPPRESS(bounds.1) // NO-FORMAT: attribute // clang-format on constexpr reference operator[](size_type idx) const noexcept { Expects(idx < size()); return data()[idx]; } constexpr reference front() const noexcept { Expects(size() > 0); return data()[0]; } constexpr reference back() const noexcept { Expects(size() > 0); return data()[size() - 1]; } constexpr pointer data() const noexcept { return storage_.data(); } // [span.iter], span iterator support constexpr iterator begin() const noexcept { const auto data = storage_.data(); // clang-format off GSL_SUPPRESS(bounds.1) // NO-FORMAT: attribute // clang-format on return {data, data + size(), data}; } constexpr iterator end() const noexcept { const auto data = storage_.data(); // clang-format off GSL_SUPPRESS(bounds.1) // NO-FORMAT: attribute // clang-format on const auto endData = data + storage_.size(); return {data, endData, endData}; } constexpr reverse_iterator rbegin() const noexcept { return reverse_iterator{end()}; } constexpr reverse_iterator rend() const noexcept { return reverse_iterator{begin()}; } #ifdef _MSC_VER // Tell MSVC how to unwrap spans in range-based-for constexpr pointer _Unchecked_begin() const noexcept { return data(); } constexpr pointer _Unchecked_end() const noexcept { // clang-format off GSL_SUPPRESS(bounds.1) // NO-FORMAT: attribute // clang-format on return data() + size(); } #endif // _MSC_VER private: // Needed to remove unnecessary null check in subspans struct KnownNotNull { pointer p; }; // this implementation detail class lets us take advantage of the // empty base class optimization to pay for only storage of a single // pointer in the case of fixed-size spans template class storage_type : public ExtentType { public: // KnownNotNull parameter is needed to remove unnecessary null check // in subspans and constructors from arrays template constexpr storage_type(KnownNotNull data, OtherExtentType ext) : ExtentType(ext), data_(data.p) {} template constexpr storage_type(pointer data, OtherExtentType ext) : ExtentType(ext), data_(data) { Expects(data || ExtentType::size() == 0); } constexpr pointer data() const noexcept { return data_; } private: pointer data_; }; storage_type> storage_; // The rest is needed to remove unnecessary null check // in subspans and constructors from arrays constexpr span(KnownNotNull ptr, size_type count) noexcept : storage_(ptr, count) {} template class subspan_selector { }; template constexpr span make_subspan(size_type offset, size_type count, subspan_selector) const noexcept { const span tmp(*this); return tmp.subspan(offset, count); } // clang-format off GSL_SUPPRESS(bounds.1) // NO-FORMAT: attribute // clang-format on constexpr span make_subspan(size_type offset, size_type count, subspan_selector) const noexcept { Expects(size() >= offset); if (count == dynamic_extent) { return {KnownNotNull{data() + offset}, size() - offset}; } Expects(size() - offset >= count); return {KnownNotNull{data() + offset}, count}; } }; #if (defined(__cpp_deduction_guides) && (__cpp_deduction_guides >= 201611L)) // Deduction Guides template span(Type (&)[Extent]) -> span; template span(std::array&) -> span; template span(const std::array&) -> span; template ().data())>> span(Container&) -> span; template ().data())>> span(const Container&) -> span; #endif // ( defined(__cpp_deduction_guides) && (__cpp_deduction_guides >= 201611L) ) #if defined(GSL_USE_STATIC_CONSTEXPR_WORKAROUND) #if defined(__clang__) && defined(_MSC_VER) && defined(__cplusplus) && (__cplusplus < 201703L) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated" // Bug in clang-cl.exe which raises a C++17 -Wdeprecated warning about this static constexpr workaround in C++14 mode. #endif // defined(__clang__) && defined(_MSC_VER) && defined(__cplusplus) && (__cplusplus < 201703L) template constexpr const typename span::size_type span::extent; #if defined(__clang__) && defined(_MSC_VER) && defined(__cplusplus) && (__cplusplus < 201703L) #pragma clang diagnostic pop #endif // defined(__clang__) && defined(_MSC_VER) && defined(__cplusplus) && (__cplusplus < 201703L) #endif namespace details { // if we only supported compilers with good constexpr support then // this pair of classes could collapse down to a constexpr function // we should use a narrow_cast<> to go to std::size_t, but older compilers may not see it as // constexpr // and so will fail compilation of the template template struct calculate_byte_size : std::integral_constant { static_assert(Extent < dynamic_extent / sizeof(ElementType), "Size is too big."); }; template struct calculate_byte_size : std::integral_constant { }; } // namespace details // [span.objectrep], views of object representation template span::value> as_bytes(span s) noexcept { using type = span::value>; // clang-format off GSL_SUPPRESS(type.1) // NO-FORMAT: attribute // clang-format on return type{reinterpret_cast(s.data()), s.size_bytes()}; } template ::value, int> = 0> span::value> as_writable_bytes(span s) noexcept { using type = span::value>; // clang-format off GSL_SUPPRESS(type.1) // NO-FORMAT: attribute // clang-format on return type{reinterpret_cast(s.data()), s.size_bytes()}; } } // namespace gsl #if defined(_MSC_VER) && !defined(__clang__) #pragma warning(pop) #endif // _MSC_VER #if defined(__GNUC__) && __GNUC__ > 6 #pragma GCC diagnostic pop #endif // __GNUC__ > 6 #if defined(__clang__) #if __has_warning("-Wunsafe-buffer-usage") #pragma clang diagnostic pop #endif // __has_warning("-Wunsafe-buffer-usage") #endif // defined(__clang__) #endif // GSL_SPAN_H kcat-openal-soft-75c0059/gsl/include/gsl/span_ext000066400000000000000000000151501512220627100217000ustar00rootroot00000000000000/////////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2015 Microsoft Corporation. All rights reserved. // // This code is licensed under the MIT License (MIT). // // 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 GSL_SPAN_EXT_H #define GSL_SPAN_EXT_H /////////////////////////////////////////////////////////////////////////////// // // File: span_ext // Purpose: continue offering features that have been cut from the official // implementation of span. // While modernizing gsl::span a number of features needed to be removed to // be compliant with the design of std::span // /////////////////////////////////////////////////////////////////////////////// #include "./assert" // GSL_KERNEL_MODE #include "./util" // for narrow_cast, narrow #include // for ptrdiff_t, size_t #include #ifndef GSL_KERNEL_MODE #include // for lexicographical_compare #endif // GSL_KERNEL_MODE namespace gsl { // [span.views.constants], constants GSL_INLINE constexpr const std::size_t dynamic_extent = narrow_cast(-1); template class span; // std::equal and std::lexicographical_compare are not /kernel compatible // so all comparison operators must be removed for kernel mode. #ifndef GSL_KERNEL_MODE // [span.comparison], span comparison operators template constexpr bool operator==(span l, span r) { return std::equal(l.begin(), l.end(), r.begin(), r.end()); } template constexpr bool operator!=(span l, span r) { return !(l == r); } template constexpr bool operator<(span l, span r) { return std::lexicographical_compare(l.begin(), l.end(), r.begin(), r.end()); } template constexpr bool operator<=(span l, span r) { return !(l > r); } template constexpr bool operator>(span l, span r) { return r < l; } template constexpr bool operator>=(span l, span r) { return !(l < r); } #endif // GSL_KERNEL_MODE // // make_span() - Utility functions for creating spans // template constexpr span make_span(ElementType* ptr, typename span::size_type count) { return span(ptr, count); } template constexpr span make_span(ElementType* firstElem, ElementType* lastElem) { return span(firstElem, lastElem); } template constexpr span make_span(ElementType (&arr)[N]) noexcept { return span(arr); } template constexpr span make_span(Container& cont) { return span(cont); } template constexpr span make_span(const Container& cont) { return span(cont); } template GSL_DEPRECATED("This function is deprecated. See GSL issue #1092.") constexpr span make_span(Ptr& cont, std::size_t count) { return span(cont, count); } template GSL_DEPRECATED("This function is deprecated. See GSL issue #1092.") constexpr span make_span(Ptr& cont) { return span(cont); } // Specialization of gsl::at for span template constexpr ElementType& at(span s, index i) { // No bounds checking here because it is done in span::operator[] called below Ensures(i >= 0); return s[narrow_cast(i)]; } // [span.obs] Free observer functions template constexpr std::ptrdiff_t ssize(const span& s) noexcept { return static_cast(s.size()); } // [span.iter] Free functions for begin/end functions template constexpr typename span::iterator begin(const span& s) noexcept { return s.begin(); } template constexpr typename span::iterator end(const span& s) noexcept { return s.end(); } template constexpr typename span::reverse_iterator rbegin(const span& s) noexcept { return s.rbegin(); } template constexpr typename span::reverse_iterator rend(const span& s) noexcept { return s.rend(); } template constexpr typename span::iterator cbegin(const span& s) noexcept { return s.begin(); } template constexpr typename span::iterator cend(const span& s) noexcept { return s.end(); } template constexpr typename span::reverse_iterator crbegin(const span& s) noexcept { return s.rbegin(); } template constexpr typename span::reverse_iterator crend(const span& s) noexcept { return s.rend(); } } // namespace gsl #endif // GSL_SPAN_EXT_H kcat-openal-soft-75c0059/gsl/include/gsl/util000066400000000000000000000140151512220627100210330ustar00rootroot00000000000000/////////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2015 Microsoft Corporation. All rights reserved. // // This code is licensed under the MIT License (MIT). // // 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 GSL_UTIL_H #define GSL_UTIL_H #include "./assert" // for Expects #include #include // for ptrdiff_t, size_t #include // for numeric_limits #include // for initializer_list #include // for is_signed, integral_constant #include // for exchange, forward #if defined(__has_include) && __has_include() #include #if defined(__cpp_lib_span) && __cpp_lib_span >= 202002L #include #endif // __cpp_lib_span >= 202002L #endif //__has_include() #if defined(_MSC_VER) && !defined(__clang__) #pragma warning(push) #pragma warning(disable : 4127) // conditional expression is constant #endif // _MSC_VER // Turn off clang unsafe buffer warnings as all accessed are guarded by runtime checks #if defined(__clang__) #if __has_warning("-Wunsafe-buffer-usage") #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunsafe-buffer-usage" #endif // __has_warning("-Wunsafe-buffer-usage") #endif // defined(__clang__) #if defined(__cplusplus) && (__cplusplus >= 201703L) #define GSL_NODISCARD [[nodiscard]] #else #define GSL_NODISCARD #endif // defined(__cplusplus) && (__cplusplus >= 201703L) #if defined(__cpp_inline_variables) #define GSL_INLINE inline #else #define GSL_INLINE #endif #if defined(__has_cpp_attribute) #if __has_cpp_attribute(deprecated) #define GSL_DEPRECATED(msg) [[deprecated(msg)]] #endif // __has_cpp_attribute(deprecated) #endif // defined(__has_cpp_attribute) #if !defined(GSL_DEPRECATED) #if defined(__cplusplus) #if __cplusplus >= 201309L #define GSL_DEPRECATED(msg) [[deprecated(msg)]] #endif // __cplusplus >= 201309L #endif // defined(__cplusplus) #endif // !defined(GSL_DEPRECATED) #if !defined(GSL_DEPRECATED) #if defined(_MSC_VER) #define GSL_DEPRECATED(msg) __declspec(deprecated(msg)) #elif defined(__GNUC__) #define GSL_DEPRECATED(msg) __attribute__((deprecated(msg))) #endif // defined(_MSC_VER) #endif // !defined(GSL_DEPRECATED) #if !defined(GSL_DEPRECATED) #define GSL_DEPRECATED(msg) #endif // !defined(GSL_DEPRECATED) namespace gsl { // // GSL.util: utilities // // index type for all container indexes/subscripts/sizes using index = std::ptrdiff_t; // final_action allows you to ensure something gets run at the end of a scope template class final_action { public: explicit final_action(const F& ff) noexcept : f{ff} { } explicit final_action(F&& ff) noexcept : f{std::move(ff)} { } ~final_action() noexcept { if (invoke) f(); } final_action(final_action&& other) noexcept : f(std::move(other.f)), invoke(std::exchange(other.invoke, false)) { } final_action(const final_action&) = delete; void operator=(const final_action&) = delete; void operator=(final_action&&) = delete; private: F f; bool invoke = true; }; // finally() - convenience function to generate a final_action template GSL_NODISCARD auto finally(F&& f) noexcept { return final_action>{std::forward(f)}; } // narrow_cast(): a searchable way to do narrowing casts of values template // clang-format off GSL_SUPPRESS(type.1) // NO-FORMAT: attribute // clang-format on constexpr T narrow_cast(U&& u) noexcept { return static_cast(std::forward(u)); } // // at() - Bounds-checked way of accessing builtin arrays, std::array, std::vector // template // clang-format off GSL_SUPPRESS(bounds.4) // NO-FORMAT: attribute GSL_SUPPRESS(bounds.2) // NO-FORMAT: attribute // clang-format on constexpr T& at(T (&arr)[N], const index i) { static_assert(N <= static_cast((std::numeric_limits::max)()), "We only support arrays up to PTRDIFF_MAX bytes."); Expects(i >= 0 && i < narrow_cast(N)); return arr[narrow_cast(i)]; } template // clang-format off GSL_SUPPRESS(bounds.4) // NO-FORMAT: attribute GSL_SUPPRESS(bounds.2) // NO-FORMAT: attribute // clang-format on constexpr auto at(Cont& cont, const index i) -> decltype(cont[cont.size()]) { Expects(i >= 0 && i < narrow_cast(cont.size())); using size_type = decltype(cont.size()); return cont[narrow_cast(i)]; } template // clang-format off GSL_SUPPRESS(bounds.1) // NO-FORMAT: attribute // clang-format on constexpr T at(const std::initializer_list cont, const index i) { Expects(i >= 0 && i < narrow_cast(cont.size())); return *(cont.begin() + i); } template ::value && std::is_move_constructible::value>> void swap(T& a, T& b) { std::swap(a, b); } #if defined(__cpp_lib_span) && __cpp_lib_span >= 202002L template constexpr auto at(std::span sp, const index i) -> decltype(sp[sp.size()]) { Expects(i >= 0 && i < narrow_cast(sp.size())); return sp[gsl::narrow_cast(i)]; } #endif // __cpp_lib_span >= 202002L } // namespace gsl #if defined(_MSC_VER) && !defined(__clang__) #pragma warning(pop) #endif // _MSC_VER #if defined(__clang__) #if __has_warning("-Wunsafe-buffer-usage") #pragma clang diagnostic pop #endif // __has_warning("-Wunsafe-buffer-usage") #endif // defined(__clang__) #endif // GSL_UTIL_H kcat-openal-soft-75c0059/gsl/include/gsl/zstring000066400000000000000000000036721512220627100215650ustar00rootroot00000000000000/////////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2015 Microsoft Corporation. All rights reserved. // // This code is licensed under the MIT License (MIT). // // 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 GSL_ZSTRING_H #define GSL_ZSTRING_H #include "./span_ext" // for dynamic_extent #include // for size_t, nullptr_t namespace gsl { // // czstring and wzstring // // These are "tag" typedefs for C-style strings (i.e. null-terminated character arrays) // that allow static analysis to help find bugs. // // There are no additional features/semantics that we can find a way to add inside the // type system for these types that will not either incur significant runtime costs or // (sometimes needlessly) break existing programs when introduced. // template using basic_zstring = CharT*; using czstring = basic_zstring; using cwzstring = basic_zstring; using cu16zstring = basic_zstring; using cu32zstring = basic_zstring; using zstring = basic_zstring; using wzstring = basic_zstring; using u16zstring = basic_zstring; using u32zstring = basic_zstring; } // namespace gsl #endif // GSL_ZSTRING_H kcat-openal-soft-75c0059/hrtf/000077500000000000000000000000001512220627100161005ustar00rootroot00000000000000kcat-openal-soft-75c0059/hrtf/Default HRTF.mhr000066400000000000000000004701411512220627100207270ustar00rootroot00000000000000MinPHR03€»@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-)&#""%kcat-openal-soft-75c0059/include/000077500000000000000000000000001512220627100165605ustar00rootroot00000000000000kcat-openal-soft-75c0059/include/AL/000077500000000000000000000000001512220627100170545ustar00rootroot00000000000000kcat-openal-soft-75c0059/include/AL/al.h000066400000000000000000000742011512220627100176250ustar00rootroot00000000000000/* This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * 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 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. * * For more information, please refer to */ /* This file is auto-generated! Please do not edit it manually. * Instead, modify the API in al.xml and regenerate using genheaders.py. * * Last regenerated: 2025-10-13 19:40:01.687690+00:00 */ #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 #ifndef AL_VERSION_1_0 #define AL_VERSION_1_0 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; /** 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. * Type: ALint, ALfloat * 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 /** 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). * Type: ALint */ #define AL_FREQUENCY 0x2001 /** * Buffer data size in bytes (query only). * Type: ALint */ #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). * Type: ALfloat * * A multiplier applied to the Speed of Sound. */ #define AL_DOPPLER_VELOCITY 0xC001 /** * 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 /* 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 /* Distance model values. */ #define AL_INVERSE_DISTANCE 0xD001 #define AL_INVERSE_DISTANCE_CLAMPED 0xD002 #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 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 void (AL_APIENTRY *LPALDOPPLERFACTOR)(ALfloat value) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALDOPPLERVELOCITY)(ALfloat value) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALDISTANCEMODEL)(ALenum distanceModel) 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 *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 *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 *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; #endif #ifndef AL_VERSION_1_1 #define AL_VERSION_1_1 1 /** * Source buffer offset, in seconds. * Type: ALfloat, ALint * Range: [0 - ] */ #define AL_SEC_OFFSET 0x1024 /** * Source buffer offset, in sample frames. * Type: ALint * Range: [0 - ] */ #define AL_SAMPLE_OFFSET 0x1025 /** * Source buffer offset, in bytes. * Type: ALint * Range: [0 - ] */ #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 /** * Buffer bits per sample (query only). * Type: ALint */ #define AL_BITS 0x2002 /** * Buffer channel count (query only). * Type: ALint */ #define AL_CHANNELS 0x2003 /** * 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 #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 AL_API void AL_APIENTRY alSpeedOfSound(ALfloat value) AL_API_NOEXCEPT; #endif /* AL_NO_PROTOTYPES */ /* Pointer-to-function types, useful for storing dynamically loaded AL entry * points. */ typedef void (AL_APIENTRY *LPALSPEEDOFSOUND)(ALfloat value) AL_API_NOEXCEPT17; #endif #ifdef __cplusplus } /* extern "C" */ #endif /* NOLINTEND */ #endif /* AL_AL_H */ kcat-openal-soft-75c0059/include/AL/alc.h000066400000000000000000000305401512220627100177660ustar00rootroot00000000000000/* This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * 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 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. * * For more information, please refer to */ /* This file is auto-generated! Please do not edit it manually. * Instead, modify the API in al.xml and regenerate using genheaders.py. * * Last regenerated: 2025-10-13 19:40:01.687690+00:00 */ #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 #ifndef ALC_VERSION_1_0 #define ALC_VERSION_1_0 1 /* Deprecated macros. */ #define ALCAPI ALC_API #define ALCAPIENTRY ALC_APIENTRY /** Deprecated enum. */ #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; /** 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 /** 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 #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; #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; #endif #ifndef ALC_VERSION_1_1 #define ALC_VERSION_1_1 1 /** Context attribute: requested Mono (3D) Sources. */ #define ALC_MONO_SOURCES 0x1010 /** Context attribute: requested Stereo Sources. */ #define ALC_STEREO_SOURCES 0x1011 /** Capture extension */ #define ALC_EXT_CAPTURE 1 /** * Capture 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_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 /** * 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 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; #endif #ifdef __cplusplus } /* extern "C" */ #endif /* NOLINTEND */ #endif /* AL_ALC_H */ kcat-openal-soft-75c0059/include/AL/alext.h000066400000000000000000002257541512220627100203610ustar00rootroot00000000000000/* This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * 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 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. * * For more information, please refer to */ /* This file is auto-generated! Please do not edit it manually. * Instead, modify the API in al.xml and regenerate using genheaders.py. * * Last regenerated: 2025-10-13 19:40:01.687690+00:00 */ #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_impl_int64_t; typedef uint64_t alsoft_impl_uint64_t; #elif defined(_WIN32) typedef __int64 alsoft_impl_int64_t; typedef unsigned __int64 alsoft_impl_uint64_t; #else /* Fallback if nothing above works */ #include typedef int64_t alsoft_impl_int64_t; typedef uint64_t alsoft_impl_uint64_t; #endif #include "alc.h" #include "al.h" #ifdef __cplusplus extern "C" { #endif #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)(ALuint buffer, ALenum format, ALvoid *data, ALsizei size, ALsizei freq) AL_API_NOEXCEPT17; #ifdef AL_ALEXT_PROTOTYPES void AL_APIENTRY alBufferDataStatic(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 buffer, ALenum format, const ALvoid *data, ALsizei offset, ALsizei length) 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 mode, ALsizei count, ALsizei length, ALfloat *mem, LPALFOLDBACKCALLBACK callback) 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 buffer, ALuint samplerate, ALenum internalformat, ALsizei samples, ALenum channels, ALenum type, const ALvoid *data) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALBUFFERSUBSAMPLESSOFT)(ALuint buffer, ALsizei offset, ALsizei samples, ALenum channels, ALenum type, const ALvoid *data) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETBUFFERSAMPLESSOFT)(ALuint buffer, ALsizei offset, ALsizei samples, ALenum channels, ALenum type, ALvoid *data) AL_API_NOEXCEPT17; typedef ALboolean (AL_APIENTRY *LPALISBUFFERFORMATSUPPORTEDSOFT)(ALenum format) 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 *deviceName) ALC_API_NOEXCEPT17; typedef ALCboolean (ALC_APIENTRY *LPALCISRENDERFORMATSUPPORTEDSOFT)(ALCdevice *device, ALCsizei freq, ALCenum channels, ALCenum type) ALC_API_NOEXCEPT17; typedef void (ALC_APIENTRY *LPALCRENDERSAMPLESSOFT)(ALCdevice *device, ALCvoid *buffer, ALCsizei samples) ALC_API_NOEXCEPT17; #ifdef AL_ALEXT_PROTOTYPES ALC_API ALCdevice* ALC_APIENTRY alcLoopbackOpenDeviceSOFT(const ALCchar *deviceName) ALC_API_NOEXCEPT; ALC_API ALCboolean ALC_APIENTRY alcIsRenderFormatSupportedSOFT(ALCdevice *device, ALCsizei freq, ALCenum channels, ALCenum type) ALC_API_NOEXCEPT; ALC_API void ALC_APIENTRY alcRenderSamplesSOFT(ALCdevice *device, ALCvoid *buffer, ALCsizei samples) ALC_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_impl_int64_t ALint64SOFT; typedef alsoft_impl_uint64_t ALuint64SOFT; typedef void (AL_APIENTRY *LPALSOURCEDSOFT)(ALuint source, ALenum param, ALdouble value) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALSOURCE3DSOFT)(ALuint source, ALenum param, ALdouble value1, ALdouble value2, ALdouble value3) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALSOURCEDVSOFT)(ALuint source, ALenum param, const ALdouble *values) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETSOURCEDSOFT)(ALuint source, ALenum param, ALdouble *value) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETSOURCE3DSOFT)(ALuint source, ALenum param, ALdouble *value1, ALdouble *value2, ALdouble *value3) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETSOURCEDVSOFT)(ALuint source, ALenum param, ALdouble *values) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALSOURCEI64SOFT)(ALuint source, ALenum param, ALint64SOFT value) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALSOURCE3I64SOFT)(ALuint source, ALenum param, ALint64SOFT value1, ALint64SOFT value2, ALint64SOFT value3) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALSOURCEI64VSOFT)(ALuint source, ALenum param, const ALint64SOFT *values) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETSOURCEI64SOFT)(ALuint source, ALenum param, ALint64SOFT *value) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETSOURCE3I64SOFT)(ALuint source, ALenum param, ALint64SOFT *value1, ALint64SOFT *value2, ALint64SOFT *value3) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETSOURCEI64VSOFT)(ALuint source, ALenum param, ALint64SOFT *values) 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 1 #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 1 #define AL_SOURCE_SPATIALIZE_SOFT 0x1214 #define AL_AUTO_SOFT 0x0002 #endif #ifndef ALC_SOFT_output_limiter #define ALC_SOFT_output_limiter 1 #define ALC_OUTPUT_LIMITER_SOFT 0x199A #endif #ifndef ALC_SOFT_device_clock #define ALC_SOFT_device_clock 1 typedef alsoft_impl_int64_t ALCint64SOFT; typedef alsoft_impl_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_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_SN3D_SOFT 0x0001 #define ALC_N3D_SOFT 0x0002 #endif #ifndef AL_SOFT_effect_target #define AL_SOFT_effect_target 1 #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 1 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 1 #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 **ptr) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETBUFFER3PTRSOFT)(ALuint buffer, ALenum param, ALvoid **ptr0, ALvoid **ptr1, ALvoid **ptr2) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETBUFFERPTRVSOFT)(ALuint buffer, ALenum param, ALvoid **ptr) 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 1 #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 1 #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 1 #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 1 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 1 #define ALC_CONTEXT_FLAGS_EXT 0x19CF #define ALC_CONTEXT_DEBUG_BIT_EXT 0x0001 #endif #ifndef AL_EXT_debug #define AL_EXT_debug 1 #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 #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 1 #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 1 struct _GUID; typedef ALCvoid* (ALC_APIENTRY *LPALCGETPROCADDRESS2)(ALCdevice *device, const ALCchar *funcName) ALC_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; 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 iValue) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALEFFECTIVDIRECT)(ALCcontext *context, ALuint effect, ALenum param, const ALint *piValues) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALEFFECTFDIRECT)(ALCcontext *context, ALuint effect, ALenum param, ALfloat flValue) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALEFFECTFVDIRECT)(ALCcontext *context, ALuint effect, ALenum param, const ALfloat *pflValues) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETEFFECTIDIRECT)(ALCcontext *context, ALuint effect, ALenum param, ALint *piValue) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETEFFECTIVDIRECT)(ALCcontext *context, ALuint effect, ALenum param, ALint *piValues) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETEFFECTFDIRECT)(ALCcontext *context, ALuint effect, ALenum param, ALfloat *pflValue) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETEFFECTFVDIRECT)(ALCcontext *context, ALuint effect, ALenum param, ALfloat *pflValues) 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 iValue) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALFILTERIVDIRECT)(ALCcontext *context, ALuint filter, ALenum param, const ALint *piValues) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALFILTERFDIRECT)(ALCcontext *context, ALuint filter, ALenum param, ALfloat flValue) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALFILTERFVDIRECT)(ALCcontext *context, ALuint filter, ALenum param, const ALfloat *pflValues) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETFILTERIDIRECT)(ALCcontext *context, ALuint filter, ALenum param, ALint *piValue) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETFILTERIVDIRECT)(ALCcontext *context, ALuint filter, ALenum param, ALint *piValues) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETFILTERFDIRECT)(ALCcontext *context, ALuint filter, ALenum param, ALfloat *pflValue) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETFILTERFVDIRECT)(ALCcontext *context, ALuint filter, ALenum param, ALfloat *pflValues) 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 iValue) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALAUXILIARYEFFECTSLOTIVDIRECT)(ALCcontext *context, ALuint effectslot, ALenum param, const ALint *piValues) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALAUXILIARYEFFECTSLOTFDIRECT)(ALCcontext *context, ALuint effectslot, ALenum param, ALfloat flValue) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALAUXILIARYEFFECTSLOTFVDIRECT)(ALCcontext *context, ALuint effectslot, ALenum param, const ALfloat *pflValues) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETAUXILIARYEFFECTSLOTIDIRECT)(ALCcontext *context, ALuint effectslot, ALenum param, ALint *piValue) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETAUXILIARYEFFECTSLOTIVDIRECT)(ALCcontext *context, ALuint effectslot, ALenum param, ALint *piValues) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETAUXILIARYEFFECTSLOTFDIRECT)(ALCcontext *context, ALuint effectslot, ALenum param, ALfloat *pflValue) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETAUXILIARYEFFECTSLOTFVDIRECT)(ALCcontext *context, ALuint effectslot, ALenum param, ALfloat *pflValues) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALBUFFERDATASTATICDIRECT)(ALCcontext *context, ALuint buffer, ALenum format, ALvoid *data, ALsizei size, ALsizei freq) AL_API_NOEXCEPT17; 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; 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; typedef void (AL_APIENTRY *LPALBUFFERSUBDATADIRECTSOFT)(ALCcontext *context, ALuint buffer, ALenum format, const ALvoid *data, ALsizei offset, ALsizei length) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALSOURCEDDIRECTSOFT)(ALCcontext *context, ALuint source, ALenum param, ALdouble value) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALSOURCE3DDIRECTSOFT)(ALCcontext *context, ALuint source, ALenum param, ALdouble value1, ALdouble value2, ALdouble value3) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALSOURCEDVDIRECTSOFT)(ALCcontext *context, ALuint source, ALenum param, const ALdouble *values) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETSOURCEDDIRECTSOFT)(ALCcontext *context, ALuint source, ALenum param, ALdouble *value) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETSOURCE3DDIRECTSOFT)(ALCcontext *context, ALuint source, ALenum param, ALdouble *value1, ALdouble *value2, ALdouble *value3) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETSOURCEDVDIRECTSOFT)(ALCcontext *context, ALuint source, ALenum param, ALdouble *values) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALSOURCEI64DIRECTSOFT)(ALCcontext *context, ALuint source, ALenum param, ALint64SOFT value) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALSOURCE3I64DIRECTSOFT)(ALCcontext *context, ALuint source, ALenum param, ALint64SOFT value1, ALint64SOFT value2, ALint64SOFT value3) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALSOURCEI64VDIRECTSOFT)(ALCcontext *context, ALuint source, ALenum param, const ALint64SOFT *values) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETSOURCEI64DIRECTSOFT)(ALCcontext *context, ALuint source, ALenum param, ALint64SOFT *value) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETSOURCE3I64DIRECTSOFT)(ALCcontext *context, ALuint source, ALenum param, ALint64SOFT *value1, ALint64SOFT *value2, ALint64SOFT *value3) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETSOURCEI64VDIRECTSOFT)(ALCcontext *context, ALuint source, ALenum param, ALint64SOFT *values) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALDEFERUPDATESDIRECTSOFT)(ALCcontext *context) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALPROCESSUPDATESDIRECTSOFT)(ALCcontext *context) AL_API_NOEXCEPT17; typedef const ALchar* (AL_APIENTRY *LPALGETSTRINGIDIRECTSOFT)(ALCcontext *context, ALenum pname, ALsizei index) AL_API_NOEXCEPT17; 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; 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 **ptr) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETBUFFER3PTRDIRECTSOFT)(ALCcontext *context, ALuint buffer, ALenum param, ALvoid **ptr0, ALvoid **ptr1, ALvoid **ptr2) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETBUFFERPTRVDIRECTSOFT)(ALCcontext *context, ALuint buffer, ALenum param, ALvoid **ptr) AL_API_NOEXCEPT17; 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; 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) ALC_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 1 #define AL_UNPACK_AMBISONIC_ORDER_SOFT 0x199D #endif #ifdef __cplusplus } /* extern "C" */ #endif /* NOLINTEND */ #endif /* AL_ALEXT_H */ kcat-openal-soft-75c0059/include/AL/efx-creative.h000066400000000000000000000002641512220627100216110ustar00rootroot00000000000000/* 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. */ kcat-openal-soft-75c0059/include/AL/efx-presets.h000066400000000000000000001052721512220627100215010ustar00rootroot00000000000000/* 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 */ kcat-openal-soft-75c0059/include/AL/efx.h000066400000000000000000001070431512220627100200140ustar00rootroot00000000000000/* This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * 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 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. * * For more information, please refer to */ /* This file is auto-generated! Please do not edit it manually. * Instead, modify the API in al.xml and regenerate using genheaders.py. * * Last regenerated: 2025-10-13 19:40:01.687690+00:00 */ #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 /* 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 /* 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 /* 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 /* 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 #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 typedef void (AL_APIENTRY *LPALGENEFFECTS)(ALsizei n, ALuint *effects) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALDELETEEFFECTS)(ALsizei n, const ALuint *effects) AL_API_NOEXCEPT17; typedef ALboolean (AL_APIENTRY *LPALISEFFECT)(ALuint effect) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALEFFECTI)(ALuint effect, ALenum param, ALint iValue) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALEFFECTIV)(ALuint effect, ALenum param, const ALint *piValues) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALEFFECTF)(ALuint effect, ALenum param, ALfloat flValue) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALEFFECTFV)(ALuint effect, ALenum param, const ALfloat *pflValues) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETEFFECTI)(ALuint effect, ALenum param, ALint *iValue) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETEFFECTIV)(ALuint effect, ALenum param, ALint *piValues) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETEFFECTF)(ALuint effect, ALenum param, ALfloat *flValue) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETEFFECTFV)(ALuint effect, ALenum param, ALfloat *pflValues) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGENFILTERS)(ALsizei n, ALuint *filters) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALDELETEFILTERS)(ALsizei n, const ALuint *filters) AL_API_NOEXCEPT17; typedef ALboolean (AL_APIENTRY *LPALISFILTER)(ALuint filter) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALFILTERI)(ALuint filter, ALenum param, ALint iValue) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALFILTERIV)(ALuint filter, ALenum param, const ALint *piValues) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALFILTERF)(ALuint filter, ALenum param, ALfloat flValue) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALFILTERFV)(ALuint filter, ALenum param, const ALfloat *pflValues) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETFILTERI)(ALuint filter, ALenum param, ALint *iValue) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETFILTERIV)(ALuint filter, ALenum param, ALint *piValues) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETFILTERF)(ALuint filter, ALenum param, ALfloat *flValue) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETFILTERFV)(ALuint filter, ALenum param, ALfloat *pflValues) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGENAUXILIARYEFFECTSLOTS)(ALsizei n, ALuint *effectslots) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALDELETEAUXILIARYEFFECTSLOTS)(ALsizei n, const ALuint *effectslots) AL_API_NOEXCEPT17; typedef ALboolean (AL_APIENTRY *LPALISAUXILIARYEFFECTSLOT)(ALuint effectslot) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALAUXILIARYEFFECTSLOTI)(ALuint effectslot, ALenum param, ALint iValue) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALAUXILIARYEFFECTSLOTIV)(ALuint effectslot, ALenum param, const ALint *piValues) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALAUXILIARYEFFECTSLOTF)(ALuint effectslot, ALenum param, ALfloat flValue) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALAUXILIARYEFFECTSLOTFV)(ALuint effectslot, ALenum param, const ALfloat *pflValues) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETAUXILIARYEFFECTSLOTI)(ALuint effectslot, ALenum param, ALint *iValue) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETAUXILIARYEFFECTSLOTIV)(ALuint effectslot, ALenum param, ALint *piValues) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETAUXILIARYEFFECTSLOTF)(ALuint effectslot, ALenum param, ALfloat *flValue) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY *LPALGETAUXILIARYEFFECTSLOTFV)(ALuint effectslot, ALenum param, ALfloat *pflValues) AL_API_NOEXCEPT17; #ifdef AL_ALEXT_PROTOTYPES /* Effect object function types. */ 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 *iValue) 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 *flValue) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alGetEffectfv(ALuint effect, ALenum param, ALfloat *pflValues) AL_API_NOEXCEPT; /* Filter object function types. */ 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 *iValue) 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 *flValue) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alGetFilterfv(ALuint filter, ALenum param, ALfloat *pflValues) AL_API_NOEXCEPT; /* Auxiliary Effect Slot object function types. */ 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 *iValue) 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 *flValue) AL_API_NOEXCEPT; AL_API void AL_APIENTRY alGetAuxiliaryEffectSlotfv(ALuint effectslot, ALenum param, ALfloat *pflValues) AL_API_NOEXCEPT; #endif #ifdef __cplusplus } /* extern "C" */ #endif /* NOLINTEND */ #endif /* AL_EFX_H */ kcat-openal-soft-75c0059/libopenal.version000066400000000000000000000066461512220627100205250ustar00rootroot00000000000000{ 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: *; }; kcat-openal-soft-75c0059/logging/000077500000000000000000000000001512220627100165635ustar00rootroot00000000000000kcat-openal-soft-75c0059/logging/log.cmd000066400000000000000000000124131512220627100200320ustar00rootroot00000000000000@Echo OFF Setlocal EnableDelayedExpansion Set LogFileFolderPath=%APPDATA%\OpenAL\ If not "%~1"=="" ( Set ALSOFT_LOGFILE=alsoft_error.txt Set ALSOFT_LOGLEVEL=3 Set DSOAL_LOGFILE=dsoal_error.txt Set DSOAL_LOGLEVEL=4 For %%A in (%1) do ( CD "%%~dpA" PushD "%%~dpA" "%%~nxA" ) IF EXIST "!DSOAL_LOGFILE!" ( Set LogExists=True Echo !DSOAL_LOGFILE! has been created. ) IF EXIST "!ALSOFT_LOGFILE!" ( Set LogExists=True Echo !ALSOFT_LOGFILE! has been created. ) IF DEFINED LogExists ( Echo Press any key to open log file/s in your text editor or close this window. pause >NUL IF EXIST "!DSOAL_LOGFILE!" (START /B /SEPARATE "" "!DSOAL_LOGFILE!" >NUL) IF EXIST "!ALSOFT_LOGFILE!" (START /B /SEPARATE "" "!ALSOFT_LOGFILE!" >NUL) ) else ( Echo Log file/s were not created. Echo This can be caused by the executable not having permissions to create files in its folder or being run as administrator Echo So instead of dropping an executable, you can try running this script directly to set environmental variables Echo Press any key to close this window. pause>Nul exit ) ) else ( NET SESSION >nul 2>&1 IF !ERRORLEVEL! EQU 0 ( REG delete HKCU\Environment /F /V DSOAL_LOGLEVEL 1>>NUL 2>&1 REG delete HKCU\Environment /F /V DSOAL_LOGFILE 1>>NUL 2>&1 REG delete HKCU\Environment /F /V ALSOFT_LOGLEVEL 1>>NUL 2>&1 REG delete HKCU\Environment /F /V ALSOFT_LOGFILE 1>>NUL 2>&1 REG delete "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" /F /V DSOAL_LOGLEVEL 1>>NUL 2>&1 REG delete "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" /F /V DSOAL_LOGFILE 1>>NUL 2>&1 REG delete "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" /F /V ALSOFT_LOGLEVEL 1>>NUL 2>&1 REG delete "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" /F /V ALSOFT_LOGFILE 1>>NUL 2>&1 Echo SYSTEM environment variables have now been removed so logging is now disabled. Echo You can close this window or proceed to ^(re^)enable. Echo. Echo *This is ONLY meant for executables that are NOT run as administrator but ARE located in protected folders Echo like Program files\*\, which require administrator rights to create/modify ^(log^) files* Echo The log files will be saved into "!LogFileFolderPath!". Echo - If the executable ISN'T run as administrator and ISN'T in a protected folder, Echo then close this window and drop the executable directly into the script. Echo - If the executable IS run as administrator, Echo then close this window and run the script NOT as an administrator to use USER environment variables. Echo Press any key to enable DSOAL and OpenAL Soft logging via SYSTEM environment variables. Pause>Nul Echo Setting SYSTEM environment variables to enable logging... If not exist "!LogFileFolderPath!" (MkDir "!LogFileFolderPath!") setX DSOAL_LOGLEVEL "4" /M 1>>NUL 2>&1 setX DSOAL_LOGFILE "!LogFileFolderPath!dsoal_error.txt" /M 1>>NUL 2>&1 setX ALSOFT_LOGLEVEL "3" 1>>NUL /M 2>&1 setX ALSOFT_LOGFILE "!LogFileFolderPath!alsoft_error.txt" /M 1>>NUL 2>&1 Echo. Echo SYSTEM environment variables set Echo - If you still don't see the log files, the game might not be using DirectSound or OpenAL, Echo - Keep in mind that log files can reach GBs in size and affect performance, Echo so to disable logging, just run the script again without pressing a key that sets environment variables Echo. Echo Press any key to open the location where log files will be saved: ^(!LogFileFolderPath!^) Pause>Nul explorer.exe "!LogFileFolderPath!" Exit ) else ( REG delete HKCU\Environment /F /V DSOAL_LOGLEVEL 1>>NUL 2>&1 REG delete HKCU\Environment /F /V DSOAL_LOGFILE 1>>NUL 2>&1 REG delete HKCU\Environment /F /V ALSOFT_LOGLEVEL 1>>NUL 2>&1 REG delete HKCU\Environment /F /V ALSOFT_LOGFILE 1>>NUL 2>&1 Echo USER environment variables have now been removed so logging is now disabled. Echo You can close this window or proceed to ^(re^)enable. Echo. Echo *This is ONLY meant for executables that ARE run as administrator AND/OR are located in protected folders Echo like Program files\*\, which require administrator rights to create/modify ^(log^) files* Echo The log files will be created in the same folder as the executable. Echo - If the executable ISN'T run as administrator and ISN'T in a protected folder, Echo then close this window and drop the executable directly into the script. Echo - If the executable ISNT run as administrator but IS in a protected folder, Echo then close this window and run the SCRIPT as an administrator to use SYSTEM environment variables. Echo Press any key to enable DSOAL and OpenAL Soft logging via USER environment variables. Pause>Nul Echo Setting USER environment variables to enable logging... setX DSOAL_LOGLEVEL "4" 1>>NUL 2>&1 setX DSOAL_LOGFILE "dsoal_error.txt" 1>>NUL 2>&1 setX ALSOFT_LOGLEVEL "3" 1>>NUL 2>&1 setX ALSOFT_LOGFILE "alsoft_error.txt" 1>>NUL 2>&1 Echo. Echo USER environment variables set Echo - If you still don't see the log files, the game might not be using DirectSound or OpenAL, Echo - Keep in mind that log files can reach GBs in size and affect performance, Echo so to disable logging, just run the script again without pressing a key that sets environment variables Pause>Nul ) ) Exitkcat-openal-soft-75c0059/modules/000077500000000000000000000000001512220627100166055ustar00rootroot00000000000000kcat-openal-soft-75c0059/modules/al.cppm000066400000000000000000001015071512220627100200660ustar00rootroot00000000000000/* This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * 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 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. * * For more information, please refer to */ /* This file is auto-generated! Please do not edit it manually. * Instead, modify the API in al.xml and regenerate using genheaders.py. * * Last regenerated: 2025-10-13 19:40:01.687690+00:00 */ module; /* The AL module provides core functionality of the AL API, without any non- * standard extensions. * * There are some limitations with the AL module. Stuff like AL_API and * AL_APIENTRY can't be used by code importing it since macros can't be * exported from modules, and there's no way to make aliases for these * properties that can be exported. Luckily AL_API isn't typically needed by * user code since it's used to indicate functions as being imported from the * library, which is only relevant to the declarations made in the module * itself. * * AL_APIENTRY is similarly typically only needed for specifying the calling * convention for functions and function pointers declared in the module. * However, some extensions use callbacks that need user code to define * functions with the same calling convention. Currently this is set to use the * platform's default calling convention (that is, it's defined to nothing), * except on Windows where it's defined to __cdecl. Interestingly, capture-less * lambdas seem to generate conversion operators that match function pointers * of any calling convention, but short of that, the user will be responsible * for ensuring callbacks use the cdecl calling convention on Windows and the * default for other OSs. * * Additionally, enums are declared as global inline constexpr ints. This * should generally be fine, as long as user code doesn't try to use them in * the preprocessor which will no longer recognize or expand them to integer * literals. Being global ints also defines them as actual objects stored in * memory, lvalues whose addresses can be taken, instead of as integer literals * or prvalues, which may have subtle implications. An unnamed enum would be * better here, since the enumerators associate a value with a name and don't * become referenceable objects in memory, except that gives the name a new * type (e.g. typeid(AL_NO_ERROR) != typeid(int)) which could create problems * for type deduction. * * Note that defining AL_LIBTYPE_STATIC, AL_DISABLE_NOEXCEPT, and/or * AL_NO_PROTOTYPES does still influence the function and function pointer type * declarations, but only when compiling the module. The user-defined macros * have no effect when importing the module. */ #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 #ifndef AL_DISABLE_NOEXCEPT #define AL_API_NOEXCEPT noexcept #else #define AL_API_NOEXCEPT #endif #define ENUMDCL inline constexpr auto export module openal.al; export extern "C" { /*** AL_VERSION_1_0 ***/ /** 8-bit boolean */ using ALboolean = char; /** character */ using ALchar = char; /** signed 8-bit integer */ using ALbyte = signed char; /** unsigned 8-bit integer */ using ALubyte = unsigned char; /** signed 16-bit integer */ using ALshort = short; /** unsigned 16-bit integer */ using ALushort = unsigned short; /** signed 32-bit integer */ using ALint = int; /** unsigned 32-bit integer */ using ALuint = unsigned int; /** non-negative 32-bit integer size */ using ALsizei = int; /** 32-bit enumeration value */ using ALenum = int; /** 32-bit IEEE-754 floating-point */ using ALfloat = float; /** 64-bit IEEE-754 floating-point */ using ALdouble = double; /** void type (opaque pointers only) */ using ALvoid = void; /** No distance model or no buffer */ ENUMDCL AL_NONE = 0; /** Boolean False. */ ENUMDCL AL_FALSE = 0; /** Boolean True. */ ENUMDCL AL_TRUE = 1; /** * Relative source. * Type: ALboolean * Range: [AL_FALSE, AL_TRUE] * Default: AL_FALSE * * Specifies if the source uses relative coordinates. */ ENUMDCL 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. */ ENUMDCL AL_CONE_INNER_ANGLE = 0x1001; /** * Outer cone angle, in degrees. * Type: ALint, ALfloat * 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. */ ENUMDCL 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. */ ENUMDCL 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. */ ENUMDCL 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. */ ENUMDCL 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. */ ENUMDCL AL_VELOCITY = 0x1006; /** * Source looping. * Type: ALboolean * Range: [AL_FALSE, AL_TRUE] * Default: AL_FALSE * * Specifies whether source playback loops. */ ENUMDCL 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. */ ENUMDCL 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. */ ENUMDCL 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). */ ENUMDCL 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). */ ENUMDCL 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. */ ENUMDCL AL_ORIENTATION = 0x100F; /** * Source state (query only). * Type: ALenum * Range: [AL_INITIAL, AL_PLAYING, AL_PAUSED, AL_STOPPED] */ ENUMDCL AL_SOURCE_STATE = 0x1010; /* Source state values. */ ENUMDCL AL_INITIAL = 0x1011; ENUMDCL AL_PLAYING = 0x1012; ENUMDCL AL_PAUSED = 0x1013; ENUMDCL AL_STOPPED = 0x1014; /** * Source Buffer Queue size (query only). * Type: ALint * * The number of buffers queued using alSourceQueueBuffers, minus the buffers * removed with alSourceUnqueueBuffers. */ ENUMDCL 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. */ ENUMDCL 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. */ ENUMDCL 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. */ ENUMDCL 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. */ ENUMDCL 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. */ ENUMDCL AL_MAX_DISTANCE = 0x1023; /** Unsigned 8-bit mono buffer format. */ ENUMDCL AL_FORMAT_MONO8 = 0x1100; /** Signed 16-bit mono buffer format. */ ENUMDCL AL_FORMAT_MONO16 = 0x1101; /** Unsigned 8-bit stereo buffer format. */ ENUMDCL AL_FORMAT_STEREO8 = 0x1102; /** Signed 16-bit stereo buffer format. */ ENUMDCL AL_FORMAT_STEREO16 = 0x1103; /** * Buffer frequency/sample rate (query only). * Type: ALint */ ENUMDCL AL_FREQUENCY = 0x2001; /** * Buffer data size in bytes (query only). * Type: ALint */ ENUMDCL AL_SIZE = 0x2004; /* Buffer state. Not for public use. */ ENUMDCL AL_UNUSED = 0x2010; ENUMDCL AL_PENDING = 0x2011; ENUMDCL AL_PROCESSED = 0x2012; /** No error. */ ENUMDCL AL_NO_ERROR = 0; /** Invalid name (ID) passed to an AL call. */ ENUMDCL AL_INVALID_NAME = 0xA001; /** Invalid enumeration passed to AL call. */ ENUMDCL AL_INVALID_ENUM = 0xA002; /** Invalid value passed to AL call. */ ENUMDCL AL_INVALID_VALUE = 0xA003; /** Illegal AL call. */ ENUMDCL AL_INVALID_OPERATION = 0xA004; /** Not enough memory to execute the AL call. */ ENUMDCL AL_OUT_OF_MEMORY = 0xA005; /** Context string: Vendor name. */ ENUMDCL AL_VENDOR = 0xB001; /** Context string: Version. */ ENUMDCL AL_VERSION = 0xB002; /** Context string: Renderer name. */ ENUMDCL AL_RENDERER = 0xB003; /** Context string: Space-separated extension list. */ ENUMDCL AL_EXTENSIONS = 0xB004; /** * Doppler scale. * Type: ALfloat * Range: [0.0 - ] * Default: 1.0 * * Scale for source and listener velocities. */ ENUMDCL AL_DOPPLER_FACTOR = 0xC000; /** * Doppler velocity (deprecated). * Type: ALfloat * * A multiplier applied to the Speed of Sound. */ ENUMDCL AL_DOPPLER_VELOCITY = 0xC001; /** * 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. */ ENUMDCL AL_DISTANCE_MODEL = 0xD000; /* Deprecated macros. */ ENUMDCL AL_INVALID [[deprecated("Use -1 instead")]] = -1; ENUMDCL AL_ILLEGAL_ENUM [[deprecated("Use AL_INVALID_ENUM instead")]] = AL_INVALID_ENUM; ENUMDCL AL_ILLEGAL_COMMAND [[deprecated("Use AL_INVALID_OPERATION instead")]] = AL_INVALID_OPERATION; /* Distance model values. */ ENUMDCL AL_INVERSE_DISTANCE = 0xD001; ENUMDCL AL_INVERSE_DISTANCE_CLAMPED = 0xD002; #ifndef AL_NO_PROTOTYPES /* Renderer State management. */ AL_API auto AL_APIENTRY alEnable(ALenum capability) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alDisable(ALenum capability) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alIsEnabled(ALenum capability) AL_API_NOEXCEPT -> ALboolean; /* Context state setting. */ AL_API auto AL_APIENTRY alDopplerFactor(ALfloat value) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alDopplerVelocity(ALfloat value) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alDistanceModel(ALenum distanceModel) AL_API_NOEXCEPT -> void; /* Context state retrieval. */ AL_API auto AL_APIENTRY alGetString(ALenum param) AL_API_NOEXCEPT -> const ALchar*; AL_API auto AL_APIENTRY alGetBooleanv(ALenum param, ALboolean *values) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alGetIntegerv(ALenum param, ALint *values) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alGetFloatv(ALenum param, ALfloat *values) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alGetDoublev(ALenum param, ALdouble *values) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alGetBoolean(ALenum param) AL_API_NOEXCEPT -> ALboolean; AL_API auto AL_APIENTRY alGetInteger(ALenum param) AL_API_NOEXCEPT -> ALint; AL_API auto AL_APIENTRY alGetFloat(ALenum param) AL_API_NOEXCEPT -> ALfloat; AL_API auto AL_APIENTRY alGetDouble(ALenum param) AL_API_NOEXCEPT -> ALdouble; /** * Obtain the first error generated in the AL context since the last call to * this function. */ AL_API auto AL_APIENTRY alGetError() AL_API_NOEXCEPT -> ALenum; /** Query for the presence of an extension on the AL context. */ AL_API auto AL_APIENTRY alIsExtensionPresent(const ALchar *extname) AL_API_NOEXCEPT -> ALboolean; /** * Retrieve the address of a function. The returned function may be context- * specific. */ AL_API auto AL_APIENTRY alGetProcAddress(const ALchar *fname) AL_API_NOEXCEPT -> void*; /** Retrieve the value of an enum. The returned value may be context-specific. */ AL_API auto AL_APIENTRY alGetEnumValue(const ALchar *ename) AL_API_NOEXCEPT -> ALenum; /* Set listener parameters. */ AL_API auto AL_APIENTRY alListenerf(ALenum param, ALfloat value) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alListener3f(ALenum param, ALfloat value1, ALfloat value2, ALfloat value3) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alListenerfv(ALenum param, const ALfloat *values) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alListeneri(ALenum param, ALint value) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alListener3i(ALenum param, ALint value1, ALint value2, ALint value3) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alListeneriv(ALenum param, const ALint *values) AL_API_NOEXCEPT -> void; /* Get listener parameters. */ AL_API auto AL_APIENTRY alGetListenerf(ALenum param, ALfloat *value) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alGetListener3f(ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alGetListenerfv(ALenum param, ALfloat *values) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alGetListeneri(ALenum param, ALint *value) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alGetListener3i(ALenum param, ALint *value1, ALint *value2, ALint *value3) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alGetListeneriv(ALenum param, ALint *values) AL_API_NOEXCEPT -> void; /** Create source objects. */ AL_API auto AL_APIENTRY alGenSources(ALsizei n, ALuint *sources) AL_API_NOEXCEPT -> void; /** Delete source objects. */ AL_API auto AL_APIENTRY alDeleteSources(ALsizei n, const ALuint *sources) AL_API_NOEXCEPT -> void; /** Verify an ID is for a valid source. */ AL_API auto AL_APIENTRY alIsSource(ALuint source) AL_API_NOEXCEPT -> ALboolean; /* Set source parameters. */ AL_API auto AL_APIENTRY alSourcef(ALuint source, ALenum param, ALfloat value) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alSource3f(ALuint source, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alSourcefv(ALuint source, ALenum param, const ALfloat *values) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alSourcei(ALuint source, ALenum param, ALint value) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alSource3i(ALuint source, ALenum param, ALint value1, ALint value2, ALint value3) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alSourceiv(ALuint source, ALenum param, const ALint *values) AL_API_NOEXCEPT -> void; /* Get source parameters. */ AL_API auto AL_APIENTRY alGetSourcef(ALuint source, ALenum param, ALfloat *value) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alGetSource3f(ALuint source, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alGetSourcefv(ALuint source, ALenum param, ALfloat *values) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alGetSourcei(ALuint source, ALenum param, ALint *value) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alGetSource3i(ALuint source, ALenum param, ALint *value1, ALint *value2, ALint *value3) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alGetSourceiv(ALuint source, ALenum param, ALint *values) AL_API_NOEXCEPT -> void; /** Play, restart, or resume a source, setting its state to AL_PLAYING. */ AL_API auto AL_APIENTRY alSourcePlay(ALuint source) AL_API_NOEXCEPT -> void; /** Stop a source, setting its state to AL_STOPPED if playing or paused. */ AL_API auto AL_APIENTRY alSourceStop(ALuint source) AL_API_NOEXCEPT -> void; /** Rewind a source, setting its state to AL_INITIAL. */ AL_API auto AL_APIENTRY alSourceRewind(ALuint source) AL_API_NOEXCEPT -> void; /** Pause a source, setting its state to AL_PAUSED if playing. */ AL_API auto AL_APIENTRY alSourcePause(ALuint source) AL_API_NOEXCEPT -> void; /** Play, restart, or resume a list of sources atomically. */ AL_API auto AL_APIENTRY alSourcePlayv(ALsizei n, const ALuint *sources) AL_API_NOEXCEPT -> void; /** Stop a list of sources atomically. */ AL_API auto AL_APIENTRY alSourceStopv(ALsizei n, const ALuint *sources) AL_API_NOEXCEPT -> void; /** Rewind a list of sources atomically. */ AL_API auto AL_APIENTRY alSourceRewindv(ALsizei n, const ALuint *sources) AL_API_NOEXCEPT -> void; /** Pause a list of sources atomically. */ AL_API auto AL_APIENTRY alSourcePausev(ALsizei n, const ALuint *sources) AL_API_NOEXCEPT -> void; /** Queue buffers onto a source */ AL_API auto AL_APIENTRY alSourceQueueBuffers(ALuint source, ALsizei nb, const ALuint *buffers) AL_API_NOEXCEPT -> void; /** Unqueue processed buffers from a source */ AL_API auto AL_APIENTRY alSourceUnqueueBuffers(ALuint source, ALsizei nb, ALuint *buffers) AL_API_NOEXCEPT -> void; /** Create buffer objects */ AL_API auto AL_APIENTRY alGenBuffers(ALsizei n, ALuint *buffers) AL_API_NOEXCEPT -> void; /** Delete buffer objects */ AL_API auto AL_APIENTRY alDeleteBuffers(ALsizei n, const ALuint *buffers) AL_API_NOEXCEPT -> void; /** Verify an ID is a valid buffer (including the NULL buffer) */ AL_API auto AL_APIENTRY alIsBuffer(ALuint buffer) AL_API_NOEXCEPT -> ALboolean; /** * Copies data into the buffer, interpreting it using the specified format and * samplerate. */ AL_API auto AL_APIENTRY alBufferData(ALuint buffer, ALenum format, const ALvoid *data, ALsizei size, ALsizei samplerate) AL_API_NOEXCEPT -> void; /* Set buffer parameters. */ AL_API auto AL_APIENTRY alBufferf(ALuint buffer, ALenum param, ALfloat value) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alBuffer3f(ALuint buffer, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alBufferfv(ALuint buffer, ALenum param, const ALfloat *values) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alBufferi(ALuint buffer, ALenum param, ALint value) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alBuffer3i(ALuint buffer, ALenum param, ALint value1, ALint value2, ALint value3) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alBufferiv(ALuint buffer, ALenum param, const ALint *values) AL_API_NOEXCEPT -> void; /* Get buffer parameters. */ AL_API auto AL_APIENTRY alGetBufferf(ALuint buffer, ALenum param, ALfloat *value) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alGetBuffer3f(ALuint buffer, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alGetBufferfv(ALuint buffer, ALenum param, ALfloat *values) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alGetBufferi(ALuint buffer, ALenum param, ALint *value) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alGetBuffer3i(ALuint buffer, ALenum param, ALint *value1, ALint *value2, ALint *value3) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alGetBufferiv(ALuint buffer, ALenum param, ALint *values) AL_API_NOEXCEPT -> void; #endif /* AL_NO_PROTOTYPES */ /* Pointer-to-function types, useful for storing dynamically loaded AL entry * points. */ using LPALENABLE = auto (AL_APIENTRY*)(ALenum capability) AL_API_NOEXCEPT -> void; using LPALDISABLE = auto (AL_APIENTRY*)(ALenum capability) AL_API_NOEXCEPT -> void; using LPALISENABLED = auto (AL_APIENTRY*)(ALenum capability) AL_API_NOEXCEPT -> ALboolean; using LPALDOPPLERFACTOR = auto (AL_APIENTRY*)(ALfloat value) AL_API_NOEXCEPT -> void; using LPALDOPPLERVELOCITY = auto (AL_APIENTRY*)(ALfloat value) AL_API_NOEXCEPT -> void; using LPALDISTANCEMODEL = auto (AL_APIENTRY*)(ALenum distanceModel) AL_API_NOEXCEPT -> void; using LPALGETSTRING = auto (AL_APIENTRY*)(ALenum param) AL_API_NOEXCEPT -> const ALchar*; using LPALGETBOOLEANV = auto (AL_APIENTRY*)(ALenum param, ALboolean *values) AL_API_NOEXCEPT -> void; using LPALGETINTEGERV = auto (AL_APIENTRY*)(ALenum param, ALint *values) AL_API_NOEXCEPT -> void; using LPALGETFLOATV = auto (AL_APIENTRY*)(ALenum param, ALfloat *values) AL_API_NOEXCEPT -> void; using LPALGETDOUBLEV = auto (AL_APIENTRY*)(ALenum param, ALdouble *values) AL_API_NOEXCEPT -> void; using LPALGETBOOLEAN = auto (AL_APIENTRY*)(ALenum param) AL_API_NOEXCEPT -> ALboolean; using LPALGETINTEGER = auto (AL_APIENTRY*)(ALenum param) AL_API_NOEXCEPT -> ALint; using LPALGETFLOAT = auto (AL_APIENTRY*)(ALenum param) AL_API_NOEXCEPT -> ALfloat; using LPALGETDOUBLE = auto (AL_APIENTRY*)(ALenum param) AL_API_NOEXCEPT -> ALdouble; using LPALGETERROR = auto (AL_APIENTRY*)() AL_API_NOEXCEPT -> ALenum; using LPALISEXTENSIONPRESENT = auto (AL_APIENTRY*)(const ALchar *extname) AL_API_NOEXCEPT -> ALboolean; using LPALGETPROCADDRESS = auto (AL_APIENTRY*)(const ALchar *fname) AL_API_NOEXCEPT -> void*; using LPALGETENUMVALUE = auto (AL_APIENTRY*)(const ALchar *ename) AL_API_NOEXCEPT -> ALenum; using LPALLISTENERF = auto (AL_APIENTRY*)(ALenum param, ALfloat value) AL_API_NOEXCEPT -> void; using LPALLISTENER3F = auto (AL_APIENTRY*)(ALenum param, ALfloat value1, ALfloat value2, ALfloat value3) AL_API_NOEXCEPT -> void; using LPALLISTENERFV = auto (AL_APIENTRY*)(ALenum param, const ALfloat *values) AL_API_NOEXCEPT -> void; using LPALLISTENERI = auto (AL_APIENTRY*)(ALenum param, ALint value) AL_API_NOEXCEPT -> void; using LPALLISTENER3I = auto (AL_APIENTRY*)(ALenum param, ALint value1, ALint value2, ALint value3) AL_API_NOEXCEPT -> void; using LPALLISTENERIV = auto (AL_APIENTRY*)(ALenum param, const ALint *values) AL_API_NOEXCEPT -> void; using LPALGETLISTENERF = auto (AL_APIENTRY*)(ALenum param, ALfloat *value) AL_API_NOEXCEPT -> void; using LPALGETLISTENER3F = auto (AL_APIENTRY*)(ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3) AL_API_NOEXCEPT -> void; using LPALGETLISTENERFV = auto (AL_APIENTRY*)(ALenum param, ALfloat *values) AL_API_NOEXCEPT -> void; using LPALGETLISTENERI = auto (AL_APIENTRY*)(ALenum param, ALint *value) AL_API_NOEXCEPT -> void; using LPALGETLISTENER3I = auto (AL_APIENTRY*)(ALenum param, ALint *value1, ALint *value2, ALint *value3) AL_API_NOEXCEPT -> void; using LPALGETLISTENERIV = auto (AL_APIENTRY*)(ALenum param, ALint *values) AL_API_NOEXCEPT -> void; using LPALGENSOURCES = auto (AL_APIENTRY*)(ALsizei n, ALuint *sources) AL_API_NOEXCEPT -> void; using LPALDELETESOURCES = auto (AL_APIENTRY*)(ALsizei n, const ALuint *sources) AL_API_NOEXCEPT -> void; using LPALISSOURCE = auto (AL_APIENTRY*)(ALuint source) AL_API_NOEXCEPT -> ALboolean; using LPALSOURCEF = auto (AL_APIENTRY*)(ALuint source, ALenum param, ALfloat value) AL_API_NOEXCEPT -> void; using LPALSOURCE3F = auto (AL_APIENTRY*)(ALuint source, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3) AL_API_NOEXCEPT -> void; using LPALSOURCEFV = auto (AL_APIENTRY*)(ALuint source, ALenum param, const ALfloat *values) AL_API_NOEXCEPT -> void; using LPALSOURCEI = auto (AL_APIENTRY*)(ALuint source, ALenum param, ALint value) AL_API_NOEXCEPT -> void; using LPALSOURCE3I = auto (AL_APIENTRY*)(ALuint source, ALenum param, ALint value1, ALint value2, ALint value3) AL_API_NOEXCEPT -> void; using LPALSOURCEIV = auto (AL_APIENTRY*)(ALuint source, ALenum param, const ALint *values) AL_API_NOEXCEPT -> void; using LPALGETSOURCEF = auto (AL_APIENTRY*)(ALuint source, ALenum param, ALfloat *value) AL_API_NOEXCEPT -> void; using LPALGETSOURCE3F = auto (AL_APIENTRY*)(ALuint source, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3) AL_API_NOEXCEPT -> void; using LPALGETSOURCEFV = auto (AL_APIENTRY*)(ALuint source, ALenum param, ALfloat *values) AL_API_NOEXCEPT -> void; using LPALGETSOURCEI = auto (AL_APIENTRY*)(ALuint source, ALenum param, ALint *value) AL_API_NOEXCEPT -> void; using LPALGETSOURCE3I = auto (AL_APIENTRY*)(ALuint source, ALenum param, ALint *value1, ALint *value2, ALint *value3) AL_API_NOEXCEPT -> void; using LPALGETSOURCEIV = auto (AL_APIENTRY*)(ALuint source, ALenum param, ALint *values) AL_API_NOEXCEPT -> void; using LPALSOURCEPLAY = auto (AL_APIENTRY*)(ALuint source) AL_API_NOEXCEPT -> void; using LPALSOURCESTOP = auto (AL_APIENTRY*)(ALuint source) AL_API_NOEXCEPT -> void; using LPALSOURCEREWIND = auto (AL_APIENTRY*)(ALuint source) AL_API_NOEXCEPT -> void; using LPALSOURCEPAUSE = auto (AL_APIENTRY*)(ALuint source) AL_API_NOEXCEPT -> void; using LPALSOURCEPLAYV = auto (AL_APIENTRY*)(ALsizei n, const ALuint *sources) AL_API_NOEXCEPT -> void; using LPALSOURCESTOPV = auto (AL_APIENTRY*)(ALsizei n, const ALuint *sources) AL_API_NOEXCEPT -> void; using LPALSOURCEREWINDV = auto (AL_APIENTRY*)(ALsizei n, const ALuint *sources) AL_API_NOEXCEPT -> void; using LPALSOURCEPAUSEV = auto (AL_APIENTRY*)(ALsizei n, const ALuint *sources) AL_API_NOEXCEPT -> void; using LPALSOURCEQUEUEBUFFERS = auto (AL_APIENTRY*)(ALuint source, ALsizei nb, const ALuint *buffers) AL_API_NOEXCEPT -> void; using LPALSOURCEUNQUEUEBUFFERS = auto (AL_APIENTRY*)(ALuint source, ALsizei nb, ALuint *buffers) AL_API_NOEXCEPT -> void; using LPALGENBUFFERS = auto (AL_APIENTRY*)(ALsizei n, ALuint *buffers) AL_API_NOEXCEPT -> void; using LPALDELETEBUFFERS = auto (AL_APIENTRY*)(ALsizei n, const ALuint *buffers) AL_API_NOEXCEPT -> void; using LPALISBUFFER = auto (AL_APIENTRY*)(ALuint buffer) AL_API_NOEXCEPT -> ALboolean; using LPALBUFFERDATA = auto (AL_APIENTRY*)(ALuint buffer, ALenum format, const ALvoid *data, ALsizei size, ALsizei samplerate) AL_API_NOEXCEPT -> void; using LPALBUFFERF = auto (AL_APIENTRY*)(ALuint buffer, ALenum param, ALfloat value) AL_API_NOEXCEPT -> void; using LPALBUFFER3F = auto (AL_APIENTRY*)(ALuint buffer, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3) AL_API_NOEXCEPT -> void; using LPALBUFFERFV = auto (AL_APIENTRY*)(ALuint buffer, ALenum param, const ALfloat *values) AL_API_NOEXCEPT -> void; using LPALBUFFERI = auto (AL_APIENTRY*)(ALuint buffer, ALenum param, ALint value) AL_API_NOEXCEPT -> void; using LPALBUFFER3I = auto (AL_APIENTRY*)(ALuint buffer, ALenum param, ALint value1, ALint value2, ALint value3) AL_API_NOEXCEPT -> void; using LPALBUFFERIV = auto (AL_APIENTRY*)(ALuint buffer, ALenum param, const ALint *values) AL_API_NOEXCEPT -> void; using LPALGETBUFFERF = auto (AL_APIENTRY*)(ALuint buffer, ALenum param, ALfloat *value) AL_API_NOEXCEPT -> void; using LPALGETBUFFER3F = auto (AL_APIENTRY*)(ALuint buffer, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3) AL_API_NOEXCEPT -> void; using LPALGETBUFFERFV = auto (AL_APIENTRY*)(ALuint buffer, ALenum param, ALfloat *values) AL_API_NOEXCEPT -> void; using LPALGETBUFFERI = auto (AL_APIENTRY*)(ALuint buffer, ALenum param, ALint *value) AL_API_NOEXCEPT -> void; using LPALGETBUFFER3I = auto (AL_APIENTRY*)(ALuint buffer, ALenum param, ALint *value1, ALint *value2, ALint *value3) AL_API_NOEXCEPT -> void; using LPALGETBUFFERIV = auto (AL_APIENTRY*)(ALuint buffer, ALenum param, ALint *values) AL_API_NOEXCEPT -> void; /*** AL_VERSION_1_1 ***/ /** * Source buffer offset, in seconds. * Type: ALfloat, ALint * Range: [0 - ] */ ENUMDCL AL_SEC_OFFSET = 0x1024; /** * Source buffer offset, in sample frames. * Type: ALint * Range: [0 - ] */ ENUMDCL AL_SAMPLE_OFFSET = 0x1025; /** * Source buffer offset, in bytes. * Type: ALint * Range: [0 - ] */ ENUMDCL 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. */ ENUMDCL AL_SOURCE_TYPE = 0x1027; /* Source type values. */ ENUMDCL AL_STATIC = 0x1028; ENUMDCL AL_STREAMING = 0x1029; ENUMDCL AL_UNDETERMINED = 0x1030; /** * Buffer bits per sample (query only). * Type: ALint */ ENUMDCL AL_BITS = 0x2002; /** * Buffer channel count (query only). * Type: ALint */ ENUMDCL AL_CHANNELS = 0x2003; /** * 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. */ ENUMDCL AL_SPEED_OF_SOUND = 0xC003; ENUMDCL AL_LINEAR_DISTANCE = 0xD003; ENUMDCL AL_LINEAR_DISTANCE_CLAMPED = 0xD004; ENUMDCL AL_EXPONENT_DISTANCE = 0xD005; ENUMDCL AL_EXPONENT_DISTANCE_CLAMPED = 0xD006; #ifndef AL_NO_PROTOTYPES AL_API auto AL_APIENTRY alSpeedOfSound(ALfloat value) AL_API_NOEXCEPT -> void; #endif /* AL_NO_PROTOTYPES */ /* Pointer-to-function types, useful for storing dynamically loaded AL entry * points. */ using LPALSPEEDOFSOUND = auto (AL_APIENTRY*)(ALfloat value) AL_API_NOEXCEPT -> void; } /* extern "C" */ kcat-openal-soft-75c0059/modules/alc.cppm000066400000000000000000000340541512220627100202330ustar00rootroot00000000000000/* This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * 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 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. * * For more information, please refer to */ /* This file is auto-generated! Please do not edit it manually. * Instead, modify the API in al.xml and regenerate using genheaders.py. * * Last regenerated: 2025-10-13 19:40:01.687690+00:00 */ module; /* The ALC module provides core functionality of the device/system ALC API, * without any non-standard extensions. * * There are some limitations with the ALC module. Stuff like ALC_API and * ALC_APIENTRY can't be used by code importing it since macros can't be * exported from modules, and there's no way to make aliases for these * properties that can be exported. Luckily ALC_API isn't typically needed by * user code since it's used to indicate functions as being imported from the * library, which is only relevant to the declarations made in the module * itself. * * ALC_APIENTRY is similarly typically only needed for specifying the calling * convention for functions and function pointers declared in the module. * However, some extensions use callbacks that need user code to define * functions with the same calling convention. Currently this is set to use the * platform's default calling convention (that is, it's defined to nothing), * except on Windows where it's defined to __cdecl. Interestingly, capture-less * lambdas seem to generate conversion operators that match function pointers * of any calling convention, but short of that, the user will be responsible * for ensuring callbacks use the cdecl calling convention on Windows and the * default for other OSs. * * Additionally, enums are declared as global inline constexpr ints. This * should generally be fine as long as user code doesn't try to use them in * the preprocessor, which will no longer recognize or expand them to integer * literals. Being global ints also defines them as actual objects stored in * memory, lvalues whose addresses can be taken, instead of as integer literals * or prvalues, which may have subtle implications. An unnamed enum would be * better here, since the enumerators associate a value with a name and don't * become referenceable objects in memory, except that gives the name a new * type (e.g. typeid(ALC_NO_ERROR) != typeid(int)) which could create problems * for type deduction. * * Note that defining AL_LIBTYPE_STATIC, AL_DISABLE_NOEXCEPT, and/or * ALC_NO_PROTOTYPES does still influence the function and function pointer * type declarations, but only when compiling the module. The user-defined * macros have no effect when importing the module. */ #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 #ifndef AL_DISABLE_NOEXCEPT #define ALC_API_NOEXCEPT noexcept #else #define ALC_API_NOEXCEPT #endif #define ENUMDCL inline constexpr auto export module openal.alc; export extern "C" { /*** ALC_VERSION_1_0 ***/ /* Deprecated macros. */ /** Deprecated enum. */ ENUMDCL ALC_INVALID [[deprecated("Use 0 instead")]] = 0; /** Opaque device handle */ using ALCdevice = struct ALCdevice; /** Opaque context handle */ using ALCcontext = struct ALCcontext; /** 8-bit boolean */ using ALCboolean = char; /** character */ using ALCchar = char; /** signed 8-bit integer */ using ALCbyte = signed char; /** unsigned 8-bit integer */ using ALCubyte = unsigned char; /** signed 16-bit integer */ using ALCshort = short; /** unsigned 16-bit integer */ using ALCushort = unsigned short; /** signed 32-bit integer */ using ALCint = int; /** unsigned 32-bit integer */ using ALCuint = unsigned int; /** non-negative 32-bit integer size */ using ALCsizei = int; /** 32-bit enumeration value */ using ALCenum = int; /** 32-bit IEEE-754 floating-point */ using ALCfloat = float; /** 64-bit IEEE-754 floating-point */ using ALCdouble = double; /** void type (for opaque pointers only) */ using ALCvoid = void; /** Boolean False. */ ENUMDCL ALC_FALSE = 0; /** Boolean True. */ ENUMDCL ALC_TRUE = 1; /** Context attribute: Hz. */ ENUMDCL ALC_FREQUENCY = 0x1007; /** Context attribute: Hz. */ ENUMDCL ALC_REFRESH = 0x1008; /** Context attribute: AL_TRUE or AL_FALSE synchronous context? */ ENUMDCL ALC_SYNC = 0x1009; /** No error. */ ENUMDCL ALC_NO_ERROR = 0; /** Invalid device handle. */ ENUMDCL ALC_INVALID_DEVICE = 0xA001; /** Invalid context handle. */ ENUMDCL ALC_INVALID_CONTEXT = 0xA002; /** Invalid enumeration passed to an ALC call. */ ENUMDCL ALC_INVALID_ENUM = 0xA003; /** Invalid value passed to an ALC call. */ ENUMDCL ALC_INVALID_VALUE = 0xA004; /** Out of memory. */ ENUMDCL ALC_OUT_OF_MEMORY = 0xA005; /** Runtime ALC major version. */ ENUMDCL ALC_MAJOR_VERSION = 0x1000; /** Runtime ALC minor version. */ ENUMDCL ALC_MINOR_VERSION = 0x1001; /** Context attribute list size. */ ENUMDCL ALC_ATTRIBUTES_SIZE = 0x1002; /** Context attribute list properties. */ ENUMDCL ALC_ALL_ATTRIBUTES = 0x1003; /** String for the default device specifier. */ ENUMDCL 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). */ ENUMDCL ALC_DEVICE_SPECIFIER = 0x1005; /** String for space-separated list of ALC extensions. */ ENUMDCL ALC_EXTENSIONS = 0x1006; #ifndef ALC_NO_PROTOTYPES /* Context management. */ /** Create and attach a context to the given device. */ ALC_API auto ALC_APIENTRY alcCreateContext(ALCdevice *device, const ALCint *attrlist) ALC_API_NOEXCEPT -> ALCcontext*; /** * Makes the given context the active process-wide context. Passing NULL clears * the active context. */ ALC_API auto ALC_APIENTRY alcMakeContextCurrent(ALCcontext *context) ALC_API_NOEXCEPT -> ALCboolean; /** Resumes processing updates for the given context. */ ALC_API auto ALC_APIENTRY alcProcessContext(ALCcontext *context) ALC_API_NOEXCEPT -> void; /** Suspends updates for the given context. */ ALC_API auto ALC_APIENTRY alcSuspendContext(ALCcontext *context) ALC_API_NOEXCEPT -> void; /** Remove a context from its device and destroys it. */ ALC_API auto ALC_APIENTRY alcDestroyContext(ALCcontext *context) ALC_API_NOEXCEPT -> void; /** Returns the currently active context. */ ALC_API auto ALC_APIENTRY alcGetCurrentContext() ALC_API_NOEXCEPT -> ALCcontext*; /** Returns the device that a particular context is attached to. */ ALC_API auto ALC_APIENTRY alcGetContextsDevice(ALCcontext *context) ALC_API_NOEXCEPT -> ALCdevice*; /* Device management. */ /** Opens the named playback device. */ ALC_API auto ALC_APIENTRY alcOpenDevice(const ALCchar *devicename) ALC_API_NOEXCEPT -> ALCdevice*; /** Closes the given playback device. */ ALC_API auto ALC_APIENTRY alcCloseDevice(ALCdevice *device) ALC_API_NOEXCEPT -> ALCboolean; /* Error support. */ /** Obtain the most recent Device error. */ ALC_API auto ALC_APIENTRY alcGetError(ALCdevice *device) ALC_API_NOEXCEPT -> ALCenum; /* Extension support. */ /** * Query for the presence of an extension on the device. Pass a NULL device to * query a device-inspecific extension. */ ALC_API auto ALC_APIENTRY alcIsExtensionPresent(ALCdevice *device, const ALCchar *extname) ALC_API_NOEXCEPT -> ALCboolean; /** * Retrieve the address of a function. Given a non-NULL device, the returned * function may be device-specific. */ ALC_API auto ALC_APIENTRY alcGetProcAddress(ALCdevice *device, const ALCchar *funcname) ALC_API_NOEXCEPT -> ALCvoid*; /** * Retrieve the value of an enum. Given a non-NULL device, the returned value * may be device-specific. */ ALC_API auto ALC_APIENTRY alcGetEnumValue(ALCdevice *device, const ALCchar *enumname) ALC_API_NOEXCEPT -> ALCenum; /* Query functions. */ /** Returns information about the device, and error strings. */ ALC_API auto ALC_APIENTRY alcGetString(ALCdevice *device, ALCenum param) ALC_API_NOEXCEPT -> const ALCchar*; /** Returns information about the device and the version of OpenAL. */ ALC_API auto ALC_APIENTRY alcGetIntegerv(ALCdevice *device, ALCenum param, ALCsizei size, ALCint *values) ALC_API_NOEXCEPT -> void; #endif /* ALC_NO_PROTOTYPES */ /* Pointer-to-function types, useful for storing dynamically loaded ALC entry * points. */ using LPALCCREATECONTEXT = auto (ALC_APIENTRY*)(ALCdevice *device, const ALCint *attrlist) ALC_API_NOEXCEPT -> ALCcontext*; using LPALCMAKECONTEXTCURRENT = auto (ALC_APIENTRY*)(ALCcontext *context) ALC_API_NOEXCEPT -> ALCboolean; using LPALCPROCESSCONTEXT = auto (ALC_APIENTRY*)(ALCcontext *context) ALC_API_NOEXCEPT -> void; using LPALCSUSPENDCONTEXT = auto (ALC_APIENTRY*)(ALCcontext *context) ALC_API_NOEXCEPT -> void; using LPALCDESTROYCONTEXT = auto (ALC_APIENTRY*)(ALCcontext *context) ALC_API_NOEXCEPT -> void; using LPALCGETCURRENTCONTEXT = auto (ALC_APIENTRY*)() ALC_API_NOEXCEPT -> ALCcontext*; using LPALCGETCONTEXTSDEVICE = auto (ALC_APIENTRY*)(ALCcontext *context) ALC_API_NOEXCEPT -> ALCdevice*; using LPALCOPENDEVICE = auto (ALC_APIENTRY*)(const ALCchar *devicename) ALC_API_NOEXCEPT -> ALCdevice*; using LPALCCLOSEDEVICE = auto (ALC_APIENTRY*)(ALCdevice *device) ALC_API_NOEXCEPT -> ALCboolean; using LPALCGETERROR = auto (ALC_APIENTRY*)(ALCdevice *device) ALC_API_NOEXCEPT -> ALCenum; using LPALCISEXTENSIONPRESENT = auto (ALC_APIENTRY*)(ALCdevice *device, const ALCchar *extname) ALC_API_NOEXCEPT -> ALCboolean; using LPALCGETPROCADDRESS = auto (ALC_APIENTRY*)(ALCdevice *device, const ALCchar *funcname) ALC_API_NOEXCEPT -> ALCvoid*; using LPALCGETENUMVALUE = auto (ALC_APIENTRY*)(ALCdevice *device, const ALCchar *enumname) ALC_API_NOEXCEPT -> ALCenum; using LPALCGETSTRING = auto (ALC_APIENTRY*)(ALCdevice *device, ALCenum param) ALC_API_NOEXCEPT -> const ALCchar*; using LPALCGETINTEGERV = auto (ALC_APIENTRY*)(ALCdevice *device, ALCenum param, ALCsizei size, ALCint *values) ALC_API_NOEXCEPT -> void; /*** ALC_VERSION_1_1 ***/ /** Context attribute: requested Mono (3D) Sources. */ ENUMDCL ALC_MONO_SOURCES = 0x1010; /** Context attribute: requested Stereo Sources. */ ENUMDCL ALC_STEREO_SOURCES = 0x1011; /** * Capture 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). */ ENUMDCL ALC_CAPTURE_DEVICE_SPECIFIER = 0x310; /** String for the default capture device specifier. */ ENUMDCL ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER = 0x311; /** Number of sample frames available for capture. */ ENUMDCL ALC_CAPTURE_SAMPLES = 0x312; /** String for the default extended device specifier. */ ENUMDCL 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). */ ENUMDCL ALC_ALL_DEVICES_SPECIFIER = 0x1013; #ifndef ALC_NO_PROTOTYPES /** * Opens the named capture device with the given frequency, format, and buffer * size. */ ALC_API auto ALC_APIENTRY alcCaptureOpenDevice(const ALCchar *devicename, ALCuint frequency, ALCenum format, ALCsizei buffersize) ALC_API_NOEXCEPT -> ALCdevice*; /** Closes the given capture device. */ ALC_API auto ALC_APIENTRY alcCaptureCloseDevice(ALCdevice *device) ALC_API_NOEXCEPT -> ALCboolean; /** Starts capturing samples into the device buffer. */ ALC_API auto ALC_APIENTRY alcCaptureStart(ALCdevice *device) ALC_API_NOEXCEPT -> void; /** Stops capturing samples. Samples in the device buffer remain available. */ ALC_API auto ALC_APIENTRY alcCaptureStop(ALCdevice *device) ALC_API_NOEXCEPT -> void; /** Reads samples from the device buffer. */ ALC_API auto ALC_APIENTRY alcCaptureSamples(ALCdevice *device, ALCvoid *buffer, ALCsizei samples) ALC_API_NOEXCEPT -> void; #endif /* ALC_NO_PROTOTYPES */ /* Pointer-to-function types, useful for storing dynamically loaded ALC entry * points. */ using LPALCCAPTUREOPENDEVICE = auto (ALC_APIENTRY*)(const ALCchar *devicename, ALCuint frequency, ALCenum format, ALCsizei buffersize) ALC_API_NOEXCEPT -> ALCdevice*; using LPALCCAPTURECLOSEDEVICE = auto (ALC_APIENTRY*)(ALCdevice *device) ALC_API_NOEXCEPT -> ALCboolean; using LPALCCAPTURESTART = auto (ALC_APIENTRY*)(ALCdevice *device) ALC_API_NOEXCEPT -> void; using LPALCCAPTURESTOP = auto (ALC_APIENTRY*)(ALCdevice *device) ALC_API_NOEXCEPT -> void; using LPALCCAPTURESAMPLES = auto (ALC_APIENTRY*)(ALCdevice *device, ALCvoid *buffer, ALCsizei samples) ALC_API_NOEXCEPT -> void; } /* extern "C" */ kcat-openal-soft-75c0059/modules/alext.cppm000066400000000000000000002301361512220627100206100ustar00rootroot00000000000000/* This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * 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 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. * * For more information, please refer to */ /* This file is auto-generated! Please do not edit it manually. * Instead, modify the API in al.xml and regenerate using genheaders.py. * * Last regenerated: 2025-10-13 19:40:01.687690+00:00 */ module; #include #include #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 #ifndef AL_DISABLE_NOEXCEPT #define ALC_API_NOEXCEPT noexcept #else #define ALC_API_NOEXCEPT #endif #ifndef AL_API #define AL_API ALC_API #endif #define AL_APIENTRY ALC_APIENTRY #define AL_API_NOEXCEPT ALC_API_NOEXCEPT #define ENUMDCL inline constexpr auto export module openal.ext; export import openal.efx; import openal.std; using alsoft_impl_int64_t = std::int64_t; using alsoft_impl_uint64_t = std::uint64_t; extern "C" struct _GUID; /* NOLINT(*-reserved-identifier) */ export extern "C" { /*** AL_LOKI_IMA_ADPCM_format ***/ ENUMDCL AL_FORMAT_IMA_ADPCM_MONO16_EXT = 0x10000; ENUMDCL AL_FORMAT_IMA_ADPCM_STEREO16_EXT = 0x10001; /*** AL_LOKI_WAVE_format ***/ ENUMDCL AL_FORMAT_WAVE_EXT = 0x10002; /*** AL_EXT_vorbis ***/ ENUMDCL AL_FORMAT_VORBIS_EXT = 0x10003; /*** AL_LOKI_quadriphonic ***/ ENUMDCL AL_FORMAT_QUAD8_LOKI = 0x10004; ENUMDCL AL_FORMAT_QUAD16_LOKI = 0x10005; /*** AL_EXT_float32 ***/ ENUMDCL AL_FORMAT_MONO_FLOAT32 = 0x10010; ENUMDCL AL_FORMAT_STEREO_FLOAT32 = 0x10011; /*** AL_EXT_double ***/ ENUMDCL AL_FORMAT_MONO_DOUBLE_EXT = 0x10012; ENUMDCL AL_FORMAT_STEREO_DOUBLE_EXT = 0x10013; /*** AL_EXT_MULAW ***/ ENUMDCL AL_FORMAT_MONO_MULAW_EXT = 0x10014; ENUMDCL AL_FORMAT_STEREO_MULAW_EXT = 0x10015; /*** AL_EXT_ALAW ***/ ENUMDCL AL_FORMAT_MONO_ALAW_EXT = 0x10016; ENUMDCL AL_FORMAT_STEREO_ALAW_EXT = 0x10017; /*** ALC_LOKI_audio_channel ***/ ENUMDCL ALC_CHAN_MAIN_LOKI = 0x500001; ENUMDCL ALC_CHAN_PCM_LOKI = 0x500002; ENUMDCL ALC_CHAN_CD_LOKI = 0x500003; /*** AL_EXT_MCFORMATS ***/ /* 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. */ ENUMDCL AL_FORMAT_QUAD8 = 0x1204; ENUMDCL AL_FORMAT_QUAD16 = 0x1205; ENUMDCL AL_FORMAT_QUAD32 = 0x1206; ENUMDCL AL_FORMAT_REAR8 = 0x1207; ENUMDCL AL_FORMAT_REAR16 = 0x1208; ENUMDCL AL_FORMAT_REAR32 = 0x1209; ENUMDCL AL_FORMAT_51CHN8 = 0x120A; ENUMDCL AL_FORMAT_51CHN16 = 0x120B; ENUMDCL AL_FORMAT_51CHN32 = 0x120C; ENUMDCL AL_FORMAT_61CHN8 = 0x120D; ENUMDCL AL_FORMAT_61CHN16 = 0x120E; ENUMDCL AL_FORMAT_61CHN32 = 0x120F; ENUMDCL AL_FORMAT_71CHN8 = 0x1210; ENUMDCL AL_FORMAT_71CHN16 = 0x1211; ENUMDCL AL_FORMAT_71CHN32 = 0x1212; /*** AL_EXT_MULAW_MCFORMATS ***/ ENUMDCL AL_FORMAT_MONO_MULAW = 0x10014; ENUMDCL AL_FORMAT_STEREO_MULAW = 0x10015; ENUMDCL AL_FORMAT_QUAD_MULAW = 0x10021; ENUMDCL AL_FORMAT_REAR_MULAW = 0x10022; ENUMDCL AL_FORMAT_51CHN_MULAW = 0x10023; ENUMDCL AL_FORMAT_61CHN_MULAW = 0x10024; ENUMDCL AL_FORMAT_71CHN_MULAW = 0x10025; /*** AL_EXT_IMA4 ***/ ENUMDCL AL_FORMAT_MONO_IMA4 = 0x1300; ENUMDCL AL_FORMAT_STEREO_IMA4 = 0x1301; /*** AL_EXT_STATIC_BUFFER ***/ using PFNALBUFFERDATASTATICPROC = auto (AL_APIENTRY*)(ALuint buffer, ALenum format, ALvoid *data, ALsizei size, ALsizei freq) AL_API_NOEXCEPT -> void; #ifdef AL_ALEXT_PROTOTYPES auto AL_APIENTRY alBufferDataStatic(ALuint buffer, ALenum format, ALvoid *data, ALsizei size, ALsizei freq) AL_API_NOEXCEPT -> void; #endif /*** ALC_EXT_disconnect ***/ ENUMDCL ALC_CONNECTED = 0x313; /*** ALC_EXT_thread_local_context ***/ using PFNALCSETTHREADCONTEXTPROC = auto (ALC_APIENTRY*)(ALCcontext *context) ALC_API_NOEXCEPT -> ALCboolean; using PFNALCGETTHREADCONTEXTPROC = auto (ALC_APIENTRY*)() ALC_API_NOEXCEPT -> ALCcontext*; #ifdef AL_ALEXT_PROTOTYPES ALC_API auto ALC_APIENTRY alcSetThreadContext(ALCcontext *context) ALC_API_NOEXCEPT -> ALCboolean; ALC_API auto ALC_APIENTRY alcGetThreadContext() ALC_API_NOEXCEPT -> ALCcontext*; #endif /*** AL_EXT_source_distance_model ***/ ENUMDCL AL_SOURCE_DISTANCE_MODEL = 0x200; /*** AL_SOFT_buffer_sub_data ***/ ENUMDCL AL_BYTE_RW_OFFSETS_SOFT = 0x1031; ENUMDCL AL_SAMPLE_RW_OFFSETS_SOFT = 0x1032; using PFNALBUFFERSUBDATASOFTPROC = auto (AL_APIENTRY*)(ALuint buffer, ALenum format, const ALvoid *data, ALsizei offset, ALsizei length) AL_API_NOEXCEPT -> void; #ifdef AL_ALEXT_PROTOTYPES AL_API auto AL_APIENTRY alBufferSubDataSOFT(ALuint buffer, ALenum format, const ALvoid *data, ALsizei offset, ALsizei length) AL_API_NOEXCEPT -> void; #endif /*** AL_SOFT_loop_points ***/ ENUMDCL AL_LOOP_POINTS_SOFT = 0x2015; /*** AL_EXT_FOLDBACK ***/ inline constexpr auto AL_EXT_FOLDBACK_NAME = std::to_array("AL_EXT_FOLDBACK"); ENUMDCL AL_FOLDBACK_EVENT_BLOCK = 0x4112; ENUMDCL AL_FOLDBACK_EVENT_START = 0x4111; ENUMDCL AL_FOLDBACK_EVENT_STOP = 0x4113; ENUMDCL AL_FOLDBACK_MODE_MONO = 0x4101; ENUMDCL AL_FOLDBACK_MODE_STEREO = 0x4102; using LPALFOLDBACKCALLBACK = auto (AL_APIENTRY*)(ALenum,ALsizei) AL_API_NOEXCEPT -> void; using LPALREQUESTFOLDBACKSTART = auto (AL_APIENTRY*)(ALenum mode, ALsizei count, ALsizei length, ALfloat *mem, LPALFOLDBACKCALLBACK callback) AL_API_NOEXCEPT -> void; using LPALREQUESTFOLDBACKSTOP = auto (AL_APIENTRY*)() AL_API_NOEXCEPT -> void; #ifdef AL_ALEXT_PROTOTYPES AL_API auto AL_APIENTRY alRequestFoldbackStart(ALenum mode, ALsizei count, ALsizei length, ALfloat *mem, LPALFOLDBACKCALLBACK callback) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alRequestFoldbackStop() AL_API_NOEXCEPT -> void; #endif /*** ALC_EXT_DEDICATED ***/ ENUMDCL AL_DEDICATED_GAIN = 0x0001; ENUMDCL AL_EFFECT_DEDICATED_DIALOGUE = 0x9001; ENUMDCL AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT = 0x9000; /*** AL_SOFT_buffer_samples ***/ /* Channel configurations */ ENUMDCL AL_MONO_SOFT = 0x1500; ENUMDCL AL_STEREO_SOFT = 0x1501; ENUMDCL AL_REAR_SOFT = 0x1502; ENUMDCL AL_QUAD_SOFT = 0x1503; ENUMDCL AL_5POINT1_SOFT = 0x1504; ENUMDCL AL_6POINT1_SOFT = 0x1505; ENUMDCL AL_7POINT1_SOFT = 0x1506; /* Sample types */ ENUMDCL AL_BYTE_SOFT = 0x1400; ENUMDCL AL_UNSIGNED_BYTE_SOFT = 0x1401; ENUMDCL AL_SHORT_SOFT = 0x1402; ENUMDCL AL_UNSIGNED_SHORT_SOFT = 0x1403; ENUMDCL AL_INT_SOFT = 0x1404; ENUMDCL AL_UNSIGNED_INT_SOFT = 0x1405; ENUMDCL AL_FLOAT_SOFT = 0x1406; ENUMDCL AL_DOUBLE_SOFT = 0x1407; ENUMDCL AL_BYTE3_SOFT = 0x1408; ENUMDCL AL_UNSIGNED_BYTE3_SOFT = 0x1409; /* Storage formats */ ENUMDCL AL_MONO8_SOFT = 0x1100; ENUMDCL AL_MONO16_SOFT = 0x1101; ENUMDCL AL_MONO32F_SOFT = 0x10010; ENUMDCL AL_STEREO8_SOFT = 0x1102; ENUMDCL AL_STEREO16_SOFT = 0x1103; ENUMDCL AL_STEREO32F_SOFT = 0x10011; ENUMDCL AL_QUAD8_SOFT = 0x1204; ENUMDCL AL_QUAD16_SOFT = 0x1205; ENUMDCL AL_QUAD32F_SOFT = 0x1206; ENUMDCL AL_REAR8_SOFT = 0x1207; ENUMDCL AL_REAR16_SOFT = 0x1208; ENUMDCL AL_REAR32F_SOFT = 0x1209; ENUMDCL AL_5POINT1_8_SOFT = 0x120A; ENUMDCL AL_5POINT1_16_SOFT = 0x120B; ENUMDCL AL_5POINT1_32F_SOFT = 0x120C; ENUMDCL AL_6POINT1_8_SOFT = 0x120D; ENUMDCL AL_6POINT1_16_SOFT = 0x120E; ENUMDCL AL_6POINT1_32F_SOFT = 0x120F; ENUMDCL AL_7POINT1_8_SOFT = 0x1210; ENUMDCL AL_7POINT1_16_SOFT = 0x1211; ENUMDCL AL_7POINT1_32F_SOFT = 0x1212; /* Buffer attributes */ ENUMDCL AL_INTERNAL_FORMAT_SOFT = 0x2008; ENUMDCL AL_BYTE_LENGTH_SOFT = 0x2009; ENUMDCL AL_SAMPLE_LENGTH_SOFT = 0x200A; ENUMDCL AL_SEC_LENGTH_SOFT = 0x200B; using LPALBUFFERSAMPLESSOFT = auto (AL_APIENTRY*)(ALuint buffer, ALuint samplerate, ALenum internalformat, ALsizei samples, ALenum channels, ALenum type, const ALvoid *data) AL_API_NOEXCEPT -> void; using LPALBUFFERSUBSAMPLESSOFT = auto (AL_APIENTRY*)(ALuint buffer, ALsizei offset, ALsizei samples, ALenum channels, ALenum type, const ALvoid *data) AL_API_NOEXCEPT -> void; using LPALGETBUFFERSAMPLESSOFT = auto (AL_APIENTRY*)(ALuint buffer, ALsizei offset, ALsizei samples, ALenum channels, ALenum type, ALvoid *data) AL_API_NOEXCEPT -> void; using LPALISBUFFERFORMATSUPPORTEDSOFT = auto (AL_APIENTRY*)(ALenum format) AL_API_NOEXCEPT -> ALboolean; #ifdef AL_ALEXT_PROTOTYPES AL_API auto AL_APIENTRY alBufferSamplesSOFT(ALuint buffer, ALuint samplerate, ALenum internalformat, ALsizei samples, ALenum channels, ALenum type, const ALvoid *data) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alBufferSubSamplesSOFT(ALuint buffer, ALsizei offset, ALsizei samples, ALenum channels, ALenum type, const ALvoid *data) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alGetBufferSamplesSOFT(ALuint buffer, ALsizei offset, ALsizei samples, ALenum channels, ALenum type, ALvoid *data) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alIsBufferFormatSupportedSOFT(ALenum format) AL_API_NOEXCEPT -> ALboolean; #endif /*** AL_SOFT_direct_channels ***/ ENUMDCL AL_DIRECT_CHANNELS_SOFT = 0x1033; /*** ALC_SOFT_loopback ***/ ENUMDCL ALC_FORMAT_CHANNELS_SOFT = 0x1990; ENUMDCL ALC_FORMAT_TYPE_SOFT = 0x1991; /* Sample types */ ENUMDCL ALC_BYTE_SOFT = 0x1400; ENUMDCL ALC_UNSIGNED_BYTE_SOFT = 0x1401; ENUMDCL ALC_SHORT_SOFT = 0x1402; ENUMDCL ALC_UNSIGNED_SHORT_SOFT = 0x1403; ENUMDCL ALC_INT_SOFT = 0x1404; ENUMDCL ALC_UNSIGNED_INT_SOFT = 0x1405; ENUMDCL ALC_FLOAT_SOFT = 0x1406; /* Channel configurations */ ENUMDCL ALC_MONO_SOFT = 0x1500; ENUMDCL ALC_STEREO_SOFT = 0x1501; ENUMDCL ALC_QUAD_SOFT = 0x1503; ENUMDCL ALC_5POINT1_SOFT = 0x1504; ENUMDCL ALC_6POINT1_SOFT = 0x1505; ENUMDCL ALC_7POINT1_SOFT = 0x1506; using LPALCLOOPBACKOPENDEVICESOFT = auto (ALC_APIENTRY*)(const ALCchar *deviceName) ALC_API_NOEXCEPT -> ALCdevice*; using LPALCISRENDERFORMATSUPPORTEDSOFT = auto (ALC_APIENTRY*)(ALCdevice *device, ALCsizei freq, ALCenum channels, ALCenum type) ALC_API_NOEXCEPT -> ALCboolean; using LPALCRENDERSAMPLESSOFT = auto (ALC_APIENTRY*)(ALCdevice *device, ALCvoid *buffer, ALCsizei samples) ALC_API_NOEXCEPT -> void; #ifdef AL_ALEXT_PROTOTYPES ALC_API auto ALC_APIENTRY alcLoopbackOpenDeviceSOFT(const ALCchar *deviceName) ALC_API_NOEXCEPT -> ALCdevice*; ALC_API auto ALC_APIENTRY alcIsRenderFormatSupportedSOFT(ALCdevice *device, ALCsizei freq, ALCenum channels, ALCenum type) ALC_API_NOEXCEPT -> ALCboolean; ALC_API auto ALC_APIENTRY alcRenderSamplesSOFT(ALCdevice *device, ALCvoid *buffer, ALCsizei samples) ALC_API_NOEXCEPT -> void; #endif /*** AL_EXT_STEREO_ANGLES ***/ ENUMDCL AL_STEREO_ANGLES = 0x1030; /*** AL_EXT_SOURCE_RADIUS ***/ ENUMDCL AL_SOURCE_RADIUS = 0x1031; /*** AL_SOFT_source_latency ***/ ENUMDCL AL_SAMPLE_OFFSET_LATENCY_SOFT = 0x1200; ENUMDCL AL_SEC_OFFSET_LATENCY_SOFT = 0x1201; using ALint64SOFT = alsoft_impl_int64_t; using ALuint64SOFT = alsoft_impl_uint64_t; using LPALSOURCEDSOFT = auto (AL_APIENTRY*)(ALuint source, ALenum param, ALdouble value) AL_API_NOEXCEPT -> void; using LPALSOURCE3DSOFT = auto (AL_APIENTRY*)(ALuint source, ALenum param, ALdouble value1, ALdouble value2, ALdouble value3) AL_API_NOEXCEPT -> void; using LPALSOURCEDVSOFT = auto (AL_APIENTRY*)(ALuint source, ALenum param, const ALdouble *values) AL_API_NOEXCEPT -> void; using LPALGETSOURCEDSOFT = auto (AL_APIENTRY*)(ALuint source, ALenum param, ALdouble *value) AL_API_NOEXCEPT -> void; using LPALGETSOURCE3DSOFT = auto (AL_APIENTRY*)(ALuint source, ALenum param, ALdouble *value1, ALdouble *value2, ALdouble *value3) AL_API_NOEXCEPT -> void; using LPALGETSOURCEDVSOFT = auto (AL_APIENTRY*)(ALuint source, ALenum param, ALdouble *values) AL_API_NOEXCEPT -> void; using LPALSOURCEI64SOFT = auto (AL_APIENTRY*)(ALuint source, ALenum param, ALint64SOFT value) AL_API_NOEXCEPT -> void; using LPALSOURCE3I64SOFT = auto (AL_APIENTRY*)(ALuint source, ALenum param, ALint64SOFT value1, ALint64SOFT value2, ALint64SOFT value3) AL_API_NOEXCEPT -> void; using LPALSOURCEI64VSOFT = auto (AL_APIENTRY*)(ALuint source, ALenum param, const ALint64SOFT *values) AL_API_NOEXCEPT -> void; using LPALGETSOURCEI64SOFT = auto (AL_APIENTRY*)(ALuint source, ALenum param, ALint64SOFT *value) AL_API_NOEXCEPT -> void; using LPALGETSOURCE3I64SOFT = auto (AL_APIENTRY*)(ALuint source, ALenum param, ALint64SOFT *value1, ALint64SOFT *value2, ALint64SOFT *value3) AL_API_NOEXCEPT -> void; using LPALGETSOURCEI64VSOFT = auto (AL_APIENTRY*)(ALuint source, ALenum param, ALint64SOFT *values) AL_API_NOEXCEPT -> void; #ifdef AL_ALEXT_PROTOTYPES AL_API auto AL_APIENTRY alSourcedSOFT(ALuint source, ALenum param, ALdouble value) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alSource3dSOFT(ALuint source, ALenum param, ALdouble value1, ALdouble value2, ALdouble value3) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alSourcedvSOFT(ALuint source, ALenum param, const ALdouble *values) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alGetSourcedSOFT(ALuint source, ALenum param, ALdouble *value) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alGetSource3dSOFT(ALuint source, ALenum param, ALdouble *value1, ALdouble *value2, ALdouble *value3) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alGetSourcedvSOFT(ALuint source, ALenum param, ALdouble *values) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alSourcei64SOFT(ALuint source, ALenum param, ALint64SOFT value) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alSource3i64SOFT(ALuint source, ALenum param, ALint64SOFT value1, ALint64SOFT value2, ALint64SOFT value3) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alSourcei64vSOFT(ALuint source, ALenum param, const ALint64SOFT *values) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alGetSourcei64SOFT(ALuint source, ALenum param, ALint64SOFT *value) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alGetSource3i64SOFT(ALuint source, ALenum param, ALint64SOFT *value1, ALint64SOFT *value2, ALint64SOFT *value3) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alGetSourcei64vSOFT(ALuint source, ALenum param, ALint64SOFT *values) AL_API_NOEXCEPT -> void; #endif /*** ALC_EXT_DEFAULT_FILTER_ORDER ***/ ENUMDCL ALC_DEFAULT_FILTER_ORDER = 0x1100; /*** AL_SOFT_deferred_updates ***/ ENUMDCL AL_DEFERRED_UPDATES_SOFT = 0xC002; using LPALDEFERUPDATESSOFT = auto (AL_APIENTRY*)() AL_API_NOEXCEPT -> void; using LPALPROCESSUPDATESSOFT = auto (AL_APIENTRY*)() AL_API_NOEXCEPT -> void; #ifdef AL_ALEXT_PROTOTYPES AL_API auto AL_APIENTRY alDeferUpdatesSOFT() AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alProcessUpdatesSOFT() AL_API_NOEXCEPT -> void; #endif /*** AL_SOFT_block_alignment ***/ ENUMDCL AL_UNPACK_BLOCK_ALIGNMENT_SOFT = 0x200C; ENUMDCL AL_PACK_BLOCK_ALIGNMENT_SOFT = 0x200D; /*** AL_SOFT_MSADPCM ***/ ENUMDCL AL_FORMAT_MONO_MSADPCM_SOFT = 0x1302; ENUMDCL AL_FORMAT_STEREO_MSADPCM_SOFT = 0x1303; /*** AL_SOFT_source_length ***/ /*ENUMDCL AL_BYTE_LENGTH_SOFT = 0x2009;*/ /*ENUMDCL AL_SAMPLE_LENGTH_SOFT = 0x200A;*/ /*ENUMDCL AL_SEC_LENGTH_SOFT = 0x200B;*/ /*** AL_SOFT_buffer_length_query ***/ /*ENUMDCL AL_BYTE_LENGTH_SOFT = 0x2009;*/ /*ENUMDCL AL_SAMPLE_LENGTH_SOFT = 0x200A;*/ /*ENUMDCL AL_SEC_LENGTH_SOFT = 0x200B;*/ /*** ALC_SOFT_pause_device ***/ using LPALCDEVICEPAUSESOFT = auto (ALC_APIENTRY*)(ALCdevice *device) ALC_API_NOEXCEPT -> void; using LPALCDEVICERESUMESOFT = auto (ALC_APIENTRY*)(ALCdevice *device) ALC_API_NOEXCEPT -> void; #ifdef AL_ALEXT_PROTOTYPES ALC_API auto ALC_APIENTRY alcDevicePauseSOFT(ALCdevice *device) ALC_API_NOEXCEPT -> void; ALC_API auto ALC_APIENTRY alcDeviceResumeSOFT(ALCdevice *device) ALC_API_NOEXCEPT -> void; #endif /*** AL_EXT_BFORMAT ***/ /* 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). */ ENUMDCL AL_FORMAT_BFORMAT2D_8 = 0x20021; ENUMDCL AL_FORMAT_BFORMAT2D_16 = 0x20022; ENUMDCL AL_FORMAT_BFORMAT2D_FLOAT32 = 0x20023; ENUMDCL AL_FORMAT_BFORMAT3D_8 = 0x20031; ENUMDCL AL_FORMAT_BFORMAT3D_16 = 0x20032; ENUMDCL AL_FORMAT_BFORMAT3D_FLOAT32 = 0x20033; /*** AL_EXT_MULAW_BFORMAT ***/ ENUMDCL AL_FORMAT_BFORMAT2D_MULAW = 0x10031; ENUMDCL AL_FORMAT_BFORMAT3D_MULAW = 0x10032; /*** ALC_SOFT_HRTF ***/ ENUMDCL ALC_HRTF_SOFT = 0x1992; ENUMDCL ALC_DONT_CARE_SOFT = 0x0002; ENUMDCL ALC_HRTF_STATUS_SOFT = 0x1993; ENUMDCL ALC_HRTF_DISABLED_SOFT = 0x0000; ENUMDCL ALC_HRTF_ENABLED_SOFT = 0x0001; ENUMDCL ALC_HRTF_DENIED_SOFT = 0x0002; ENUMDCL ALC_HRTF_REQUIRED_SOFT = 0x0003; ENUMDCL ALC_HRTF_HEADPHONES_DETECTED_SOFT = 0x0004; ENUMDCL ALC_HRTF_UNSUPPORTED_FORMAT_SOFT = 0x0005; ENUMDCL ALC_NUM_HRTF_SPECIFIERS_SOFT = 0x1994; ENUMDCL ALC_HRTF_SPECIFIER_SOFT = 0x1995; ENUMDCL ALC_HRTF_ID_SOFT = 0x1996; using LPALCGETSTRINGISOFT = auto (ALC_APIENTRY*)(ALCdevice *device, ALCenum paramName, ALCsizei index) ALC_API_NOEXCEPT -> const ALCchar*; using LPALCRESETDEVICESOFT = auto (ALC_APIENTRY*)(ALCdevice *device, const ALCint *attribs) ALC_API_NOEXCEPT -> ALCboolean; #ifdef AL_ALEXT_PROTOTYPES ALC_API auto ALC_APIENTRY alcGetStringiSOFT(ALCdevice *device, ALCenum paramName, ALCsizei index) ALC_API_NOEXCEPT -> const ALCchar*; ALC_API auto ALC_APIENTRY alcResetDeviceSOFT(ALCdevice *device, const ALCint *attribs) ALC_API_NOEXCEPT -> ALCboolean; #endif /*** AL_SOFT_gain_clamp_ex ***/ ENUMDCL AL_GAIN_LIMIT_SOFT = 0x200E; /*** AL_SOFT_source_resampler ***/ ENUMDCL AL_NUM_RESAMPLERS_SOFT = 0x1210; ENUMDCL AL_DEFAULT_RESAMPLER_SOFT = 0x1211; ENUMDCL AL_SOURCE_RESAMPLER_SOFT = 0x1212; ENUMDCL AL_RESAMPLER_NAME_SOFT = 0x1213; using LPALGETSTRINGISOFT = auto (AL_APIENTRY*)(ALenum pname, ALsizei index) AL_API_NOEXCEPT -> const ALchar*; #ifdef AL_ALEXT_PROTOTYPES AL_API auto AL_APIENTRY alGetStringiSOFT(ALenum pname, ALsizei index) AL_API_NOEXCEPT -> const ALchar*; #endif /*** AL_SOFT_source_spatialize ***/ ENUMDCL AL_SOURCE_SPATIALIZE_SOFT = 0x1214; ENUMDCL AL_AUTO_SOFT = 0x0002; /*** ALC_SOFT_output_limiter ***/ ENUMDCL ALC_OUTPUT_LIMITER_SOFT = 0x199A; /*** ALC_SOFT_device_clock ***/ using ALCint64SOFT = alsoft_impl_int64_t; using ALCuint64SOFT = alsoft_impl_uint64_t; ENUMDCL ALC_DEVICE_CLOCK_SOFT = 0x1600; ENUMDCL ALC_DEVICE_LATENCY_SOFT = 0x1601; ENUMDCL ALC_DEVICE_CLOCK_LATENCY_SOFT = 0x1602; ENUMDCL AL_SAMPLE_OFFSET_CLOCK_SOFT = 0x1202; ENUMDCL AL_SEC_OFFSET_CLOCK_SOFT = 0x1203; using LPALCGETINTEGER64VSOFT = auto (ALC_APIENTRY*)(ALCdevice *device, ALCenum pname, ALsizei size, ALCint64SOFT *values) ALC_API_NOEXCEPT -> void; #ifdef AL_ALEXT_PROTOTYPES ALC_API auto ALC_APIENTRY alcGetInteger64vSOFT(ALCdevice *device, ALCenum pname, ALsizei size, ALCint64SOFT *values) ALC_API_NOEXCEPT -> void; #endif /*** AL_SOFT_direct_channels_remix ***/ ENUMDCL AL_DROP_UNMATCHED_SOFT = 0x0001; ENUMDCL AL_REMIX_UNMATCHED_SOFT = 0x0002; /*** AL_SOFT_bformat_ex ***/ ENUMDCL AL_AMBISONIC_LAYOUT_SOFT = 0x1997; ENUMDCL AL_AMBISONIC_SCALING_SOFT = 0x1998; /* Ambisonic layouts */ ENUMDCL AL_FUMA_SOFT = 0x0000; ENUMDCL AL_ACN_SOFT = 0x0001; /* Ambisonic scalings (normalization) */ ENUMDCL AL_SN3D_SOFT = 0x0001; ENUMDCL AL_N3D_SOFT = 0x0002; /*** ALC_SOFT_loopback_bformat ***/ ENUMDCL ALC_AMBISONIC_LAYOUT_SOFT = 0x1997; ENUMDCL ALC_AMBISONIC_SCALING_SOFT = 0x1998; ENUMDCL ALC_AMBISONIC_ORDER_SOFT = 0x1999; ENUMDCL ALC_MAX_AMBISONIC_ORDER_SOFT = 0x199B; ENUMDCL ALC_BFORMAT3D_SOFT = 0x1507; /* Ambisonic layouts */ ENUMDCL ALC_FUMA_SOFT = 0x0000; ENUMDCL ALC_ACN_SOFT = 0x0001; /* Ambisonic scalings (normalization) */ ENUMDCL ALC_SN3D_SOFT = 0x0001; ENUMDCL ALC_N3D_SOFT = 0x0002; /*** AL_SOFT_effect_target ***/ ENUMDCL AL_EFFECTSLOT_TARGET_SOFT = 0x199C; /*** AL_SOFT_events ***/ ENUMDCL AL_EVENT_CALLBACK_FUNCTION_SOFT = 0x19A2; ENUMDCL AL_EVENT_CALLBACK_USER_PARAM_SOFT = 0x19A3; ENUMDCL AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT = 0x19A4; ENUMDCL AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT = 0x19A5; ENUMDCL AL_EVENT_TYPE_DISCONNECTED_SOFT = 0x19A6; using ALEVENTPROCSOFT = auto (AL_APIENTRY*)(ALenum eventType, ALuint object, ALuint param, ALsizei length, const ALchar *message, void *userParam) AL_API_NOEXCEPT -> void; using LPALEVENTCONTROLSOFT = auto (AL_APIENTRY*)(ALsizei count, const ALenum *types, ALboolean enable) AL_API_NOEXCEPT -> void; using LPALEVENTCALLBACKSOFT = auto (AL_APIENTRY*)(ALEVENTPROCSOFT callback, void *userParam) AL_API_NOEXCEPT -> void; using LPALGETPOINTERSOFT = auto (AL_APIENTRY*)(ALenum pname) AL_API_NOEXCEPT -> void*; using LPALGETPOINTERVSOFT = auto (AL_APIENTRY*)(ALenum pname, void **values) AL_API_NOEXCEPT -> void; #ifdef AL_ALEXT_PROTOTYPES AL_API auto AL_APIENTRY alEventControlSOFT(ALsizei count, const ALenum *types, ALboolean enable) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alEventCallbackSOFT(ALEVENTPROCSOFT callback, void *userParam) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alGetPointerSOFT(ALenum pname) AL_API_NOEXCEPT -> void*; AL_API auto AL_APIENTRY alGetPointervSOFT(ALenum pname, void **values) AL_API_NOEXCEPT -> void; #endif /*** ALC_SOFT_reopen_device ***/ using LPALCREOPENDEVICESOFT = auto (ALC_APIENTRY*)(ALCdevice *device, const ALCchar *deviceName, const ALCint *attribs) ALC_API_NOEXCEPT -> ALCboolean; #ifdef AL_ALEXT_PROTOTYPES auto ALC_APIENTRY alcReopenDeviceSOFT(ALCdevice *device, const ALCchar *deviceName, const ALCint *attribs) ALC_API_NOEXCEPT -> ALCboolean; #endif /*** AL_SOFT_callback_buffer ***/ ENUMDCL AL_BUFFER_CALLBACK_FUNCTION_SOFT = 0x19A0; ENUMDCL AL_BUFFER_CALLBACK_USER_PARAM_SOFT = 0x19A1; using ALBUFFERCALLBACKTYPESOFT = auto (AL_APIENTRY*)(ALvoid *userptr, ALvoid *sampledata, ALsizei numbytes) AL_API_NOEXCEPT -> ALsizei; using LPALBUFFERCALLBACKSOFT = auto (AL_APIENTRY*)(ALuint buffer, ALenum format, ALsizei freq, ALBUFFERCALLBACKTYPESOFT callback, ALvoid *userptr) AL_API_NOEXCEPT -> void; using LPALGETBUFFERPTRSOFT = auto (AL_APIENTRY*)(ALuint buffer, ALenum param, ALvoid **ptr) AL_API_NOEXCEPT -> void; using LPALGETBUFFER3PTRSOFT = auto (AL_APIENTRY*)(ALuint buffer, ALenum param, ALvoid **ptr0, ALvoid **ptr1, ALvoid **ptr2) AL_API_NOEXCEPT -> void; using LPALGETBUFFERPTRVSOFT = auto (AL_APIENTRY*)(ALuint buffer, ALenum param, ALvoid **ptr) AL_API_NOEXCEPT -> void; #ifdef AL_ALEXT_PROTOTYPES AL_API auto AL_APIENTRY alBufferCallbackSOFT(ALuint buffer, ALenum format, ALsizei freq, ALBUFFERCALLBACKTYPESOFT callback, ALvoid *userptr) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alGetBufferPtrSOFT(ALuint buffer, ALenum param, ALvoid **ptr) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alGetBuffer3PtrSOFT(ALuint buffer, ALenum param, ALvoid **ptr0, ALvoid **ptr1, ALvoid **ptr2) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alGetBufferPtrvSOFT(ALuint buffer, ALenum param, ALvoid **ptr) AL_API_NOEXCEPT -> void; #endif /*** AL_SOFT_UHJ ***/ ENUMDCL AL_FORMAT_UHJ2CHN8_SOFT = 0x19A2; ENUMDCL AL_FORMAT_UHJ2CHN16_SOFT = 0x19A3; ENUMDCL AL_FORMAT_UHJ2CHN_FLOAT32_SOFT = 0x19A4; ENUMDCL AL_FORMAT_UHJ3CHN8_SOFT = 0x19A5; ENUMDCL AL_FORMAT_UHJ3CHN16_SOFT = 0x19A6; ENUMDCL AL_FORMAT_UHJ3CHN_FLOAT32_SOFT = 0x19A7; ENUMDCL AL_FORMAT_UHJ4CHN8_SOFT = 0x19A8; ENUMDCL AL_FORMAT_UHJ4CHN16_SOFT = 0x19A9; ENUMDCL AL_FORMAT_UHJ4CHN_FLOAT32_SOFT = 0x19AA; ENUMDCL AL_STEREO_MODE_SOFT = 0x19B0; ENUMDCL AL_NORMAL_SOFT = 0x0000; ENUMDCL AL_SUPER_STEREO_SOFT = 0x0001; ENUMDCL AL_SUPER_STEREO_WIDTH_SOFT = 0x19B1; /*** AL_SOFT_UHJ_ex ***/ ENUMDCL AL_FORMAT_UHJ2CHN_MULAW_SOFT = 0x19B3; ENUMDCL AL_FORMAT_UHJ2CHN_ALAW_SOFT = 0x19B4; ENUMDCL AL_FORMAT_UHJ2CHN_IMA4_SOFT = 0x19B5; ENUMDCL AL_FORMAT_UHJ2CHN_MSADPCM_SOFT = 0x19B6; ENUMDCL AL_FORMAT_UHJ3CHN_MULAW_SOFT = 0x19B7; ENUMDCL AL_FORMAT_UHJ3CHN_ALAW_SOFT = 0x19B8; ENUMDCL AL_FORMAT_UHJ4CHN_MULAW_SOFT = 0x19B9; ENUMDCL AL_FORMAT_UHJ4CHN_ALAW_SOFT = 0x19BA; /*** ALC_SOFT_output_mode ***/ ENUMDCL ALC_OUTPUT_MODE_SOFT = 0x19AC; ENUMDCL ALC_ANY_SOFT = 0x19AD; /*ENUMDCL ALC_MONO_SOFT = 0x1500;*/ /*ENUMDCL ALC_STEREO_SOFT = 0x1501;*/ ENUMDCL ALC_STEREO_BASIC_SOFT = 0x19AE; ENUMDCL ALC_STEREO_UHJ_SOFT = 0x19AF; ENUMDCL ALC_STEREO_HRTF_SOFT = 0x19B2; /*ENUMDCL ALC_QUAD_SOFT = 0x1503;*/ ENUMDCL ALC_SURROUND_5_1_SOFT = 0x1504; ENUMDCL ALC_SURROUND_6_1_SOFT = 0x1505; ENUMDCL ALC_SURROUND_7_1_SOFT = 0x1506; /*** AL_SOFT_source_start_delay ***/ using LPALSOURCEPLAYATTIMESOFT = auto (AL_APIENTRY*)(ALuint source, ALint64SOFT start_time) AL_API_NOEXCEPT -> void; using LPALSOURCEPLAYATTIMEVSOFT = auto (AL_APIENTRY*)(ALsizei n, const ALuint *sources, ALint64SOFT start_time) AL_API_NOEXCEPT -> void; #ifdef AL_ALEXT_PROTOTYPES auto AL_APIENTRY alSourcePlayAtTimeSOFT(ALuint source, ALint64SOFT start_time) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alSourcePlayAtTimevSOFT(ALsizei n, const ALuint *sources, ALint64SOFT start_time) AL_API_NOEXCEPT -> void; #endif /*** ALC_EXT_debug ***/ ENUMDCL ALC_CONTEXT_FLAGS_EXT = 0x19CF; ENUMDCL ALC_CONTEXT_DEBUG_BIT_EXT = 0x0001; /*** AL_EXT_debug ***/ ENUMDCL AL_DONT_CARE_EXT = 0x0002; ENUMDCL AL_DEBUG_OUTPUT_EXT = 0x19B2; ENUMDCL AL_DEBUG_CALLBACK_FUNCTION_EXT = 0x19B3; ENUMDCL AL_DEBUG_CALLBACK_USER_PARAM_EXT = 0x19B4; ENUMDCL AL_DEBUG_SOURCE_API_EXT = 0x19B5; ENUMDCL AL_DEBUG_SOURCE_AUDIO_SYSTEM_EXT = 0x19B6; ENUMDCL AL_DEBUG_SOURCE_THIRD_PARTY_EXT = 0x19B7; ENUMDCL AL_DEBUG_SOURCE_APPLICATION_EXT = 0x19B8; ENUMDCL AL_DEBUG_SOURCE_OTHER_EXT = 0x19B9; ENUMDCL AL_DEBUG_TYPE_ERROR_EXT = 0x19BA; ENUMDCL AL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_EXT = 0x19BB; ENUMDCL AL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_EXT = 0x19BC; ENUMDCL AL_DEBUG_TYPE_PORTABILITY_EXT = 0x19BD; ENUMDCL AL_DEBUG_TYPE_PERFORMANCE_EXT = 0x19BE; ENUMDCL AL_DEBUG_TYPE_MARKER_EXT = 0x19BF; ENUMDCL AL_DEBUG_TYPE_PUSH_GROUP_EXT = 0x19C0; ENUMDCL AL_DEBUG_TYPE_POP_GROUP_EXT = 0x19C1; ENUMDCL AL_DEBUG_TYPE_OTHER_EXT = 0x19C2; ENUMDCL AL_DEBUG_SEVERITY_HIGH_EXT = 0x19C3; ENUMDCL AL_DEBUG_SEVERITY_MEDIUM_EXT = 0x19C4; ENUMDCL AL_DEBUG_SEVERITY_LOW_EXT = 0x19C5; ENUMDCL AL_DEBUG_SEVERITY_NOTIFICATION_EXT = 0x19C6; ENUMDCL AL_DEBUG_LOGGED_MESSAGES_EXT = 0x19C7; ENUMDCL AL_DEBUG_NEXT_LOGGED_MESSAGE_LENGTH_EXT = 0x19C8; ENUMDCL AL_MAX_DEBUG_MESSAGE_LENGTH_EXT = 0x19C9; ENUMDCL AL_MAX_DEBUG_LOGGED_MESSAGES_EXT = 0x19CA; ENUMDCL AL_MAX_DEBUG_GROUP_STACK_DEPTH_EXT = 0x19CB; ENUMDCL AL_MAX_LABEL_LENGTH_EXT = 0x19CC; ENUMDCL AL_STACK_OVERFLOW_EXT = 0x19CD; ENUMDCL AL_STACK_UNDERFLOW_EXT = 0x19CE; ENUMDCL AL_CONTEXT_FLAGS_EXT = 0x19CF; ENUMDCL AL_BUFFER_EXT = 0x1009; ENUMDCL AL_SOURCE_EXT = 0x19D0; ENUMDCL AL_FILTER_EXT = 0x19D1; ENUMDCL AL_EFFECT_EXT = 0x19D2; ENUMDCL AL_AUXILIARY_EFFECT_SLOT_EXT = 0x19D3; using ALDEBUGPROCEXT = auto (AL_APIENTRY*)(ALenum source, ALenum type, ALuint id, ALenum severity, ALsizei length, const ALchar *message, void *userParam) AL_API_NOEXCEPT -> void; using LPALDEBUGMESSAGECALLBACKEXT = auto (AL_APIENTRY*)(ALDEBUGPROCEXT callback, void *userParam) AL_API_NOEXCEPT -> void; using LPALDEBUGMESSAGEINSERTEXT = auto (AL_APIENTRY*)(ALenum source, ALenum type, ALuint id, ALenum severity, ALsizei length, const ALchar *message) AL_API_NOEXCEPT -> void; using LPALDEBUGMESSAGECONTROLEXT = auto (AL_APIENTRY*)(ALenum source, ALenum type, ALenum severity, ALsizei count, const ALuint *ids, ALboolean enable) AL_API_NOEXCEPT -> void; using LPALPUSHDEBUGGROUPEXT = auto (AL_APIENTRY*)(ALenum source, ALuint id, ALsizei length, const ALchar *message) AL_API_NOEXCEPT -> void; using LPALPOPDEBUGGROUPEXT = auto (AL_APIENTRY*)() AL_API_NOEXCEPT -> void; using LPALGETDEBUGMESSAGELOGEXT = auto (AL_APIENTRY*)(ALuint count, ALsizei logBufSize, ALenum *sources, ALenum *types, ALuint *ids, ALenum *severities, ALsizei *lengths, ALchar *logBuf) AL_API_NOEXCEPT -> ALuint; using LPALOBJECTLABELEXT = auto (AL_APIENTRY*)(ALenum identifier, ALuint name, ALsizei length, const ALchar *label) AL_API_NOEXCEPT -> void; using LPALGETOBJECTLABELEXT = auto (AL_APIENTRY*)(ALenum identifier, ALuint name, ALsizei bufSize, ALsizei *length, ALchar *label) AL_API_NOEXCEPT -> void; using LPALGETPOINTEREXT = auto (AL_APIENTRY*)(ALenum pname) AL_API_NOEXCEPT -> void*; using LPALGETPOINTERVEXT = auto (AL_APIENTRY*)(ALenum pname, void **values) AL_API_NOEXCEPT -> void; #ifdef AL_ALEXT_PROTOTYPES auto AL_APIENTRY alDebugMessageCallbackEXT(ALDEBUGPROCEXT callback, void *userParam) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alDebugMessageInsertEXT(ALenum source, ALenum type, ALuint id, ALenum severity, ALsizei length, const ALchar *message) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alDebugMessageControlEXT(ALenum source, ALenum type, ALenum severity, ALsizei count, const ALuint *ids, ALboolean enable) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alPushDebugGroupEXT(ALenum source, ALuint id, ALsizei length, const ALchar *message) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alPopDebugGroupEXT() AL_API_NOEXCEPT -> void; auto AL_APIENTRY alGetDebugMessageLogEXT(ALuint count, ALsizei logBufSize, ALenum *sources, ALenum *types, ALuint *ids, ALenum *severities, ALsizei *lengths, ALchar *logBuf) AL_API_NOEXCEPT -> ALuint; auto AL_APIENTRY alObjectLabelEXT(ALenum identifier, ALuint name, ALsizei length, const ALchar *label) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alGetObjectLabelEXT(ALenum identifier, ALuint name, ALsizei bufSize, ALsizei *length, ALchar *label) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alGetPointerEXT(ALenum pname) AL_API_NOEXCEPT -> void*; auto AL_APIENTRY alGetPointervEXT(ALenum pname, void **values) AL_API_NOEXCEPT -> void; #endif /*** ALC_SOFT_system_events ***/ ENUMDCL ALC_PLAYBACK_DEVICE_SOFT = 0x19D4; ENUMDCL ALC_CAPTURE_DEVICE_SOFT = 0x19D5; ENUMDCL ALC_EVENT_TYPE_DEFAULT_DEVICE_CHANGED_SOFT = 0x19D6; ENUMDCL ALC_EVENT_TYPE_DEVICE_ADDED_SOFT = 0x19D7; ENUMDCL ALC_EVENT_TYPE_DEVICE_REMOVED_SOFT = 0x19D8; ENUMDCL ALC_EVENT_SUPPORTED_SOFT = 0x19D9; ENUMDCL ALC_EVENT_NOT_SUPPORTED_SOFT = 0x19DA; using ALCEVENTPROCTYPESOFT = auto (ALC_APIENTRY*)(ALCenum eventType, ALCenum deviceType, ALCdevice *device, ALCsizei length, const ALCchar *message, void *userParam) ALC_API_NOEXCEPT -> void; using LPALCEVENTISSUPPORTEDSOFT = auto (ALC_APIENTRY*)(ALCenum eventType, ALCenum deviceType) ALC_API_NOEXCEPT -> ALCenum; using LPALCEVENTCONTROLSOFT = auto (ALC_APIENTRY*)(ALCsizei count, const ALCenum *events, ALCboolean enable) ALC_API_NOEXCEPT -> ALCboolean; using LPALCEVENTCALLBACKSOFT = auto (ALC_APIENTRY*)(ALCEVENTPROCTYPESOFT callback, void *userParam) ALC_API_NOEXCEPT -> void; #ifdef AL_ALEXT_PROTOTYPES auto ALC_APIENTRY alcEventIsSupportedSOFT(ALCenum eventType, ALCenum deviceType) ALC_API_NOEXCEPT -> ALCenum; auto ALC_APIENTRY alcEventControlSOFT(ALCsizei count, const ALCenum *events, ALCboolean enable) ALC_API_NOEXCEPT -> ALCboolean; auto ALC_APIENTRY alcEventCallbackSOFT(ALCEVENTPROCTYPESOFT callback, void *userParam) ALC_API_NOEXCEPT -> void; #endif /*** AL_EXT_direct_context ***/ using LPALCGETPROCADDRESS2 = auto (ALC_APIENTRY*)(ALCdevice *device, const ALCchar *funcName) ALC_API_NOEXCEPT -> ALCvoid*; using LPALENABLEDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALenum capability) AL_API_NOEXCEPT -> void; using LPALDISABLEDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALenum capability) AL_API_NOEXCEPT -> void; using LPALISENABLEDDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALenum capability) AL_API_NOEXCEPT -> ALboolean; using LPALDOPPLERFACTORDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALfloat value) AL_API_NOEXCEPT -> void; using LPALSPEEDOFSOUNDDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALfloat value) AL_API_NOEXCEPT -> void; using LPALDISTANCEMODELDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALenum distanceModel) AL_API_NOEXCEPT -> void; using LPALGETSTRINGDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALenum param) AL_API_NOEXCEPT -> const ALchar*; using LPALGETBOOLEANVDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALenum param, ALboolean *values) AL_API_NOEXCEPT -> void; using LPALGETINTEGERVDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALenum param, ALint *values) AL_API_NOEXCEPT -> void; using LPALGETFLOATVDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALenum param, ALfloat *values) AL_API_NOEXCEPT -> void; using LPALGETDOUBLEVDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALenum param, ALdouble *values) AL_API_NOEXCEPT -> void; using LPALGETBOOLEANDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALenum param) AL_API_NOEXCEPT -> ALboolean; using LPALGETINTEGERDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALenum param) AL_API_NOEXCEPT -> ALint; using LPALGETFLOATDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALenum param) AL_API_NOEXCEPT -> ALfloat; using LPALGETDOUBLEDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALenum param) AL_API_NOEXCEPT -> ALdouble; using LPALGETERRORDIRECT = auto (AL_APIENTRY*)(ALCcontext *context) AL_API_NOEXCEPT -> ALenum; using LPALISEXTENSIONPRESENTDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, const ALchar *extname) AL_API_NOEXCEPT -> ALboolean; using LPALGETPROCADDRESSDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, const ALchar *fname) AL_API_NOEXCEPT -> void*; using LPALGETENUMVALUEDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, const ALchar *ename) AL_API_NOEXCEPT -> ALenum; using LPALLISTENERFDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALenum param, ALfloat value) AL_API_NOEXCEPT -> void; using LPALLISTENER3FDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3) AL_API_NOEXCEPT -> void; using LPALLISTENERFVDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALenum param, const ALfloat *values) AL_API_NOEXCEPT -> void; using LPALLISTENERIDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALenum param, ALint value) AL_API_NOEXCEPT -> void; using LPALLISTENER3IDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALenum param, ALint value1, ALint value2, ALint value3) AL_API_NOEXCEPT -> void; using LPALLISTENERIVDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALenum param, const ALint *values) AL_API_NOEXCEPT -> void; using LPALGETLISTENERFDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALenum param, ALfloat *value) AL_API_NOEXCEPT -> void; using LPALGETLISTENER3FDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3) AL_API_NOEXCEPT -> void; using LPALGETLISTENERFVDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALenum param, ALfloat *values) AL_API_NOEXCEPT -> void; using LPALGETLISTENERIDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALenum param, ALint *value) AL_API_NOEXCEPT -> void; using LPALGETLISTENER3IDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALenum param, ALint *value1, ALint *value2, ALint *value3) AL_API_NOEXCEPT -> void; using LPALGETLISTENERIVDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALenum param, ALint *values) AL_API_NOEXCEPT -> void; using LPALGENSOURCESDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALsizei n, ALuint *sources) AL_API_NOEXCEPT -> void; using LPALDELETESOURCESDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALsizei n, const ALuint *sources) AL_API_NOEXCEPT -> void; using LPALISSOURCEDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint source) AL_API_NOEXCEPT -> ALboolean; using LPALSOURCEFDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint source, ALenum param, ALfloat value) AL_API_NOEXCEPT -> void; using LPALSOURCE3FDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint source, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3) AL_API_NOEXCEPT -> void; using LPALSOURCEFVDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint source, ALenum param, const ALfloat *values) AL_API_NOEXCEPT -> void; using LPALSOURCEIDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint source, ALenum param, ALint value) AL_API_NOEXCEPT -> void; using LPALSOURCE3IDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint source, ALenum param, ALint value1, ALint value2, ALint value3) AL_API_NOEXCEPT -> void; using LPALSOURCEIVDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint source, ALenum param, const ALint *values) AL_API_NOEXCEPT -> void; using LPALGETSOURCEFDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint source, ALenum param, ALfloat *value) AL_API_NOEXCEPT -> void; using LPALGETSOURCE3FDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint source, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3) AL_API_NOEXCEPT -> void; using LPALGETSOURCEFVDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint source, ALenum param, ALfloat *values) AL_API_NOEXCEPT -> void; using LPALGETSOURCEIDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint source, ALenum param, ALint *value) AL_API_NOEXCEPT -> void; using LPALGETSOURCE3IDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint source, ALenum param, ALint *value1, ALint *value2, ALint *value3) AL_API_NOEXCEPT -> void; using LPALGETSOURCEIVDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint source, ALenum param, ALint *values) AL_API_NOEXCEPT -> void; using LPALSOURCEPLAYDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint source) AL_API_NOEXCEPT -> void; using LPALSOURCESTOPDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint source) AL_API_NOEXCEPT -> void; using LPALSOURCEREWINDDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint source) AL_API_NOEXCEPT -> void; using LPALSOURCEPAUSEDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint source) AL_API_NOEXCEPT -> void; using LPALSOURCEPLAYVDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALsizei n, const ALuint *sources) AL_API_NOEXCEPT -> void; using LPALSOURCESTOPVDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALsizei n, const ALuint *sources) AL_API_NOEXCEPT -> void; using LPALSOURCEREWINDVDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALsizei n, const ALuint *sources) AL_API_NOEXCEPT -> void; using LPALSOURCEPAUSEVDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALsizei n, const ALuint *sources) AL_API_NOEXCEPT -> void; using LPALSOURCEQUEUEBUFFERSDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint source, ALsizei nb, const ALuint *buffers) AL_API_NOEXCEPT -> void; using LPALSOURCEUNQUEUEBUFFERSDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint source, ALsizei nb, ALuint *buffers) AL_API_NOEXCEPT -> void; using LPALGENBUFFERSDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALsizei n, ALuint *buffers) AL_API_NOEXCEPT -> void; using LPALDELETEBUFFERSDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALsizei n, const ALuint *buffers) AL_API_NOEXCEPT -> void; using LPALISBUFFERDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint buffer) AL_API_NOEXCEPT -> ALboolean; using LPALBUFFERDATADIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint buffer, ALenum format, const ALvoid *data, ALsizei size, ALsizei samplerate) AL_API_NOEXCEPT -> void; using LPALBUFFERFDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint buffer, ALenum param, ALfloat value) AL_API_NOEXCEPT -> void; using LPALBUFFER3FDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint buffer, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3) AL_API_NOEXCEPT -> void; using LPALBUFFERFVDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint buffer, ALenum param, const ALfloat *values) AL_API_NOEXCEPT -> void; using LPALBUFFERIDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint buffer, ALenum param, ALint value) AL_API_NOEXCEPT -> void; using LPALBUFFER3IDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint buffer, ALenum param, ALint value1, ALint value2, ALint value3) AL_API_NOEXCEPT -> void; using LPALBUFFERIVDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint buffer, ALenum param, const ALint *values) AL_API_NOEXCEPT -> void; using LPALGETBUFFERFDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint buffer, ALenum param, ALfloat *value) AL_API_NOEXCEPT -> void; using LPALGETBUFFER3FDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint buffer, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3) AL_API_NOEXCEPT -> void; using LPALGETBUFFERFVDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint buffer, ALenum param, ALfloat *values) AL_API_NOEXCEPT -> void; using LPALGETBUFFERIDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint buffer, ALenum param, ALint *value) AL_API_NOEXCEPT -> void; using LPALGETBUFFER3IDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint buffer, ALenum param, ALint *value1, ALint *value2, ALint *value3) AL_API_NOEXCEPT -> void; using LPALGETBUFFERIVDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint buffer, ALenum param, ALint *values) AL_API_NOEXCEPT -> void; using LPALGENEFFECTSDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALsizei n, ALuint *effects) AL_API_NOEXCEPT -> void; using LPALDELETEEFFECTSDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALsizei n, const ALuint *effects) AL_API_NOEXCEPT -> void; using LPALISEFFECTDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint effect) AL_API_NOEXCEPT -> ALboolean; using LPALEFFECTIDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint effect, ALenum param, ALint iValue) AL_API_NOEXCEPT -> void; using LPALEFFECTIVDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint effect, ALenum param, const ALint *piValues) AL_API_NOEXCEPT -> void; using LPALEFFECTFDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint effect, ALenum param, ALfloat flValue) AL_API_NOEXCEPT -> void; using LPALEFFECTFVDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint effect, ALenum param, const ALfloat *pflValues) AL_API_NOEXCEPT -> void; using LPALGETEFFECTIDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint effect, ALenum param, ALint *piValue) AL_API_NOEXCEPT -> void; using LPALGETEFFECTIVDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint effect, ALenum param, ALint *piValues) AL_API_NOEXCEPT -> void; using LPALGETEFFECTFDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint effect, ALenum param, ALfloat *pflValue) AL_API_NOEXCEPT -> void; using LPALGETEFFECTFVDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint effect, ALenum param, ALfloat *pflValues) AL_API_NOEXCEPT -> void; using LPALGENFILTERSDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALsizei n, ALuint *filters) AL_API_NOEXCEPT -> void; using LPALDELETEFILTERSDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALsizei n, const ALuint *filters) AL_API_NOEXCEPT -> void; using LPALISFILTERDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint filter) AL_API_NOEXCEPT -> ALboolean; using LPALFILTERIDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint filter, ALenum param, ALint iValue) AL_API_NOEXCEPT -> void; using LPALFILTERIVDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint filter, ALenum param, const ALint *piValues) AL_API_NOEXCEPT -> void; using LPALFILTERFDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint filter, ALenum param, ALfloat flValue) AL_API_NOEXCEPT -> void; using LPALFILTERFVDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint filter, ALenum param, const ALfloat *pflValues) AL_API_NOEXCEPT -> void; using LPALGETFILTERIDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint filter, ALenum param, ALint *piValue) AL_API_NOEXCEPT -> void; using LPALGETFILTERIVDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint filter, ALenum param, ALint *piValues) AL_API_NOEXCEPT -> void; using LPALGETFILTERFDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint filter, ALenum param, ALfloat *pflValue) AL_API_NOEXCEPT -> void; using LPALGETFILTERFVDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint filter, ALenum param, ALfloat *pflValues) AL_API_NOEXCEPT -> void; using LPALGENAUXILIARYEFFECTSLOTSDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALsizei n, ALuint *effectslots) AL_API_NOEXCEPT -> void; using LPALDELETEAUXILIARYEFFECTSLOTSDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALsizei n, const ALuint *effectslots) AL_API_NOEXCEPT -> void; using LPALISAUXILIARYEFFECTSLOTDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint effectslot) AL_API_NOEXCEPT -> ALboolean; using LPALAUXILIARYEFFECTSLOTIDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint effectslot, ALenum param, ALint iValue) AL_API_NOEXCEPT -> void; using LPALAUXILIARYEFFECTSLOTIVDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint effectslot, ALenum param, const ALint *piValues) AL_API_NOEXCEPT -> void; using LPALAUXILIARYEFFECTSLOTFDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint effectslot, ALenum param, ALfloat flValue) AL_API_NOEXCEPT -> void; using LPALAUXILIARYEFFECTSLOTFVDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint effectslot, ALenum param, const ALfloat *pflValues) AL_API_NOEXCEPT -> void; using LPALGETAUXILIARYEFFECTSLOTIDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint effectslot, ALenum param, ALint *piValue) AL_API_NOEXCEPT -> void; using LPALGETAUXILIARYEFFECTSLOTIVDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint effectslot, ALenum param, ALint *piValues) AL_API_NOEXCEPT -> void; using LPALGETAUXILIARYEFFECTSLOTFDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint effectslot, ALenum param, ALfloat *pflValue) AL_API_NOEXCEPT -> void; using LPALGETAUXILIARYEFFECTSLOTFVDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint effectslot, ALenum param, ALfloat *pflValues) AL_API_NOEXCEPT -> void; using LPALBUFFERDATASTATICDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint buffer, ALenum format, ALvoid *data, ALsizei size, ALsizei freq) AL_API_NOEXCEPT -> void; using LPALDEBUGMESSAGECALLBACKDIRECTEXT = auto (AL_APIENTRY*)(ALCcontext *context, ALDEBUGPROCEXT callback, void *userParam) AL_API_NOEXCEPT -> void; using LPALDEBUGMESSAGEINSERTDIRECTEXT = auto (AL_APIENTRY*)(ALCcontext *context, ALenum source, ALenum type, ALuint id, ALenum severity, ALsizei length, const ALchar *message) AL_API_NOEXCEPT -> void; using LPALDEBUGMESSAGECONTROLDIRECTEXT = auto (AL_APIENTRY*)(ALCcontext *context, ALenum source, ALenum type, ALenum severity, ALsizei count, const ALuint *ids, ALboolean enable) AL_API_NOEXCEPT -> void; using LPALPUSHDEBUGGROUPDIRECTEXT = auto (AL_APIENTRY*)(ALCcontext *context, ALenum source, ALuint id, ALsizei length, const ALchar *message) AL_API_NOEXCEPT -> void; using LPALPOPDEBUGGROUPDIRECTEXT = auto (AL_APIENTRY*)(ALCcontext *context) AL_API_NOEXCEPT -> void; using LPALGETDEBUGMESSAGELOGDIRECTEXT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint count, ALsizei logBufSize, ALenum *sources, ALenum *types, ALuint *ids, ALenum *severities, ALsizei *lengths, ALchar *logBuf) AL_API_NOEXCEPT -> ALuint; using LPALOBJECTLABELDIRECTEXT = auto (AL_APIENTRY*)(ALCcontext *context, ALenum identifier, ALuint name, ALsizei length, const ALchar *label) AL_API_NOEXCEPT -> void; using LPALGETOBJECTLABELDIRECTEXT = auto (AL_APIENTRY*)(ALCcontext *context, ALenum identifier, ALuint name, ALsizei bufSize, ALsizei *length, ALchar *label) AL_API_NOEXCEPT -> void; using LPALGETPOINTERDIRECTEXT = auto (AL_APIENTRY*)(ALCcontext *context, ALenum pname) AL_API_NOEXCEPT -> void*; using LPALGETPOINTERVDIRECTEXT = auto (AL_APIENTRY*)(ALCcontext *context, ALenum pname, void **values) AL_API_NOEXCEPT -> void; using LPALREQUESTFOLDBACKSTARTDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALenum mode, ALsizei count, ALsizei length, ALfloat *mem, LPALFOLDBACKCALLBACK callback) AL_API_NOEXCEPT -> void; using LPALREQUESTFOLDBACKSTOPDIRECT = auto (AL_APIENTRY*)(ALCcontext *context) AL_API_NOEXCEPT -> void; using LPALBUFFERSUBDATADIRECTSOFT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint buffer, ALenum format, const ALvoid *data, ALsizei offset, ALsizei length) AL_API_NOEXCEPT -> void; using LPALSOURCEDDIRECTSOFT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint source, ALenum param, ALdouble value) AL_API_NOEXCEPT -> void; using LPALSOURCE3DDIRECTSOFT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint source, ALenum param, ALdouble value1, ALdouble value2, ALdouble value3) AL_API_NOEXCEPT -> void; using LPALSOURCEDVDIRECTSOFT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint source, ALenum param, const ALdouble *values) AL_API_NOEXCEPT -> void; using LPALGETSOURCEDDIRECTSOFT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint source, ALenum param, ALdouble *value) AL_API_NOEXCEPT -> void; using LPALGETSOURCE3DDIRECTSOFT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint source, ALenum param, ALdouble *value1, ALdouble *value2, ALdouble *value3) AL_API_NOEXCEPT -> void; using LPALGETSOURCEDVDIRECTSOFT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint source, ALenum param, ALdouble *values) AL_API_NOEXCEPT -> void; using LPALSOURCEI64DIRECTSOFT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint source, ALenum param, ALint64SOFT value) AL_API_NOEXCEPT -> void; using LPALSOURCE3I64DIRECTSOFT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint source, ALenum param, ALint64SOFT value1, ALint64SOFT value2, ALint64SOFT value3) AL_API_NOEXCEPT -> void; using LPALSOURCEI64VDIRECTSOFT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint source, ALenum param, const ALint64SOFT *values) AL_API_NOEXCEPT -> void; using LPALGETSOURCEI64DIRECTSOFT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint source, ALenum param, ALint64SOFT *value) AL_API_NOEXCEPT -> void; using LPALGETSOURCE3I64DIRECTSOFT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint source, ALenum param, ALint64SOFT *value1, ALint64SOFT *value2, ALint64SOFT *value3) AL_API_NOEXCEPT -> void; using LPALGETSOURCEI64VDIRECTSOFT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint source, ALenum param, ALint64SOFT *values) AL_API_NOEXCEPT -> void; using LPALDEFERUPDATESDIRECTSOFT = auto (AL_APIENTRY*)(ALCcontext *context) AL_API_NOEXCEPT -> void; using LPALPROCESSUPDATESDIRECTSOFT = auto (AL_APIENTRY*)(ALCcontext *context) AL_API_NOEXCEPT -> void; using LPALGETSTRINGIDIRECTSOFT = auto (AL_APIENTRY*)(ALCcontext *context, ALenum pname, ALsizei index) AL_API_NOEXCEPT -> const ALchar*; using LPALEVENTCONTROLDIRECTSOFT = auto (AL_APIENTRY*)(ALCcontext *context, ALsizei count, const ALenum *types, ALboolean enable) AL_API_NOEXCEPT -> void; using LPALEVENTCALLBACKDIRECTSOFT = auto (AL_APIENTRY*)(ALCcontext *context, ALEVENTPROCSOFT callback, void *userParam) AL_API_NOEXCEPT -> void; using LPALGETPOINTERDIRECTSOFT = auto (AL_APIENTRY*)(ALCcontext *context, ALenum pname) AL_API_NOEXCEPT -> void*; using LPALGETPOINTERVDIRECTSOFT = auto (AL_APIENTRY*)(ALCcontext *context, ALenum pname, void **values) AL_API_NOEXCEPT -> void; using LPALBUFFERCALLBACKDIRECTSOFT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint buffer, ALenum format, ALsizei freq, ALBUFFERCALLBACKTYPESOFT callback, ALvoid *userptr) AL_API_NOEXCEPT -> void; using LPALGETBUFFERPTRDIRECTSOFT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint buffer, ALenum param, ALvoid **ptr) AL_API_NOEXCEPT -> void; using LPALGETBUFFER3PTRDIRECTSOFT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint buffer, ALenum param, ALvoid **ptr0, ALvoid **ptr1, ALvoid **ptr2) AL_API_NOEXCEPT -> void; using LPALGETBUFFERPTRVDIRECTSOFT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint buffer, ALenum param, ALvoid **ptr) AL_API_NOEXCEPT -> void; using LPALSOURCEPLAYATTIMEDIRECTSOFT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint source, ALint64SOFT start_time) AL_API_NOEXCEPT -> void; using LPALSOURCEPLAYATTIMEVDIRECTSOFT = auto (AL_APIENTRY*)(ALCcontext *context, ALsizei n, const ALuint *sources, ALint64SOFT start_time) AL_API_NOEXCEPT -> void; using LPEAXSETDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, const struct _GUID *property_set_id, ALuint property_id, ALuint source_id, ALvoid *value, ALuint value_size) AL_API_NOEXCEPT -> ALenum; using LPEAXGETDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, const struct _GUID *property_set_id, ALuint property_id, ALuint source_id, ALvoid *value, ALuint value_size) AL_API_NOEXCEPT -> ALenum; using LPEAXSETBUFFERMODEDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALsizei n, const ALuint *buffers, ALint value) AL_API_NOEXCEPT -> ALboolean; using LPEAXGETBUFFERMODEDIRECT = auto (AL_APIENTRY*)(ALCcontext *context, ALuint buffer, ALint *pReserved) AL_API_NOEXCEPT -> ALenum; #ifdef AL_ALEXT_PROTOTYPES auto ALC_APIENTRY alcGetProcAddress2(ALCdevice *device, const ALCchar *funcName) ALC_API_NOEXCEPT -> ALCvoid*; auto AL_APIENTRY alEnableDirect(ALCcontext *context, ALenum capability) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alDisableDirect(ALCcontext *context, ALenum capability) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alIsEnabledDirect(ALCcontext *context, ALenum capability) AL_API_NOEXCEPT -> ALboolean; auto AL_APIENTRY alDopplerFactorDirect(ALCcontext *context, ALfloat value) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alSpeedOfSoundDirect(ALCcontext *context, ALfloat value) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alDistanceModelDirect(ALCcontext *context, ALenum distanceModel) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alGetStringDirect(ALCcontext *context, ALenum param) AL_API_NOEXCEPT -> const ALchar*; auto AL_APIENTRY alGetBooleanvDirect(ALCcontext *context, ALenum param, ALboolean *values) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alGetIntegervDirect(ALCcontext *context, ALenum param, ALint *values) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alGetFloatvDirect(ALCcontext *context, ALenum param, ALfloat *values) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alGetDoublevDirect(ALCcontext *context, ALenum param, ALdouble *values) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alGetBooleanDirect(ALCcontext *context, ALenum param) AL_API_NOEXCEPT -> ALboolean; auto AL_APIENTRY alGetIntegerDirect(ALCcontext *context, ALenum param) AL_API_NOEXCEPT -> ALint; auto AL_APIENTRY alGetFloatDirect(ALCcontext *context, ALenum param) AL_API_NOEXCEPT -> ALfloat; auto AL_APIENTRY alGetDoubleDirect(ALCcontext *context, ALenum param) AL_API_NOEXCEPT -> ALdouble; auto AL_APIENTRY alGetErrorDirect(ALCcontext *context) AL_API_NOEXCEPT -> ALenum; auto AL_APIENTRY alIsExtensionPresentDirect(ALCcontext *context, const ALchar *extname) AL_API_NOEXCEPT -> ALboolean; auto AL_APIENTRY alGetProcAddressDirect(ALCcontext *context, const ALchar *fname) AL_API_NOEXCEPT -> void*; auto AL_APIENTRY alGetEnumValueDirect(ALCcontext *context, const ALchar *ename) AL_API_NOEXCEPT -> ALenum; auto AL_APIENTRY alListenerfDirect(ALCcontext *context, ALenum param, ALfloat value) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alListener3fDirect(ALCcontext *context, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alListenerfvDirect(ALCcontext *context, ALenum param, const ALfloat *values) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alListeneriDirect(ALCcontext *context, ALenum param, ALint value) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alListener3iDirect(ALCcontext *context, ALenum param, ALint value1, ALint value2, ALint value3) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alListenerivDirect(ALCcontext *context, ALenum param, const ALint *values) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alGetListenerfDirect(ALCcontext *context, ALenum param, ALfloat *value) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alGetListener3fDirect(ALCcontext *context, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alGetListenerfvDirect(ALCcontext *context, ALenum param, ALfloat *values) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alGetListeneriDirect(ALCcontext *context, ALenum param, ALint *value) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alGetListener3iDirect(ALCcontext *context, ALenum param, ALint *value1, ALint *value2, ALint *value3) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alGetListenerivDirect(ALCcontext *context, ALenum param, ALint *values) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alGenSourcesDirect(ALCcontext *context, ALsizei n, ALuint *sources) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alDeleteSourcesDirect(ALCcontext *context, ALsizei n, const ALuint *sources) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alIsSourceDirect(ALCcontext *context, ALuint source) AL_API_NOEXCEPT -> ALboolean; auto AL_APIENTRY alSourcefDirect(ALCcontext *context, ALuint source, ALenum param, ALfloat value) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alSource3fDirect(ALCcontext *context, ALuint source, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alSourcefvDirect(ALCcontext *context, ALuint source, ALenum param, const ALfloat *values) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alSourceiDirect(ALCcontext *context, ALuint source, ALenum param, ALint value) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alSource3iDirect(ALCcontext *context, ALuint source, ALenum param, ALint value1, ALint value2, ALint value3) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alSourceivDirect(ALCcontext *context, ALuint source, ALenum param, const ALint *values) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alGetSourcefDirect(ALCcontext *context, ALuint source, ALenum param, ALfloat *value) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alGetSource3fDirect(ALCcontext *context, ALuint source, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alGetSourcefvDirect(ALCcontext *context, ALuint source, ALenum param, ALfloat *values) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alGetSourceiDirect(ALCcontext *context, ALuint source, ALenum param, ALint *value) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alGetSource3iDirect(ALCcontext *context, ALuint source, ALenum param, ALint *value1, ALint *value2, ALint *value3) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alGetSourceivDirect(ALCcontext *context, ALuint source, ALenum param, ALint *values) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alSourcePlayDirect(ALCcontext *context, ALuint source) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alSourceStopDirect(ALCcontext *context, ALuint source) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alSourceRewindDirect(ALCcontext *context, ALuint source) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alSourcePauseDirect(ALCcontext *context, ALuint source) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alSourcePlayvDirect(ALCcontext *context, ALsizei n, const ALuint *sources) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alSourceStopvDirect(ALCcontext *context, ALsizei n, const ALuint *sources) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alSourceRewindvDirect(ALCcontext *context, ALsizei n, const ALuint *sources) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alSourcePausevDirect(ALCcontext *context, ALsizei n, const ALuint *sources) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alSourceQueueBuffersDirect(ALCcontext *context, ALuint source, ALsizei nb, const ALuint *buffers) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alSourceUnqueueBuffersDirect(ALCcontext *context, ALuint source, ALsizei nb, ALuint *buffers) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alGenBuffersDirect(ALCcontext *context, ALsizei n, ALuint *buffers) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alDeleteBuffersDirect(ALCcontext *context, ALsizei n, const ALuint *buffers) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alIsBufferDirect(ALCcontext *context, ALuint buffer) AL_API_NOEXCEPT -> ALboolean; auto AL_APIENTRY alBufferDataDirect(ALCcontext *context, ALuint buffer, ALenum format, const ALvoid *data, ALsizei size, ALsizei samplerate) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alBufferfDirect(ALCcontext *context, ALuint buffer, ALenum param, ALfloat value) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alBuffer3fDirect(ALCcontext *context, ALuint buffer, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alBufferfvDirect(ALCcontext *context, ALuint buffer, ALenum param, const ALfloat *values) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alBufferiDirect(ALCcontext *context, ALuint buffer, ALenum param, ALint value) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alBuffer3iDirect(ALCcontext *context, ALuint buffer, ALenum param, ALint value1, ALint value2, ALint value3) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alBufferivDirect(ALCcontext *context, ALuint buffer, ALenum param, const ALint *values) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alGetBufferfDirect(ALCcontext *context, ALuint buffer, ALenum param, ALfloat *value) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alGetBuffer3fDirect(ALCcontext *context, ALuint buffer, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alGetBufferfvDirect(ALCcontext *context, ALuint buffer, ALenum param, ALfloat *values) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alGetBufferiDirect(ALCcontext *context, ALuint buffer, ALenum param, ALint *value) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alGetBuffer3iDirect(ALCcontext *context, ALuint buffer, ALenum param, ALint *value1, ALint *value2, ALint *value3) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alGetBufferivDirect(ALCcontext *context, ALuint buffer, ALenum param, ALint *values) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alGenEffectsDirect(ALCcontext *context, ALsizei n, ALuint *effects) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alDeleteEffectsDirect(ALCcontext *context, ALsizei n, const ALuint *effects) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alIsEffectDirect(ALCcontext *context, ALuint effect) AL_API_NOEXCEPT -> ALboolean; auto AL_APIENTRY alEffectiDirect(ALCcontext *context, ALuint effect, ALenum param, ALint iValue) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alEffectivDirect(ALCcontext *context, ALuint effect, ALenum param, const ALint *piValues) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alEffectfDirect(ALCcontext *context, ALuint effect, ALenum param, ALfloat flValue) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alEffectfvDirect(ALCcontext *context, ALuint effect, ALenum param, const ALfloat *pflValues) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alGetEffectiDirect(ALCcontext *context, ALuint effect, ALenum param, ALint *piValue) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alGetEffectivDirect(ALCcontext *context, ALuint effect, ALenum param, ALint *piValues) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alGetEffectfDirect(ALCcontext *context, ALuint effect, ALenum param, ALfloat *pflValue) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alGetEffectfvDirect(ALCcontext *context, ALuint effect, ALenum param, ALfloat *pflValues) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alGenFiltersDirect(ALCcontext *context, ALsizei n, ALuint *filters) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alDeleteFiltersDirect(ALCcontext *context, ALsizei n, const ALuint *filters) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alIsFilterDirect(ALCcontext *context, ALuint filter) AL_API_NOEXCEPT -> ALboolean; auto AL_APIENTRY alFilteriDirect(ALCcontext *context, ALuint filter, ALenum param, ALint iValue) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alFilterivDirect(ALCcontext *context, ALuint filter, ALenum param, const ALint *piValues) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alFilterfDirect(ALCcontext *context, ALuint filter, ALenum param, ALfloat flValue) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alFilterfvDirect(ALCcontext *context, ALuint filter, ALenum param, const ALfloat *pflValues) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alGetFilteriDirect(ALCcontext *context, ALuint filter, ALenum param, ALint *piValue) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alGetFilterivDirect(ALCcontext *context, ALuint filter, ALenum param, ALint *piValues) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alGetFilterfDirect(ALCcontext *context, ALuint filter, ALenum param, ALfloat *pflValue) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alGetFilterfvDirect(ALCcontext *context, ALuint filter, ALenum param, ALfloat *pflValues) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alGenAuxiliaryEffectSlotsDirect(ALCcontext *context, ALsizei n, ALuint *effectslots) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alDeleteAuxiliaryEffectSlotsDirect(ALCcontext *context, ALsizei n, const ALuint *effectslots) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alIsAuxiliaryEffectSlotDirect(ALCcontext *context, ALuint effectslot) AL_API_NOEXCEPT -> ALboolean; auto AL_APIENTRY alAuxiliaryEffectSlotiDirect(ALCcontext *context, ALuint effectslot, ALenum param, ALint iValue) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alAuxiliaryEffectSlotivDirect(ALCcontext *context, ALuint effectslot, ALenum param, const ALint *piValues) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alAuxiliaryEffectSlotfDirect(ALCcontext *context, ALuint effectslot, ALenum param, ALfloat flValue) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alAuxiliaryEffectSlotfvDirect(ALCcontext *context, ALuint effectslot, ALenum param, const ALfloat *pflValues) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alGetAuxiliaryEffectSlotiDirect(ALCcontext *context, ALuint effectslot, ALenum param, ALint *piValue) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alGetAuxiliaryEffectSlotivDirect(ALCcontext *context, ALuint effectslot, ALenum param, ALint *piValues) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alGetAuxiliaryEffectSlotfDirect(ALCcontext *context, ALuint effectslot, ALenum param, ALfloat *pflValue) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alGetAuxiliaryEffectSlotfvDirect(ALCcontext *context, ALuint effectslot, ALenum param, ALfloat *pflValues) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alBufferDataStaticDirect(ALCcontext *context, ALuint buffer, ALenum format, ALvoid *data, ALsizei size, ALsizei freq) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alDebugMessageCallbackDirectEXT(ALCcontext *context, ALDEBUGPROCEXT callback, void *userParam) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alDebugMessageInsertDirectEXT(ALCcontext *context, ALenum source, ALenum type, ALuint id, ALenum severity, ALsizei length, const ALchar *message) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alDebugMessageControlDirectEXT(ALCcontext *context, ALenum source, ALenum type, ALenum severity, ALsizei count, const ALuint *ids, ALboolean enable) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alPushDebugGroupDirectEXT(ALCcontext *context, ALenum source, ALuint id, ALsizei length, const ALchar *message) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alPopDebugGroupDirectEXT(ALCcontext *context) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alGetDebugMessageLogDirectEXT(ALCcontext *context, ALuint count, ALsizei logBufSize, ALenum *sources, ALenum *types, ALuint *ids, ALenum *severities, ALsizei *lengths, ALchar *logBuf) AL_API_NOEXCEPT -> ALuint; auto AL_APIENTRY alObjectLabelDirectEXT(ALCcontext *context, ALenum identifier, ALuint name, ALsizei length, const ALchar *label) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alGetObjectLabelDirectEXT(ALCcontext *context, ALenum identifier, ALuint name, ALsizei bufSize, ALsizei *length, ALchar *label) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alGetPointerDirectEXT(ALCcontext *context, ALenum pname) AL_API_NOEXCEPT -> void*; auto AL_APIENTRY alGetPointervDirectEXT(ALCcontext *context, ALenum pname, void **values) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alRequestFoldbackStartDirect(ALCcontext *context, ALenum mode, ALsizei count, ALsizei length, ALfloat *mem, LPALFOLDBACKCALLBACK callback) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alRequestFoldbackStopDirect(ALCcontext *context) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alBufferSubDataDirectSOFT(ALCcontext *context, ALuint buffer, ALenum format, const ALvoid *data, ALsizei offset, ALsizei length) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alSourcedDirectSOFT(ALCcontext *context, ALuint source, ALenum param, ALdouble value) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alSource3dDirectSOFT(ALCcontext *context, ALuint source, ALenum param, ALdouble value1, ALdouble value2, ALdouble value3) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alSourcedvDirectSOFT(ALCcontext *context, ALuint source, ALenum param, const ALdouble *values) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alGetSourcedDirectSOFT(ALCcontext *context, ALuint source, ALenum param, ALdouble *value) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alGetSource3dDirectSOFT(ALCcontext *context, ALuint source, ALenum param, ALdouble *value1, ALdouble *value2, ALdouble *value3) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alGetSourcedvDirectSOFT(ALCcontext *context, ALuint source, ALenum param, ALdouble *values) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alSourcei64DirectSOFT(ALCcontext *context, ALuint source, ALenum param, ALint64SOFT value) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alSource3i64DirectSOFT(ALCcontext *context, ALuint source, ALenum param, ALint64SOFT value1, ALint64SOFT value2, ALint64SOFT value3) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alSourcei64vDirectSOFT(ALCcontext *context, ALuint source, ALenum param, const ALint64SOFT *values) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alGetSourcei64DirectSOFT(ALCcontext *context, ALuint source, ALenum param, ALint64SOFT *value) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alGetSource3i64DirectSOFT(ALCcontext *context, ALuint source, ALenum param, ALint64SOFT *value1, ALint64SOFT *value2, ALint64SOFT *value3) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alGetSourcei64vDirectSOFT(ALCcontext *context, ALuint source, ALenum param, ALint64SOFT *values) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alDeferUpdatesDirectSOFT(ALCcontext *context) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alProcessUpdatesDirectSOFT(ALCcontext *context) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alGetStringiDirectSOFT(ALCcontext *context, ALenum pname, ALsizei index) AL_API_NOEXCEPT -> const ALchar*; auto AL_APIENTRY alEventControlDirectSOFT(ALCcontext *context, ALsizei count, const ALenum *types, ALboolean enable) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alEventCallbackDirectSOFT(ALCcontext *context, ALEVENTPROCSOFT callback, void *userParam) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alGetPointerDirectSOFT(ALCcontext *context, ALenum pname) AL_API_NOEXCEPT -> void*; auto AL_APIENTRY alGetPointervDirectSOFT(ALCcontext *context, ALenum pname, void **values) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alBufferCallbackDirectSOFT(ALCcontext *context, ALuint buffer, ALenum format, ALsizei freq, ALBUFFERCALLBACKTYPESOFT callback, ALvoid *userptr) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alGetBufferPtrDirectSOFT(ALCcontext *context, ALuint buffer, ALenum param, ALvoid **ptr) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alGetBuffer3PtrDirectSOFT(ALCcontext *context, ALuint buffer, ALenum param, ALvoid **ptr0, ALvoid **ptr1, ALvoid **ptr2) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alGetBufferPtrvDirectSOFT(ALCcontext *context, ALuint buffer, ALenum param, ALvoid **ptr) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alSourcePlayAtTimeDirectSOFT(ALCcontext *context, ALuint source, ALint64SOFT start_time) AL_API_NOEXCEPT -> void; auto AL_APIENTRY alSourcePlayAtTimevDirectSOFT(ALCcontext *context, ALsizei n, const ALuint *sources, ALint64SOFT start_time) AL_API_NOEXCEPT -> void; auto 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; auto 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 -> ALenum; auto AL_APIENTRY EAXSetBufferModeDirect(ALCcontext *context, ALsizei n, const ALuint *buffers, ALint value) AL_API_NOEXCEPT -> ALboolean; auto AL_APIENTRY EAXGetBufferModeDirect(ALCcontext *context, ALuint buffer, ALint *pReserved) AL_API_NOEXCEPT -> ALenum; #endif /*** AL_SOFT_bformat_hoa ***/ ENUMDCL AL_UNPACK_AMBISONIC_ORDER_SOFT = 0x199D; } /* extern "C" */ kcat-openal-soft-75c0059/modules/alstd.cppm000066400000000000000000000027561512220627100206070ustar00rootroot00000000000000/* This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * 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 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. * * For more information, please refer to */ /* The std OpenAL module simply provides both the ALC and AL modules. It's * intended to provide just the standard interface, without any extensions. */ export module openal.std; export import openal.alc; export import openal.al; kcat-openal-soft-75c0059/modules/efx.cppm000066400000000000000000001115211512220627100202510ustar00rootroot00000000000000/* This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * 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 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. * * For more information, please refer to */ /* This file is auto-generated! Please do not edit it manually. * Instead, modify the API in al.xml and regenerate using genheaders.py. * * Last regenerated: 2025-10-13 19:40:01.687690+00:00 */ module; #include #include #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 #ifndef AL_DISABLE_NOEXCEPT #define AL_API_NOEXCEPT noexcept #else #define AL_API_NOEXCEPT #endif #define ENUMDCL inline constexpr auto export module openal.efx; import openal.std; export extern "C" { /*** ALC_EXT_EFX ***/ inline constexpr auto ALC_EXT_EFX_NAME = std::to_array("ALC_EXT_EFX"); ENUMDCL ALC_EFX_MAJOR_VERSION = 0x20001; ENUMDCL ALC_EFX_MINOR_VERSION = 0x20002; ENUMDCL ALC_MAX_AUXILIARY_SENDS = 0x20003; /* Listener properties */ ENUMDCL AL_METERS_PER_UNIT = 0x20004; /* Source properties. */ ENUMDCL AL_DIRECT_FILTER = 0x20005; ENUMDCL AL_AUXILIARY_SEND_FILTER = 0x20006; ENUMDCL AL_AIR_ABSORPTION_FACTOR = 0x20007; ENUMDCL AL_ROOM_ROLLOFF_FACTOR = 0x20008; ENUMDCL AL_CONE_OUTER_GAINHF = 0x20009; ENUMDCL AL_DIRECT_FILTER_GAINHF_AUTO = 0x2000A; ENUMDCL AL_AUXILIARY_SEND_FILTER_GAIN_AUTO = 0x2000B; ENUMDCL AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO = 0x2000C; /* Reverb effect parameters */ ENUMDCL AL_REVERB_DENSITY = 0x0001; ENUMDCL AL_REVERB_DIFFUSION = 0x0002; ENUMDCL AL_REVERB_GAIN = 0x0003; ENUMDCL AL_REVERB_GAINHF = 0x0004; ENUMDCL AL_REVERB_DECAY_TIME = 0x0005; ENUMDCL AL_REVERB_DECAY_HFRATIO = 0x0006; ENUMDCL AL_REVERB_REFLECTIONS_GAIN = 0x0007; ENUMDCL AL_REVERB_REFLECTIONS_DELAY = 0x0008; ENUMDCL AL_REVERB_LATE_REVERB_GAIN = 0x0009; ENUMDCL AL_REVERB_LATE_REVERB_DELAY = 0x000A; ENUMDCL AL_REVERB_AIR_ABSORPTION_GAINHF = 0x000B; ENUMDCL AL_REVERB_ROOM_ROLLOFF_FACTOR = 0x000C; ENUMDCL AL_REVERB_DECAY_HFLIMIT = 0x000D; /* EAX Reverb effect parameters */ ENUMDCL AL_EAXREVERB_DENSITY = 0x0001; ENUMDCL AL_EAXREVERB_DIFFUSION = 0x0002; ENUMDCL AL_EAXREVERB_GAIN = 0x0003; ENUMDCL AL_EAXREVERB_GAINHF = 0x0004; ENUMDCL AL_EAXREVERB_GAINLF = 0x0005; ENUMDCL AL_EAXREVERB_DECAY_TIME = 0x0006; ENUMDCL AL_EAXREVERB_DECAY_HFRATIO = 0x0007; ENUMDCL AL_EAXREVERB_DECAY_LFRATIO = 0x0008; ENUMDCL AL_EAXREVERB_REFLECTIONS_GAIN = 0x0009; ENUMDCL AL_EAXREVERB_REFLECTIONS_DELAY = 0x000A; ENUMDCL AL_EAXREVERB_REFLECTIONS_PAN = 0x000B; ENUMDCL AL_EAXREVERB_LATE_REVERB_GAIN = 0x000C; ENUMDCL AL_EAXREVERB_LATE_REVERB_DELAY = 0x000D; ENUMDCL AL_EAXREVERB_LATE_REVERB_PAN = 0x000E; ENUMDCL AL_EAXREVERB_ECHO_TIME = 0x000F; ENUMDCL AL_EAXREVERB_ECHO_DEPTH = 0x0010; ENUMDCL AL_EAXREVERB_MODULATION_TIME = 0x0011; ENUMDCL AL_EAXREVERB_MODULATION_DEPTH = 0x0012; ENUMDCL AL_EAXREVERB_AIR_ABSORPTION_GAINHF = 0x0013; ENUMDCL AL_EAXREVERB_HFREFERENCE = 0x0014; ENUMDCL AL_EAXREVERB_LFREFERENCE = 0x0015; ENUMDCL AL_EAXREVERB_ROOM_ROLLOFF_FACTOR = 0x0016; ENUMDCL AL_EAXREVERB_DECAY_HFLIMIT = 0x0017; /* Chorus effect parameters */ ENUMDCL AL_CHORUS_WAVEFORM = 0x0001; ENUMDCL AL_CHORUS_PHASE = 0x0002; ENUMDCL AL_CHORUS_RATE = 0x0003; ENUMDCL AL_CHORUS_DEPTH = 0x0004; ENUMDCL AL_CHORUS_FEEDBACK = 0x0005; ENUMDCL AL_CHORUS_DELAY = 0x0006; /* Distortion effect parameters */ ENUMDCL AL_DISTORTION_EDGE = 0x0001; ENUMDCL AL_DISTORTION_GAIN = 0x0002; ENUMDCL AL_DISTORTION_LOWPASS_CUTOFF = 0x0003; ENUMDCL AL_DISTORTION_EQCENTER = 0x0004; ENUMDCL AL_DISTORTION_EQBANDWIDTH = 0x0005; /* Echo effect parameters */ ENUMDCL AL_ECHO_DELAY = 0x0001; ENUMDCL AL_ECHO_LRDELAY = 0x0002; ENUMDCL AL_ECHO_DAMPING = 0x0003; ENUMDCL AL_ECHO_FEEDBACK = 0x0004; ENUMDCL AL_ECHO_SPREAD = 0x0005; /* Flanger effect parameters */ ENUMDCL AL_FLANGER_WAVEFORM = 0x0001; ENUMDCL AL_FLANGER_PHASE = 0x0002; ENUMDCL AL_FLANGER_RATE = 0x0003; ENUMDCL AL_FLANGER_DEPTH = 0x0004; ENUMDCL AL_FLANGER_FEEDBACK = 0x0005; ENUMDCL AL_FLANGER_DELAY = 0x0006; /* Frequency shifter effect parameters */ ENUMDCL AL_FREQUENCY_SHIFTER_FREQUENCY = 0x0001; ENUMDCL AL_FREQUENCY_SHIFTER_LEFT_DIRECTION = 0x0002; ENUMDCL AL_FREQUENCY_SHIFTER_RIGHT_DIRECTION = 0x0003; /* Vocal morpher effect parameters */ ENUMDCL AL_VOCAL_MORPHER_PHONEMEA = 0x0001; ENUMDCL AL_VOCAL_MORPHER_PHONEMEA_COARSE_TUNING = 0x0002; ENUMDCL AL_VOCAL_MORPHER_PHONEMEB = 0x0003; ENUMDCL AL_VOCAL_MORPHER_PHONEMEB_COARSE_TUNING = 0x0004; ENUMDCL AL_VOCAL_MORPHER_WAVEFORM = 0x0005; ENUMDCL AL_VOCAL_MORPHER_RATE = 0x0006; /* Pitchshifter effect parameters */ ENUMDCL AL_PITCH_SHIFTER_COARSE_TUNE = 0x0001; ENUMDCL AL_PITCH_SHIFTER_FINE_TUNE = 0x0002; /* Ringmodulator effect parameters */ ENUMDCL AL_RING_MODULATOR_FREQUENCY = 0x0001; ENUMDCL AL_RING_MODULATOR_HIGHPASS_CUTOFF = 0x0002; ENUMDCL AL_RING_MODULATOR_WAVEFORM = 0x0003; /* Autowah effect parameters */ ENUMDCL AL_AUTOWAH_ATTACK_TIME = 0x0001; ENUMDCL AL_AUTOWAH_RELEASE_TIME = 0x0002; ENUMDCL AL_AUTOWAH_RESONANCE = 0x0003; ENUMDCL AL_AUTOWAH_PEAK_GAIN = 0x0004; /* Compressor effect parameters */ ENUMDCL AL_COMPRESSOR_ONOFF = 0x0001; /* Equalizer effect parameters */ ENUMDCL AL_EQUALIZER_LOW_GAIN = 0x0001; ENUMDCL AL_EQUALIZER_LOW_CUTOFF = 0x0002; ENUMDCL AL_EQUALIZER_MID1_GAIN = 0x0003; ENUMDCL AL_EQUALIZER_MID1_CENTER = 0x0004; ENUMDCL AL_EQUALIZER_MID1_WIDTH = 0x0005; ENUMDCL AL_EQUALIZER_MID2_GAIN = 0x0006; ENUMDCL AL_EQUALIZER_MID2_CENTER = 0x0007; ENUMDCL AL_EQUALIZER_MID2_WIDTH = 0x0008; ENUMDCL AL_EQUALIZER_HIGH_GAIN = 0x0009; ENUMDCL AL_EQUALIZER_HIGH_CUTOFF = 0x000A; /* Effect type */ ENUMDCL AL_EFFECT_FIRST_PARAMETER = 0x0000; ENUMDCL AL_EFFECT_LAST_PARAMETER = 0x8000; ENUMDCL AL_EFFECT_TYPE = 0x8001; /* Effect types, used with the AL_EFFECT_TYPE property */ ENUMDCL AL_EFFECT_NULL = 0x0000; ENUMDCL AL_EFFECT_REVERB = 0x0001; ENUMDCL AL_EFFECT_CHORUS = 0x0002; ENUMDCL AL_EFFECT_DISTORTION = 0x0003; ENUMDCL AL_EFFECT_ECHO = 0x0004; ENUMDCL AL_EFFECT_FLANGER = 0x0005; ENUMDCL AL_EFFECT_FREQUENCY_SHIFTER = 0x0006; ENUMDCL AL_EFFECT_VOCAL_MORPHER = 0x0007; ENUMDCL AL_EFFECT_PITCH_SHIFTER = 0x0008; ENUMDCL AL_EFFECT_RING_MODULATOR = 0x0009; ENUMDCL AL_EFFECT_AUTOWAH = 0x000A; ENUMDCL AL_EFFECT_COMPRESSOR = 0x000B; ENUMDCL AL_EFFECT_EQUALIZER = 0x000C; ENUMDCL AL_EFFECT_EAXREVERB = 0x8000; /* Auxiliary Effect Slot properties. */ ENUMDCL AL_EFFECTSLOT_EFFECT = 0x0001; ENUMDCL AL_EFFECTSLOT_GAIN = 0x0002; ENUMDCL AL_EFFECTSLOT_AUXILIARY_SEND_AUTO = 0x0003; /* NULL Auxiliary Slot ID to disable a source send. */ ENUMDCL AL_EFFECTSLOT_NULL = 0x0000; /* Lowpass filter parameters */ ENUMDCL AL_LOWPASS_GAIN = 0x0001; ENUMDCL AL_LOWPASS_GAINHF = 0x0002; /* Highpass filter parameters */ ENUMDCL AL_HIGHPASS_GAIN = 0x0001; ENUMDCL AL_HIGHPASS_GAINLF = 0x0002; /* Bandpass filter parameters */ ENUMDCL AL_BANDPASS_GAIN = 0x0001; ENUMDCL AL_BANDPASS_GAINLF = 0x0002; ENUMDCL AL_BANDPASS_GAINHF = 0x0003; /* Filter type */ ENUMDCL AL_FILTER_FIRST_PARAMETER = 0x0000; ENUMDCL AL_FILTER_LAST_PARAMETER = 0x8000; ENUMDCL AL_FILTER_TYPE = 0x8001; /* Filter types, used with the AL_FILTER_TYPE property */ ENUMDCL AL_FILTER_NULL = 0x0000; ENUMDCL AL_FILTER_LOWPASS = 0x0001; ENUMDCL AL_FILTER_HIGHPASS = 0x0002; ENUMDCL AL_FILTER_BANDPASS = 0x0003; /* Lowpass filter */ ENUMDCL AL_LOWPASS_MIN_GAIN = 0.0f; ENUMDCL AL_LOWPASS_MAX_GAIN = 1.0f; ENUMDCL AL_LOWPASS_DEFAULT_GAIN = 1.0f; ENUMDCL AL_LOWPASS_MIN_GAINHF = 0.0f; ENUMDCL AL_LOWPASS_MAX_GAINHF = 1.0f; ENUMDCL AL_LOWPASS_DEFAULT_GAINHF = 1.0f; /* Highpass filter */ ENUMDCL AL_HIGHPASS_MIN_GAIN = 0.0f; ENUMDCL AL_HIGHPASS_MAX_GAIN = 1.0f; ENUMDCL AL_HIGHPASS_DEFAULT_GAIN = 1.0f; ENUMDCL AL_HIGHPASS_MIN_GAINLF = 0.0f; ENUMDCL AL_HIGHPASS_MAX_GAINLF = 1.0f; ENUMDCL AL_HIGHPASS_DEFAULT_GAINLF = 1.0f; /* Bandpass filter */ ENUMDCL AL_BANDPASS_MIN_GAIN = 0.0f; ENUMDCL AL_BANDPASS_MAX_GAIN = 1.0f; ENUMDCL AL_BANDPASS_DEFAULT_GAIN = 1.0f; ENUMDCL AL_BANDPASS_MIN_GAINHF = 0.0f; ENUMDCL AL_BANDPASS_MAX_GAINHF = 1.0f; ENUMDCL AL_BANDPASS_DEFAULT_GAINHF = 1.0f; ENUMDCL AL_BANDPASS_MIN_GAINLF = 0.0f; ENUMDCL AL_BANDPASS_MAX_GAINLF = 1.0f; ENUMDCL AL_BANDPASS_DEFAULT_GAINLF = 1.0f; /* Standard reverb effect */ ENUMDCL AL_REVERB_MIN_DENSITY = 0.0f; ENUMDCL AL_REVERB_MAX_DENSITY = 1.0f; ENUMDCL AL_REVERB_DEFAULT_DENSITY = 1.0f; ENUMDCL AL_REVERB_MIN_DIFFUSION = 0.0f; ENUMDCL AL_REVERB_MAX_DIFFUSION = 1.0f; ENUMDCL AL_REVERB_DEFAULT_DIFFUSION = 1.0f; ENUMDCL AL_REVERB_MIN_GAIN = 0.0f; ENUMDCL AL_REVERB_MAX_GAIN = 1.0f; ENUMDCL AL_REVERB_DEFAULT_GAIN = 0.32f; ENUMDCL AL_REVERB_MIN_GAINHF = 0.0f; ENUMDCL AL_REVERB_MAX_GAINHF = 1.0f; ENUMDCL AL_REVERB_DEFAULT_GAINHF = 0.89f; ENUMDCL AL_REVERB_MIN_DECAY_TIME = 0.1f; ENUMDCL AL_REVERB_MAX_DECAY_TIME = 20.0f; ENUMDCL AL_REVERB_DEFAULT_DECAY_TIME = 1.49f; ENUMDCL AL_REVERB_MIN_DECAY_HFRATIO = 0.1f; ENUMDCL AL_REVERB_MAX_DECAY_HFRATIO = 2.0f; ENUMDCL AL_REVERB_DEFAULT_DECAY_HFRATIO = 0.83f; ENUMDCL AL_REVERB_MIN_REFLECTIONS_GAIN = 0.0f; ENUMDCL AL_REVERB_MAX_REFLECTIONS_GAIN = 3.16f; ENUMDCL AL_REVERB_DEFAULT_REFLECTIONS_GAIN = 0.05f; ENUMDCL AL_REVERB_MIN_REFLECTIONS_DELAY = 0.0f; ENUMDCL AL_REVERB_MAX_REFLECTIONS_DELAY = 0.3f; ENUMDCL AL_REVERB_DEFAULT_REFLECTIONS_DELAY = 0.007f; ENUMDCL AL_REVERB_MIN_LATE_REVERB_GAIN = 0.0f; ENUMDCL AL_REVERB_MAX_LATE_REVERB_GAIN = 10.0f; ENUMDCL AL_REVERB_DEFAULT_LATE_REVERB_GAIN = 1.26f; ENUMDCL AL_REVERB_MIN_LATE_REVERB_DELAY = 0.0f; ENUMDCL AL_REVERB_MAX_LATE_REVERB_DELAY = 0.1f; ENUMDCL AL_REVERB_DEFAULT_LATE_REVERB_DELAY = 0.011f; ENUMDCL AL_REVERB_MIN_AIR_ABSORPTION_GAINHF = 0.892f; ENUMDCL AL_REVERB_MAX_AIR_ABSORPTION_GAINHF = 1.0f; ENUMDCL AL_REVERB_DEFAULT_AIR_ABSORPTION_GAINHF = 0.994f; ENUMDCL AL_REVERB_MIN_ROOM_ROLLOFF_FACTOR = 0.0f; ENUMDCL AL_REVERB_MAX_ROOM_ROLLOFF_FACTOR = 10.0f; ENUMDCL AL_REVERB_DEFAULT_ROOM_ROLLOFF_FACTOR = 0.0f; ENUMDCL AL_REVERB_MIN_DECAY_HFLIMIT = AL_FALSE; ENUMDCL AL_REVERB_MAX_DECAY_HFLIMIT = AL_TRUE; ENUMDCL AL_REVERB_DEFAULT_DECAY_HFLIMIT = AL_TRUE; /* EAX reverb effect */ ENUMDCL AL_EAXREVERB_MIN_DENSITY = 0.0f; ENUMDCL AL_EAXREVERB_MAX_DENSITY = 1.0f; ENUMDCL AL_EAXREVERB_DEFAULT_DENSITY = 1.0f; ENUMDCL AL_EAXREVERB_MIN_DIFFUSION = 0.0f; ENUMDCL AL_EAXREVERB_MAX_DIFFUSION = 1.0f; ENUMDCL AL_EAXREVERB_DEFAULT_DIFFUSION = 1.0f; ENUMDCL AL_EAXREVERB_MIN_GAIN = 0.0f; ENUMDCL AL_EAXREVERB_MAX_GAIN = 1.0f; ENUMDCL AL_EAXREVERB_DEFAULT_GAIN = 0.32f; ENUMDCL AL_EAXREVERB_MIN_GAINHF = 0.0f; ENUMDCL AL_EAXREVERB_MAX_GAINHF = 1.0f; ENUMDCL AL_EAXREVERB_DEFAULT_GAINHF = 0.89f; ENUMDCL AL_EAXREVERB_MIN_GAINLF = 0.0f; ENUMDCL AL_EAXREVERB_MAX_GAINLF = 1.0f; ENUMDCL AL_EAXREVERB_DEFAULT_GAINLF = 1.0f; ENUMDCL AL_EAXREVERB_MIN_DECAY_TIME = 0.1f; ENUMDCL AL_EAXREVERB_MAX_DECAY_TIME = 20.0f; ENUMDCL AL_EAXREVERB_DEFAULT_DECAY_TIME = 1.49f; ENUMDCL AL_EAXREVERB_MIN_DECAY_HFRATIO = 0.1f; ENUMDCL AL_EAXREVERB_MAX_DECAY_HFRATIO = 2.0f; ENUMDCL AL_EAXREVERB_DEFAULT_DECAY_HFRATIO = 0.83f; ENUMDCL AL_EAXREVERB_MIN_DECAY_LFRATIO = 0.1f; ENUMDCL AL_EAXREVERB_MAX_DECAY_LFRATIO = 2.0f; ENUMDCL AL_EAXREVERB_DEFAULT_DECAY_LFRATIO = 1.0f; ENUMDCL AL_EAXREVERB_MIN_REFLECTIONS_GAIN = 0.0f; ENUMDCL AL_EAXREVERB_MAX_REFLECTIONS_GAIN = 3.16f; ENUMDCL AL_EAXREVERB_DEFAULT_REFLECTIONS_GAIN = 0.05f; ENUMDCL AL_EAXREVERB_MIN_REFLECTIONS_DELAY = 0.0f; ENUMDCL AL_EAXREVERB_MAX_REFLECTIONS_DELAY = 0.3f; ENUMDCL AL_EAXREVERB_DEFAULT_REFLECTIONS_DELAY = 0.007f; ENUMDCL AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ = 0.0f; ENUMDCL AL_EAXREVERB_MIN_LATE_REVERB_GAIN = 0.0f; ENUMDCL AL_EAXREVERB_MAX_LATE_REVERB_GAIN = 10.0f; ENUMDCL AL_EAXREVERB_DEFAULT_LATE_REVERB_GAIN = 1.26f; ENUMDCL AL_EAXREVERB_MIN_LATE_REVERB_DELAY = 0.0f; ENUMDCL AL_EAXREVERB_MAX_LATE_REVERB_DELAY = 0.1f; ENUMDCL AL_EAXREVERB_DEFAULT_LATE_REVERB_DELAY = 0.011f; ENUMDCL AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ = 0.0f; ENUMDCL AL_EAXREVERB_MIN_ECHO_TIME = 0.075f; ENUMDCL AL_EAXREVERB_MAX_ECHO_TIME = 0.25f; ENUMDCL AL_EAXREVERB_DEFAULT_ECHO_TIME = 0.25f; ENUMDCL AL_EAXREVERB_MIN_ECHO_DEPTH = 0.0f; ENUMDCL AL_EAXREVERB_MAX_ECHO_DEPTH = 1.0f; ENUMDCL AL_EAXREVERB_DEFAULT_ECHO_DEPTH = 0.0f; ENUMDCL AL_EAXREVERB_MIN_MODULATION_TIME = 0.04f; ENUMDCL AL_EAXREVERB_MAX_MODULATION_TIME = 4.0f; ENUMDCL AL_EAXREVERB_DEFAULT_MODULATION_TIME = 0.25f; ENUMDCL AL_EAXREVERB_MIN_MODULATION_DEPTH = 0.0f; ENUMDCL AL_EAXREVERB_MAX_MODULATION_DEPTH = 1.0f; ENUMDCL AL_EAXREVERB_DEFAULT_MODULATION_DEPTH = 0.0f; ENUMDCL AL_EAXREVERB_MIN_AIR_ABSORPTION_GAINHF = 0.892f; ENUMDCL AL_EAXREVERB_MAX_AIR_ABSORPTION_GAINHF = 1.0f; ENUMDCL AL_EAXREVERB_DEFAULT_AIR_ABSORPTION_GAINHF = 0.994f; ENUMDCL AL_EAXREVERB_MIN_HFREFERENCE = 1000.0f; ENUMDCL AL_EAXREVERB_MAX_HFREFERENCE = 20000.0f; ENUMDCL AL_EAXREVERB_DEFAULT_HFREFERENCE = 5000.0f; ENUMDCL AL_EAXREVERB_MIN_LFREFERENCE = 20.0f; ENUMDCL AL_EAXREVERB_MAX_LFREFERENCE = 1000.0f; ENUMDCL AL_EAXREVERB_DEFAULT_LFREFERENCE = 250.0f; ENUMDCL AL_EAXREVERB_MIN_ROOM_ROLLOFF_FACTOR = 0.0f; ENUMDCL AL_EAXREVERB_MAX_ROOM_ROLLOFF_FACTOR = 10.0f; ENUMDCL AL_EAXREVERB_DEFAULT_ROOM_ROLLOFF_FACTOR = 0.0f; ENUMDCL AL_EAXREVERB_MIN_DECAY_HFLIMIT = AL_FALSE; ENUMDCL AL_EAXREVERB_MAX_DECAY_HFLIMIT = AL_TRUE; ENUMDCL AL_EAXREVERB_DEFAULT_DECAY_HFLIMIT = AL_TRUE; ENUMDCL AL_CHORUS_WAVEFORM_SINUSOID = 0; ENUMDCL AL_CHORUS_WAVEFORM_TRIANGLE = 1; ENUMDCL AL_CHORUS_MIN_WAVEFORM = 0; ENUMDCL AL_CHORUS_MAX_WAVEFORM = 1; ENUMDCL AL_CHORUS_DEFAULT_WAVEFORM = 1; ENUMDCL AL_CHORUS_MIN_PHASE = -180; ENUMDCL AL_CHORUS_MAX_PHASE = 180; ENUMDCL AL_CHORUS_DEFAULT_PHASE = 90; ENUMDCL AL_CHORUS_MIN_RATE = 0.0f; ENUMDCL AL_CHORUS_MAX_RATE = 10.0f; ENUMDCL AL_CHORUS_DEFAULT_RATE = 1.1f; ENUMDCL AL_CHORUS_MIN_DEPTH = 0.0f; ENUMDCL AL_CHORUS_MAX_DEPTH = 1.0f; ENUMDCL AL_CHORUS_DEFAULT_DEPTH = 0.1f; ENUMDCL AL_CHORUS_MIN_FEEDBACK = -1.0f; ENUMDCL AL_CHORUS_MAX_FEEDBACK = 1.0f; ENUMDCL AL_CHORUS_DEFAULT_FEEDBACK = 0.25f; ENUMDCL AL_CHORUS_MIN_DELAY = 0.0f; ENUMDCL AL_CHORUS_MAX_DELAY = 0.016f; ENUMDCL AL_CHORUS_DEFAULT_DELAY = 0.016f; /* Distortion effect */ ENUMDCL AL_DISTORTION_MIN_EDGE = 0.0f; ENUMDCL AL_DISTORTION_MAX_EDGE = 1.0f; ENUMDCL AL_DISTORTION_DEFAULT_EDGE = 0.2f; ENUMDCL AL_DISTORTION_MIN_GAIN = 0.01f; ENUMDCL AL_DISTORTION_MAX_GAIN = 1.0f; ENUMDCL AL_DISTORTION_DEFAULT_GAIN = 0.05f; ENUMDCL AL_DISTORTION_MIN_LOWPASS_CUTOFF = 80.0f; ENUMDCL AL_DISTORTION_MAX_LOWPASS_CUTOFF = 24000.0f; ENUMDCL AL_DISTORTION_DEFAULT_LOWPASS_CUTOFF = 8000.0f; ENUMDCL AL_DISTORTION_MIN_EQCENTER = 80.0f; ENUMDCL AL_DISTORTION_MAX_EQCENTER = 24000.0f; ENUMDCL AL_DISTORTION_DEFAULT_EQCENTER = 3600.0f; ENUMDCL AL_DISTORTION_MIN_EQBANDWIDTH = 80.0f; ENUMDCL AL_DISTORTION_MAX_EQBANDWIDTH = 24000.0f; ENUMDCL AL_DISTORTION_DEFAULT_EQBANDWIDTH = 3600.0f; /* Echo effect */ ENUMDCL AL_ECHO_MIN_DELAY = 0.0f; ENUMDCL AL_ECHO_MAX_DELAY = 0.207f; ENUMDCL AL_ECHO_DEFAULT_DELAY = 0.1f; ENUMDCL AL_ECHO_MIN_LRDELAY = 0.0f; ENUMDCL AL_ECHO_MAX_LRDELAY = 0.404f; ENUMDCL AL_ECHO_DEFAULT_LRDELAY = 0.1f; ENUMDCL AL_ECHO_MIN_DAMPING = 0.0f; ENUMDCL AL_ECHO_MAX_DAMPING = 0.99f; ENUMDCL AL_ECHO_DEFAULT_DAMPING = 0.5f; ENUMDCL AL_ECHO_MIN_FEEDBACK = 0.0f; ENUMDCL AL_ECHO_MAX_FEEDBACK = 1.0f; ENUMDCL AL_ECHO_DEFAULT_FEEDBACK = 0.5f; ENUMDCL AL_ECHO_MIN_SPREAD = -1.0f; ENUMDCL AL_ECHO_MAX_SPREAD = 1.0f; ENUMDCL AL_ECHO_DEFAULT_SPREAD = -1.0f; /* Flanger effect */ ENUMDCL AL_FLANGER_WAVEFORM_SINUSOID = 0; ENUMDCL AL_FLANGER_WAVEFORM_TRIANGLE = 1; ENUMDCL AL_FLANGER_MIN_WAVEFORM = 0; ENUMDCL AL_FLANGER_MAX_WAVEFORM = 1; ENUMDCL AL_FLANGER_DEFAULT_WAVEFORM = 1; ENUMDCL AL_FLANGER_MIN_PHASE = -180; ENUMDCL AL_FLANGER_MAX_PHASE = 180; ENUMDCL AL_FLANGER_DEFAULT_PHASE = 0; ENUMDCL AL_FLANGER_MIN_RATE = 0.0f; ENUMDCL AL_FLANGER_MAX_RATE = 10.0f; ENUMDCL AL_FLANGER_DEFAULT_RATE = 0.27f; ENUMDCL AL_FLANGER_MIN_DEPTH = 0.0f; ENUMDCL AL_FLANGER_MAX_DEPTH = 1.0f; ENUMDCL AL_FLANGER_DEFAULT_DEPTH = 1.0f; ENUMDCL AL_FLANGER_MIN_FEEDBACK = -1.0f; ENUMDCL AL_FLANGER_MAX_FEEDBACK = 1.0f; ENUMDCL AL_FLANGER_DEFAULT_FEEDBACK = -0.5f; ENUMDCL AL_FLANGER_MIN_DELAY = 0.0f; ENUMDCL AL_FLANGER_MAX_DELAY = 0.004f; ENUMDCL AL_FLANGER_DEFAULT_DELAY = 0.002f; /* Frequency shifter effect */ ENUMDCL AL_FREQUENCY_SHIFTER_MIN_FREQUENCY = 0.0f; ENUMDCL AL_FREQUENCY_SHIFTER_MAX_FREQUENCY = 24000.0f; ENUMDCL AL_FREQUENCY_SHIFTER_DEFAULT_FREQUENCY = 0.0f; ENUMDCL AL_FREQUENCY_SHIFTER_MIN_LEFT_DIRECTION = 0; ENUMDCL AL_FREQUENCY_SHIFTER_MAX_LEFT_DIRECTION = 2; ENUMDCL AL_FREQUENCY_SHIFTER_DEFAULT_LEFT_DIRECTION = 0; ENUMDCL AL_FREQUENCY_SHIFTER_DIRECTION_DOWN = 0; ENUMDCL AL_FREQUENCY_SHIFTER_DIRECTION_UP = 1; ENUMDCL AL_FREQUENCY_SHIFTER_DIRECTION_OFF = 2; ENUMDCL AL_FREQUENCY_SHIFTER_MIN_RIGHT_DIRECTION = 0; ENUMDCL AL_FREQUENCY_SHIFTER_MAX_RIGHT_DIRECTION = 2; ENUMDCL AL_FREQUENCY_SHIFTER_DEFAULT_RIGHT_DIRECTION = 0; /* Vocal morpher effect */ ENUMDCL AL_VOCAL_MORPHER_MIN_PHONEMEA = 0; ENUMDCL AL_VOCAL_MORPHER_MAX_PHONEMEA = 29; ENUMDCL AL_VOCAL_MORPHER_DEFAULT_PHONEMEA = 0; ENUMDCL AL_VOCAL_MORPHER_MIN_PHONEMEA_COARSE_TUNING = -24; ENUMDCL AL_VOCAL_MORPHER_MAX_PHONEMEA_COARSE_TUNING = 24; ENUMDCL AL_VOCAL_MORPHER_DEFAULT_PHONEMEA_COARSE_TUNING = 0; ENUMDCL AL_VOCAL_MORPHER_MIN_PHONEMEB = 0; ENUMDCL AL_VOCAL_MORPHER_MAX_PHONEMEB = 29; ENUMDCL AL_VOCAL_MORPHER_DEFAULT_PHONEMEB = 10; ENUMDCL AL_VOCAL_MORPHER_MIN_PHONEMEB_COARSE_TUNING = -24; ENUMDCL AL_VOCAL_MORPHER_MAX_PHONEMEB_COARSE_TUNING = 24; ENUMDCL AL_VOCAL_MORPHER_DEFAULT_PHONEMEB_COARSE_TUNING = 0; ENUMDCL AL_VOCAL_MORPHER_PHONEME_A = 0; ENUMDCL AL_VOCAL_MORPHER_PHONEME_E = 1; ENUMDCL AL_VOCAL_MORPHER_PHONEME_I = 2; ENUMDCL AL_VOCAL_MORPHER_PHONEME_O = 3; ENUMDCL AL_VOCAL_MORPHER_PHONEME_U = 4; ENUMDCL AL_VOCAL_MORPHER_PHONEME_AA = 5; ENUMDCL AL_VOCAL_MORPHER_PHONEME_AE = 6; ENUMDCL AL_VOCAL_MORPHER_PHONEME_AH = 7; ENUMDCL AL_VOCAL_MORPHER_PHONEME_AO = 8; ENUMDCL AL_VOCAL_MORPHER_PHONEME_EH = 9; ENUMDCL AL_VOCAL_MORPHER_PHONEME_ER = 10; ENUMDCL AL_VOCAL_MORPHER_PHONEME_IH = 11; ENUMDCL AL_VOCAL_MORPHER_PHONEME_IY = 12; ENUMDCL AL_VOCAL_MORPHER_PHONEME_UH = 13; ENUMDCL AL_VOCAL_MORPHER_PHONEME_UW = 14; ENUMDCL AL_VOCAL_MORPHER_PHONEME_B = 15; ENUMDCL AL_VOCAL_MORPHER_PHONEME_D = 16; ENUMDCL AL_VOCAL_MORPHER_PHONEME_F = 17; ENUMDCL AL_VOCAL_MORPHER_PHONEME_G = 18; ENUMDCL AL_VOCAL_MORPHER_PHONEME_J = 19; ENUMDCL AL_VOCAL_MORPHER_PHONEME_K = 20; ENUMDCL AL_VOCAL_MORPHER_PHONEME_L = 21; ENUMDCL AL_VOCAL_MORPHER_PHONEME_M = 22; ENUMDCL AL_VOCAL_MORPHER_PHONEME_N = 23; ENUMDCL AL_VOCAL_MORPHER_PHONEME_P = 24; ENUMDCL AL_VOCAL_MORPHER_PHONEME_R = 25; ENUMDCL AL_VOCAL_MORPHER_PHONEME_S = 26; ENUMDCL AL_VOCAL_MORPHER_PHONEME_T = 27; ENUMDCL AL_VOCAL_MORPHER_PHONEME_V = 28; ENUMDCL AL_VOCAL_MORPHER_PHONEME_Z = 29; ENUMDCL AL_VOCAL_MORPHER_WAVEFORM_SINUSOID = 0; ENUMDCL AL_VOCAL_MORPHER_WAVEFORM_TRIANGLE = 1; ENUMDCL AL_VOCAL_MORPHER_WAVEFORM_SAWTOOTH = 2; ENUMDCL AL_VOCAL_MORPHER_MIN_WAVEFORM = 0; ENUMDCL AL_VOCAL_MORPHER_MAX_WAVEFORM = 2; ENUMDCL AL_VOCAL_MORPHER_DEFAULT_WAVEFORM = 0; ENUMDCL AL_VOCAL_MORPHER_MIN_RATE = 0.0f; ENUMDCL AL_VOCAL_MORPHER_MAX_RATE = 10.0f; ENUMDCL AL_VOCAL_MORPHER_DEFAULT_RATE = 1.41f; /* Pitch shifter effect */ ENUMDCL AL_PITCH_SHIFTER_MIN_COARSE_TUNE = -12; ENUMDCL AL_PITCH_SHIFTER_MAX_COARSE_TUNE = 12; ENUMDCL AL_PITCH_SHIFTER_DEFAULT_COARSE_TUNE = 12; ENUMDCL AL_PITCH_SHIFTER_MIN_FINE_TUNE = -50; ENUMDCL AL_PITCH_SHIFTER_MAX_FINE_TUNE = 50; ENUMDCL AL_PITCH_SHIFTER_DEFAULT_FINE_TUNE = 0; /* Ring modulator effect */ ENUMDCL AL_RING_MODULATOR_MIN_FREQUENCY = 0.0f; ENUMDCL AL_RING_MODULATOR_MAX_FREQUENCY = 8000.0f; ENUMDCL AL_RING_MODULATOR_DEFAULT_FREQUENCY = 440.0f; ENUMDCL AL_RING_MODULATOR_MIN_HIGHPASS_CUTOFF = 0.0f; ENUMDCL AL_RING_MODULATOR_MAX_HIGHPASS_CUTOFF = 24000.0f; ENUMDCL AL_RING_MODULATOR_DEFAULT_HIGHPASS_CUTOFF = 800.0f; ENUMDCL AL_RING_MODULATOR_SINUSOID = 0; ENUMDCL AL_RING_MODULATOR_SAWTOOTH = 1; ENUMDCL AL_RING_MODULATOR_SQUARE = 2; ENUMDCL AL_RING_MODULATOR_MIN_WAVEFORM = 0; ENUMDCL AL_RING_MODULATOR_MAX_WAVEFORM = 2; ENUMDCL AL_RING_MODULATOR_DEFAULT_WAVEFORM = 0; /* Autowah effect */ ENUMDCL AL_AUTOWAH_MIN_ATTACK_TIME = 0.0001f; ENUMDCL AL_AUTOWAH_MAX_ATTACK_TIME = 1.0f; ENUMDCL AL_AUTOWAH_DEFAULT_ATTACK_TIME = 0.06f; ENUMDCL AL_AUTOWAH_MIN_RELEASE_TIME = 0.0001f; ENUMDCL AL_AUTOWAH_MAX_RELEASE_TIME = 1.0f; ENUMDCL AL_AUTOWAH_DEFAULT_RELEASE_TIME = 0.06f; ENUMDCL AL_AUTOWAH_MIN_RESONANCE = 2.0f; ENUMDCL AL_AUTOWAH_MAX_RESONANCE = 1000.0f; ENUMDCL AL_AUTOWAH_DEFAULT_RESONANCE = 1000.0f; ENUMDCL AL_AUTOWAH_MIN_PEAK_GAIN = 0.00003f; ENUMDCL AL_AUTOWAH_MAX_PEAK_GAIN = 31621.0f; ENUMDCL AL_AUTOWAH_DEFAULT_PEAK_GAIN = 11.22f; /* Compressor effect */ ENUMDCL AL_COMPRESSOR_MIN_ONOFF = 0; ENUMDCL AL_COMPRESSOR_MAX_ONOFF = 1; ENUMDCL AL_COMPRESSOR_DEFAULT_ONOFF = 1; /* Equalizer effect */ ENUMDCL AL_EQUALIZER_MIN_LOW_GAIN = 0.126f; ENUMDCL AL_EQUALIZER_MAX_LOW_GAIN = 7.943f; ENUMDCL AL_EQUALIZER_DEFAULT_LOW_GAIN = 1.0f; ENUMDCL AL_EQUALIZER_MIN_LOW_CUTOFF = 50.0f; ENUMDCL AL_EQUALIZER_MAX_LOW_CUTOFF = 800.0f; ENUMDCL AL_EQUALIZER_DEFAULT_LOW_CUTOFF = 200.0f; ENUMDCL AL_EQUALIZER_MIN_MID1_GAIN = 0.126f; ENUMDCL AL_EQUALIZER_MAX_MID1_GAIN = 7.943f; ENUMDCL AL_EQUALIZER_DEFAULT_MID1_GAIN = 1.0f; ENUMDCL AL_EQUALIZER_MIN_MID1_CENTER = 200.0f; ENUMDCL AL_EQUALIZER_MAX_MID1_CENTER = 3000.0f; ENUMDCL AL_EQUALIZER_DEFAULT_MID1_CENTER = 500.0f; ENUMDCL AL_EQUALIZER_MIN_MID1_WIDTH = 0.01f; ENUMDCL AL_EQUALIZER_MAX_MID1_WIDTH = 1.0f; ENUMDCL AL_EQUALIZER_DEFAULT_MID1_WIDTH = 1.0f; ENUMDCL AL_EQUALIZER_MIN_MID2_GAIN = 0.126f; ENUMDCL AL_EQUALIZER_MAX_MID2_GAIN = 7.943f; ENUMDCL AL_EQUALIZER_DEFAULT_MID2_GAIN = 1.0f; ENUMDCL AL_EQUALIZER_MIN_MID2_CENTER = 1000.0f; ENUMDCL AL_EQUALIZER_MAX_MID2_CENTER = 8000.0f; ENUMDCL AL_EQUALIZER_DEFAULT_MID2_CENTER = 3000.0f; ENUMDCL AL_EQUALIZER_MIN_MID2_WIDTH = 0.01f; ENUMDCL AL_EQUALIZER_MAX_MID2_WIDTH = 1.0f; ENUMDCL AL_EQUALIZER_DEFAULT_MID2_WIDTH = 1.0f; ENUMDCL AL_EQUALIZER_MIN_HIGH_GAIN = 0.126f; ENUMDCL AL_EQUALIZER_MAX_HIGH_GAIN = 7.943f; ENUMDCL AL_EQUALIZER_DEFAULT_HIGH_GAIN = 1.0f; ENUMDCL AL_EQUALIZER_MIN_HIGH_CUTOFF = 4000.0f; ENUMDCL AL_EQUALIZER_MAX_HIGH_CUTOFF = 16000.0f; ENUMDCL AL_EQUALIZER_DEFAULT_HIGH_CUTOFF = 6000.0f; /* Source parameter value ranges and defaults. */ ENUMDCL AL_MIN_AIR_ABSORPTION_FACTOR = 0.0f; ENUMDCL AL_MAX_AIR_ABSORPTION_FACTOR = 10.0f; ENUMDCL AL_DEFAULT_AIR_ABSORPTION_FACTOR = 0.0f; ENUMDCL AL_MIN_ROOM_ROLLOFF_FACTOR = 0.0f; ENUMDCL AL_MAX_ROOM_ROLLOFF_FACTOR = 10.0f; ENUMDCL AL_DEFAULT_ROOM_ROLLOFF_FACTOR = 0.0f; ENUMDCL AL_MIN_CONE_OUTER_GAINHF = 0.0f; ENUMDCL AL_MAX_CONE_OUTER_GAINHF = 1.0f; ENUMDCL AL_DEFAULT_CONE_OUTER_GAINHF = 1.0f; ENUMDCL AL_MIN_DIRECT_FILTER_GAINHF_AUTO = AL_FALSE; ENUMDCL AL_MAX_DIRECT_FILTER_GAINHF_AUTO = AL_TRUE; ENUMDCL AL_DEFAULT_DIRECT_FILTER_GAINHF_AUTO = AL_TRUE; ENUMDCL AL_MIN_AUXILIARY_SEND_FILTER_GAIN_AUTO = AL_FALSE; ENUMDCL AL_MAX_AUXILIARY_SEND_FILTER_GAIN_AUTO = AL_TRUE; ENUMDCL AL_DEFAULT_AUXILIARY_SEND_FILTER_GAIN_AUTO = AL_TRUE; ENUMDCL AL_MIN_AUXILIARY_SEND_FILTER_GAINHF_AUTO = AL_FALSE; ENUMDCL AL_MAX_AUXILIARY_SEND_FILTER_GAINHF_AUTO = AL_TRUE; ENUMDCL AL_DEFAULT_AUXILIARY_SEND_FILTER_GAINHF_AUTO = AL_TRUE; /* Listener parameter value ranges and defaults */ ENUMDCL AL_MIN_METERS_PER_UNIT = FLT_MIN; ENUMDCL AL_MAX_METERS_PER_UNIT = FLT_MAX; ENUMDCL AL_DEFAULT_METERS_PER_UNIT = 1.0f; using LPALGENEFFECTS = auto (AL_APIENTRY*)(ALsizei n, ALuint *effects) AL_API_NOEXCEPT -> void; using LPALDELETEEFFECTS = auto (AL_APIENTRY*)(ALsizei n, const ALuint *effects) AL_API_NOEXCEPT -> void; using LPALISEFFECT = auto (AL_APIENTRY*)(ALuint effect) AL_API_NOEXCEPT -> ALboolean; using LPALEFFECTI = auto (AL_APIENTRY*)(ALuint effect, ALenum param, ALint iValue) AL_API_NOEXCEPT -> void; using LPALEFFECTIV = auto (AL_APIENTRY*)(ALuint effect, ALenum param, const ALint *piValues) AL_API_NOEXCEPT -> void; using LPALEFFECTF = auto (AL_APIENTRY*)(ALuint effect, ALenum param, ALfloat flValue) AL_API_NOEXCEPT -> void; using LPALEFFECTFV = auto (AL_APIENTRY*)(ALuint effect, ALenum param, const ALfloat *pflValues) AL_API_NOEXCEPT -> void; using LPALGETEFFECTI = auto (AL_APIENTRY*)(ALuint effect, ALenum param, ALint *iValue) AL_API_NOEXCEPT -> void; using LPALGETEFFECTIV = auto (AL_APIENTRY*)(ALuint effect, ALenum param, ALint *piValues) AL_API_NOEXCEPT -> void; using LPALGETEFFECTF = auto (AL_APIENTRY*)(ALuint effect, ALenum param, ALfloat *flValue) AL_API_NOEXCEPT -> void; using LPALGETEFFECTFV = auto (AL_APIENTRY*)(ALuint effect, ALenum param, ALfloat *pflValues) AL_API_NOEXCEPT -> void; using LPALGENFILTERS = auto (AL_APIENTRY*)(ALsizei n, ALuint *filters) AL_API_NOEXCEPT -> void; using LPALDELETEFILTERS = auto (AL_APIENTRY*)(ALsizei n, const ALuint *filters) AL_API_NOEXCEPT -> void; using LPALISFILTER = auto (AL_APIENTRY*)(ALuint filter) AL_API_NOEXCEPT -> ALboolean; using LPALFILTERI = auto (AL_APIENTRY*)(ALuint filter, ALenum param, ALint iValue) AL_API_NOEXCEPT -> void; using LPALFILTERIV = auto (AL_APIENTRY*)(ALuint filter, ALenum param, const ALint *piValues) AL_API_NOEXCEPT -> void; using LPALFILTERF = auto (AL_APIENTRY*)(ALuint filter, ALenum param, ALfloat flValue) AL_API_NOEXCEPT -> void; using LPALFILTERFV = auto (AL_APIENTRY*)(ALuint filter, ALenum param, const ALfloat *pflValues) AL_API_NOEXCEPT -> void; using LPALGETFILTERI = auto (AL_APIENTRY*)(ALuint filter, ALenum param, ALint *iValue) AL_API_NOEXCEPT -> void; using LPALGETFILTERIV = auto (AL_APIENTRY*)(ALuint filter, ALenum param, ALint *piValues) AL_API_NOEXCEPT -> void; using LPALGETFILTERF = auto (AL_APIENTRY*)(ALuint filter, ALenum param, ALfloat *flValue) AL_API_NOEXCEPT -> void; using LPALGETFILTERFV = auto (AL_APIENTRY*)(ALuint filter, ALenum param, ALfloat *pflValues) AL_API_NOEXCEPT -> void; using LPALGENAUXILIARYEFFECTSLOTS = auto (AL_APIENTRY*)(ALsizei n, ALuint *effectslots) AL_API_NOEXCEPT -> void; using LPALDELETEAUXILIARYEFFECTSLOTS = auto (AL_APIENTRY*)(ALsizei n, const ALuint *effectslots) AL_API_NOEXCEPT -> void; using LPALISAUXILIARYEFFECTSLOT = auto (AL_APIENTRY*)(ALuint effectslot) AL_API_NOEXCEPT -> ALboolean; using LPALAUXILIARYEFFECTSLOTI = auto (AL_APIENTRY*)(ALuint effectslot, ALenum param, ALint iValue) AL_API_NOEXCEPT -> void; using LPALAUXILIARYEFFECTSLOTIV = auto (AL_APIENTRY*)(ALuint effectslot, ALenum param, const ALint *piValues) AL_API_NOEXCEPT -> void; using LPALAUXILIARYEFFECTSLOTF = auto (AL_APIENTRY*)(ALuint effectslot, ALenum param, ALfloat flValue) AL_API_NOEXCEPT -> void; using LPALAUXILIARYEFFECTSLOTFV = auto (AL_APIENTRY*)(ALuint effectslot, ALenum param, const ALfloat *pflValues) AL_API_NOEXCEPT -> void; using LPALGETAUXILIARYEFFECTSLOTI = auto (AL_APIENTRY*)(ALuint effectslot, ALenum param, ALint *iValue) AL_API_NOEXCEPT -> void; using LPALGETAUXILIARYEFFECTSLOTIV = auto (AL_APIENTRY*)(ALuint effectslot, ALenum param, ALint *piValues) AL_API_NOEXCEPT -> void; using LPALGETAUXILIARYEFFECTSLOTF = auto (AL_APIENTRY*)(ALuint effectslot, ALenum param, ALfloat *flValue) AL_API_NOEXCEPT -> void; using LPALGETAUXILIARYEFFECTSLOTFV = auto (AL_APIENTRY*)(ALuint effectslot, ALenum param, ALfloat *pflValues) AL_API_NOEXCEPT -> void; #ifdef AL_ALEXT_PROTOTYPES /* Effect object function types. */ AL_API auto AL_APIENTRY alGenEffects(ALsizei n, ALuint *effects) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alDeleteEffects(ALsizei n, const ALuint *effects) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alIsEffect(ALuint effect) AL_API_NOEXCEPT -> ALboolean; AL_API auto AL_APIENTRY alEffecti(ALuint effect, ALenum param, ALint iValue) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alEffectiv(ALuint effect, ALenum param, const ALint *piValues) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alEffectf(ALuint effect, ALenum param, ALfloat flValue) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alEffectfv(ALuint effect, ALenum param, const ALfloat *pflValues) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alGetEffecti(ALuint effect, ALenum param, ALint *iValue) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alGetEffectiv(ALuint effect, ALenum param, ALint *piValues) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alGetEffectf(ALuint effect, ALenum param, ALfloat *flValue) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alGetEffectfv(ALuint effect, ALenum param, ALfloat *pflValues) AL_API_NOEXCEPT -> void; /* Filter object function types. */ AL_API auto AL_APIENTRY alGenFilters(ALsizei n, ALuint *filters) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alDeleteFilters(ALsizei n, const ALuint *filters) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alIsFilter(ALuint filter) AL_API_NOEXCEPT -> ALboolean; AL_API auto AL_APIENTRY alFilteri(ALuint filter, ALenum param, ALint iValue) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alFilteriv(ALuint filter, ALenum param, const ALint *piValues) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alFilterf(ALuint filter, ALenum param, ALfloat flValue) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alFilterfv(ALuint filter, ALenum param, const ALfloat *pflValues) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alGetFilteri(ALuint filter, ALenum param, ALint *iValue) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alGetFilteriv(ALuint filter, ALenum param, ALint *piValues) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alGetFilterf(ALuint filter, ALenum param, ALfloat *flValue) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alGetFilterfv(ALuint filter, ALenum param, ALfloat *pflValues) AL_API_NOEXCEPT -> void; /* Auxiliary Effect Slot object function types. */ AL_API auto AL_APIENTRY alGenAuxiliaryEffectSlots(ALsizei n, ALuint *effectslots) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alDeleteAuxiliaryEffectSlots(ALsizei n, const ALuint *effectslots) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alIsAuxiliaryEffectSlot(ALuint effectslot) AL_API_NOEXCEPT -> ALboolean; AL_API auto AL_APIENTRY alAuxiliaryEffectSloti(ALuint effectslot, ALenum param, ALint iValue) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alAuxiliaryEffectSlotiv(ALuint effectslot, ALenum param, const ALint *piValues) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alAuxiliaryEffectSlotf(ALuint effectslot, ALenum param, ALfloat flValue) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alAuxiliaryEffectSlotfv(ALuint effectslot, ALenum param, const ALfloat *pflValues) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alGetAuxiliaryEffectSloti(ALuint effectslot, ALenum param, ALint *iValue) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alGetAuxiliaryEffectSlotiv(ALuint effectslot, ALenum param, ALint *piValues) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alGetAuxiliaryEffectSlotf(ALuint effectslot, ALenum param, ALfloat *flValue) AL_API_NOEXCEPT -> void; AL_API auto AL_APIENTRY alGetAuxiliaryEffectSlotfv(ALuint effectslot, ALenum param, ALfloat *pflValues) AL_API_NOEXCEPT -> void; #endif } /* extern "C" */ kcat-openal-soft-75c0059/modules/openal.cppm000066400000000000000000000027311512220627100207470ustar00rootroot00000000000000/* This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * 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 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. * * For more information, please refer to */ /* The base OpenAL module provides the std and ext modules. It's intended to * provide standard and all known extension interfaces. */ export module openal; export import openal.std; export import openal.ext; kcat-openal-soft-75c0059/openal.pc.in000066400000000000000000000005461512220627100173510ustar00rootroot00000000000000prefix=@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@ kcat-openal-soft-75c0059/presets/000077500000000000000000000000001512220627100166225ustar00rootroot00000000000000kcat-openal-soft-75c0059/presets/3D7.1.ambdec000066400000000000000000000053051512220627100204560ustar00rootroot00000000000000# 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 7 /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 kcat-openal-soft-75c0059/presets/hex-quad.ambdec000066400000000000000000000036361512220627100215030ustar00rootroot00000000000000# 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 kcat-openal-soft-75c0059/presets/hexagon.ambdec000066400000000000000000000031511512220627100214100ustar00rootroot00000000000000# 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 kcat-openal-soft-75c0059/presets/itu5.1-nocenter.ambdec000066400000000000000000000032621512220627100226220ustar00rootroot00000000000000# 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 kcat-openal-soft-75c0059/presets/itu5.1.ambdec000066400000000000000000000031131512220627100210020ustar00rootroot00000000000000# 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 kcat-openal-soft-75c0059/presets/presets.txt000066400000000000000000000057271512220627100210630ustar00rootroot00000000000000Ambisonic 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. kcat-openal-soft-75c0059/presets/rectangle.ambdec000066400000000000000000000022051512220627100217220ustar00rootroot00000000000000# 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 kcat-openal-soft-75c0059/presets/square.ambdec000066400000000000000000000022021512220627100212530ustar00rootroot00000000000000# 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 kcat-openal-soft-75c0059/registry/000077500000000000000000000000001512220627100170055ustar00rootroot00000000000000kcat-openal-soft-75c0059/registry/README.md000066400000000000000000000032271512220627100202700ustar00rootroot00000000000000# The OpenAL Registry This contains the OpenAL registry. Today, it only contains an XML registry limited to OpenAL Soft only. However, in the future there are aspirations for this to function much like the Vulkan/OpenCL registry (i.e. manpages and specifications generated from the contents of this registry) and cover implementations other than OpenAL Soft. The OpenGL registry heavily inspired the XML schema used. > [!NOTE] > A [community effort](https://github.com/Raulshc/OpenAL-EXT-Repository/tree/master/xml) has made good progress at > defining this, and it is possible that this can be used as a base for future work completing the OpenAL Registry. > The current contents of the OpenAL Registry were made from scratch to ensure that the existing OpenAL Soft headers > could be generated from the XML without material changes to the previously-handwritten headers. In addition, there are > some [pending license questions](https://github.com/Raulshc/OpenAL-EXT-Repository/issues/6) that should be resolved > before incorporating its contents into this repository. The following OpenAL Soft headers are generated from the OpenAL Registry: - al.h - alc.h - alext.h - efx.h The following OpenAL Soft headers are not generated from the OpenAL Registry: - **efx-creative.h** - This is only a stub - **efx-presets.h** - This is considered non-normative. The `struct` defined in this header is not referenced as part of the OpenAL API. It can be used to aid the loading of effects (as it is in the examples in the OpenAL Soft repository), and is (as of writing) used as an implementation detail, but this does not make it a core part of the OpenAL API as it currently stands. kcat-openal-soft-75c0059/registry/scripts/000077500000000000000000000000001512220627100204745ustar00rootroot00000000000000kcat-openal-soft-75c0059/registry/scripts/genheaders.py000077500000000000000000000664771512220627100232020ustar00rootroot00000000000000#!/usr/bin/env python3 # -*- encoding utf-8 -*- # Copyright (c) [2025] Dylan Perks (@Perksey) and Contributors # SPDX-License-Identifier: Unlicense import dataclasses import os import os.path import datetime import typing import xml.etree.ElementTree import reg """ A very primitive C header/module generator that makes use of the al.xml parser defined in reg.py. This has been designed to be as close to the original headers/modules (i.e. the headers/modules before the generator was introduced) as possible, which is why this is so verbose at times. The files that are generated are mostly defined by the CONFIGURATION variable defined below (after the preambles). """ @dataclasses.dataclass class FileToGenerate: """ A configuration for the header/module generator. Attributes ---------- output_file : str The path to the header/module file to generate, relative to this Python file (i.e. not necessarily the working directory) is_header : bool True if this file is a header, false if it is a module. registry_file : str The path to the XML to parse, relative to this Python file (i.e. not necessarily the working directory) api : str or list of str The "api" attribute values to include in this header. Currently in OpenAL we have one header for "al", one header for "alc", and then one header for just the extensions (notwithstanding the EFX annex) which has both "al" and "alc" defined for this property. is_extension_header : bool True if this is an extension header, false otherwise. If include/exclude are not defined, this flag determines whether all s will be used for the default value of include or whether all s will be used. include : list of str, optional A list of or names to generate C code for. See the is_extension_header docs for the default behaviour if this is not provided. exclude : list of str, optional A list of or names to explicitly exclude from C code generation. This is empty by default. preamble : str, optional A snippet of C code to include at the start of the file. TODO: really we should be working to minimise this as much as possible (this is required for compatibility with the Khronos scripts for instance) """ output_file: str is_header: bool registry_file: str api: str | typing.List[str] is_extension_only: bool = False include: typing.Optional[typing.List[str]] = None exclude: typing.Optional[typing.List[str]] = None preamble: typing.Optional[str] = None PLATFORM_HEADER_PREAMBLE = """#ifdef __cplusplus extern "C" {{ #ifdef _MSVC_LANG #define {prefix}_CPLUSPLUS _MSVC_LANG #else #define {prefix}_CPLUSPLUS __cplusplus #endif #ifndef AL_DISABLE_NOEXCEPT #if {prefix}_CPLUSPLUS >= 201103L #define {prefix}_API_NOEXCEPT noexcept #else #define {prefix}_API_NOEXCEPT #endif #if {prefix}_CPLUSPLUS >= 201703L #define {prefix}_API_NOEXCEPT17 noexcept #else #define {prefix}_API_NOEXCEPT17 #endif #else /* AL_DISABLE_NOEXCEPT */ #define {prefix}_API_NOEXCEPT #define {prefix}_API_NOEXCEPT17 #endif #undef {prefix}_CPLUSPLUS #else /* __cplusplus */ #define {prefix}_API_NOEXCEPT #define {prefix}_API_NOEXCEPT17 #endif #ifndef {prefix}_API #if defined(AL_LIBTYPE_STATIC) #define {prefix}_API #elif defined(_WIN32) #define {prefix}_API __declspec(dllimport) #else #define {prefix}_API extern #endif #endif #ifdef _WIN32 #define {prefix}_APIENTRY __cdecl #else #define {prefix}_APIENTRY #endif """ EXT_HEADER_PREAMBLE = """#include /* Define int64 and uint64 types */ #if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || \\ (defined(__cplusplus) && __cplusplus >= 201103L) #include typedef int64_t alsoft_impl_int64_t; typedef uint64_t alsoft_impl_uint64_t; #elif defined(_WIN32) typedef __int64 alsoft_impl_int64_t; typedef unsigned __int64 alsoft_impl_uint64_t; #else /* Fallback if nothing above works */ #include typedef int64_t alsoft_impl_int64_t; typedef uint64_t alsoft_impl_uint64_t; #endif #include "alc.h" #include "al.h" #ifdef __cplusplus extern "C" { #endif """ MAIN_MODULE_COMMENT = """ /* The AL module provides core functionality of the AL API, without any non- * standard extensions. * * There are some limitations with the AL module. Stuff like AL_API and * AL_APIENTRY can't be used by code importing it since macros can't be * exported from modules, and there's no way to make aliases for these * properties that can be exported. Luckily AL_API isn't typically needed by * user code since it's used to indicate functions as being imported from the * library, which is only relevant to the declarations made in the module * itself. * * AL_APIENTRY is similarly typically only needed for specifying the calling * convention for functions and function pointers declared in the module. * However, some extensions use callbacks that need user code to define * functions with the same calling convention. Currently this is set to use the * platform's default calling convention (that is, it's defined to nothing), * except on Windows where it's defined to __cdecl. Interestingly, capture-less * lambdas seem to generate conversion operators that match function pointers * of any calling convention, but short of that, the user will be responsible * for ensuring callbacks use the cdecl calling convention on Windows and the * default for other OSs. * * Additionally, enums are declared as global inline constexpr ints. This * should generally be fine, as long as user code doesn't try to use them in * the preprocessor which will no longer recognize or expand them to integer * literals. Being global ints also defines them as actual objects stored in * memory, lvalues whose addresses can be taken, instead of as integer literals * or prvalues, which may have subtle implications. An unnamed enum would be * better here, since the enumerators associate a value with a name and don't * become referenceable objects in memory, except that gives the name a new * type (e.g. typeid(AL_NO_ERROR) != typeid(int)) which could create problems * for type deduction. * * Note that defining AL_LIBTYPE_STATIC, AL_DISABLE_NOEXCEPT, and/or * AL_NO_PROTOTYPES does still influence the function and function pointer type * declarations, but only when compiling the module. The user-defined macros * have no effect when importing the module. */ """ ALC_MODULE_COMMENT = """ /* The ALC module provides core functionality of the device/system ALC API, * without any non-standard extensions. * * There are some limitations with the ALC module. Stuff like ALC_API and * ALC_APIENTRY can't be used by code importing it since macros can't be * exported from modules, and there's no way to make aliases for these * properties that can be exported. Luckily ALC_API isn't typically needed by * user code since it's used to indicate functions as being imported from the * library, which is only relevant to the declarations made in the module * itself. * * ALC_APIENTRY is similarly typically only needed for specifying the calling * convention for functions and function pointers declared in the module. * However, some extensions use callbacks that need user code to define * functions with the same calling convention. Currently this is set to use the * platform's default calling convention (that is, it's defined to nothing), * except on Windows where it's defined to __cdecl. Interestingly, capture-less * lambdas seem to generate conversion operators that match function pointers * of any calling convention, but short of that, the user will be responsible * for ensuring callbacks use the cdecl calling convention on Windows and the * default for other OSs. * * Additionally, enums are declared as global inline constexpr ints. This * should generally be fine as long as user code doesn't try to use them in * the preprocessor, which will no longer recognize or expand them to integer * literals. Being global ints also defines them as actual objects stored in * memory, lvalues whose addresses can be taken, instead of as integer literals * or prvalues, which may have subtle implications. An unnamed enum would be * better here, since the enumerators associate a value with a name and don't * become referenceable objects in memory, except that gives the name a new * type (e.g. typeid(ALC_NO_ERROR) != typeid(int)) which could create problems * for type deduction. * * Note that defining AL_LIBTYPE_STATIC, AL_DISABLE_NOEXCEPT, and/or * ALC_NO_PROTOTYPES does still influence the function and function pointer * type declarations, but only when compiling the module. The user-defined * macros have no effect when importing the module. */ """ PLATFORM_MODULE_PREAMBLE = """ #ifndef {ns}_API #if defined(AL_LIBTYPE_STATIC) #define {ns}_API #elif defined(_WIN32) #define {ns}_API __declspec(dllimport) #else #define {ns}_API extern #endif #endif #ifdef _WIN32 #define {ns}_APIENTRY __cdecl #else #define {ns}_APIENTRY #endif #ifndef AL_DISABLE_NOEXCEPT #define {ns}_API_NOEXCEPT noexcept #else #define {ns}_API_NOEXCEPT #endif #define ENUMDCL inline constexpr auto export module openal.{mod}; """ EXT_MODULE_PREAMBLE = """ #include #include #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 #ifndef AL_DISABLE_NOEXCEPT #define ALC_API_NOEXCEPT noexcept #else #define ALC_API_NOEXCEPT #endif #ifndef AL_API #define AL_API ALC_API #endif #define AL_APIENTRY ALC_APIENTRY #define AL_API_NOEXCEPT ALC_API_NOEXCEPT #define ENUMDCL inline constexpr auto export module openal.ext; export import openal.efx; import openal.std; using alsoft_impl_int64_t = std::int64_t; using alsoft_impl_uint64_t = std::uint64_t; extern "C" struct _GUID; /* NOLINT(*-reserved-identifier) */ """ @dataclasses.dataclass class ResetRegistryState: registry_file: str CONFIGURATION = [ FileToGenerate( "../../include/AL/alc.h", True, "../xml/al.xml", "alc", False, preamble=PLATFORM_HEADER_PREAMBLE.format(prefix="ALC"), ), FileToGenerate( "../../include/AL/al.h", True, "../xml/al.xml", "al", False, preamble=PLATFORM_HEADER_PREAMBLE.format(prefix="AL"), ), FileToGenerate( "../../include/AL/alext.h", True, "../xml/al.xml", ["al", "alc"], True, preamble=EXT_HEADER_PREAMBLE, ), ResetRegistryState("../xml/al.xml"), FileToGenerate( "../../modules/al.cppm", False, "../xml/al.xml", "al", False, preamble=MAIN_MODULE_COMMENT + PLATFORM_MODULE_PREAMBLE.format(ns="AL", mod="al"), ), FileToGenerate( "../../modules/alc.cppm", False, "../xml/al.xml", "alc", False, preamble=ALC_MODULE_COMMENT + PLATFORM_MODULE_PREAMBLE.format(ns="ALC", mod="alc"), ), FileToGenerate( "../../modules/alext.cppm", False, "../xml/al.xml", ["al", "alc"], True, exclude=["ALC_EXT_EFX"], preamble=EXT_MODULE_PREAMBLE, ), FileToGenerate( "../../modules/efx.cppm", False, "../xml/al.xml", ["al", "alc"], True, include=["ALC_EXT_EFX"], preamble="\n#include \n#include \n" + PLATFORM_MODULE_PREAMBLE.format(ns="AL", mod="efx") + "\nimport openal.std;\n", ), ] STANDARD_HEADER = """/* This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * 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 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. * * For more information, please refer to */ /* This file is auto-generated! Please do not edit it manually. * Instead, modify the API in al.xml and regenerate using genheaders.py. * * Last regenerated: {date} */ """.format( date=datetime.datetime.now(datetime.timezone.utc) ) STANDARD_HEADER_TEMPLATE = ( STANDARD_HEADER + """ #ifndef {guard} #define {guard} /* NOLINTBEGIN */ {preamble} {content} #ifdef __cplusplus }} /* extern "C" */ #endif /* NOLINTEND */ #endif /* {guard} */ """ ) STANDARD_MODULE_TEMPLATE = ( STANDARD_HEADER + """ module; {preamble} export extern "C" {{ {content} }} /* extern "C" */ """ ) ENUM_NAME_COLS = 49 def render_api(api: reg.Api, pfn: bool, registry: reg.Registry, header: bool) -> str: """ Renders the C declaration for the given API. Parameters ---------- api : reg.Api The API to render. pfn : bool If api is a reg.Command, whether to render a function pointer (true) or a function declaration (false). registry : reg.Registry The registry to which api belongs. This is used to lookup value ranges. header : bool True if generating a header, false if generating a C++ module. Returns ------- str The C declaration. """ if isinstance(api, reg.Command): if len(api.parameters) == 0: params = "(void)" if header else "()" else: params = f"({', '.join(x.repr.strip() for x in api.parameters)})" if pfn: if header: return ( f"typedef {api.return_type} ({api.namespace}_APIENTRY *" f"{api.pfn_name}){params} {api.namespace}_API_NOEXCEPT17;" ) else: return ( f"using {api.pfn_name} = auto ({api.namespace}_APIENTRY*)" f"{params} {api.namespace}_API_NOEXCEPT -> {api.return_type};" ) else: export = f"{api.namespace}_API " if api.export is not None else "" doc = reg.render_doc_comment(api.doc, registry) if header: return ( f"{doc}{export}{api.return_type} {api.namespace}_APIENTRY " f"{api.name}{params} {api.namespace}_API_NOEXCEPT;" ) else: return ( f"{doc}{export}auto {api.namespace}_APIENTRY " f"{api.name}{params} {api.namespace}_API_NOEXCEPT -> {api.return_type};" ) elif isinstance(api, reg.Enum): doc = reg.render_doc_comment(api.doc, registry, api.property) if header: return f"{doc}{f'#define {api.name} ':<{ENUM_NAME_COLS}}{api.value}" else: deprecated = ( f' [[deprecated("{api.deprecated}")]] ' if api.deprecated is not None else "" ) return f"{doc}{f'ENUMDCL {api.name}{deprecated} = ':<{ENUM_NAME_COLS}}{api.value};" elif header and (isinstance(api, reg.Typedef) or isinstance(api, reg.Verbatim)): return f"{reg.render_doc_comment(api.doc, registry)}{api.repr}" elif isinstance(api, reg.Typedef): return ( f"{reg.render_doc_comment(api.doc, registry)}using {api.name} = {api.type};" ) elif isinstance(api, reg.Verbatim): if api.category == "basetype": print( f"Warning: verbatim basetype not being handled for modules today, define {api.name} in preamble" ) return "" if api.category != "funcpointer": # Is it a string constant? define = api.repr.replace(f" {api.name} ", "").strip() if define.startswith("#define"): define = define[len("#define") :].strip() if define[0] == '"' and define[-1] == '"': return ( f"{f'inline constexpr auto {api.name} = ':<{ENUM_NAME_COLS}}" f"std::to_array({define});" ) return "" # Function pointers are defined in the XML as typedefs, we need to convert this typedef into a using. type = api.repr.strip() if not type.startswith("typedef"): raise ValueError("funcpointer did not start with typedef") type = type[len("typedef") :].strip() if api.name not in type: raise ValueError( "Could not identify name component of function pointer type" ) type = type.replace(api.name, "").rstrip(";") return_type = type[: type.index("(")].strip() type = f"auto {type[type.index('('):]} -> {return_type}".replace("NOEXCEPT17", "NOEXCEPT") return f"{reg.render_doc_comment(api.doc, registry)}using {api.name} = {type};" raise TypeError def render_set( api_set: reg.ApiSet, registry: reg.Registry, current_file: FileToGenerate, include_guard: bool = True, ) -> typing.Generator[str]: """ Renders all APIs in the given API set (feature or extension). Note that this may implicitly generate a new header if the API set is annexed (e.g. EFX). Parameters ---------- api_set : reg.ApiSet The API set to render. registry : reg.Registry The registry to which this API set belongs. Used to lookup value ranges. current_file : Header The header currently being generated. This is used to determine whether to include API-specific requirements, and also to determine whether an annexed API set header needs to be generated. include_guard : bool Whether to generate the #ifndef/#define include guards for this API set specifically. Always false for an annex. Returns ------- iterable of str The lines of C code that make up this API set. Note that one element iterated by this generator may itself have multiple lines. """ if not current_file.is_header: yield f"/*** {api_set.name} ***/" if include_guard and current_file.is_header: yield f"#ifndef {api_set.name}" yield f"#define {api_set.name} 1" if api_set.doc is not None: if len(api_set.doc) == 1: yield f"/* {api_set.doc[0].strip()} */" else: yield f"/* {api_set.doc[0].strip()}" for line in api_set.doc[1:]: yield f" * {line.strip()}" yield " */" annex_header = None if api_set.annex is not None: annex_header = f"{api_set.annex}.h" if ( annex_header is not None and current_file.is_header and os.path.basename(current_file.output_file) != annex_header ): ext_header_file = os.path.join( os.path.dirname(__file__), os.path.dirname(current_file.output_file), annex_header, ) with open(ext_header_file, "w") as f: external_headers = "".join( f"#include <{h.name}>\n" for h in registry.apis.values() if isinstance(h, reg.Include) ) other_headers = "".join( f'#include "{os.path.basename(h.output_file)}"\n' for h in CONFIGURATION if isinstance(h, FileToGenerate) and h.is_header and h.output_file != current_file.output_file ) preamble = f'{external_headers}\n{other_headers}\n#ifdef __cplusplus\nextern "C" {{\n#endif'.strip() f.write( STANDARD_HEADER_TEMPLATE.format( guard=f"AL_{os.path.basename(annex_header).upper().replace('.', '_')}", preamble=preamble, content="\n".join( render_set( api_set, registry, FileToGenerate( ext_header_file, True, current_file.registry_file, current_file.api, current_file.include, current_file.exclude, preamble, ), False, ) ), ) ) yield f'#include "{annex_header}"' if include_guard and current_file.is_header: yield "#endif" yield "" return passes = ( ("non-command", "command-function", "command-pfn") if api_set.is_feature else ("non-command", "command-pfn", "command-function") ) for pass_no, pass_name in enumerate(passes): written_preamble = False last_namespace = None for req_no, requirement in enumerate(api_set.require): if ( requirement.api_specific is not None and requirement.api_specific not in current_file.api ): continue should_write_comment = ( requirement.comment is not None and pass_name != "command-pfn" ) written_comment = False written_any = False for api_name in requirement.apis: # We pop on the last pass to take account of promotions api = registry.apis.get(api_name) if api is None: print(f"Skipping {api_name} for {api_set.name} as it doesn't exist") continue if isinstance(api, reg.Include): continue if pass_name.startswith("command-") != isinstance(api, reg.Command): continue if ( last_namespace is not None and pass_name == "command-function" and last_namespace != api.namespace ): yield "#endif" written_preamble = False if not written_preamble: if pass_name == "command-function": if api_set.is_feature: yield f"#ifndef {api.namespace}_NO_PROTOTYPES" last_namespace = api.namespace else: yield "#ifdef AL_ALEXT_PROTOTYPES" elif pass_name == "command-pfn" and api_set.is_feature: yield f"/* Pointer-to-function types, useful for storing dynamically loaded {api.namespace} entry" yield " * points." yield " */" written_preamble = True if not written_comment and should_write_comment: yield f"/* {requirement.comment} */" written_comment = True api = render_api( api, pass_name == "command-pfn", registry, current_file.is_header ) api_lines = api.splitlines() for line in api_lines: already_set = registry.written.get(api_name) if already_set is not None: if line.startswith("/*") or line.startswith(" *"): continue print( f"{api_name} is used in {api_set.name} but was already written by {already_set}" ) yield f"/*{line}*/" else: yield line if len(api_lines) > 1: yield "" written_any = True # if requirement.comment is not None and written_comment: if ( written_any and req_no != len(api_set.require) - 1 and len(api_lines) <= 1 ): yield "" if pass_name == "command-function" and written_preamble: if api_set.is_feature: yield f"#endif /* {last_namespace}_NO_PROTOTYPES */" else: yield "#endif" if pass_no != 2: yield "" if include_guard and current_file.is_header: yield "#endif" yield "" for requirement in api_set.require: for api in requirement.apis: registry.written[api] = api_set.name def create_header(registry: reg.Registry, header: FileToGenerate): """ Creates a header file using the given configuration and XML registry. Parameters ---------- registry : reg.Registry The registry to get API declarations from. header : Header The header generation configuration. """ output_file = os.path.join(os.path.dirname(__file__), header.output_file) template = ( STANDARD_HEADER_TEMPLATE if header.is_header else STANDARD_MODULE_TEMPLATE ) header = template.format( guard=f"AL_{os.path.basename(output_file).upper().replace('.', '_')}", preamble=header.preamble, content="\n".join( x for y in ( render_set(registry.sets[s], registry, header) for s in header.include ) for x in y ), date=datetime.datetime.now(datetime.timezone.utc), ) with open(output_file, "w") as f: f.write(header) def main(): """ The main entry-point for the header generation script. """ print("Reading registries...") registries: typing.Dict[str, reg.Registry] = {} for registry in set( x.registry_file for x in CONFIGURATION if isinstance(x, FileToGenerate) ): # Read XML relative to this file registry_xml = xml.etree.ElementTree.parse( os.path.join(os.path.dirname(__file__), registry) ) # Parse registries[registry] = reg.Registry(registry_xml) for header in CONFIGURATION: if isinstance(header, ResetRegistryState): registries[header.registry_file].written = dict() continue if isinstance(header.api, str): header.api = [header.api] if header.include is None: header.include = [ k for k, v in registries[header.registry_file].sets.items() if not v.is_feature == header.is_extension_only and (header.exclude is None or k not in header.exclude) and any(a in v.api for a in header.api) ] print(f"Generating {header.output_file}...") create_header(registries[header.registry_file], header) if __name__ == "__main__": main() kcat-openal-soft-75c0059/registry/scripts/reg.py000066400000000000000000000521471512220627100216340ustar00rootroot00000000000000#!/usr/bin/env python3 # -*- encoding utf-8 -*- # Copyright (c) [2025] Dylan Perks (@Perksey) and Contributors # SPDX-License-Identifier: Unlicense import typing import xml.etree.ElementTree import dataclasses """ A purpose-built Khronos-style XML parser for al.xml. Full compatibility, completeness, and correctness with the official Khronos scripts is an explicit non-goal. It is also expected that this script will not be adaptable to non-OpenAL uses. """ # Max columns a doc comment can take up (excluding the " * ") DOC_MAX_COLS = 80 - 3 @dataclasses.dataclass class Requirement: apis: typing.List[str] comment: typing.Optional[str] = None api_specific: typing.Optional[str] = None @dataclasses.dataclass class ApiSet: """ A or an . Properties ---------- is_feature : bool True if this API set is a feature, false if it is an extension. name : str The name of the set. api : list of str The names of the APIs (e.g. "al", "alc") that this API set is supported on. For features, this is usually just a single element (from the "api" attribute). For extensions, this may be multiple (from the "supported" attribute). Requirements may be API-specific. require : list of Requirement The APIs that are referenced by this API set. annex : str Optionally, an annex name for this feature/extension. This is used to extract this API set to a different header/module. doc : list of str, optional Optionally, lines of documentation about this API set. """ is_feature: bool name: str api: typing.List[str] require: typing.List[Requirement] annex: typing.Optional[str] = None doc: typing.Optional[typing.List[str]] = None @dataclasses.dataclass class Parameter: """ Represents a command parameter. Attributes ---------- type : str The C type string. name : str The parameter name. repr : str The C representation of both the type and name. """ type: str name: str repr: str @dataclasses.dataclass class Command: """ Represents a element. Attributes ---------- name : str The name of the command. namespace : str The namespace in which the command resides (e.g. "AL", "ALC") return_type : str The C return type of this command. parameters : list of Parameter The parameters of this command. pfn_name : str The preferred name of the function pointer type for this command. export : str, optional The "api" value that, if matching that of the header being generated, indicates the command is exposed as a linkable native export. doc : list of str, optional A list of documentation lines for this command. deprecated : str, optional If present, the deprecation message for this command. If not present, this command is not deprecated. """ name: str namespace: str return_type: str parameters: typing.List[Parameter] pfn_name: str export: typing.Optional[str] = None doc: typing.Optional[typing.List[str]] = None deprecated: typing.Optional[str] = None @dataclasses.dataclass class Include: """ Represents a file include. Attributes ---------- name : str The name of the include. This is actually the header to be included (i.e. this string is the ... within #include <...>) """ name: str @dataclasses.dataclass class Property: """ Additional metadata for enums that represent properties on classes. This is used to influence rendering of documentation comments. Attributes ---------- on : str The "class" value on which this property resides. type : str The type of the property. e.g. ALuint, ALint, ALfloat, etc. range : str, optional The range of acceptable values for this property. This can be a numerical range like "0.0..=1.0" or an enum group name (for the latter, it's expected that the type attribute is "ALenum" or "ALCenum"). default : str, optional The default value for this property. value_class : str, optional If "type" is a "ALuint" or "ALCuint", the "class" value of the handle stored in this property. """ on: str type: str range: typing.Optional[str] default: typing.Optional[str] value_class: typing.Optional[str] = None @dataclasses.dataclass class Enum: """ Represents an . Attributes ---------- name : str The name of the enum. value : str The enum value. This is the RHS of the #define verbatim. property : Property, optional Additional property metadata, if applicable. See the Property class for more info. groups : list of str, optional A list of strongly-typed groups in which this enum resides. doc : list of str, optional A list of documentation lines for this enum. deprecated : str, optional If present, the deprecation message for this enum. If not present, this enum is not deprecated. """ name: str value: str property: typing.Optional[Property] groups: typing.Optional[typing.List[str]] = None doc: typing.Optional[typing.List[str]] = None deprecated: typing.Optional[str] = None def innertext( tag: xml.etree.ElementTree.Element, recurse_fn: typing.Optional[ typing.Callable[[xml.etree.ElementTree.Element], bool] ] = None, ) -> str: """ Recursively get the text representation of an element's contents. The Python built-in text property does not include the text within nested elements. Parameters ---------- tag : xml.etree.ElementTree.Element The element to get the inner text contents of. recurse_fn : lambda An optional filter to determine which elements to recurse into. Returns ------- str The inner text. """ return ( (tag.text or "") # <-- get the text preceding nested elements. + "".join( # Concatenate the result of this function for each of the nested elements. innertext(e, recurse_fn) for e in tag if recurse_fn is None or recurse_fn(e) ) + (tag.tail or "") # <-- don't forget the text after the nested elements! ) @dataclasses.dataclass class Typedef: """ Represents a i.e. a C typedef. Attributes ---------- type : str The C type on the LHS of the typedef. name : str The name of this typedef i.e. the RHS. repr : str The C representation of this typedef verbatim. doc : list of str, optional A list of documentation lines for this typedef. deprecated : str, optional If present, the deprecation message for this typedef. If not present, this typedef is not deprecated. """ type: str name: str repr: str doc: typing.Optional[typing.List[str]] = None deprecated: typing.Optional[str] = None @dataclasses.dataclass class Verbatim: """ Represents any other type of . The element has always been somewhat abused in the Khronos XMLs in that any old verbatim C code can be shoved in there. Attributes ---------- name : str The name used to refer to this verbatim C code. category : str A category string. Note that "basetype" is handled as a Typedef, see that class for more info. repr : str The verbatim C code. namespace : str The namespace (AL or ALC) in which the type was declared. doc : list of str, optional A list of documentation lines for this definition. deprecated : str, optional If present, the deprecation message for this definition. If not present, this definition is not deprecated. """ name: str category: str repr: str namespace: str doc: typing.Optional[typing.List[str]] = None deprecated: typing.Optional[str] = None def doc_from_element( element: xml.etree.ElementTree.Element, ) -> typing.Optional[typing.List[str]]: """ Extracts the comments from an XML element. The "comment" attribute can be used to define a short documentation comment, as well as the element within the element provided. Both can be used simultaneously if desirable. Parameters ---------- element : xml.etree.ElementTree.Element The element. Returns ------- list of str or None The documentation lines, or None if none were found. """ top_doc = element.attrib.get("comment") rest_of_doc = element.find("comment") if top_doc is None and rest_of_doc is None: return None doclines = [] if top_doc is not None: doclines.append(top_doc) if rest_of_doc is not None: doclines.append("") if rest_of_doc is not None: doclines.extend(l.strip() for l in rest_of_doc.text.strip().splitlines()) return doclines Api = typing.Union[Command, Enum, Typedef, Verbatim] class Registry: """ Represents the OpenAL XML registry (al.xml). Attributes ---------- apis : dict A mapping of API names to API declarations. sets : dict A mapping of API set names (i.e. or names) to their declarations. groups : dict A mapping of strongly-typed enum group names to the enum names contained within them. written : dict A mapping of API names that have already been output as part of generation to the API set that was being output that referenced the API. """ # A map of API names to rendered strings. # Commands, enums, etc are all rolled into here given that C guarantees that none of these identifiers will collide apis: typing.Dict[str, Api] # A map of feature or extension names to API sets sets: typing.Dict[str, ApiSet] groups: typing.Dict[str, typing.List[str]] written: typing.Dict[str, str] = dict() def __init__(self, registry: xml.etree.ElementTree.ElementTree): """ Parses an XML registry. Parameters ---------- registry : xml.etree.ElementTree.ElementTree The parsed XML file. """ self.groups = {} # Get all the declarations using XPath. for enum in registry.findall(".//enums/enum"): # Is this enum in any groups? Groups are comma separated in the group="..." attribute. for group in enum.attrib.get("group", "").split(","): group = group.strip() if group == "": continue # If the group exists, retrieve the existing array. If not, use a new empty one. members = self.groups.get(group, []) if len(members) == 0: # If the group didn't exist, make sure we track the new array we've created. self.groups[group] = members # Add this enum to the group. members.append(enum.attrib["name"]) def return_type(proto: xml.etree.ElementTree.Element) -> str: return innertext(proto, lambda x: x.tag != "name").strip() self.apis = {} # Get all the declarations using XPath. for types in registry.findall(".//types"): namespace = types.attrib["namespace"] for type in types: if type.tag != "type": continue # Find name name = type.attrib.get("name") or type.find("name") if name is None: print(f"Skipping: {type}") continue if not isinstance(name, str): name = name.text name = name.strip() category = type.attrib.get("category") if category == "include": self.apis[name] = Include(name) continue repr = innertext(type).strip() if category == "basetype" and repr.startswith("typedef"): type_deffed = innertext(type, lambda x: x.tag != "name").strip() if type_deffed.startswith("typedef"): type_deffed = type_deffed[len("typedef") :].strip() self.apis[name] = Typedef( type_deffed, name, repr, doc_from_element(type), type.attrib.get("deprecated"), ) continue # Types are verbatim self.apis[name] = Verbatim( name, category, repr, namespace, doc_from_element(type), type.attrib.get("deprecated"), ) # Get all the objects - we do nested iteration because we need to get the namespace attribute! for commands in registry.findall(".//commands"): namespace = commands.attrib.get("namespace", "AL") # Get all declarations in this namespace. for command in commands.iter("command"): proto = command.find("proto") name = proto.find("name") params = command.findall("param") if name is None: print(f"Skipping: {command}") continue name = name.text.strip() self.apis[name] = Command( name, namespace, return_type(proto), [ Parameter( innertext(p, lambda x: x.tag != "name"), p.find("name").text, innertext(p), ) for p in params ], command.attrib.get("funcpointer", f"LP{name.upper()}"), command.attrib.get("export"), doc_from_element(command), command.attrib.get("deprecated"), ) for enum in registry.findall(".//enums/enum"): name = enum.attrib.get("name") value = enum.attrib.get("value") if name is None or value is None: continue value = value.strip() property = enum.find("property") if property is not None: property = Property( property.attrib["on"], property.attrib.get("type"), property.attrib.get("group") or property.attrib.get("range"), property.attrib.get("default"), property.attrib.get("class"), ) groups = enum.attrib.get("group") if groups is not None: groups = [x.strip() for x in groups.split(",")] self.apis[name.strip()] = Enum( name, value, property, groups, doc_from_element(enum), enum.attrib.get("deprecated"), ) self.sets = {} for api_set in ( x for y in ( registry.findall(".//feature"), registry.findall(".//extensions/extension"), ) for x in y # <-- flatten ): # TODO if ever we use , add support here. self.sets[api_set.attrib["name"]] = ApiSet( api_set.tag == "feature", api_set.attrib["name"], ( [api_set.attrib["api"]] if api_set.tag == "feature" else api_set.attrib["supported"].split("|") ), [ Requirement( [y.attrib["name"] for y in x if "name" in y.attrib], x.attrib.get("comment"), x.attrib.get("api"), ) for x in api_set if x.tag == "require" ], api_set.attrib.get("annex"), doc_from_element(api_set), ) def render_doc_comment( documentation: typing.Optional[typing.List[str]], registry: Registry, property: typing.Optional[Property] = None, ) -> str: """ Converts the given documentation lines into a C documentation comment. Parameters ---------- documentation : list of str, optional The documentation lines. If not provided, this function simply returns an empty string for ease of integration. registry : Registry The registry to use to look up enum range (group) values. property : Property, optional Information for an enum representing a class' property, if applicable. Returns ------- str The C documentation comment. """ if documentation is None or len(documentation) == 0: return "" def render_range(range_str: str, prefix_cols: int) -> typing.Generator[str]: # Is it a numeric range? if ".." in range_str: bounds = range_str.split("..") # Do we have two bounds (ignoring whitespace)? if sum(1 for x in bounds if x.strip() != "") > 1: # We have an upper bound. bounds[1] = bounds[1][1:] if "=" in bounds[1] else f"<{bounds[1][1:]}" else: # No upper bound. bounds[1] = "" yield f"{'Range: ':<{prefix_cols}}[{bounds[0]} - {bounds[1]}]" return # Otherwise, it is likely an enum range (we have handled class ranges in the outer function) value_range = [x.strip() for x in range_str.split(",")] ret = f"{'Range: ':<{prefix_cols}}" if len(value_range) > 1: # There are multiple acceptable ranges, so print them in array form. ret += "[" for i, value in enumerate(value_range): if i == 0: # First acceptable value is always on the first line and doesn't need a comma. ret += value elif len(ret) + 2 + len(value) + 1 > DOC_MAX_COLS: # Adding the acceptable value to the current line would push us past the max columns for docs. # Use a new line... yield f"{ret}," ret = f"{' ' * (prefix_cols + 1)}{value}" else: # Use the current line! ret = f"{ret}, {value}" if len(value_range) > 1: ret += "]" yield ret # If the first line comes from the comment="..." attribute, we put this before the property info. # The way we can tell whether this is the case is whether the first line is followed by a blank line. doclines = ( [documentation[0]] if documentation is not None and len(documentation) > 0 else [] ) extended_docs = ( documentation is not None and len(documentation) > 2 and documentation[1].strip() == "" ) if not extended_docs and documentation is not None and len(documentation) > 1: # It's not a blank line splitting the small comment (in the attribute) and the big comment (in the # block), so let's just put all the words atop the property info (if any). doclines.extend(documentation[1:]) if property is not None: doclines.append("") if property is not None: # Figure out how many columns the right hand side of the colon should be indented by. cols = 0 if property.default is not None: cols = len("Default: ") elif property.range is not None or property.value_class is not None: cols = len("Range: ") else: cols = len("Type: ") # Property type. if property.type is not None: doclines.append(f"{'Type: ':<{cols}}{property.type.replace(',', ', ')}") # Range of acceptable values. range_str = property.range if range_str is not None: # Does the range string refer to an enum group? gr = registry.groups.get(range_str) if gr is not None: # Yes, let's expand the enum group before going into render_range. range_str = ",".join(gr) doclines += [x for x in render_range(range_str, cols)] # The class of the ALuint handles. if property.value_class is not None: doclines.append( f"{'Range: ':<{cols}}any valid {property.value_class.title()} ID" ) # Default value. default = property.default if default is not None: if "," in default: default = "{" + default.replace(",", ", ") + "}" doclines.append(f"{'Default: ':<{cols}}{default}") if extended_docs and documentation is not None: doclines.extend(documentation[1:]) if len(doclines) == 0: return "" if len(doclines) == 1: return f"/** {doclines[0]} */\n" return f"/**\n{''.join(f' * {line}'.rstrip(' ') + '\n' for line in doclines)} */\n" kcat-openal-soft-75c0059/registry/xml/000077500000000000000000000000001512220627100176055ustar00rootroot00000000000000kcat-openal-soft-75c0059/registry/xml/al.xml000066400000000000000000011511331512220627100207300ustar00rootroot00000000000000 Copyright (C) 2025 Dylan Perks and Contributors This file, al.xml, is the OpenAL API Registry. The canonical version of the registry, together with documentation and Python generator scripts used to generate C header files for OpenAL, can always be found in the OpenAL Soft repository at https://github.com/kcat/openal-soft. This file is based upon the following documents and reproduces the same copyright notice: - OpenAL Specification and Reference v1.0 Draft Edition (Published June 2000) Copyright (C) 1999-2000 by Loki Software - OpenAL Specification and Reference v1.1 (Published June 2005) Copyright (C) 2005 by authors In addition, this file is based on OpenAL Soft, and makes use of only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length) in accordance with the Lesser General Public License v2.0. Usage of this file alone is unencumbered by LGPL and does not attract the copyleft provisions thereof. Permission is granted to make and distribute verbatim copies of this file provided the copyright notice and this permission notice are preserved on all copies. Permission is granted to copy and distribute translations of this file into another language, under the above conditions for modified versions, except that this permission notice may be stated in a translation approved by the copyright owners. #define OPENAL #define ALAPI AL_API #define ALAPIENTRY AL_APIENTRY typedef char ALboolean; typedef char ALchar; typedef signed char ALbyte; typedef unsigned char ALubyte; typedef short ALshort; typedef unsigned short ALushort; typedef int ALint; typedef unsigned int ALuint; typedef int ALsizei; typedef int ALenum; typedef float ALfloat; typedef double ALdouble; typedef void ALvoid; struct _GUID; #define ALC_EXT_CAPTURE 1 #define ALC_ENUMERATE_ALL_EXT 1 typedef alsoft_impl_int64_t ALint64SOFT; typedef alsoft_impl_uint64_t ALuint64SOFT; #define AL_EXT_FOLDBACK_NAME "AL_EXT_FOLDBACK" typedef void (AL_APIENTRY*LPALFOLDBACKCALLBACK)(ALenum,ALsizei) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY*ALEVENTPROCSOFT)(ALenum eventType, ALuint object, ALuint param, ALsizei length, const ALchar *message, void *userParam) AL_API_NOEXCEPT17; typedef ALsizei (AL_APIENTRY*ALBUFFERCALLBACKTYPESOFT)(ALvoid *userptr, ALvoid *sampledata, ALsizei numbytes) AL_API_NOEXCEPT17; typedef void (AL_APIENTRY*ALDEBUGPROCEXT)(ALenum source, ALenum type, ALuint id, ALenum severity, ALsizei length, const ALchar *message, void *userParam) AL_API_NOEXCEPT17; #define ALCAPI ALC_API #define ALCAPIENTRY ALC_APIENTRY #define ALC_VERSION_0_1 1 typedef struct ALCdevice ALCdevice; typedef struct ALCcontext ALCcontext; typedef char ALCboolean; typedef char ALCchar; typedef signed char ALCbyte; typedef unsigned char ALCubyte; typedef short ALCshort; typedef unsigned short ALCushort; typedef int ALCint; typedef unsigned int ALCuint; typedef int ALCsizei; typedef int ALCenum; typedef float ALCfloat; typedef double ALCdouble; typedef void ALCvoid; typedef alsoft_impl_int64_t ALCint64SOFT; typedef alsoft_impl_uint64_t ALCuint64SOFT; #define ALC_EXT_EFX_NAME "ALC_EXT_EFX" typedef void (ALC_APIENTRY*ALCEVENTPROCTYPESOFT)(ALCenum eventType, ALCenum deviceType, ALCdevice *device, ALCsizei length, const ALCchar *message, void *userParam) ALC_API_NOEXCEPT17; Specifies if the source uses relative coordinates. The angle covered by the inner cone, the area within which the source will not be attenuated by direction. The angle covered by the outer cone, the area outside of which the source will be fully attenuated by direction. A multiplier for the sample rate of the source's buffer. 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. 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. Specifies the current velocity, relative to the position. To change from or to a left handed coordinate system, negate the Z component. Specifies whether source playback loops. Specifies the buffer to provide sound samples for a source. 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. The minimum gain allowed for a source, after distance and cone attenuation are applied (if applicable). The maximum gain allowed for a source, after distance and cone attenuation are applied (if applicable). 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. The number of buffers queued using alSourceQueueBuffers, minus the buffers removed with alSourceUnqueueBuffers. 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. The distance in units that no distance attenuation occurs. At 0.0, no distance attenuation occurs with non-linear attenuation models. Multiplier to exaggerate or diminish distance attenuation. At 0.0, no distance attenuation ever occurs. The gain attenuation applied when the listener is outside of the source's outer cone angle. 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. 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. Scale for source and listener velocities. A multiplier applied to the Speed of Sound. The speed at which sound waves are assumed to travel, when calculating the doppler effect from source and listener velocities. 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. 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). Capture 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). 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). void alEnable ALenum capability void alDisable ALenum capability ALboolean alIsEnabled ALenum capability void alDopplerFactor ALfloat value void alDopplerVelocity ALfloat value void alSpeedOfSound ALfloat value void alDistanceModel ALenum distanceModel const ALchar* alGetString ALenum param void alGetBooleanv ALenum param ALboolean *values void alGetIntegerv ALenum param ALint *values void alGetFloatv ALenum param ALfloat *values void alGetDoublev ALenum param ALdouble *values ALboolean alGetBoolean ALenum param ALint alGetInteger ALenum param ALfloat alGetFloat ALenum param ALdouble alGetDouble ALenum param Obtain the first error generated in the AL context since the last call to this function. ALenum alGetError ALboolean alIsExtensionPresent const ALchar *extname Retrieve the address of a function. The returned function may be context- specific. void* alGetProcAddress const ALchar *fname Retrieve the value of an enum. The returned value may be context-specific. ALenum alGetEnumValue const ALchar *ename void alListenerf ALenum param ALfloat value void alListener3f ALenum param ALfloat value1 ALfloat value2 ALfloat value3 void alListenerfv ALenum param const ALfloat *values void alListeneri ALenum param ALint value void alListener3i ALenum param ALint value1 ALint value2 ALint value3 void alListeneriv ALenum param const ALint *values void alGetListenerf ALenum param ALfloat *value void alGetListener3f ALenum param ALfloat *value1 ALfloat *value2 ALfloat *value3 void alGetListenerfv ALenum param ALfloat *values void alGetListeneri ALenum param ALint *value void alGetListener3i ALenum param ALint *value1 ALint *value2 ALint *value3 void alGetListeneriv ALenum param ALint *values void alGenSources ALsizei n ALuint *sources void alDeleteSources ALsizei n const ALuint *sources ALboolean alIsSource ALuint source void alSourcef ALuint source ALenum param ALfloat value void alSource3f ALuint source ALenum param ALfloat value1 ALfloat value2 ALfloat value3 void alSourcefv ALuint source ALenum param const ALfloat *values void alSourcei ALuint source ALenum param ALint value void alSource3i ALuint source ALenum param ALint value1 ALint value2 ALint value3 void alSourceiv ALuint source ALenum param const ALint *values void alGetSourcef ALuint source ALenum param ALfloat *value void alGetSourcefv ALuint source ALenum param ALfloat *values void alGetSourcei ALuint source ALenum param ALint *value void alGetSourceiv ALuint source ALenum param ALint *values void alGetSource3i ALuint source ALenum param ALint *value1 ALint *value2 ALint *value3 void alGetSource3f ALuint source ALenum param ALfloat *value1 ALfloat *value2 ALfloat *value3 void alSourcePlay ALuint source void alSourceStop ALuint source void alSourceRewind ALuint source void alSourcePause ALuint source void alSourcePlayv ALsizei n const ALuint *sources void alSourceStopv ALsizei n const ALuint *sources void alSourceRewindv ALsizei n const ALuint *sources void alSourcePausev ALsizei n const ALuint *sources void alSourceQueueBuffers ALuint source ALsizei nb const ALuint *buffers void alSourceUnqueueBuffers ALuint source ALsizei nb ALuint *buffers void alGenBuffers ALsizei n ALuint *buffers void alDeleteBuffers ALsizei n const ALuint *buffers ALboolean alIsBuffer ALuint buffer Copies data into the buffer, interpreting it using the specified format and samplerate. void alBufferData ALuint buffer ALenum format const ALvoid *data ALsizei size ALsizei samplerate void alBufferf ALuint buffer ALenum param ALfloat value void alBuffer3f ALuint buffer ALenum param ALfloat value1 ALfloat value2 ALfloat value3 void alBufferfv ALuint buffer ALenum param const ALfloat *values void alBufferi ALuint buffer ALenum param ALint value void alBuffer3i ALuint buffer ALenum param ALint value1 ALint value2 ALint value3 void alBufferiv ALuint buffer ALenum param const ALint *values void alGetBufferf ALuint buffer ALenum param ALfloat *value void alGetBufferfv ALuint buffer ALenum param ALfloat *values void alGetBufferi ALuint buffer ALenum param ALint *value void alGetBufferiv ALuint buffer ALenum param ALint *values void alGetBuffer3i ALuint buffer ALenum param ALint *value1 ALint *value2 ALint *value3 void alGetBuffer3f ALuint buffer ALenum param ALfloat *value1 ALfloat *value2 ALfloat *value3 void alBufferDataStatic ALuint buffer ALenum format ALvoid *data ALsizei size ALsizei freq void alGenEffects ALsizei n ALuint *effects void alDeleteEffects ALsizei n const ALuint *effects ALboolean alIsEffect ALuint effect void alEffecti ALuint effect ALenum param ALint iValue void alEffectiv ALuint effect ALenum param const ALint *piValues void alEffectf ALuint effect ALenum param ALfloat flValue void alEffectfv ALuint effect ALenum param const ALfloat *pflValues void alGetEffecti ALuint effect ALenum param ALint *iValue void alGetEffectiv ALuint effect ALenum param ALint *piValues void alGetEffectf ALuint effect ALenum param ALfloat *flValue void alGetEffectfv ALuint effect ALenum param ALfloat *pflValues void alGenFilters ALsizei n ALuint *filters void alDeleteFilters ALsizei n const ALuint *filters ALboolean alIsFilter ALuint filter void alFilteri ALuint filter ALenum param ALint iValue void alFilteriv ALuint filter ALenum param const ALint *piValues void alFilterf ALuint filter ALenum param ALfloat flValue void alFilterfv ALuint filter ALenum param const ALfloat *pflValues void alGetFilteri ALuint filter ALenum param ALint *iValue void alGetFilteriv ALuint filter ALenum param ALint *piValues void alGetFilterf ALuint filter ALenum param ALfloat *flValue void alGetFilterfv ALuint filter ALenum param ALfloat *pflValues void alGenAuxiliaryEffectSlots ALsizei n ALuint *effectslots void alDeleteAuxiliaryEffectSlots ALsizei n const ALuint *effectslots ALboolean alIsAuxiliaryEffectSlot ALuint effectslot void alAuxiliaryEffectSloti ALuint effectslot ALenum param ALint iValue void alAuxiliaryEffectSlotiv ALuint effectslot ALenum param const ALint *piValues void alAuxiliaryEffectSlotf ALuint effectslot ALenum param ALfloat flValue void alAuxiliaryEffectSlotfv ALuint effectslot ALenum param const ALfloat *pflValues void alGetAuxiliaryEffectSloti ALuint effectslot ALenum param ALint *iValue void alGetAuxiliaryEffectSlotiv ALuint effectslot ALenum param ALint *piValues void alGetAuxiliaryEffectSlotf ALuint effectslot ALenum param ALfloat *flValue void alGetAuxiliaryEffectSlotfv ALuint effectslot ALenum param ALfloat *pflValues void alBufferSubDataSOFT ALuint buffer ALenum format const ALvoid *data ALsizei offset ALsizei length void alRequestFoldbackStart ALenum mode ALsizei count ALsizei length ALfloat *mem LPALFOLDBACKCALLBACK callback void alRequestFoldbackStop void alBufferSamplesSOFT ALuint buffer ALuint samplerate ALenum internalformat ALsizei samples ALenum channels ALenum type const ALvoid *data void alBufferSubSamplesSOFT ALuint buffer ALsizei offset ALsizei samples ALenum channels ALenum type const ALvoid *data void alGetBufferSamplesSOFT ALuint buffer ALsizei offset ALsizei samples ALenum channels ALenum type ALvoid *data ALboolean alIsBufferFormatSupportedSOFT ALenum format void alSourcedSOFT ALuint source ALenum param ALdouble value void alSource3dSOFT ALuint source ALenum param ALdouble value1 ALdouble value2 ALdouble value3 void alSourcedvSOFT ALuint source ALenum param const ALdouble *values void alGetSourcedSOFT ALuint source ALenum param ALdouble *value void alGetSource3dSOFT ALuint source ALenum param ALdouble *value1 ALdouble *value2 ALdouble *value3 void alGetSourcedvSOFT ALuint source ALenum param ALdouble *values void alSourcei64SOFT ALuint source ALenum param ALint64SOFT value void alSource3i64SOFT ALuint source ALenum param ALint64SOFT value1 ALint64SOFT value2 ALint64SOFT value3 void alSourcei64vSOFT ALuint source ALenum param const ALint64SOFT *values void alGetSourcei64SOFT ALuint source ALenum param ALint64SOFT *value void alGetSource3i64SOFT ALuint source ALenum param ALint64SOFT *value1 ALint64SOFT *value2 ALint64SOFT *value3 void alGetSourcei64vSOFT ALuint source ALenum param ALint64SOFT *values void alDeferUpdatesSOFT void alProcessUpdatesSOFT const ALchar* alGetStringiSOFT ALenum pname ALsizei index void alEventControlSOFT ALsizei count const ALenum *types ALboolean enable void alEventCallbackSOFT ALEVENTPROCSOFT callback void *userParam void* alGetPointerSOFT ALenum pname void alGetPointervSOFT ALenum pname void **values void alBufferCallbackSOFT ALuint buffer ALenum format ALsizei freq ALBUFFERCALLBACKTYPESOFT callback ALvoid *userptr void alGetBufferPtrSOFT ALuint buffer ALenum param ALvoid **ptr void alGetBuffer3PtrSOFT ALuint buffer ALenum param ALvoid **ptr0 ALvoid **ptr1 ALvoid **ptr2 void alGetBufferPtrvSOFT ALuint buffer ALenum param ALvoid **ptr void alSourcePlayAtTimeSOFT ALuint source ALint64SOFT start_time void alSourcePlayAtTimevSOFT ALsizei n const ALuint *sources ALint64SOFT start_time void alDebugMessageCallbackEXT ALDEBUGPROCEXT callback void *userParam void alDebugMessageInsertEXT ALenum source ALenum type ALuint id ALenum severity ALsizei length const ALchar *message void alDebugMessageControlEXT ALenum source ALenum type ALenum severity ALsizei count const ALuint *ids ALboolean enable void alPushDebugGroupEXT ALenum source ALuint id ALsizei length const ALchar *message void alPopDebugGroupEXT ALuint alGetDebugMessageLogEXT ALuint count ALsizei logBufSize ALenum *sources ALenum *types ALuint *ids ALenum *severities ALsizei *lengths ALchar *logBuf void alObjectLabelEXT ALenum identifier ALuint name ALsizei length const ALchar *label void alGetObjectLabelEXT ALenum identifier ALuint name ALsizei bufSize ALsizei *length ALchar *label void* alGetPointerEXT ALenum pname void alGetPointervEXT ALenum pname void **values void alEnableDirect ALCcontext *context ALenum capability void alDisableDirect ALCcontext *context ALenum capability ALboolean alIsEnabledDirect ALCcontext *context ALenum capability void alDopplerFactorDirect ALCcontext *context ALfloat value void alSpeedOfSoundDirect ALCcontext *context ALfloat value void alDistanceModelDirect ALCcontext *context ALenum distanceModel const ALchar* alGetStringDirect ALCcontext *context ALenum param void alGetBooleanvDirect ALCcontext *context ALenum param ALboolean *values void alGetIntegervDirect ALCcontext *context ALenum param ALint *values void alGetFloatvDirect ALCcontext *context ALenum param ALfloat *values void alGetDoublevDirect ALCcontext *context ALenum param ALdouble *values ALboolean alGetBooleanDirect ALCcontext *context ALenum param ALint alGetIntegerDirect ALCcontext *context ALenum param ALfloat alGetFloatDirect ALCcontext *context ALenum param ALdouble alGetDoubleDirect ALCcontext *context ALenum param ALenum alGetErrorDirect ALCcontext *context ALboolean alIsExtensionPresentDirect ALCcontext *context const ALchar *extname void* alGetProcAddressDirect ALCcontext *context const ALchar *fname ALenum alGetEnumValueDirect ALCcontext *context const ALchar *ename void alListenerfDirect ALCcontext *context ALenum param ALfloat value void alListener3fDirect ALCcontext *context ALenum param ALfloat value1 ALfloat value2 ALfloat value3 void alListenerfvDirect ALCcontext *context ALenum param const ALfloat *values void alListeneriDirect ALCcontext *context ALenum param ALint value void alListener3iDirect ALCcontext *context ALenum param ALint value1 ALint value2 ALint value3 void alListenerivDirect ALCcontext *context ALenum param const ALint *values void alGetListenerfDirect ALCcontext *context ALenum param ALfloat *value void alGetListener3fDirect ALCcontext *context ALenum param ALfloat *value1 ALfloat *value2 ALfloat *value3 void alGetListenerfvDirect ALCcontext *context ALenum param ALfloat *values void alGetListeneriDirect ALCcontext *context ALenum param ALint *value void alGetListener3iDirect ALCcontext *context ALenum param ALint *value1 ALint *value2 ALint *value3 void alGetListenerivDirect ALCcontext *context ALenum param ALint *values void alGenSourcesDirect ALCcontext *context ALsizei n ALuint *sources void alDeleteSourcesDirect ALCcontext *context ALsizei n const ALuint *sources ALboolean alIsSourceDirect ALCcontext *context ALuint source void alSourcefDirect ALCcontext *context ALuint source ALenum param ALfloat value void alSource3fDirect ALCcontext *context ALuint source ALenum param ALfloat value1 ALfloat value2 ALfloat value3 void alSourcefvDirect ALCcontext *context ALuint source ALenum param const ALfloat *values void alSourceiDirect ALCcontext *context ALuint source ALenum param ALint value void alSource3iDirect ALCcontext *context ALuint source ALenum param ALint value1 ALint value2 ALint value3 void alSourceivDirect ALCcontext *context ALuint source ALenum param const ALint *values void alGetSourcefDirect ALCcontext *context ALuint source ALenum param ALfloat *value void alGetSource3fDirect ALCcontext *context ALuint source ALenum param ALfloat *value1 ALfloat *value2 ALfloat *value3 void alGetSourcefvDirect ALCcontext *context ALuint source ALenum param ALfloat *values void alGetSourceiDirect ALCcontext *context ALuint source ALenum param ALint *value void alGetSource3iDirect ALCcontext *context ALuint source ALenum param ALint *value1 ALint *value2 ALint *value3 void alGetSourceivDirect ALCcontext *context ALuint source ALenum param ALint *values void alSourcePlayDirect ALCcontext *context ALuint source void alSourceStopDirect ALCcontext *context ALuint source void alSourceRewindDirect ALCcontext *context ALuint source void alSourcePauseDirect ALCcontext *context ALuint source void alSourcePlayvDirect ALCcontext *context ALsizei n const ALuint *sources void alSourceStopvDirect ALCcontext *context ALsizei n const ALuint *sources void alSourceRewindvDirect ALCcontext *context ALsizei n const ALuint *sources void alSourcePausevDirect ALCcontext *context ALsizei n const ALuint *sources void alSourceQueueBuffersDirect ALCcontext *context ALuint source ALsizei nb const ALuint *buffers void alSourceUnqueueBuffersDirect ALCcontext *context ALuint source ALsizei nb ALuint *buffers void alGenBuffersDirect ALCcontext *context ALsizei n ALuint *buffers void alDeleteBuffersDirect ALCcontext *context ALsizei n const ALuint *buffers ALboolean alIsBufferDirect ALCcontext *context ALuint buffer void alBufferDataDirect ALCcontext *context ALuint buffer ALenum format const ALvoid *data ALsizei size ALsizei samplerate void alBufferfDirect ALCcontext *context ALuint buffer ALenum param ALfloat value void alBuffer3fDirect ALCcontext *context ALuint buffer ALenum param ALfloat value1 ALfloat value2 ALfloat value3 void alBufferfvDirect ALCcontext *context ALuint buffer ALenum param const ALfloat *values void alBufferiDirect ALCcontext *context ALuint buffer ALenum param ALint value void alBuffer3iDirect ALCcontext *context ALuint buffer ALenum param ALint value1 ALint value2 ALint value3 void alBufferivDirect ALCcontext *context ALuint buffer ALenum param const ALint *values void alGetBufferfDirect ALCcontext *context ALuint buffer ALenum param ALfloat *value void alGetBuffer3fDirect ALCcontext *context ALuint buffer ALenum param ALfloat *value1 ALfloat *value2 ALfloat *value3 void alGetBufferfvDirect ALCcontext *context ALuint buffer ALenum param ALfloat *values void alGetBufferiDirect ALCcontext *context ALuint buffer ALenum param ALint *value void alGetBuffer3iDirect ALCcontext *context ALuint buffer ALenum param ALint *value1 ALint *value2 ALint *value3 void alGetBufferivDirect ALCcontext *context ALuint buffer ALenum param ALint *values void alGenEffectsDirect ALCcontext *context ALsizei n ALuint *effects void alDeleteEffectsDirect ALCcontext *context ALsizei n const ALuint *effects ALboolean alIsEffectDirect ALCcontext *context ALuint effect void alEffectiDirect ALCcontext *context ALuint effect ALenum param ALint iValue void alEffectivDirect ALCcontext *context ALuint effect ALenum param const ALint *piValues void alEffectfDirect ALCcontext *context ALuint effect ALenum param ALfloat flValue void alEffectfvDirect ALCcontext *context ALuint effect ALenum param const ALfloat *pflValues void alGetEffectiDirect ALCcontext *context ALuint effect ALenum param ALint *piValue void alGetEffectivDirect ALCcontext *context ALuint effect ALenum param ALint *piValues void alGetEffectfDirect ALCcontext *context ALuint effect ALenum param ALfloat *pflValue void alGetEffectfvDirect ALCcontext *context ALuint effect ALenum param ALfloat *pflValues void alGenFiltersDirect ALCcontext *context ALsizei n ALuint *filters void alDeleteFiltersDirect ALCcontext *context ALsizei n const ALuint *filters ALboolean alIsFilterDirect ALCcontext *context ALuint filter void alFilteriDirect ALCcontext *context ALuint filter ALenum param ALint iValue void alFilterivDirect ALCcontext *context ALuint filter ALenum param const ALint *piValues void alFilterfDirect ALCcontext *context ALuint filter ALenum param ALfloat flValue void alFilterfvDirect ALCcontext *context ALuint filter ALenum param const ALfloat *pflValues void alGetFilteriDirect ALCcontext *context ALuint filter ALenum param ALint *piValue void alGetFilterivDirect ALCcontext *context ALuint filter ALenum param ALint *piValues void alGetFilterfDirect ALCcontext *context ALuint filter ALenum param ALfloat *pflValue void alGetFilterfvDirect ALCcontext *context ALuint filter ALenum param ALfloat *pflValues void alGenAuxiliaryEffectSlotsDirect ALCcontext *context ALsizei n ALuint *effectslots void alDeleteAuxiliaryEffectSlotsDirect ALCcontext *context ALsizei n const ALuint *effectslots ALboolean alIsAuxiliaryEffectSlotDirect ALCcontext *context ALuint effectslot void alAuxiliaryEffectSlotiDirect ALCcontext *context ALuint effectslot ALenum param ALint iValue void alAuxiliaryEffectSlotivDirect ALCcontext *context ALuint effectslot ALenum param const ALint *piValues void alAuxiliaryEffectSlotfDirect ALCcontext *context ALuint effectslot ALenum param ALfloat flValue void alAuxiliaryEffectSlotfvDirect ALCcontext *context ALuint effectslot ALenum param const ALfloat *pflValues void alGetAuxiliaryEffectSlotiDirect ALCcontext *context ALuint effectslot ALenum param ALint *piValue void alGetAuxiliaryEffectSlotivDirect ALCcontext *context ALuint effectslot ALenum param ALint *piValues void alGetAuxiliaryEffectSlotfDirect ALCcontext *context ALuint effectslot ALenum param ALfloat *pflValue void alGetAuxiliaryEffectSlotfvDirect ALCcontext *context ALuint effectslot ALenum param ALfloat *pflValues void alBufferDataStaticDirect ALCcontext *context ALuint buffer ALenum format ALvoid *data ALsizei size ALsizei freq void alDebugMessageCallbackDirectEXT ALCcontext *context ALDEBUGPROCEXT callback void *userParam void alDebugMessageInsertDirectEXT ALCcontext *context ALenum source ALenum type ALuint id ALenum severity ALsizei length const ALchar *message void alDebugMessageControlDirectEXT ALCcontext *context ALenum source ALenum type ALenum severity ALsizei count const ALuint *ids ALboolean enable void alPushDebugGroupDirectEXT ALCcontext *context ALenum source ALuint id ALsizei length const ALchar *message void alPopDebugGroupDirectEXT ALCcontext *context ALuint alGetDebugMessageLogDirectEXT ALCcontext *context ALuint count ALsizei logBufSize ALenum *sources ALenum *types ALuint *ids ALenum *severities ALsizei *lengths ALchar *logBuf void alObjectLabelDirectEXT ALCcontext *context ALenum identifier ALuint name ALsizei length const ALchar *label void alGetObjectLabelDirectEXT ALCcontext *context ALenum identifier ALuint name ALsizei bufSize ALsizei *length ALchar *label void* alGetPointerDirectEXT ALCcontext *context ALenum pname void alGetPointervDirectEXT ALCcontext *context ALenum pname void **values void alRequestFoldbackStartDirect ALCcontext *context ALenum mode ALsizei count ALsizei length ALfloat *mem LPALFOLDBACKCALLBACK callback void alRequestFoldbackStopDirect ALCcontext *context void alBufferSubDataDirectSOFT ALCcontext *context ALuint buffer ALenum format const ALvoid *data ALsizei offset ALsizei length void alSourcedDirectSOFT ALCcontext *context ALuint source ALenum param ALdouble value void alSource3dDirectSOFT ALCcontext *context ALuint source ALenum param ALdouble value1 ALdouble value2 ALdouble value3 void alSourcedvDirectSOFT ALCcontext *context ALuint source ALenum param const ALdouble *values void alGetSourcedDirectSOFT ALCcontext *context ALuint source ALenum param ALdouble *value void alGetSource3dDirectSOFT ALCcontext *context ALuint source ALenum param ALdouble *value1 ALdouble *value2 ALdouble *value3 void alGetSourcedvDirectSOFT ALCcontext *context ALuint source ALenum param ALdouble *values void alSourcei64DirectSOFT ALCcontext *context ALuint source ALenum param ALint64SOFT value void alSource3i64DirectSOFT ALCcontext *context ALuint source ALenum param ALint64SOFT value1 ALint64SOFT value2 ALint64SOFT value3 void alSourcei64vDirectSOFT ALCcontext *context ALuint source ALenum param const ALint64SOFT *values void alGetSourcei64DirectSOFT ALCcontext *context ALuint source ALenum param ALint64SOFT *value void alGetSource3i64DirectSOFT ALCcontext *context ALuint source ALenum param ALint64SOFT *value1 ALint64SOFT *value2 ALint64SOFT *value3 void alGetSourcei64vDirectSOFT ALCcontext *context ALuint source ALenum param ALint64SOFT *values void alDeferUpdatesDirectSOFT ALCcontext *context void alProcessUpdatesDirectSOFT ALCcontext *context const ALchar* alGetStringiDirectSOFT ALCcontext *context ALenum pname ALsizei index void alEventControlDirectSOFT ALCcontext *context ALsizei count const ALenum *types ALboolean enable void alEventCallbackDirectSOFT ALCcontext *context ALEVENTPROCSOFT callback void *userParam void* alGetPointerDirectSOFT ALCcontext *context ALenum pname void alGetPointervDirectSOFT ALCcontext *context ALenum pname void **values void alBufferCallbackDirectSOFT ALCcontext *context ALuint buffer ALenum format ALsizei freq ALBUFFERCALLBACKTYPESOFT callback ALvoid *userptr void alGetBufferPtrDirectSOFT ALCcontext *context ALuint buffer ALenum param ALvoid **ptr void alGetBuffer3PtrDirectSOFT ALCcontext *context ALuint buffer ALenum param ALvoid **ptr0 ALvoid **ptr1 ALvoid **ptr2 void alGetBufferPtrvDirectSOFT ALCcontext *context ALuint buffer ALenum param ALvoid **ptr void alSourcePlayAtTimeDirectSOFT ALCcontext *context ALuint source ALint64SOFT start_time void alSourcePlayAtTimevDirectSOFT ALCcontext *context ALsizei n const ALuint *sources ALint64SOFT start_time ALenum EAXSetDirect ALCcontext *context const struct _GUID *property_set_id ALuint property_id ALuint source_id ALvoid *value ALuint value_size ALenum EAXGetDirect ALCcontext *context const struct _GUID *property_set_id ALuint property_id ALuint source_id ALvoid *value ALuint value_size ALboolean EAXSetBufferModeDirect ALCcontext *context ALsizei n const ALuint *buffers ALint value ALenum EAXGetBufferModeDirect ALCcontext *context ALuint buffer ALint *pReserved ALCcontext* alcCreateContext ALCdevice *device const ALCint *attrlist Makes the given context the active process-wide context. Passing NULL clears the active context. ALCboolean alcMakeContextCurrent ALCcontext *context void alcProcessContext ALCcontext *context void alcSuspendContext ALCcontext *context void alcDestroyContext ALCcontext *context ALCcontext* alcGetCurrentContext ALCdevice* alcGetContextsDevice ALCcontext *context ALCdevice* alcOpenDevice const ALCchar *devicename ALCboolean alcCloseDevice ALCdevice *device ALCenum alcGetError ALCdevice *device Query for the presence of an extension on the device. Pass a NULL device to query a device-inspecific extension. ALCboolean alcIsExtensionPresent ALCdevice *device const ALCchar *extname Retrieve the address of a function. Given a non-NULL device, the returned function may be device-specific. ALCvoid* alcGetProcAddress ALCdevice *device const ALCchar *funcname Retrieve the value of an enum. Given a non-NULL device, the returned value may be device-specific. ALCenum alcGetEnumValue ALCdevice *device const ALCchar *enumname const ALCchar* alcGetString ALCdevice *device ALCenum param void alcGetIntegerv ALCdevice *device ALCenum param ALCsizei size ALCint *values Opens the named capture device with the given frequency, format, and buffer size. ALCdevice* alcCaptureOpenDevice const ALCchar *devicename ALCuint frequency ALCenum format ALCsizei buffersize ALCboolean alcCaptureCloseDevice ALCdevice *device void alcCaptureStart ALCdevice *device void alcCaptureStop ALCdevice *device void alcCaptureSamples ALCdevice *device ALCvoid *buffer ALCsizei samples ALCdevice* alcLoopbackOpenDeviceSOFT const ALCchar *deviceName ALCboolean alcIsRenderFormatSupportedSOFT ALCdevice *device ALCsizei freq ALCenum channels ALCenum type void alcRenderSamplesSOFT ALCdevice *device ALCvoid *buffer ALCsizei samples void alcDevicePauseSOFT ALCdevice *device void alcDeviceResumeSOFT ALCdevice *device const ALCchar* alcGetStringiSOFT ALCdevice *device ALCenum paramName ALCsizei index ALCboolean alcResetDeviceSOFT ALCdevice *device const ALCint *attribs void alcGetInteger64vSOFT ALCdevice *device ALCenum pname ALsizei size ALCint64SOFT *values ALCboolean alcReopenDeviceSOFT ALCdevice *device const ALCchar *deviceName const ALCint *attribs ALCenum alcEventIsSupportedSOFT ALCenum eventType ALCenum deviceType ALCboolean alcEventControlSOFT ALCsizei count const ALCenum *events ALCboolean enable void alcEventCallbackSOFT ALCEVENTPROCTYPESOFT callback void *userParam ALCvoid* alcGetProcAddress2 ALCdevice *device const ALCchar *funcName ALCboolean alcSetThreadContext ALCcontext *context ALCcontext* alcGetThreadContext 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. 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). kcat-openal-soft-75c0059/resources/000077500000000000000000000000001512220627100171475ustar00rootroot00000000000000kcat-openal-soft-75c0059/resources/openal32.rc000066400000000000000000000040541512220627100211230ustar00rootroot00000000000000#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 kcat-openal-soft-75c0059/resources/resource.h000066400000000000000000000006421512220627100211510ustar00rootroot00000000000000//{{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 kcat-openal-soft-75c0059/resources/router.rc000066400000000000000000000040371512220627100210210ustar00rootroot00000000000000#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 kcat-openal-soft-75c0059/resources/soft_oal.rc000066400000000000000000000040541512220627100213060ustar00rootroot00000000000000#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 kcat-openal-soft-75c0059/router/000077500000000000000000000000001512220627100164555ustar00rootroot00000000000000kcat-openal-soft-75c0059/router/al.cpp000066400000000000000000000215621512220627100175630ustar00rootroot00000000000000 #include "config.h" #include #if HAVE_CXXMODULES import alsoft.router; import openal.al; import openal.efx; import gsl; #define AL_APIENTRY __cdecl #else #include "AL/al.h" #include "AL/efx.h" #include "gsl/gsl" #include "router.h" #endif #define DECL_THUNK1(R,n,T1) \ AL_API auto AL_APIENTRY n(T1 a) noexcept -> R \ { \ auto *iface = GetThreadDriver(); \ if(!iface) iface = CurrentCtxDriver.load(std::memory_order_acquire); \ return gsl::not_null{iface}->n(a); \ } #define DECL_THUNK2(R,n,T1,T2) \ AL_API auto AL_APIENTRY n(T1 a, T2 b) noexcept -> R \ { \ auto *iface = GetThreadDriver(); \ if(!iface) iface = CurrentCtxDriver.load(std::memory_order_acquire); \ return gsl::not_null{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 \ { \ auto *iface = GetThreadDriver(); \ if(!iface) iface = CurrentCtxDriver.load(std::memory_order_acquire); \ return gsl::not_null{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 \ { \ auto *iface = GetThreadDriver(); \ if(!iface) iface = CurrentCtxDriver.load(std::memory_order_acquire); \ return gsl::not_null{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 \ { \ auto *iface = GetThreadDriver(); \ if(!iface) iface = CurrentCtxDriver.load(std::memory_order_acquire); \ return gsl::not_null{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 { auto *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*) kcat-openal-soft-75c0059/router/alc.cpp000066400000000000000000001075251512220627100177320ustar00rootroot00000000000000 #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "alnumeric.h" #include "alstring.h" #include "strutils.hpp" #if HAVE_CXXMODULES import alsoft.router; import gsl; import openal; #define ALC_APIENTRY __cdecl #else #include "AL/alc.h" #include "AL/al.h" #include "AL/alext.h" #include "gsl/gsl" #include "router.h" #endif namespace { using namespace std::string_view_literals; using LPALCdevice = ALCdevice*; void LoadDrivers() { static constinit auto InitOnce = std::once_flag{}; std::call_once(InitOnce, []{ LoadDriverList(); }); } struct FuncExportEntry { std::string_view funcName; void *address; }; #define DECL(x) FuncExportEntry{ #x##sv, reinterpret_cast(&x) } const auto alcFunctions = std::array{ /* NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast) */ 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(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), /* NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast) */ }; #undef DECL struct EnumExportEntry { std::string_view enumName; ALCenum value; }; #define DECL(x) EnumExportEntry{ #x##sv, (x) } constexpr auto alcEnumerations = std::array{ 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), EnumExportEntry{ "AL_INVALID"sv, -1 }, /* Deprecated enum */ 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 -> gsl::czstring { return "No Error"; } [[nodiscard]] constexpr auto GetInvalidDeviceString() noexcept -> gsl::czstring { return "Invalid Device"; } [[nodiscard]] constexpr auto GetInvalidContextString() noexcept -> gsl::czstring { return "Invalid Context"; } [[nodiscard]] constexpr auto GetInvalidEnumString() noexcept -> gsl::czstring { return "Invalid Enum"; } [[nodiscard]] constexpr auto GetInvalidValueString() noexcept -> gsl::czstring { return "Invalid Value"; } [[nodiscard]] constexpr auto GetOutOfMemoryString() noexcept -> gsl::czstring { return "Out of Memory"; } [[nodiscard]] constexpr auto GetExtensionString() noexcept -> gsl::czstring { return "ALC_ENUMERATE_ALL_EXT ALC_ENUMERATION_EXT ALC_EXT_CAPTURE " "ALC_EXT_thread_local_context ALC_SOFT_loopback"; } [[nodiscard]] consteval auto GetExtensionArray() noexcept { constexpr auto extlist = std::string_view{GetExtensionString()}; auto ret = std::array{}; std::ranges::transform(extlist | std::views::split(' '), ret.begin(), [](auto&& namerange) { return std::string_view{namerange.begin(), namerange.end()}; }); return ret; } constexpr auto alcMajorVersion = 1; constexpr auto alcMinorVersion = 1; auto EnumerationLock = std::recursive_mutex{}; /* NOLINT(cert-err58-cpp) May throw on construction */ auto ContextSwitchLock = std::mutex{}; auto LastError = std::atomic{ALC_NO_ERROR}; template class ProtectedIfaceMap { std::mutex IfaceMutex; std::unordered_map IfaceMap{}; public: [[nodiscard]] auto lock() { return IfaceMutex.lock(); } [[nodiscard]] auto unlock() { return IfaceMutex.unlock(); } class IfaceMapLock : public std::unique_lock { public: using std::unique_lock::unique_lock; template void emplace(K&& key, V&& value) { this->mutex()->IfaceMap.emplace(std::forward(key), std::forward(value)); } template void erase(K&& key) { this->mutex()->IfaceMap.erase(std::forward(key)); } template [[nodiscard]] auto lookup_idx(K&& key) -> std::optional { auto iter = this->mutex()->IfaceMap.find(std::forward(key)); if(iter != this->mutex()->IfaceMap.end()) return iter->second; return std::nullopt; } }; [[nodiscard]] auto get_lock() -> IfaceMapLock { return IfaceMapLock{*this}; } }; auto DeviceIfaceMap = ProtectedIfaceMap{}; auto ContextIfaceMap = ProtectedIfaceMap{}; struct DeviceList { std::vector mNames; }; class EnumeratedList { std::vector mNamesStore; std::vector mEnumeratedDevices; public: void clear() noexcept { mEnumeratedDevices.clear(); mNamesStore.clear(); } explicit operator bool() const noexcept { return !mEnumeratedDevices.empty(); } void reserveDeviceCount(usize const count) { mEnumeratedDevices.reserve(count); } void appendDeviceList(gsl::czstring names, u32 idx); void finishEnumeration(); [[nodiscard]] auto getDriverIndexForName(std::string_view name) const -> std::optional; [[nodiscard]] auto getNameData() const noexcept -> gsl::czstring { return mNamesStore.data(); } }; auto DevicesList = EnumeratedList{}; auto AllDevicesList = EnumeratedList{}; auto CaptureDevicesList = EnumeratedList{}; void EnumeratedList::appendDeviceList(gsl::czstring const names, u32 const idx) { auto *name_end = names; if(!name_end) return; auto count = 0_uz; while(*name_end) { TRACE("Enumerated \"{}\", driver {}", name_end, idx); ++count; name_end += strlen(name_end)+1; /* NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) */ } auto const namespan = std::span{names, name_end}; if(namespan.empty()) return; mNamesStore.insert(mNamesStore.cend(), namespan.begin(), namespan.end()); if(idx >= mEnumeratedDevices.size()) mEnumeratedDevices.resize(idx+1); mEnumeratedDevices[idx].mNames.resize(count); } void EnumeratedList::finishEnumeration() { /* Ensure the list is double-null terminated. */ if(mNamesStore.empty()) mNamesStore.emplace_back('\0'); mNamesStore.emplace_back('\0'); auto base = 0_uz; std::ranges::for_each(mEnumeratedDevices, [this,&base](DeviceList &list) { std::ranges::transform(mNamesStore | std::views::split('\0') | std::views::drop(base) | std::views::take(list.mNames.size()), list.mNames.begin(), [](auto&& namerange) { return std::string_view{namerange.begin(), namerange.end()}; }); base += list.mNames.size(); }); } auto EnumeratedList::getDriverIndexForName(std::string_view const name) const -> std::optional { auto idx = 0_u32; std::ignore = std::ranges::find_if(mEnumeratedDevices, [name,&idx](const std::span names) -> bool { if(std::ranges::find(names, name) != names.end()) return true; ++idx; return false; }, &DeviceList::mNames); if(idx < mEnumeratedDevices.size()) return idx; return std::nullopt; } void InitCtxFuncs(DriverIface &iface) { auto *const device = iface.alcGetContextsDevice(iface.alcGetCurrentContext()); auto load_proc = [&iface](T &func, gsl::czstring const name) { /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) */ func = reinterpret_cast(iface.alGetProcAddress(name)); if(!func) ERR("Failed to find entry point for {} in {}", name, wstr_to_utf8(iface.Name)); }; #define LOAD_PROC(x) load_proc(iface.x, #x) 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 auto ALC_APIENTRY alcOpenDevice(ALCchar const *const devicename) noexcept -> ALCdevice* { LoadDrivers(); auto *device = LPALCdevice{nullptr}; auto idx = std::optional{}; /* Prior to the enumeration extension, apps would use 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) { { auto const enumlock = std::lock_guard{EnumerationLock}; if(!DevicesList) std::ignore = alcGetString(nullptr, ALC_DEVICE_SPECIFIER); idx = DevicesList.getDriverIndexForName(devicename); if(!idx) { if(!AllDevicesList) 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 { auto const iter = std::ranges::find_if(DriverList, [&device](DriverIface const &drv) ->bool { if(drv.ALCVer >= MakeALCVer(1, 1) || drv.alcIsExtensionPresent(nullptr, "ALC_ENUMERATION_EXT")) { TRACE("Using default device from driver {}", wstr_to_utf8(drv.Name)); device = drv.alcOpenDevice(nullptr); return true; } return false; }, &DriverIfacePtr::operator*); if(iter == DriverList.end()) { LastError.store(ALC_INVALID_DEVICE); return nullptr; } idx = gsl::narrow_cast(std::distance(DriverList.begin(), iter)); } if(!device) LastError.store(DriverList[idx.value()]->alcGetError(nullptr)); else { try { DeviceIfaceMap.get_lock().emplace(device, idx.value()); } catch(...) { DriverList[idx.value()]->alcCloseDevice(device); device = nullptr; } } return device; } ALC_API auto ALC_APIENTRY alcCloseDevice(ALCdevice *const device) noexcept -> ALCboolean { if(auto lock = DeviceIfaceMap.get_lock(); auto const idx = lock.lookup_idx(device)) { if(!DriverList[*idx]->alcCloseDevice(device)) return ALC_FALSE; lock.erase(device); return ALC_TRUE; } LastError.store(ALC_INVALID_DEVICE); return ALC_FALSE; } ALC_API auto ALC_APIENTRY alcCreateContext(ALCdevice *const device, ALCint const *const attrlist) noexcept -> ALCcontext* { auto const idx = DeviceIfaceMap.get_lock().lookup_idx(device); if(!idx) { LastError.store(ALC_INVALID_DEVICE); return nullptr; } auto *context = DriverList[*idx]->alcCreateContext(device, attrlist); if(context) { try { ContextIfaceMap.get_lock().emplace(context, *idx); } catch(...) { DriverList[*idx]->alcDestroyContext(context); context = nullptr; } } return context; } ALC_API auto ALC_APIENTRY alcMakeContextCurrent(ALCcontext *const context) noexcept -> ALCboolean { auto const ctxlock = std::lock_guard{ContextSwitchLock}; auto idx = std::optional{}; if(context) { idx = ContextIfaceMap.get_lock().lookup_idx(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) { if(auto *const oldiface = GetThreadDriver()) oldiface->alcSetThreadContext(nullptr); if(auto *const oldiface = CurrentCtxDriver.exchange(nullptr)) oldiface->alcMakeContextCurrent(nullptr); } else { if(auto *const oldiface = GetThreadDriver(); oldiface && oldiface != DriverList[*idx].get()) oldiface->alcSetThreadContext(nullptr); if(auto *const oldiface = CurrentCtxDriver.exchange(DriverList[*idx].get()); oldiface && oldiface != DriverList[*idx].get()) oldiface->alcMakeContextCurrent(nullptr); } SetThreadDriver(nullptr); return ALC_TRUE; } ALC_API void ALC_APIENTRY alcProcessContext(ALCcontext *const context) noexcept { if(auto const idx = ContextIfaceMap.get_lock().lookup_idx(context)) return DriverList[*idx]->alcProcessContext(context); LastError.store(ALC_INVALID_CONTEXT); } ALC_API void ALC_APIENTRY alcSuspendContext(ALCcontext *const context) noexcept { if(auto const idx = ContextIfaceMap.get_lock().lookup_idx(context)) return DriverList[*idx]->alcSuspendContext(context); LastError.store(ALC_INVALID_CONTEXT); } ALC_API void ALC_APIENTRY alcDestroyContext(ALCcontext *const context) noexcept { if(auto lock = ContextIfaceMap.get_lock(); auto const idx = lock.lookup_idx(context)) { DriverList[*idx]->alcDestroyContext(context); lock.erase(context); return; } LastError.store(ALC_INVALID_CONTEXT); } ALC_API auto ALC_APIENTRY alcGetCurrentContext() noexcept -> ALCcontext* { auto *iface = GetThreadDriver(); if(!iface) iface = CurrentCtxDriver.load(); return iface ? iface->alcGetCurrentContext() : nullptr; } ALC_API auto ALC_APIENTRY alcGetContextsDevice(ALCcontext *const context) noexcept -> ALCdevice* { if(auto const idx = ContextIfaceMap.get_lock().lookup_idx(context)) return DriverList[*idx]->alcGetContextsDevice(context); LastError.store(ALC_INVALID_CONTEXT); return nullptr; } ALC_API auto ALC_APIENTRY alcGetError(ALCdevice *const device) noexcept -> ALCenum { if(device) { if(auto const idx = DeviceIfaceMap.get_lock().lookup_idx(device)) return DriverList[*idx]->alcGetError(device); return ALC_INVALID_DEVICE; } return LastError.exchange(ALC_NO_ERROR); } ALC_API auto ALC_APIENTRY alcIsExtensionPresent(ALCdevice *const device, ALCchar const *const extname) noexcept -> ALCboolean { if(device) { if(auto const idx = DeviceIfaceMap.get_lock().lookup_idx(device)) return DriverList[*idx]->alcIsExtensionPresent(device, extname); LastError.store(ALC_INVALID_DEVICE); return ALC_FALSE; } auto matchext = [tofind = std::string_view{extname}](std::string_view const entry) { return tofind.size() == entry.size() && al::case_compare(tofind, entry) == 0; }; return std::ranges::any_of(GetExtensionArray(), matchext) ? ALC_TRUE : ALC_FALSE; } ALC_API auto ALC_APIENTRY alcGetProcAddress(ALCdevice *const device, ALCchar const *const funcname) noexcept -> void* { if(device) { if(auto const idx = DeviceIfaceMap.get_lock().lookup_idx(device)) return DriverList[*idx]->alcGetProcAddress(device, funcname); LastError.store(ALC_INVALID_DEVICE); return nullptr; } if(auto const iter = std::ranges::find(alcFunctions, std::string_view{funcname}, &FuncExportEntry::funcName); iter != alcFunctions.end()) return iter->address; return nullptr; } ALC_API auto ALC_APIENTRY alcGetEnumValue(ALCdevice *const device, ALCchar const *const enumname) noexcept -> ALCenum { if(device) { if(auto const idx = DeviceIfaceMap.get_lock().lookup_idx(device)) return DriverList[*idx]->alcGetEnumValue(device, enumname); LastError.store(ALC_INVALID_DEVICE); return 0; } if(auto const iter = std::ranges::find(alcEnumerations, std::string_view{enumname}, &EnumExportEntry::enumName); iter != alcEnumerations.end()) return iter->value; return 0; } ALC_API auto ALC_APIENTRY alcGetString(ALCdevice *const device, ALCenum const param) noexcept -> ALCchar const* { LoadDrivers(); if(device) { if(auto const idx = DeviceIfaceMap.get_lock().lookup_idx(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 GetExtensionString(); case ALC_DEVICE_SPECIFIER: { auto enumlock = std::lock_guard{EnumerationLock}; DevicesList.clear(); DevicesList.reserveDeviceCount(DriverList.size()); auto idx = 0_u32; std::ranges::for_each(DriverList, [&idx](DriverIface const &drv) { /* 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; }, &DriverIfacePtr::operator*); DevicesList.finishEnumeration(); return DevicesList.getNameData(); } case ALC_ALL_DEVICES_SPECIFIER: { auto enumlock = std::lock_guard{EnumerationLock}; AllDevicesList.clear(); AllDevicesList.reserveDeviceCount(DriverList.size()); auto idx = 0_u32; std::ranges::for_each(DriverList, [&idx](DriverIface const &drv) { /* 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; }, &DriverIfacePtr::operator*); AllDevicesList.finishEnumeration(); return AllDevicesList.getNameData(); } case ALC_CAPTURE_DEVICE_SPECIFIER: { auto enumlock = std::lock_guard{EnumerationLock}; CaptureDevicesList.clear(); CaptureDevicesList.reserveDeviceCount(DriverList.size()); auto idx = 0_u32; std::ranges::for_each(DriverList, [&idx](DriverIface const &drv) { if(drv.ALCVer >= MakeALCVer(1, 1) || drv.alcIsExtensionPresent(nullptr, "ALC_EXT_CAPTURE")) CaptureDevicesList.appendDeviceList( drv.alcGetString(nullptr, ALC_CAPTURE_DEVICE_SPECIFIER), idx); ++idx; }, &DriverIfacePtr::operator*); CaptureDevicesList.finishEnumeration(); return CaptureDevicesList.getNameData(); } case ALC_DEFAULT_DEVICE_SPECIFIER: { auto const iter = std::ranges::find_if(DriverList, [](DriverIface const &drv) { return drv.ALCVer >= MakeALCVer(1, 1) || drv.alcIsExtensionPresent(nullptr, "ALC_ENUMERATION_EXT"); }, &DriverIfacePtr::operator*); if(iter != DriverList.end()) return (*iter)->alcGetString(nullptr, ALC_DEFAULT_DEVICE_SPECIFIER); return ""; } case ALC_DEFAULT_ALL_DEVICES_SPECIFIER: { auto const iter = std::ranges::find_if(DriverList, [](const DriverIface &drv) { return drv.alcIsExtensionPresent(nullptr, "ALC_ENUMERATE_ALL_EXT") != ALC_FALSE; }, &DriverIfacePtr::operator*); if(iter != DriverList.end()) return (*iter)->alcGetString(nullptr, ALC_DEFAULT_ALL_DEVICES_SPECIFIER); return ""; } case ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER: { auto const iter = std::ranges::find_if(DriverList, [](const DriverIface &drv) { return drv.ALCVer >= MakeALCVer(1, 1) || drv.alcIsExtensionPresent(nullptr, "ALC_EXT_CAPTURE"); }, &DriverIfacePtr::operator*); if(iter != DriverList.end()) return (*iter)->alcGetString(nullptr, ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER); return ""; } default: LastError.store(ALC_INVALID_ENUM); break; } return nullptr; } ALC_API void ALC_APIENTRY alcGetIntegerv(ALCdevice *const device, ALCenum const param, ALCsizei const size, ALCint *const values) noexcept { if(device) { if(auto const idx = DeviceIfaceMap.get_lock().lookup_idx(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 auto ALC_APIENTRY alcCaptureOpenDevice(ALCchar const *const devicename, ALCuint const frequency, ALCenum const format, ALCsizei const buffersize) noexcept -> ALCdevice* { LoadDrivers(); auto *device = LPALCdevice{nullptr}; auto idx = std::optional{}; if(devicename && *devicename != '\0') { { auto const enumlock = std::lock_guard{EnumerationLock}; if(!CaptureDevicesList) 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 { auto const iter = std::ranges::find_if(DriverList, [frequency,format,buffersize,&device](DriverIface const &drv) -> bool { if(drv.ALCVer >= MakeALCVer(1, 1) || drv.alcIsExtensionPresent(nullptr, "ALC_EXT_CAPTURE")) { TRACE("Using default capture device from driver {}", wstr_to_utf8(drv.Name)); device = drv.alcCaptureOpenDevice(nullptr, frequency, format, buffersize); return true; } return false; }, &DriverIfacePtr::operator*); if(iter == DriverList.end()) { LastError.store(ALC_INVALID_DEVICE); return nullptr; } idx = gsl::narrow_cast(std::distance(DriverList.begin(), iter)); } if(!device) LastError.store(DriverList[idx.value()]->alcGetError(nullptr)); else { try { DeviceIfaceMap.get_lock().emplace(device, idx.value()); } catch(...) { DriverList[idx.value()]->alcCaptureCloseDevice(device); device = nullptr; } } return device; } ALC_API auto ALC_APIENTRY alcCaptureCloseDevice(ALCdevice *const device) noexcept -> ALCboolean { if(auto lock = DeviceIfaceMap.get_lock(); auto const idx = lock.lookup_idx(device)) { if(!DriverList[*idx]->alcCaptureCloseDevice(device)) return ALC_FALSE; lock.erase(device); return ALC_TRUE; } LastError.store(ALC_INVALID_DEVICE); return ALC_FALSE; } ALC_API void ALC_APIENTRY alcCaptureStart(ALCdevice *const device) noexcept { if(auto const idx = DeviceIfaceMap.get_lock().lookup_idx(device)) return DriverList[*idx]->alcCaptureStart(device); LastError.store(ALC_INVALID_DEVICE); } ALC_API void ALC_APIENTRY alcCaptureStop(ALCdevice *const device) noexcept { if(auto const idx = DeviceIfaceMap.get_lock().lookup_idx(device)) return DriverList[*idx]->alcCaptureStop(device); LastError.store(ALC_INVALID_DEVICE); } ALC_API void ALC_APIENTRY alcCaptureSamples(ALCdevice *const device, ALCvoid *const buffer, ALCsizei const samples) noexcept { if(auto const idx = DeviceIfaceMap.get_lock().lookup_idx(device)) return DriverList[*idx]->alcCaptureSamples(device, buffer, samples); LastError.store(ALC_INVALID_DEVICE); } ALC_API auto ALC_APIENTRY alcSetThreadContext(ALCcontext *const context) noexcept -> ALCboolean { if(!context) { if(auto *const oldiface = GetThreadDriver(); oldiface && !oldiface->alcSetThreadContext(nullptr)) return ALC_FALSE; SetThreadDriver(nullptr); return ALC_TRUE; } auto err = ALCenum{ALC_INVALID_CONTEXT}; if(auto const idx = ContextIfaceMap.get_lock().lookup_idx(context); idx && DriverList[*idx]->alcSetThreadContext) { if(DriverList[*idx]->alcSetThreadContext(context)) { std::call_once(DriverList[*idx]->InitOnceCtx, [idx]{InitCtxFuncs(*DriverList[*idx]);}); if(auto *const oldiface = GetThreadDriver(); 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 auto ALC_APIENTRY alcGetThreadContext() noexcept -> ALCcontext* { if(auto *const iface = GetThreadDriver()) return iface->alcGetThreadContext(); return nullptr; } ALC_API auto ALC_APIENTRY alcLoopbackOpenDeviceSOFT(ALCchar const *const deviceName) noexcept -> ALCdevice* { LoadDrivers(); auto *device = LPALCdevice{nullptr}; auto idx = std::optional{}; if(deviceName && *deviceName != '\0') { { auto const enumlock = std::lock_guard{EnumerationLock}; if(!DevicesList) std::ignore = alcGetString(nullptr, ALC_DEVICE_SPECIFIER); idx = DevicesList.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); if(!DriverList[*idx]->alcLoopbackOpenDeviceSOFT) { LastError.store(ALC_INVALID_VALUE); WARN("Loopback not supported on device {}", deviceName); return nullptr; } device = DriverList[*idx]->alcLoopbackOpenDeviceSOFT(deviceName); } else { auto const iter = std::ranges::find_if(DriverList, [&device](DriverIface const &drv) ->bool { if(drv.alcLoopbackOpenDeviceSOFT) { TRACE("Using default loopback device from driver {}", wstr_to_utf8(drv.Name)); device = drv.alcLoopbackOpenDeviceSOFT(nullptr); return true; } return false; }, &DriverIfacePtr::operator*); if(iter == DriverList.end()) { LastError.store(ALC_INVALID_DEVICE); return nullptr; } idx = gsl::narrow_cast(std::distance(DriverList.begin(), iter)); } if(!device) LastError.store(DriverList[idx.value()]->alcGetError(nullptr)); else { try { DeviceIfaceMap.get_lock().emplace(device, idx.value()); } catch(...) { DriverList[idx.value()]->alcCloseDevice(device); device = nullptr; } } return device; } ALC_API auto ALC_APIENTRY alcIsRenderFormatSupportedSOFT(ALCdevice *const device, ALCsizei const freq, ALCenum const channels, ALCenum const type) noexcept -> ALCboolean { if(auto const idx = DeviceIfaceMap.get_lock().lookup_idx(device)) return DriverList[*idx]->alcIsRenderFormatSupportedSOFT(device, freq, channels, type); LastError.store(ALC_INVALID_DEVICE); return ALC_FALSE; } ALC_API auto ALC_APIENTRY alcRenderSamplesSOFT(ALCdevice *const device, ALCvoid *const buffer, ALCsizei const samples) noexcept -> void { /* NOTE: Not real-time safe! If you need this to be real-time safe, * retrieve and use the driver-specific function directly (i.e. from * alcGetProcAddress(device, "alcRenderSamplesSOFT")) rather than the * global router thunk. */ if(auto const idx = DeviceIfaceMap.get_lock().lookup_idx(device)) return DriverList[*idx]->alcRenderSamplesSOFT(device, buffer, samples); LastError.store(ALC_INVALID_DEVICE); } kcat-openal-soft-75c0059/router/router.cpp000066400000000000000000000366001512220627100205060ustar00rootroot00000000000000 #include "config.h" #if !HAVE_CXXMODULES #include "router.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include "alstring.h" #include "filesystem.h" #include "opthelpers.h" #include "strutils.hpp" #include "version.h" #if HAVE_CXXMODULES import alsoft.router; import gsl; import openal.alc; #else #include "AL/alc.h" #include "gsl/gsl" #endif namespace { /* C++23 has this... */ struct contains_fn_ { template S, typename T, typename Proj=std::identity> requires std::indirect_binary_predicate, const T*> constexpr auto operator()(I first, S last, const T& value, Proj proj={}) const -> bool { return std::ranges::find(std::move(first), last, value, proj) != last; } template requires std::indirect_binary_predicate, Proj>, const T*> constexpr auto operator()(R&& r, const T& value, Proj proj={}) const -> bool { const auto last = std::ranges::end(r); return std::ranges::find(std::ranges::begin(r), last, value, proj) != last; } }; inline constexpr auto contains = contains_fn_{}; auto gAcceptList = std::vector{}; auto gRejectList = std::vector{}; void AddModule(HMODULE const module, std::wstring_view const name) { if(contains(DriverList, module, &DriverIface::Module)) { TRACE("Skipping already-loaded module {}", decltype(std::declval()){module}); FreeLibrary(module); return; } if(contains(DriverList, name, &DriverIface::Name)) { TRACE("Skipping similarly-named module {}", wstr_to_utf8(name)); FreeLibrary(module); return; } if(!gAcceptList.empty()) { if(std::ranges::none_of(gAcceptList, [name](std::wstring_view const accept) { return al::case_compare(name, accept) == 0; })) { TRACE("{} not found in ALROUTER_ACCEPT, skipping", wstr_to_utf8(name)); FreeLibrary(module); return; } } if(std::ranges::any_of(gRejectList, [name](std::wstring_view const reject) { return al::case_compare(name, reject) == 0; })) { TRACE("{} found in ALROUTER_REJECT, skipping", wstr_to_utf8(name)); FreeLibrary(module); return; } auto &newdrv = *DriverList.emplace_back(std::make_unique(name, module)); /* Load required functions. */ auto loadok = true; auto do_load = [module,name](T &func, gsl::czstring const fname) -> bool { if(auto const ptr = GetProcAddress(module, fname)) { /* NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast) * We can't directly cast between function pointer types, so we * need to cast to void* first. */ auto const vptr = reinterpret_cast(ptr); func = reinterpret_cast(vptr); /* NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast) */ return true; } ERR("Failed to find entry point for {} in {}", fname, wstr_to_utf8(name)); return false; }; #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) { auto alc_ver = std::array{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](T &func, gsl::czstring const fname) -> void { if(auto const ptr = GetProcAddress(module, fname)) { /* NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast) */ auto const vptr = reinterpret_cast(ptr); func = reinterpret_cast(vptr); /* NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast) */ return; } WARN("Failed to find optional entry point for {} in {}", fname, wstr_to_utf8(name)); }; #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](T &func, gsl::czstring const fname) -> bool { if(auto const ptr = newdrv.alcGetProcAddress(nullptr, fname)) { /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) */ func = reinterpret_cast(ptr); return true; } ERR("Failed to find entry point for {} in {}", fname, wstr_to_utf8(name)); return false; }; #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); } if(newdrv.alcIsExtensionPresent(nullptr, "ALC_SOFT_loopback")) { LOAD_PROC(alcLoopbackOpenDeviceSOFT); LOAD_PROC(alcIsRenderFormatSupportedSOFT); LOAD_PROC(alcRenderSamplesSOFT); } #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(std::wstring_view const path) { TRACE("Searching for drivers in {}...", wstr_to_utf8(path)); auto srchPath = std::wstring{path}; srchPath += L"\\*oal.dll"; auto fdata = WIN32_FIND_DATAW{}; auto const 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)); if(auto const mod = LoadLibraryW(srchPath.c_str())) AddModule(mod, std::data(fdata.cFileName)); else WARN("Could not load {}", wstr_to_utf8(srchPath)); } while(FindNextFileW(srchHdl, &fdata)); FindClose(srchHdl); } auto GetLoadedModuleDirectory(gsl::cwzstring const name, std::wstring *const moddir) -> bool { auto module = HMODULE{nullptr}; if(name) { module = GetModuleHandleW(name); if(!module) return false; } moddir->assign(256, '\0'); auto res = GetModuleFileNameW(module, moddir->data(), gsl::narrow_cast(moddir->size())); if(res >= moddir->size()) { do { moddir->append(256, '\0'); res = GetModuleFileNameW(module, moddir->data(), gsl::narrow_cast(moddir->size())); } while(res >= moddir->size()); } moddir->resize(res); const auto sep0 = moddir->rfind('/'); const 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 const list = al::getenv(L"ALROUTER_ACCEPT")) { std::ranges::for_each(*list | std::views::split(','), [](auto&& subrange) { if(!subrange.empty()) gAcceptList.emplace_back(std::wstring_view{subrange.begin(), subrange.end()}); }); } if(auto const list = al::getenv(L"ALROUTER_REJECT")) { std::ranges::for_each(*list | std::views::split(','), [](auto&& subrange) { if(!subrange.empty()) gRejectList.emplace_back(std::wstring_view{subrange.begin(), subrange.end()}); }); } auto dll_path = std::wstring{}; if(GetLoadedModuleDirectory(L"OpenAL32.dll", &dll_path)) TRACE("Got DLL path {}", wstr_to_utf8(dll_path)); auto cwd_path = std::wstring{}; if(auto const curpath = std::filesystem::current_path(); !curpath.empty()) { if constexpr(std::same_as) cwd_path = curpath.native(); else cwd_path = utf8_to_wstr(al::u8_as_char(curpath.u8string())); } 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)); auto proc_path = std::wstring{}; if(GetLoadedModuleDirectory(nullptr, &proc_path)) TRACE("Got proc path {}", wstr_to_utf8(proc_path)); auto sys_path = std::wstring{}; if(auto 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. */ std::ranges::stable_partition(DriverList, [](DriverIface const &drv) { return drv.ALCVer >= MakeALCVer(1, 1) || drv.alcIsExtensionPresent(nullptr, "ALC_ENUMERATE_ALL_EXT") || drv.alcIsExtensionPresent(nullptr, "ALC_ENUMERATION_EXT"); }, &DriverIfacePtr::operator*); /* 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)); } /* NOLINTNEXTLINE(misc-use-internal-linkage) Needs external linkage for Windows. */ auto APIENTRY DllMain(HINSTANCE, DWORD const reason, void*) -> BOOL { switch(reason) { case DLL_PROCESS_ATTACH: if(auto const logfname = al::getenv(L"ALROUTER_LOGFILE")) { LogFile.open(fs::path(*logfname)); if(!LogFile.is_open()) ERR("Could not open log file: {}", wstr_to_utf8(*logfname)); } if(auto const loglev = al::getenv("ALROUTER_LOGLEVEL")) { auto end = gsl::zstring{}; auto const 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 = gsl::narrow_cast(l); } break; case DLL_THREAD_ATTACH: break; case DLL_THREAD_DETACH: break; case DLL_PROCESS_DETACH: DriverList.clear(); break; } return TRUE; } kcat-openal-soft-75c0059/router/router.h000066400000000000000000000213411512220627100201470ustar00rootroot00000000000000#ifndef ROUTER_ROUTER_H #define ROUTER_ROUTER_H #include #include #include #include #include #include #include #include #include "AL/alc.h" #include "AL/al.h" #include "AL/alext.h" #include "fmt/base.h" #include "fmt/ostream.h" constexpr auto MakeALCVer(int const major, int const 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}; LPALCLOOPBACKOPENDEVICESOFT alcLoopbackOpenDeviceSOFT{nullptr}; LPALCISRENDERFORMATSUPPORTEDSOFT alcIsRenderFormatSupportedSOFT{nullptr}; LPALCRENDERSAMPLESSOFT alcRenderSamplesSOFT{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 const Name; HMODULE const Module{nullptr}; int ALCVer{0}; std::once_flag InitOnceCtx; template DriverIface(T&& name, HMODULE const 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; using LPCDriverIface = DriverIface const*; inline auto DriverList = std::vector{}; inline thread_local auto ThreadCtxDriver = LPCDriverIface{}; inline auto CurrentCtxDriver = std::atomic{}; inline auto GetThreadDriver() noexcept -> LPCDriverIface { return ThreadCtxDriver; } inline void SetThreadDriver(LPCDriverIface const driver) noexcept { ThreadCtxDriver = driver; } enum class eLogLevel { None = 0, Error = 1, Warn = 2, Trace = 3, }; inline auto LogLevel = eLogLevel::Error; inline auto LogFile = std::ofstream{}; /* NOLINT(cert-err58-cpp) */ template void TRACE(fmt::format_string const fmt, Args&& ...args) { if(LogLevel >= eLogLevel::Trace) { auto &file = LogFile ? LogFile : std::cerr; auto msg = fmt::vformat(fmt, fmt::make_format_args(args...)); fmt::vprint(file, "AL Router (II): {}\n", fmt::make_format_args(msg)); file.flush(); } } template void WARN(fmt::format_string const fmt, Args&& ...args) { if(LogLevel >= eLogLevel::Warn) { auto &file = LogFile ? LogFile : std::cerr; auto msg = fmt::vformat(fmt, fmt::make_format_args(args...)); fmt::vprint(file, "AL Router (WW): {}\n", fmt::make_format_args(msg)); file.flush(); } } template void ERR(fmt::format_string const fmt, Args&& ...args) { if(LogLevel >= eLogLevel::Error) { auto &file = LogFile ? LogFile : std::cerr; auto msg = fmt::vformat(fmt, fmt::make_format_args(args...)); fmt::vprint(file, "AL Router (EE): {}\n", fmt::make_format_args(msg)); file.flush(); } } void LoadDriverList(); #endif /* ROUTER_ROUTER_H */ kcat-openal-soft-75c0059/tests/000077500000000000000000000000001512220627100162775ustar00rootroot00000000000000kcat-openal-soft-75c0059/tests/CMakeLists.txt000066400000000000000000000010731512220627100210400ustar00rootroot00000000000000add_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) kcat-openal-soft-75c0059/tests/example.t.cpp000066400000000000000000000003641512220627100207030ustar00rootroot00000000000000#include #include class ExampleTest : public ::testing::Test { }; TEST_F(ExampleTest, Basic) { // just making sure we compile ALuint source, buffer; ALfloat offset; ALenum state; } kcat-openal-soft-75c0059/utils/000077500000000000000000000000001512220627100162755ustar00rootroot00000000000000kcat-openal-soft-75c0059/utils/CIAIR.def000066400000000000000000007320201512220627100176100ustar00rootroot00000000000000# 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 kcat-openal-soft-75c0059/utils/IRC_1005.def000066400000000000000000001024641512220627100200460ustar00rootroot00000000000000# 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 kcat-openal-soft-75c0059/utils/MIT_KEMAR.def000066400000000000000000001311021512220627100203230ustar00rootroot00000000000000# 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" kcat-openal-soft-75c0059/utils/MIT_KEMAR_sofa.def000066400000000000000000000035571512220627100213470ustar00rootroot00000000000000# 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 kcat-openal-soft-75c0059/utils/SCUT_KEMAR.def000066400000000000000000000037571512220627100204660ustar00rootroot00000000000000# 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 kcat-openal-soft-75c0059/utils/alsoft-config/000077500000000000000000000000001512220627100210305ustar00rootroot00000000000000kcat-openal-soft-75c0059/utils/alsoft-config/CMakeLists.txt000066400000000000000000000012411512220627100235660ustar00rootroot00000000000000project(alsoft-config) if(Qt6Widgets_FOUND) qt6_wrap_ui(UIS mainwindow.ui) qt6_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 Qt6::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) if(TARGET alsoft.build_version) add_dependencies(alsoft-config alsoft.build_version) endif() endif() kcat-openal-soft-75c0059/utils/alsoft-config/main.cpp000066400000000000000000000003021512220627100224530ustar00rootroot00000000000000#include "mainwindow.h" #include int main(int argc, char *argv[]) { const auto a = QApplication(argc, argv); auto w = MainWindow{}; w.show(); return a.exec(); } kcat-openal-soft-75c0059/utils/alsoft-config/mainwindow.cpp000066400000000000000000001670411512220627100237210ustar00rootroot00000000000000 #include "config.h" #include "config_backends.h" #include "config_simd.h" #include "mainwindow.h" #include #include #include #include #include #include #include #include #include #include #include "ui_mainwindow.h" #include "verstr.h" #ifdef _WIN32 #include #include #endif #include "almalloc.h" #include "gsl/gsl" namespace { /* NOLINTBEGIN(cert-err58-cpp) */ struct BackendNamePair { QString backend_name; QString full_string; }; const auto backendList = std::array{ #if HAVE_PIPEWIRE BackendNamePair{ QStringLiteral("pipewire"), QStringLiteral("PipeWire") }, #endif #if HAVE_PULSEAUDIO BackendNamePair{ QStringLiteral("pulse"), QStringLiteral("PulseAudio") }, #endif #if HAVE_WASAPI BackendNamePair{ QStringLiteral("wasapi"), QStringLiteral("WASAPI") }, #endif #if HAVE_COREAUDIO BackendNamePair{ QStringLiteral("core"), QStringLiteral("CoreAudio") }, #endif #if HAVE_OPENSL BackendNamePair{ QStringLiteral("opensl"), QStringLiteral("OpenSL") }, #endif #if HAVE_ALSA BackendNamePair{ QStringLiteral("alsa"), QStringLiteral("ALSA") }, #endif #if HAVE_SOLARIS BackendNamePair{ QStringLiteral("solaris"), QStringLiteral("Solaris") }, #endif #if HAVE_SNDIO BackendNamePair{ QStringLiteral("sndio"), QStringLiteral("SndIO") }, #endif #if HAVE_OSS BackendNamePair{ QStringLiteral("oss"), QStringLiteral("OSS") }, #endif #if HAVE_DSOUND BackendNamePair{ QStringLiteral("dsound"), QStringLiteral("DirectSound") }, #endif #if HAVE_WINMM BackendNamePair{ QStringLiteral("winmm"), QStringLiteral("Windows Multimedia") }, #endif #if HAVE_PORTAUDIO BackendNamePair{ QStringLiteral("port"), QStringLiteral("PortAudio") }, #endif #if HAVE_JACK BackendNamePair{ QStringLiteral("jack"), QStringLiteral("JACK") }, #endif BackendNamePair{ QStringLiteral("null"), QStringLiteral("Null Output") }, #if HAVE_WAVE BackendNamePair{ QStringLiteral("wave"), QStringLiteral("Wave Writer") }, #endif }; struct NameValuePair { QString name; QString value; }; const auto speakerModeList = std::array{ NameValuePair{ QStringLiteral("Autodetect"), QStringLiteral("") }, NameValuePair{ QStringLiteral("Mono"), QStringLiteral("mono") }, NameValuePair{ QStringLiteral("Stereo"), QStringLiteral("stereo") }, NameValuePair{ QStringLiteral("Quadraphonic"), QStringLiteral("quad") }, NameValuePair{ QStringLiteral("5.1 Surround"), QStringLiteral("surround51") }, NameValuePair{ QStringLiteral("6.1 Surround"), QStringLiteral("surround61") }, NameValuePair{ QStringLiteral("7.1 Surround"), QStringLiteral("surround71") }, NameValuePair{ QStringLiteral("3D7.1"), QStringLiteral("3d71") }, NameValuePair{ QStringLiteral("Ambisonic, 1st Order"), QStringLiteral("ambi1") }, NameValuePair{ QStringLiteral("Ambisonic, 2nd Order"), QStringLiteral("ambi2") }, NameValuePair{ QStringLiteral("Ambisonic, 3rd Order"), QStringLiteral("ambi3") }, NameValuePair{ QStringLiteral("Ambisonic, 4th Order"), QStringLiteral("ambi4") }, }; const auto sampleTypeList = std::array{ NameValuePair{ QStringLiteral("Autodetect"), QStringLiteral("") }, NameValuePair{ QStringLiteral("8-bit int"), QStringLiteral("int8") }, NameValuePair{ QStringLiteral("8-bit uint"), QStringLiteral("uint8") }, NameValuePair{ QStringLiteral("16-bit int"), QStringLiteral("int16") }, NameValuePair{ QStringLiteral("16-bit uint"), QStringLiteral("uint16") }, NameValuePair{ QStringLiteral("32-bit int"), QStringLiteral("int32") }, NameValuePair{ QStringLiteral("32-bit uint"), QStringLiteral("uint32") }, NameValuePair{ QStringLiteral("32-bit float"), QStringLiteral("float32") }, }; const auto resamplerList = std::array{ NameValuePair{ QStringLiteral("Point"), QStringLiteral("point") }, NameValuePair{ QStringLiteral("Linear"), QStringLiteral("linear") }, NameValuePair{ QStringLiteral("Cubic Spline"), QStringLiteral("spline") }, NameValuePair{ QStringLiteral("Default (Cubic Spline)"), QStringLiteral("") }, NameValuePair{ QStringLiteral("4-point Gaussian"), QStringLiteral("gaussian") }, NameValuePair{ QStringLiteral("11th order Sinc (fast)"), QStringLiteral("fast_bsinc12") }, NameValuePair{ QStringLiteral("11th order Sinc"), QStringLiteral("bsinc12") }, NameValuePair{ QStringLiteral("23rd order Sinc (fast)"), QStringLiteral("fast_bsinc24") }, NameValuePair{ QStringLiteral("23rd order Sinc"), QStringLiteral("bsinc24") }, NameValuePair{ QStringLiteral("47th order Sinc (fast)"), QStringLiteral("fast_bsinc48") }, NameValuePair{ QStringLiteral("47th order Sinc"), QStringLiteral("bsinc48") }, }; const auto stereoModeList = std::array{ NameValuePair{ QStringLiteral("Autodetect"), QStringLiteral("") }, NameValuePair{ QStringLiteral("Speakers"), QStringLiteral("speakers") }, NameValuePair{ QStringLiteral("Headphones"), QStringLiteral("headphones") }, }; const auto stereoEncList = std::array{ NameValuePair{ QStringLiteral("Default"), QStringLiteral("") }, NameValuePair{ QStringLiteral("Basic"), QStringLiteral("panpot") }, NameValuePair{ QStringLiteral("UHJ"), QStringLiteral("uhj") }, NameValuePair{ QStringLiteral("Binaural"), QStringLiteral("hrtf") }, }; const auto ambiFormatList = std::array{ NameValuePair{ QStringLiteral("Default"), QStringLiteral("") }, NameValuePair{ QStringLiteral("AmbiX (ACN, SN3D)"), QStringLiteral("ambix") }, NameValuePair{ QStringLiteral("Furse-Malham"), QStringLiteral("fuma") }, NameValuePair{ QStringLiteral("ACN, N3D"), QStringLiteral("acn+n3d") }, NameValuePair{ QStringLiteral("ACN, FuMa"), QStringLiteral("acn+fuma") }, }; const auto hrtfModeList = std::array{ NameValuePair{ QStringLiteral("1st Order Ambisonic"), QStringLiteral("ambi1") }, NameValuePair{ QStringLiteral("2nd Order Ambisonic"), QStringLiteral("ambi2") }, NameValuePair{ QStringLiteral("3rd Order Ambisonic"), QStringLiteral("ambi3") }, NameValuePair{ QStringLiteral("4th Order Ambisonic"), QStringLiteral("ambi4") }, NameValuePair{ QStringLiteral("Default (Full)"), QStringLiteral("") }, NameValuePair{ QStringLiteral("Full"), QStringLiteral("full") }, }; /* NOLINTEND(cert-err58-cpp) */ auto GetDefaultIndex(const std::span list) -> uint8_t { auto iter = std::ranges::find(list, QStringLiteral(""), &NameValuePair::value); if(iter != list.end()) return gsl::narrow(std::distance(list.begin(), iter)); throw std::runtime_error{"Failed to find default entry"}; } #ifdef Q_OS_WIN32 using WCharBufferPtr = std::unique_ptr; #endif QString getDefaultConfigName() { #ifdef Q_OS_WIN32 auto *fname = "alsoft.ini"; auto base = std::invoke([]() -> QString { auto buffer = WCharBufferPtr{}; if(const auto hr = SHGetKnownFolderPath(FOLDERID_RoamingAppData, KF_FLAG_DONT_UNEXPAND, nullptr, al::out_ptr(buffer)); SUCCEEDED(hr)) return QString::fromWCharArray(buffer.get()); return QString{}; }); #else auto *fname = "alsoft.conf"; auto base = QString{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 auto base = std::invoke([]() -> QString { auto buffer = WCharBufferPtr{}; if(const auto hr = SHGetKnownFolderPath(FOLDERID_RoamingAppData, KF_FLAG_DONT_UNEXPAND, nullptr, al::out_ptr(buffer)); SUCCEEDED(hr)) return QString::fromWCharArray(buffer.get()); return QString{}; }); #else auto base = QString{qgetenv("XDG_DATA_HOME")}; if(base.isEmpty()) { base = qgetenv("HOME"); if(!base.isEmpty()) base += "/.local/share"; } #endif return base; } auto getAllDataPaths(const QString &append) -> QStringList { auto list = QStringList{}; list.append(getBaseDataPath()); #ifdef Q_OS_WIN32 // TODO: Common AppData path #else auto paths = QString{qgetenv("XDG_DATA_DIRS")}; if(paths.isEmpty()) paths = "/usr/local/share/:/usr/share/"; list += paths.split(QChar(':'), Qt::SkipEmptyParts); #endif for(auto iter = list.begin();iter != list.end();) { if(iter->isEmpty()) iter = list.erase(iter); else { iter->append(append); ++iter; } } return list; } auto getValueFromName(const std::span list, const QString &str) -> QString { if(const auto iter = std::ranges::find(list, str, &NameValuePair::name); iter != list.end()) return iter->value; return QString{}; } auto getNameFromValue(const std::span list, const QString &str) -> QString { if(const auto iter = std::ranges::find(list, str, &NameValuePair::value); iter != list.end()) return iter->name; return QString{}; } auto getCheckState(const QVariant &var) -> Qt::CheckState { if(var.isNull()) return Qt::PartiallyChecked; if(var.toBool()) return Qt::Checked; return Qt::Unchecked; } auto getCheckValue(const QCheckBox *checkbox) -> QString { 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(item.name); ui->channelConfigCombo->adjustSize(); for(auto &item : sampleTypeList) ui->sampleFormatCombo->addItem(item.name); ui->sampleFormatCombo->adjustSize(); for(auto &item : stereoModeList) ui->stereoModeCombo->addItem(item.name); ui->stereoModeCombo->adjustSize(); for(auto &item : stereoEncList) ui->stereoEncodingComboBox->addItem(item.name); ui->stereoEncodingComboBox->adjustSize(); for(auto &item : ambiFormatList) ui->ambiFormatComboBox->addItem(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); /* QCheckBox::checkStateChanged was added in Qt 6.7, and * QCheckBox::stateChanged causes a deprecation warning since 6.9. Pick * whichever one we have. */ const auto qcb_checkstatechanged = std::invoke([] { if constexpr(requires { &T::checkStateChanged; }) return &T::checkStateChanged; else return &T::stateChanged; }); connect(ui->stereoEncodingComboBox, qcb_cicint, this, &MainWindow::enableApplyButton); connect(ui->ambiFormatComboBox, qcb_cicint, this, &MainWindow::enableApplyButton); connect(ui->outputLimiterCheckBox, qcb_checkstatechanged, this, &MainWindow::enableApplyButton); connect(ui->outputDitherCheckBox, qcb_checkstatechanged, this, &MainWindow::enableApplyButton); connect(ui->decoderHQModeCheckBox, qcb_checkstatechanged, this, &MainWindow::enableApplyButton); connect(ui->decoderDistCompCheckBox, qcb_checkstatechanged, this, &MainWindow::enableApplyButton); connect(ui->decoderNFEffectsCheckBox, qcb_checkstatechanged, 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, qcb_checkstatechanged, 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, qcb_checkstatechanged, this, &MainWindow::enableApplyButton); connect(ui->enableSSE2CheckBox, qcb_checkstatechanged, this, &MainWindow::enableApplyButton); connect(ui->enableSSE3CheckBox, qcb_checkstatechanged, this, &MainWindow::enableApplyButton); connect(ui->enableSSE41CheckBox, qcb_checkstatechanged, this, &MainWindow::enableApplyButton); connect(ui->enableNeonCheckBox, qcb_checkstatechanged, 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, qcb_checkstatechanged, this, &MainWindow::enableApplyButton); connect(ui->defaultReverbComboBox, qcb_cicint, this, &MainWindow::enableApplyButton); connect(ui->enableEaxReverbCheck, qcb_checkstatechanged, this, &MainWindow::enableApplyButton); connect(ui->enableStdReverbCheck, qcb_checkstatechanged, this, &MainWindow::enableApplyButton); connect(ui->enableAutowahCheck, qcb_checkstatechanged, this, &MainWindow::enableApplyButton); connect(ui->enableChorusCheck, qcb_checkstatechanged, this, &MainWindow::enableApplyButton); connect(ui->enableCompressorCheck, qcb_checkstatechanged, this, &MainWindow::enableApplyButton); connect(ui->enableDistortionCheck, qcb_checkstatechanged, this, &MainWindow::enableApplyButton); connect(ui->enableEchoCheck, qcb_checkstatechanged, this, &MainWindow::enableApplyButton); connect(ui->enableEqualizerCheck, qcb_checkstatechanged, this, &MainWindow::enableApplyButton); connect(ui->enableFlangerCheck, qcb_checkstatechanged, this, &MainWindow::enableApplyButton); connect(ui->enableFrequencyShifterCheck, qcb_checkstatechanged, this, &MainWindow::enableApplyButton); connect(ui->enableModulatorCheck, qcb_checkstatechanged, this, &MainWindow::enableApplyButton); connect(ui->enableDedicatedCheck, qcb_checkstatechanged, this, &MainWindow::enableApplyButton); connect(ui->enablePitchShifterCheck, qcb_checkstatechanged, this, &MainWindow::enableApplyButton); connect(ui->enableVocalMorpherCheck, qcb_checkstatechanged, this, &MainWindow::enableApplyButton); connect(ui->enableEaxCheck, qcb_checkstatechanged, this, &MainWindow::enableApplyButton); connect(ui->pulseAutospawnCheckBox, qcb_checkstatechanged, this, &MainWindow::enableApplyButton); connect(ui->pulseAllowMovesCheckBox, qcb_checkstatechanged, this, &MainWindow::enableApplyButton); connect(ui->pulseFixRateCheckBox, qcb_checkstatechanged, this, &MainWindow::enableApplyButton); connect(ui->pulseAdjLatencyCheckBox, qcb_checkstatechanged, this, &MainWindow::enableApplyButton); connect(ui->pwireAssumeAudioCheckBox, qcb_checkstatechanged, this, &MainWindow::enableApplyButton); connect(ui->pwireRtMixCheckBox, qcb_checkstatechanged, this, &MainWindow::enableApplyButton); connect(ui->wasapiResamplerCheckBox, qcb_checkstatechanged, this, &MainWindow::enableApplyButton); connect(ui->jackAutospawnCheckBox, qcb_checkstatechanged, this, &MainWindow::enableApplyButton); connect(ui->jackConnectPortsCheckBox, qcb_checkstatechanged, this, &MainWindow::enableApplyButton); connect(ui->jackRtMixCheckBox, qcb_checkstatechanged, 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, qcb_checkstatechanged, this, &MainWindow::enableApplyButton); connect(ui->alsaMmapCheckBox, qcb_checkstatechanged, 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, qcb_checkstatechanged, this, &MainWindow::enableApplyButton); ui->backendListWidget->setCurrentRow(0); ui->tabWidget->setCurrentIndex(0); std::ranges::for_each(std::views::iota(1, ui->backendListWidget->count()), [widget=ui->backendListWidget](int idx) { widget->setRowHidden(idx, true); }); for(size_t i{0};i < backendList.size();++i) { auto items = ui->backendListWidget->findItems(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 { const auto 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()); } auto MainWindow::collectHrtfs() const -> QStringList { QStringList ret; QStringList processed; for(int i = 0;i < ui->hrtfFileList->count();i++) { const auto dir = QDir(ui->hrtfFileList->item(i)->text()); const auto fnames = dir.entryList(QDir::Files | QDir::Readable, QDir::Name); for(const auto &fname : fnames) { if(!fname.endsWith(QStringLiteral(".mhr"), Qt::CaseInsensitive)) continue; const auto fullname = dir.absoluteFilePath(fname); if(processed.contains(fullname)) continue; processed.push_back(fullname); const auto name = fname.left(fname.length()-4); if(!ret.contains(name)) ret.push_back(name); else { auto j = size_t{2}; do { if(const auto s = name+" #"+QString::number(j); !ret.contains(s)) { ret.push_back(s); break; } ++j; } while(true); } } } if(ui->defaultHrtfPathsCheckBox->isChecked()) { const auto paths = getAllDataPaths(QStringLiteral("/openal/hrtf")); for(auto const &pathname : paths) { const auto dir = QDir{pathname}; const auto fnames = dir.entryList(QDir::Files | QDir::Readable, QDir::Name); for(auto const &fname : fnames) { if(!fname.endsWith(QStringLiteral(".mhr"), Qt::CaseInsensitive)) continue; const auto fullname = dir.absoluteFilePath(fname); if(processed.contains(fullname)) continue; processed.push_back(fullname); const auto name = fname.left(fname.length()-4); if(!ret.contains(name)) ret.push_back(name); else { size_t i{2}; do { const auto 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() { const auto fname = QFileDialog::getOpenFileName(this, tr("Select Files")); if(fname.isEmpty() == false) loadConfig(fname); } void MainWindow::loadConfig(const QString &fname) { const auto settings = QSettings{fname, QSettings::IniFormat}; const auto sampletype = settings.value(QStringLiteral("sample-type")).toString(); ui->sampleFormatCombo->setCurrentIndex(0); if(sampletype.isEmpty() == false) { const auto str = getNameFromValue(sampleTypeList, sampletype); if(!str.isEmpty()) { const int j{ui->sampleFormatCombo->findText(str)}; if(j > 0) ui->sampleFormatCombo->setCurrentIndex(j); } } auto channelconfig = settings.value(QStringLiteral("channels")).toString(); ui->channelConfigCombo->setCurrentIndex(0); if(channelconfig.isEmpty() == false) { if(channelconfig == QStringLiteral("surround51rear")) channelconfig = QStringLiteral("surround51"); const auto str = getNameFromValue(speakerModeList, channelconfig); if(!str.isEmpty()) { const int j{ui->channelConfigCombo->findText(str)}; if(j > 0) ui->channelConfigCombo->setCurrentIndex(j); } } const auto 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(); const auto defaultResamplerIndex = GetDefaultIndex(resamplerList); ui->resamplerSlider->setValue(defaultResamplerIndex); ui->resamplerLabel->setText(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;i < std::ssize(resamplerList);i++) { if(auto& [name, value] = resamplerList[gsl::narrow(i)]; resampler == value) { ui->resamplerSlider->setValue(i); ui->resamplerLabel->setText(name); break; } } const auto stereomode = settings.value(QStringLiteral("stereo-mode")).toString().trimmed(); ui->stereoModeCombo->setCurrentIndex(0); if(stereomode.isEmpty() == false) { if(const auto str = getNameFromValue(stereoModeList, stereomode); !str.isEmpty()) { if(const auto j = ui->stereoModeCombo->findText(str); j > 0) ui->stereoModeCombo->setCurrentIndex(j); } } ui->periodSizeEdit->clear(); if(const auto periodsize = settings.value("period_size").toInt(); periodsize >= 64) { ui->periodSizeEdit->insert(QString::number(periodsize)); updatePeriodSizeSlider(); } ui->periodCountEdit->clear(); if(const auto periodcount = settings.value("periods").toInt(); 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")))); ui->stereoEncodingComboBox->setCurrentIndex(0); if(const auto stereopan = settings.value(QStringLiteral("stereo-encoding")).toString(); stereopan.isEmpty() == false) { const auto str = getNameFromValue(stereoEncList, stereopan); if(!str.isEmpty()) { const int j{ui->stereoEncodingComboBox->findText(str)}; if(j > 0) ui->stereoEncodingComboBox->setCurrentIndex(j); } } ui->ambiFormatComboBox->setCurrentIndex(0); if(const auto ambiformat = settings.value(QStringLiteral("ambi-format")).toString(); ambiformat.isEmpty() == false) { const auto 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")))); ui->decoderSpeakerDistSpinBox->setValue(settings.value(QStringLiteral("decoder/speaker-dist"), 1.0).toDouble()); 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/3d71")).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(); const auto defaultHrtfModeIndex = GetDefaultIndex(hrtfModeList); ui->hrtfmodeSlider->setValue(defaultHrtfModeIndex); ui->hrtfmodeLabel->setText(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 == hrtfModeList[i].value) { ui->hrtfmodeSlider->setValue(static_cast(i)); ui->hrtfmodeLabel->setText(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()) { const auto hrtfs = collectHrtfs(); Q_FOREACH(const QString &name, hrtfs) ui->preferredHrtfComboBox->addItem(name); } const auto 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('-'))) { std::ranges::for_each(backendList | std::views::filter([&backend](const BackendNamePair &names) { return backend == names.backend_name; }), [uilist=ui->enabledBackendList](const BackendNamePair &names) { uilist->addItem(names.full_string); }); } else if(backend.size() > 1) { const auto backendref = QStringView{backend}.right(backend.size()-1); std::ranges::for_each(backendList | std::views::filter([backendref](const BackendNamePair &names) { return backendref == names.backend_name; }), [uilist=ui->disabledBackendList](const BackendNamePair &names) { uilist->addItem(names.full_string); }); } } ui->backendCheckBox->setChecked(lastWasEmpty); } const auto 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() { const auto 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 { auto settings = QSettings{fname, QSettings::IniFormat}; /* HACK: Compound any stringlist values into a comma-separated string. */ auto allkeys = settings.allKeys(); Q_FOREACH(const QString &key, allkeys) { const auto 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())); const auto 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"), resamplerList[gsl::narrow(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)); const auto 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/3d71"), 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"), hrtfModeList[gsl::narrow(ui->hrtfmodeSlider->value())].value); if(ui->preferredHrtfComboBox->currentIndex() == 0) settings.setValue(QStringLiteral("default-hrtf"), QString{}); else { const auto 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(); std::ranges::for_each(std::views::iota(0, ui->enabledBackendList->count()), [&strlist,list=ui->enabledBackendList](int idx) { const auto label = list->item(idx)->text(); std::ranges::for_each(backendList | std::views::filter([&label](const BackendNamePair &names) { return label == names.full_string; }), [&strlist](const BackendNamePair &names) { strlist.append(names.backend_name); }); }); std::ranges::for_each(std::views::iota(0, ui->disabledBackendList->count()), [&strlist,list=ui->disabledBackendList](int idx) { const auto label = list->item(idx)->text(); std::ranges::for_each(backendList | std::views::filter([&label](const BackendNamePair &names) { return label == names.full_string; }), [&strlist](const BackendNamePair &names) { strlist.append(QChar{'-'}+names.backend_name); }); }); 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 { const auto 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) { const auto 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(resamplerList[gsl::narrow(num)].name); enableApplyButton(); } void MainWindow::updatePeriodSizeEdit(int size) { ui->periodSizeEdit->clear(); if(size >= 64) ui->periodSizeEdit->insert(QString::number(size)); enableApplyButton(); } void MainWindow::updatePeriodSizeSlider() { const auto 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(); } } const auto 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(); const auto pos = static_cast(floor(log2(value) + 0.5)); ui->jackBufferSizeSlider->setSliderPosition(pos); enableApplyButton(); } void MainWindow::updateHrtfModeLabel(int num) { ui->hrtfmodeLabel->setText(hrtfModeList[static_cast(num)].name); enableApplyButton(); } void MainWindow::addHrtfFile() { const auto 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::ranges::for_each(selected, std::default_delete{}); enableApplyButton(); } } void MainWindow::updateHrtfRemoveButton() const { ui->hrtfRemoveButton->setEnabled(!ui->hrtfFileList->selectedItems().empty()); } void MainWindow::showEnabledBackendMenu(QPoint pt) { auto actionMap = QHash{}; pt = ui->enabledBackendList->mapToGlobal(pt); auto ctxmenu = QMenu{}; auto *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) { const auto &backend = backendList[i].full_string; auto *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); } auto *gotAction = ctxmenu.exec(pt); if(gotAction == removeAction) { QList> selected{ui->enabledBackendList->selectedItems()}; std::ranges::for_each(selected, 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) { auto actionMap = QHash{}; pt = ui->disabledBackendList->mapToGlobal(pt); auto ctxmenu = QMenu{}; auto *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) { const auto &backend = backendList[i].full_string; auto *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::ranges::for_each(selected, 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() { auto current = ui->ossDefaultDeviceLine->text(); if(current.isEmpty()) current = ui->ossDefaultDeviceLine->placeholderText(); const auto fname = QFileDialog::getOpenFileName(this, tr("Select Playback Device"), current); if(!fname.isEmpty()) { ui->ossDefaultDeviceLine->setText(fname); enableApplyButton(); } } void MainWindow::selectOSSCapture() { auto current = ui->ossDefaultCaptureLine->text(); if(current.isEmpty()) current = ui->ossDefaultCaptureLine->placeholderText(); const auto fname = QFileDialog::getOpenFileName(this, tr("Select Capture Device"), current); if(!fname.isEmpty()) { ui->ossDefaultCaptureLine->setText(fname); enableApplyButton(); } } void MainWindow::selectSolarisPlayback() { auto current = ui->solarisDefaultDeviceLine->text(); if(current.isEmpty()) current = ui->solarisDefaultDeviceLine->placeholderText(); const auto fname = QFileDialog::getOpenFileName(this, tr("Select Playback Device"), current); if(!fname.isEmpty()) { ui->solarisDefaultDeviceLine->setText(fname); enableApplyButton(); } } void MainWindow::selectWaveOutput() { const auto 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(); } } kcat-openal-soft-75c0059/utils/alsoft-config/mainwindow.h000066400000000000000000000037721512220627100233660ustar00rootroot00000000000000#ifndef MAINWINDOW_H #define MAINWINDOW_H #include #include #include namespace Ui { class MainWindow; } class MainWindow final : 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() const; 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); [[nodiscard]] auto collectHrtfs() const -> QStringList; void loadConfig(const QString &fname); void saveConfig(const QString &fname) const; }; #endif // MAINWINDOW_H kcat-openal-soft-75c0059/utils/alsoft-config/mainwindow.ui000066400000000000000000002235511512220627100235530ustar00rootroot00000000000000 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: 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) kcat-openal-soft-75c0059/utils/alsoft-config/verstr.cpp000066400000000000000000000002651512220627100230640ustar00rootroot00000000000000 #include "verstr.h" #include "version.h" QString GetVersionString() { return QStringLiteral(ALSOFT_VERSION "-" ALSOFT_GIT_COMMIT_HASH " (" ALSOFT_GIT_BRANCH " branch)."); } kcat-openal-soft-75c0059/utils/alsoft-config/verstr.h000066400000000000000000000001521512220627100225240ustar00rootroot00000000000000#ifndef VERSTR_H #define VERSTR_H #include QString GetVersionString(); #endif /* VERSTR_H */ kcat-openal-soft-75c0059/utils/makemhr/000077500000000000000000000000001512220627100177215ustar00rootroot00000000000000kcat-openal-soft-75c0059/utils/makemhr/loaddef.cpp000066400000000000000000001772471512220627100220450ustar00rootroot00000000000000/* * 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 "config.h" #include "loaddef.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "albit.h" #include "alnumeric.h" #include "alstring.h" #include "filesystem.h" #include "fmt/base.h" #include "fmt/ostream.h" #include "makemhr.h" #include "polyphase_resampler.h" #include "sofa-support.h" #include "mysofa.h" #if HAVE_CXXMODULES import gsl; #else #include "gsl/gsl" #endif namespace { using namespace std::string_view_literals; // Constants for accessing the token reader's ring buffer. constexpr auto TRRingBits = 16u; constexpr auto TRRingSize = 1u << TRRingBits; constexpr auto TRRingMask = TRRingSize - 1u; // The token reader's load interval in bytes. constexpr auto TRLoadSize = TRRingSize >> 2u; // 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 auto MinRadius = 0.05; constexpr auto MaxRadius = 0.15; // The maximum number of channels that can be addressed for a WAVE file // source listed in the data set definition. constexpr auto MaxWaveChannels = 65535u; // 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, }; // 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 std::span startbytes, const std::string_view filename, TokenReaderT *tr) { tr->mName = filename.substr(std::max(filename.rfind('/')+1, filename.rfind('\\')+1)); tr->mLine = 1; tr->mColumn = 1; tr->mIn = 0; tr->mOut = 0; if(!startbytes.empty()) { Expects(startbytes.size() <= tr->mRing.size()); std::ranges::copy(startbytes, tr->mRing.begin()); tr->mIn += std::ssize(startbytes); } } // Prime the reader's ring buffer, and return a result indicating that there // is text to process. auto TrLoad(TokenReaderT *tr) -> bool { auto &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 & std::streamsize{TRRingMask}; const auto count = std::streamsize{TRRingSize} - in; if(count < toLoad) { istream.read(std::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(std::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 true; return false; } // 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(std::cerr, "\nError ({}:{}:{}): ", tr->mName, line, column); fmt::println(std::cerr, 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) { while(TrLoad(tr)) { auto ch = tr->mRing[tr->mOut&TRRingMask]; tr->mOut++; if(ch == '\n') { tr->mLine += 1; tr->mColumn = 1; break; } tr->mColumn += 1; } } // Skips to the next token. auto TrSkipWhitespace(TokenReaderT *tr) -> bool { while(TrLoad(tr)) { const auto ch = tr->mRing[tr->mOut&TRRingMask]; if(isspace(ch)) { tr->mOut++; if(ch == '\n') { tr->mLine += 1; tr->mColumn = 1; } else tr->mColumn += 1; } else if(ch == '#') TrSkipLine(tr); else return true; } return false; } // 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) -> bool { if(!TrSkipWhitespace(tr)) return false; const auto 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) -> bool { if(!TrSkipWhitespace(tr)) return false; auto out = tr->mOut; auto len = 0_uz; while(len < op.size() && out < tr->mIn) { if(tr->mRing[out&TRRingMask] != op[len]) break; ++len; ++out; } return len == op.size(); } /* 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 == '_' || std::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) -> bool { auto col = tr->mColumn; if(TrSkipWhitespace(tr)) { col = tr->mColumn; auto len = 0u; auto temp = std::array{}; auto ch = tr->mRing[tr->mOut&TRRingMask]; if(ch == '+' || ch == '-') { temp[len] = ch; len++; tr->mOut++; } auto digis = 0u; 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 false; } 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 false; } return true; } } TrErrorAt(tr, tr->mLine, col, "Expected an integer."); return false; } // Reads and validates (including bounds) a float token. auto TrReadFloat(TokenReaderT *tr, const double loBound, const double hiBound, double *value) -> bool { auto col = tr->mColumn; if(TrSkipWhitespace(tr)) { col = tr->mColumn; auto temp = std::array{}; auto len = 0u; auto ch = tr->mRing[tr->mOut&TRRingMask]; if(ch == '+' || ch == '-') { temp[len] = ch; len++; tr->mOut++; } auto digis = 0u; 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 false; } 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 false; } return true; } } else tr->mColumn += len; } TrErrorAt(tr, tr->mLine, col, "Expected a float."); return false; } // 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++; auto len = 0_uz; 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) -> bool { auto col = tr->mColumn; if(TrSkipWhitespace(tr)) { col = tr->mColumn; auto len = 0_uz; 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 true; } TrErrorAt(tr, tr->mLine, col, "Expected '{}' operator.", op); return false; } /************************* *** 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 std::endian order, const uint bytes, uint32_t *out) -> bool { auto in = std::array{}; istream.read(in.data(), static_cast(bytes)); if(istream.gcount() != static_cast(bytes)) { fmt::println(std::cerr, "\nError: Bad read from file '{}'.", filename); return false; } auto tmpval = std::bit_cast(in); if constexpr(std::endian::native == std::endian::little) { if(order != std::endian::little) tmpval = al::byteswap(tmpval) >> ((4-bytes)*8); } else { if(order != std::endian::big) tmpval = al::byteswap(tmpval); else tmpval >>= ((4-bytes)*8); } *out = tmpval; return true; } // 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 std::endian order, uint64_t *out) -> bool { auto in = std::array{}; istream.read(in.data(), in.size()); if(istream.gcount() != in.size()) { fmt::println(std::cerr, "\nError: Bad read from file '{}'.", filename); return false; } auto tmpval = std::bit_cast(in); if(order != std::endian::native) tmpval = al::byteswap(tmpval); *out = tmpval; return true; } /* 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 std::endian order, const ElementTypeT type, const uint bytes, const int bits, double *out) -> bool { *out = 0.0; if(bytes > 4) { uint64_t val{}; if(!ReadBin8(istream, filename, order, &val)) return false; if(type == ET_FP) *out = std::bit_cast(val); } else { uint32_t val{}; if(!ReadBin4(istream, filename, order, bytes, &val)) return false; if(type == ET_FP) *out = std::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 true; } /* 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) -> bool { 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(std::cerr, "\nError: Bad read from file '{}'.", filename); return false; } } else { auto v = int{}; if(!TrReadInt(tr, -(1<<(bits-1)), (1<<(bits-1))-1, &v)) { fmt::println(std::cerr, "\nError: Bad read from file '{}'.", filename); return false; } *out = v / static_cast((1<<(bits-1))-1); } return true; } // 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 std::endian order, const uint hrirRate, SourceRefT *src) -> bool { auto fourCC = uint32_t{}; auto chunkSize = uint32_t{0u}; do { if(chunkSize > 0) istream.seekg(static_cast(chunkSize), std::ios::cur); if(!ReadBin4(istream, src->mPath, std::endian::little, 4, &fourCC) || !ReadBin4(istream, src->mPath, order, 4, &chunkSize)) return false; } while(fourCC != FOURCC_FMT); auto format = uint32_t{}; auto channels = uint32_t{}; auto rate = uint32_t{}; auto dummy = uint32_t{}; auto block = uint32_t{}; auto size = uint32_t{}; auto bits = uint32_t{}; 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 false; block /= channels; if(chunkSize > 14) { if(!ReadBin4(istream, src->mPath, order, 2, &size)) return false; 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 false; if(bits == 0) bits = 8 * size; istream.seekg(4, std::ios::cur); if(!ReadBin4(istream, src->mPath, order, 2, &format)) return false; 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(std::cerr, "\nError: Unsupported WAVE format in file '{}'.", src->mPath); return false; } if(src->mChannel >= channels) { fmt::println(std::cerr, "\nError: Missing source channel in WAVE file '{}'.", src->mPath); return false; } if(rate != hrirRate) { fmt::println(std::cerr, "\nError: Mismatched source sample rate in WAVE file '{}'.", src->mPath); return false; } if(format == WAVE_FORMAT_PCM) { if(size < 2 || size > 4) { fmt::println(std::cerr, "\nError: Unsupported sample size in WAVE file '{}'.", src->mPath); return false; } if(bits < 16 || bits > (8*size)) { fmt::println(std::cerr, "\nError: Bad significant bits in WAVE file '{}'.", src->mPath); return false; } src->mType = ET_INT; } else { if(size != 4 && size != 8) { fmt::println(std::cerr, "\nError: Unsupported sample size in WAVE file '{}'.", src->mPath); return false; } src->mType = ET_FP; } src->mSize = size; src->mBits = static_cast(bits); src->mSkip = channels; return true; } // Read a RIFF/RIFX WAVE data chunk, converting all elements to doubles. auto ReadWaveData(std::istream &istream, const SourceRefT *src, const std::endian order, const std::span hrir) -> bool { 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 false; skip = post; } if(skip > 0) istream.seekg(skip, std::ios::cur); return true; } // Read the RIFF/RIFX WAVE list or data chunk, converting all elements to // doubles. auto ReadWaveList(std::istream &istream, const SourceRefT *src, const std::endian order, const std::span hrir) -> int { auto chunkSize = uint32_t{}; for(;;) { auto fourCC = uint32_t{}; if(!ReadBin4(istream, src->mPath, std::endian::little, 4, &fourCC) || !ReadBin4(istream, src->mPath, order, 4, &chunkSize)) return false; if(fourCC == FOURCC_DATA) { const auto block = src->mSize * src->mSkip; if(chunkSize / block < (src->mOffset + hrir.size())) { fmt::println(std::cerr, "\nError: Bad read from file '{}'.", src->mPath); return false; } using off_type = std::istream::off_type; istream.seekg(gsl::narrow_cast(size_t{src->mOffset} * block), std::ios::cur); if(!ReadWaveData(istream, src, order, hrir)) return false; return true; } if(fourCC == FOURCC_LIST) { if(!ReadBin4(istream, src->mPath, std::endian::little, 4, &fourCC)) return false; chunkSize -= 4; if(fourCC == FOURCC_WAVL) break; } if(chunkSize > 0) istream.seekg(static_cast(chunkSize), std::ios::cur); } auto listSize = chunkSize; auto const block = src->mSize * src->mSkip; auto skip = src->mOffset; auto offset = 0u; auto lastSample = 0.0; while(offset < hrir.size() && listSize > 8) { auto fourCC = uint32_t{}; if(!ReadBin4(istream, src->mPath, std::endian::little, 4, &fourCC) || !ReadBin4(istream, src->mPath, order, 4, &chunkSize)) return false; listSize -= 8 + chunkSize; if(fourCC == FOURCC_DATA) { auto count = chunkSize / block; if(count > skip) { using off_type = std::istream::off_type; istream.seekg(gsl::narrow_cast(size_t{skip} * 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; } else if(fourCC == FOURCC_SLNT) { auto count = uint32_t{}; 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); std::ranges::fill(hrir | std::views::drop(offset) | std::views::take(count), lastSample); offset += count; } else skip -= count; } if(chunkSize > 0) istream.seekg(static_cast(chunkSize), std::ios::cur); } if(offset < hrir.size()) { fmt::println(std::cerr, "\nError: Bad read from file '{}'.", src->mPath); return false; } return true; } // 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 std::span hrir) -> bool { auto tr = TokenReaderT{istream}; TrSetup({}, {}, &tr); for(uint i{0u};i < src->mOffset;++i) { auto dummy = double{}; if(!ReadAsciiAsDouble(&tr, src->mPath, src->mType, static_cast(src->mBits), &dummy)) return false; } for(size_t i{0_uz};i < hrir.size();++i) { if(!ReadAsciiAsDouble(&tr, src->mPath, src->mType, static_cast(src->mBits), &hrir[i])) return false; for(uint j{0u};j < src->mSkip;++j) { auto dummy = double{}; if(!ReadAsciiAsDouble(&tr, src->mPath, src->mType, static_cast(src->mBits), &dummy)) return false; } } return true; } // Load a source HRIR from a binary file. auto LoadBinarySource(std::istream &istream, const SourceRefT *src, const std::endian order, const std::span hrir) -> bool { istream.seekg(static_cast(src->mOffset), std::ios::beg); for(size_t i{0_uz};i < hrir.size();++i) { if(!ReadBinAsDouble(istream, src->mPath, order, src->mType, src->mSize, src->mBits, &hrir[i])) return false; if(src->mSkip > 0) istream.seekg(static_cast(src->mSkip), std::ios::cur); } return true; } // Load a source HRIR from a RIFF/RIFX WAVE file. auto LoadWaveSource(std::istream &istream, SourceRefT *src, const uint hrirRate, const std::span hrir) -> bool { auto fourCC = uint32_t{}; auto dummy = uint32_t{}; auto order = std::endian{}; if(!ReadBin4(istream, src->mPath, std::endian::little, 4, &fourCC) || !ReadBin4(istream, src->mPath, std::endian::little, 4, &dummy)) return false; if(fourCC == FOURCC_RIFF) order = std::endian::little; else if(fourCC == FOURCC_RIFX) order = std::endian::big; else { fmt::println(std::cerr, "\nError: No RIFF/RIFX chunk in file '{}'.", src->mPath); return false; } if(!ReadBin4(istream, src->mPath, std::endian::little, 4, &fourCC)) return false; if(fourCC != FOURCC_WAVE) { fmt::println(std::cerr, "\nError: Not a RIFF/RIFX WAVE file '{}'.", src->mPath); return false; } if(!ReadWaveFormat(istream, order, hrirRate, src)) return false; if(!ReadWaveList(istream, src, order, hrir)) return false; return true; } struct SofaEasyDeleter { void operator()(gsl::owner sofa) const { 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 auto srcname = std::string_view{src->mPath}; auto iter = std::ranges::find_if(gSofaCache, [srcname,hrirRate](SofaCacheEntry &entry) -> bool { return entry.mName == srcname && entry.mSampleRate == hrirRate; }); if(iter != gSofaCache.end()) return iter->mSofa.get(); auto sofa = SofaEasyPtr{new(std::nothrow) MYSOFA_EASY{}}; if(!sofa) { fmt::println(std::cerr, "\nError: Out of memory."); return nullptr; } sofa->lookup = nullptr; sofa->neighborhood = nullptr; auto err = int{}; sofa->hrtf = mysofa_load(src->mPath.c_str(), &err); if(!sofa->hrtf) { fmt::println(std::cerr, "\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(std::cerr, "\nWarning: Supposedly malformed source file '{}': {} ({}).", src->mPath, SofaErrorStr(err), err); if((src->mOffset + n) > sofa->hrtf->N) { fmt::println(std::cerr, "\nError: Not enough samples in SOFA file '{}'.", src->mPath); return nullptr; } if(src->mChannel >= sofa->hrtf->R) { fmt::println(std::cerr, "\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(std::cerr, "\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 std::span hrir) { const auto irValues = std::span{hrtf->DataIR.values, hrtf->DataIR.elements} .subspan((index*hrtf->R + channel)*hrtf->N + offset); std::ranges::copy(irValues | std::views::take(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 std::span hrir) -> bool { auto *sofa = LoadSofaFile(src, hrirRate, static_cast(hrir.size())); if(sofa == nullptr) return false; /* 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. */ auto target = std::array{ static_cast(src->mAzimuth), static_cast(src->mElevation), static_cast(src->mRadius) }; mysofa_s2c(target.data()); auto nearest = mysofa_lookup(sofa->lookup, target.data()); if(nearest < 0) { fmt::println(std::cerr, "\nError: Lookup failed in source file '{}'.", src->mPath); return false; } auto coords = std::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(std::cerr, "\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(std::cerr, " Nearest candidate at ({:.3f}r, {:.1f}ev, {:.1f}az).", target[2], target[1], target[0]); return false; } ExtractSofaHrir(sofa->hrtf, static_cast(nearest), src->mChannel, src->mOffset, hrir); return true; } // Load a source HRIR from a supported file type. auto LoadSource(SourceRefT *src, const uint hrirRate, const std::span hrir) -> bool { auto istream = fs::ifstream{}; if(src->mFormat != SF_SOFA) { if(src->mFormat == SF_ASCII) istream.open(fs::path(al::char_as_u8(src->mPath))); else istream.open(fs::path(al::char_as_u8(src->mPath)), std::ios::binary); if(!istream.good()) { fmt::println(std::cerr, "\nError: Could not open source file '{}'.", src->mPath); return false; } } switch(src->mFormat) { case SF_ASCII: return LoadAsciiSource(istream, src, hrir); case SF_BIN_LE: return LoadBinarySource(istream, src, std::endian::little, hrir); case SF_BIN_BE: return LoadBinarySource(istream, src, std::endian::big, hrir); case SF_WAVE: return LoadWaveSource(istream, src, hrirRate, hrir); case SF_SOFA: return LoadSofaSource(src, hrirRate, hrir); case SF_NONE: break; } return false; } // 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) -> bool { auto hasRate = 0; auto hasType = 0; auto hasPoints = 0; auto hasRadius = 0; auto hasDistance = 0; auto hasAzimuths = 0; auto line = uint{}; auto col = uint{}; auto fpVal = double{}; auto points = uint{}; auto intVal = int{}; auto distances = std::array{}; auto fdCount = 0u; auto evCounts = std::array{}; auto azCounts = std::vector>(MAX_FD_COUNT); std::ranges::fill(azCounts | std::views::join, 0u); TrIndication(tr, &line, &col); while(TrIsIdent(tr)) { TrIndication(tr, &line, &col); const auto ident = TrReadIdent(tr); if(ident.empty()) return false; if(al::case_compare(ident, "rate"sv) == 0) { if(hasRate) { TrErrorAt(tr, line, col, "Redefinition of 'rate'."); return false; } if(!TrReadOperator(tr, "=")) return false; if(!TrReadInt(tr, MIN_RATE, MAX_RATE, &intVal)) return false; 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 false; } if(!TrReadOperator(tr, "=")) return false; const auto type = TrReadIdent(tr); if(type.empty()) return false; hData->mChannelType = MatchChannelType(type); if(hData->mChannelType == CT_NONE) { TrErrorAt(tr, line, col, "Expected a channel type."); return false; } 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 false; } if(!TrReadOperator(tr, "=")) return false; TrIndication(tr, &line, &col); if(!TrReadInt(tr, MIN_POINTS, MAX_POINTS, &intVal)) return false; points = static_cast(intVal); if(fftSize > 0 && points > fftSize) { TrErrorAt(tr, line, col, "Value exceeds the overridden FFT size."); return false; } if(points < truncSize) { TrErrorAt(tr, line, col, "Value is below the truncation size."); return false; } 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 false; } if(!TrReadOperator(tr, "=")) return false; if(!TrReadFloat(tr, MinRadius, MaxRadius, &fpVal)) return false; 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 false; } if(!TrReadOperator(tr, "=")) return false; for(;;) { if(!TrReadFloat(tr, MIN_DISTANCE, MAX_DISTANCE, &fpVal)) return false; if(count > 0 && fpVal <= distances[count - 1]) { TrError(tr, "Distances are not ascending."); return false; } distances[count++] = fpVal; if(!TrIsOperator(tr, ",")) break; if(count >= MAX_FD_COUNT) { TrError(tr, "Exceeded the maximum of {} fields.", MAX_FD_COUNT); return false; } TrReadOperator(tr, ","); } if(fdCount != 0 && count != fdCount) { TrError(tr, "Did not match the specified number of {} fields.", fdCount); return false; } 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 false; } if(!TrReadOperator(tr, "=")) return false; evCounts[0] = 0; for(;;) { if(!TrReadInt(tr, MIN_AZ_COUNT, MAX_AZ_COUNT, &intVal)) return false; 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 false; } 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 false; } if(azCounts[count][0] != 1 || azCounts[count][evCounts[count] - 1] != 1) { TrError(tr, "Poles are not singular for field {}.", count - 1); return false; } count++; if(!TrIsOperator(tr, ";")) break; if(count >= MAX_FD_COUNT) { TrError(tr, "Exceeded the maximum number of %d fields.", MAX_FD_COUNT); return false; } evCounts[count] = 0; TrReadOperator(tr, ";"); } } if(fdCount != 0 && count != fdCount) { TrError(tr, "Did not match the specified number of %d fields.", fdCount); return false; } fdCount = count; hasAzimuths = 1; } else { TrErrorAt(tr, line, col, "Expected a metric name."); return false; } TrSkipWhitespace(tr); } if(!(hasRate && hasPoints && hasRadius && hasDistance && hasAzimuths)) { TrErrorAt(tr, line, col, "Expected a metric name."); return false; } if(distances[0] < hData->mRadius) { TrError(tr, "Distance cannot start below head radius."); return false; } if(hData->mChannelType == CT_NONE) hData->mChannelType = CT_MONO; const auto azs = std::span{azCounts}.first(); if(!PrepareHrirData(std::span{distances}.first(fdCount), evCounts, azs, hData)) { fmt::println(std::cerr, "Error: Out of memory."); exit(-1); } return true; } // Parse an index triplet from the data set definition. auto ReadIndexTriplet(TokenReaderT *tr, const HrirDataT *hData, uint *fi, uint *ei, uint *ai) -> bool { auto intVal = int{}; if(hData->mFds.size() > 1) { if(!TrReadInt(tr, 0, static_cast(hData->mFds.size()-1), &intVal)) return false; *fi = static_cast(intVal); if(!TrReadOperator(tr, ",")) return false; } else { *fi = 0; } if(!TrReadInt(tr, 0, static_cast(hData->mFds[*fi].mEvs.size()-1), &intVal)) return false; *ei = static_cast(intVal); if(!TrReadOperator(tr, ",")) return false; if(!TrReadInt(tr, 0, static_cast(hData->mFds[*fi].mEvs[*ei].mAzs.size()-1), &intVal)) return false; *ai = static_cast(intVal); return true; } // 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) -> bool { uint line; uint col; double fpVal; int intVal; TrIndication(tr, &line, &col); auto ident = TrReadIdent(tr); if(ident.empty()) return false; src->mFormat = MatchSourceFormat(ident); if(src->mFormat == SF_NONE) { TrErrorAt(tr, line, col, "Expected a source format."); return false; } if(!TrReadOperator(tr, "(")) return false; if(src->mFormat == SF_SOFA) { if(!TrReadFloat(tr, MIN_DISTANCE, MAX_DISTANCE, &fpVal)) return false; src->mRadius = fpVal; if(!TrReadOperator(tr, ",")) return false; if(!TrReadFloat(tr, -90.0, 90.0, &fpVal)) return false; src->mElevation = fpVal; if(!TrReadOperator(tr, ",")) return false; if(!TrReadFloat(tr, -360.0, 360.0, &fpVal)) return false; src->mAzimuth = fpVal; if(!TrReadOperator(tr, ":")) return false; if(!TrReadInt(tr, 0, MaxWaveChannels, &intVal)) return false; 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 false; 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 false; src->mType = MatchElementType(ident); if(src->mType == ET_NONE) { TrErrorAt(tr, line, col, "Expected a source element type."); return false; } if(src->mFormat == SF_BIN_LE || src->mFormat == SF_BIN_BE) { if(!TrReadOperator(tr, ",")) return false; if(src->mType == ET_INT) { if(!TrReadInt(tr, MinBinSize, MaxBinSize, &intVal)) return false; 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 false; 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 false; } src->mBits = intVal; } } else { TrIndication(tr, &line, &col); if(!TrReadInt(tr, -2147483647-1, 2147483647, &intVal)) return false; if(intVal != 4 && intVal != 8) { TrErrorAt(tr, line, col, "Expected a value of 4 or 8."); return false; } src->mSize = static_cast(intVal); src->mBits = 0; } } else if(src->mFormat == SF_ASCII && src->mType == ET_INT) { if(!TrReadOperator(tr, ",")) return false; if(!TrReadInt(tr, MinASCIIBits, MaxASCIIBits, &intVal)) return false; 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 false; src->mSkip = static_cast(intVal); } } if(!TrReadOperator(tr, ")")) return false; if(TrIsOperator(tr, "@")) { TrReadOperator(tr, "@"); if(!TrReadInt(tr, 0, 0x7FFFFFFF, &intVal)) return false; src->mOffset = static_cast(intVal); } else src->mOffset = 0; if(!TrReadOperator(tr, ":")) return false; auto srcpath = TrReadString(tr); if(!srcpath) return false; src->mPath = std::move(*srcpath); return true; } // Parse and validate a SOFA source reference from the data set definition. auto ReadSofaRef(TokenReaderT *tr, SourceRefT *src) -> bool { uint line; uint col; int intVal; TrIndication(tr, &line, &col); const auto ident = TrReadIdent(tr); if(ident.empty()) return false; src->mFormat = MatchSourceFormat(ident); if(src->mFormat != SF_SOFA) { TrErrorAt(tr, line, col, "Expected the SOFA source format."); return false; } 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 false; src->mOffset = static_cast(intVal); } else src->mOffset = 0; if(!TrReadOperator(tr, ":")) return false; auto srcpath = TrReadString(tr); if(!srcpath) return false; src->mPath = std::move(*srcpath); return true; } // 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, std::span upsampled, const uint rate, const std::span hrir, const double f, const double onset) -> double { rs.process(hrir, upsampled); const auto iter = std::ranges::max_element(upsampled, std::less{}, [](const double value) -> double { return std::abs(value); }); return std::lerp(onset, gsl::narrow_cast(std::distance(upsampled.begin(), 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 std::span hrir, const double f, const std::span mag) { const uint m{1 + (fftSize/2)}; std::vector h(fftSize); std::vector r(m); const auto hiter = std::ranges::copy(hrir, h.begin()).out; std::fill(hiter, h.end(), 0.0); forward_fft(h); MagnitudeResponse(h, r); for(uint i{0};i < m;++i) mag[i] = std::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) -> bool { const auto channels = (hData->mChannelType == CT_STEREO) ? 2u : 1u; hData->mHrirsBase.resize(size_t{channels} * hData->mIrCount * hData->mIrSize); const auto hrirs = std::span{hData->mHrirsBase}; auto hrir = std::vector(hData->mIrSize, 0.0); uint line; uint col; uint fi; uint ei; uint ai; auto onsetSamples = std::vector(size_t{OnsetRateMultiple}*hData->mIrPoints, 0.0); auto onsetResampler = PPhaseResampler{}; onsetResampler.init(hData->mIrRate, OnsetRateMultiple*hData->mIrRate); auto resampler = std::optional{}; if(outRate && outRate != hData->mIrRate) resampler.emplace().init(hData->mIrRate, outRate); const auto rateScale = outRate ? static_cast(outRate) / hData->mIrRate : 1.0; const auto irPoints{outRate ? std::min(static_cast(std::ceil(hData->mIrPoints*rateScale)), hData->mIrPoints) : hData->mIrPoints}; fmt::print("Loading sources..."); std::cout.flush(); auto 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 false; TrIndication(tr, &line, &col); SourceRefT src{}; if(!ReadSofaRef(tr, &src)) return false; if(hData->mChannelType == CT_STEREO) { const auto type = TrReadIdent(tr); if(type.empty()) return false; switch(MatchChannelType(type)) { case CT_NONE: TrErrorAt(tr, line, col, "Expected a channel type."); return false; case CT_MONO: src.mChannel = 0; break; case CT_STEREO: src.mChannel = 1; break; } } else { const auto type = TrReadIdent(tr); if(type.empty()) return false; if(MatchChannelType(type) != CT_MONO) { TrErrorAt(tr, line, col, "Expected a mono channel type."); return false; } src.mChannel = 0; } auto const *const sofa = LoadSofaFile(&src, hData->mIrRate, hData->mIrPoints); if(!sofa) return false; const auto srcPosValues = std::span{sofa->hrtf->SourcePosition.values, sofa->hrtf->M*3_uz}; for(auto const si : std::views::iota(0u, sofa->hrtf->M)) { fmt::print("\rLoading sources... {} of {}", si+1, sofa->hrtf->M); std::cout.flush(); 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::ranges::find_if(hData->mFds, [&aer](const HrirFdT &fld) -> bool { return (std::abs(aer[2] - fld.mDistance) < 0.001); }); if(field == hData->mFds.end()) continue; fi = static_cast(std::distance(hData->mFds.begin(), field)); const auto evscale = 180.0 / static_cast(field->mEvs.size()-1); auto ef = (90.0 + aer[1]) / evscale; ei = static_cast(std::round(ef)); ef = (ef - ei) * evscale; if(std::abs(ef) >= 0.1) continue; const auto azscale = 360.0 / static_cast(field->mEvs[ei].mAzs.size()); auto 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 false; } const auto hrirPoints = std::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, std::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, std::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 false; if(!TrReadOperator(tr, "]")) return false; HrirAzT *azd = &hData->mFds[fi].mEvs[ei].mAzs[ai]; if(!azd->mIrs[0].empty()) { TrErrorAt(tr, line, col, "Redefinition of source."); return false; } if(!TrReadOperator(tr, "=")) return false; while(true) { SourceRefT src{}; if(!ReadSourceRef(tr, &src)) return false; // 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"); std::cout.flush(); if(!LoadSource(&src, hData->mIrRate, std::span{hrir}.first(hData->mIrPoints))) return false; auto ti = uint{0}; if(hData->mChannelType == CT_STEREO) { const auto ident = TrReadIdent(tr); if(ident.empty()) return false; if(auto earopt = MatchTargetEar(ident)) ti = *earopt; else { TrErrorAt(tr, line, col, "Expected a target ear."); return false; } } const auto hrirPoints = std::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, std::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 false; } if(azd->mIrs[1].empty()) { TrErrorAt(tr, line, col, "Missing right ear source reference(s)."); return false; } } } 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 false; } 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 false; } } } } 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 true; } TrError(tr, "Errant data at end of source list."); gSofaCache.clear(); return false; } } /* namespace */ auto LoadDefInput(std::istream &istream, const std::span startbytes, const std::string_view filename, const uint fftSize, const uint truncSize, const uint outRate, const ChannelModeT chanMode, HrirDataT *hData) -> bool { auto tr = TokenReaderT{istream}; TrSetup(startbytes, filename, &tr); if(!ProcessMetrics(&tr, fftSize, truncSize, chanMode, hData) || !ProcessSources(&tr, hData, outRate)) return false; return true; } kcat-openal-soft-75c0059/utils/makemhr/loaddef.h000066400000000000000000000005671512220627100215000ustar00rootroot00000000000000#ifndef LOADDEF_H #define LOADDEF_H #include #include #include #include "makemhr.h" bool LoadDefInput(std::istream &istream, const std::span startbytes, const std::string_view filename, const uint fftSize, const uint truncSize, const uint outRate, const ChannelModeT chanMode, HrirDataT *hData); #endif /* LOADDEF_H */ kcat-openal-soft-75c0059/utils/makemhr/loadsofa.cpp000066400000000000000000000511521512220627100222210ustar00rootroot00000000000000/* * 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 "config.h" #include "loadsofa.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "alnumeric.h" #include "fmt/base.h" #include "fmt/ostream.h" #include "makemhr.h" #include "polyphase_resampler.h" #include "sofa-support.h" #include "mysofa.h" #if HAVE_CXXMODULES import gsl; #else #include "gsl/gsl" #endif 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 std::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); auto fi = 0u; auto 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 = std::span{azCounts}.first(); return PrepareHrirData(std::span{distances}.first(fi), evCounts, azs, hData); } auto GetSampleRate(MYSOFA_HRTF const *const sofaHrtf) -> f32 { auto *srate_dim = gsl::czstring{}; auto *srate_units = gsl::czstring{}; auto const &srate_array = sofaHrtf->DataSamplingRate; auto const *srate_attrs = srate_array.attributes; while(srate_attrs) { if("DIMENSION_LIST"sv == srate_attrs->name) { if(srate_dim) { fmt::println(std::cerr, "Duplicate SampleRate.DIMENSION_LIST"); return 0.0f; } srate_dim = srate_attrs->value; } else if("Units"sv == srate_attrs->name) { if(srate_units) { fmt::println(std::cerr, "Duplicate SampleRate.Units"); return 0.0f; } srate_units = srate_attrs->value; } else fmt::println(std::cerr, "Unexpected sample rate attribute: {} = {}", srate_attrs->name, srate_attrs->value ? srate_attrs->value : ""); srate_attrs = srate_attrs->next; } if(!srate_dim) { fmt::println(std::cerr, "Missing sample rate dimensions"); return 0.0f; } if(srate_dim != "I"sv) { fmt::println(std::cerr, "Unsupported sample rate dimensions: {}", srate_dim); return 0.0f; } if(!srate_units) { fmt::println(std::cerr, "Missing sample rate unit type"); return 0.0f; } if(srate_units != "hertz"sv) { fmt::println(std::cerr, "Unsupported sample rate unit type: {}", srate_units); return 0.0f; } /* I dimensions guarantees 1 element, so just extract it. */ auto const values = std::span{srate_array.values, sofaHrtf->I}; if(values[0] < float{MIN_RATE} || values[0] > float{MAX_RATE}) { fmt::println(std::cerr, "Sample rate out of range: {:f} (expected {} to {})", values[0], MIN_RATE, MAX_RATE); return 0.0f; } return values[0]; } enum class DelayType : u8 { None, I_R, /* [1][Channels] */ M_R, /* [HRIRs][Channels] */ }; auto PrepareDelay(MYSOFA_HRTF const *const sofaHrtf) -> std::optional { auto *delay_dim = gsl::czstring{}; auto const *delay_attrs = sofaHrtf->DataDelay.attributes; while(delay_attrs) { if("DIMENSION_LIST"sv == delay_attrs->name) { if(delay_dim) { fmt::println(std::cerr, "Duplicate Delay.DIMENSION_LIST"); return std::nullopt; } delay_dim = delay_attrs->value; } else fmt::println(std::cerr, "Unexpected delay attribute: {} = {}", delay_attrs->name, delay_attrs->value ? delay_attrs->value : ""); delay_attrs = delay_attrs->next; } if(!delay_dim) { fmt::println(std::cerr, "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(std::cerr, "Unsupported delay dimensions: {}", delay_dim); return std::nullopt; } auto CheckIrData(MYSOFA_HRTF const *const sofaHrtf) -> bool { auto *ir_dim = gsl::czstring{}; auto const *ir_attrs = sofaHrtf->DataIR.attributes; while(ir_attrs) { if("DIMENSION_LIST"sv == ir_attrs->name) { if(ir_dim) { fmt::println(std::cerr, "Duplicate IR.DIMENSION_LIST"); return false; } ir_dim = ir_attrs->value; } else fmt::println(std::cerr, "Unexpected IR attribute: {} = {}", ir_attrs->name, ir_attrs->value ? ir_attrs->value : ""); ir_attrs = ir_attrs->next; } if(!ir_dim) { fmt::println(std::cerr, "Missing IR dimensions"); return false; } if(ir_dim != "M,R,N"sv) { fmt::println(std::cerr, "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, std::span upsampled, const std::span hrir) -> double { rs.process(hrir, upsampled); static constexpr auto make_abs = [](const double value) { return std::abs(value); }; const auto iter = std::ranges::max_element(upsampled, std::less{}, make_abs); return static_cast(std::distance(upsampled.begin(), iter)) / (double{OnsetRateMultiple}*rate); } /* Calculate the magnitude response of a HRIR. */ void CalcHrirMagnitude(const uint points, std::span h, const std::span hrir) { auto iter = std::copy_n(hrir.begin(), 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) { auto loaded_count = std::atomic{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 = std::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 = std::span{sofaHrtf->SourcePosition.values, sofaHrtf->M*3_uz}; const auto irValues = std::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::ranges::find_if(hData->mFds, [&aer](const HrirFdT &fld) -> bool { return (std::abs(aer[2] - fld.mDistance) < 0.001); }); if(field == hData->mFds.end()) 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(std::cerr, "\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.begin(), ir.size(), azd.mIrs[ti].begin()); else { std::copy_n(ir.begin(), 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 = std::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 = std::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); std::cout.flush(); } 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(std::cerr, "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(std::cerr, "{} emitters not supported", sofaHrtf->E); return false; } if(sofaHrtf->R > 2 || sofaHrtf->R < 1) { fmt::println(std::cerr, "{} 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(std::cerr, "Sample points exceeds the FFT size ({} > {}).", sofaHrtf->N, fftSize); return false; } if(sofaHrtf->N < truncSize) { fmt::println(std::cerr, "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(std::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(std::cerr, "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(std::cerr, "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 = std::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); std::cout.flush(); } 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()); std::cout.flush(); } while(count != calculator.mIrs.size()); fmt::println(""); for(auto &thrd : thrds) { if(thrd.joinable()) thrd.join(); } return true; } kcat-openal-soft-75c0059/utils/makemhr/loadsofa.h000066400000000000000000000004531512220627100216640ustar00rootroot00000000000000#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 */ kcat-openal-soft-75c0059/utils/makemhr/makemhr.cpp000066400000000000000000001545101512220627100220570ustar00rootroot00000000000000/* * 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 #include "alcomplex.h" #include "alnumeric.h" #include "alstring.h" #include "filesystem.h" #include "fmt/base.h" #include "fmt/ostream.h" #include "loaddef.h" #include "loadsofa.h" #include "win_main_utf8.h" #if HAVE_CXXMODULES import gsl; #else #include "gsl/gsl" #endif namespace { using namespace std::string_view_literals; // 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}; /* 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(in.starts_with(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 *** *********************/ 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 std::span out, const std::span in, const double scale, const size_t channel, const size_t step, uint *seed) { static constexpr auto PRNG_SCALE = 1.0 / std::numeric_limits::max(); Expects(channel < step); for(size_t i{0};i < in.size();++i) { const auto prn0 = dither_rng(seed); const auto 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 std::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] = std::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 std::span mags, const std::span out) { Expects(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 { /* NOLINTNEXTLINE(bugprone-suspicious-stringview-data-usage) */ if(!ostream.write(out.data(), std::ssize(out)) || ostream.bad()) { fmt::println(std::cerr, "\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(), gsl::narrow(bytes)) || ostream.bad()) { fmt::println(std::cerr, "\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::path(al::char_as_u8(filename)), std::ios::binary}; if(!ostream.is_open()) { fmt::println(std::cerr, "\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(std::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 std::span weights) { auto sum = 0.0; // The head radius acts as the limit for the inner radius. auto innerRa = hData->mRadius; for(auto fi = 0u;fi < hData->mFds.size();++fi) { auto outerRa = 10.0; // 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)}; const auto evs = std::numbers::pi/2.0/static_cast(hData->mFds[fi].mEvs.size() - 1); for(auto 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. auto ev = elev.mElevation; auto lowerEv = std::max(-std::numbers::pi / 2.0, ev - evs); auto upperEv = std::min(std::numbers::pi / 2.0, ev + evs); // Calculate the surface area of the patch band. auto solidAngle = 2.0 * std::numbers::pi * (std::sin(upperEv) - std::sin(lowerEv)); // Then the volume of the extruded patch band. auto 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(auto fi = 0u;fi < hData->mFds.size();++fi) { // Normalize the weights given the total surface coverage for all // fields. for(auto 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 std::span dfa) { auto weights = std::vector(hData->mFds.size() * MAX_EV_COUNT, double{}); if(weighted) { // Use coverage weighting to calculate the average. CalculateDfWeights(hData, weights); } else { // If coverage weighting is not used, the weights still need to be // averaged by the number of existing HRIRs. auto 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()); } auto const 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. const auto 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 std::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 HrirEvT &elev, const double az, uint *a0, uint *a1, double *af) { auto f = (std::numbers::inv_pi*0.5*az + 1.0) * static_cast(elev.mAzs.size()); const auto fact = std::modf(f, &f); const auto i = static_cast(f) % static_cast(elev.mAzs.size()); *a0 = i; *a1 = (i+1) % static_cast(elev.mAzs.size()); *af = fact; } /* 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; uint 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 + std::numbers::pi}; CalcAzIndices(field.mEvs[topElev], az, &a0, &a1, &af); /* Blend the delays, and again, swap the ears. */ field.mEvs[ei].mAzs[ai].mDelays[0] = std::lerp( field.mEvs[topElev].mAzs[a0].mDelays[1], field.mEvs[topElev].mAzs[a1].mDelays[1], af); field.mEvs[ei].mAzs[ai].mDelays[1] = std::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; uint 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 <= std::numbers::pi) az = std::numbers::pi - az; else az = (std::numbers::pi*2.0)-az + std::numbers::pi; CalcAzIndices(field.mEvs[topElev], az, &a0, &a1, &af); field.mEvs[ei].mAzs[ai].mDelays[0] = std::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; uint a1; uint a2; uint a3; double af0; double af1; const auto az = field.mEvs[ei].mAzs[ai].mAzimuth; CalcAzIndices(field.mEvs[upperElevReal], az, &a0, &a1, &af0); CalcAzIndices(field.mEvs[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::ranges::for_each(hData->mFds, 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; uint 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.mEvs[oi], std::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] = std::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] = std::lerp(1.0, lp[0], b); lp[1] = std::lerp(lp[0], lp[1], b); lp[2] = std::lerp(lp[1], lp[2], b); lp[3] = std::lerp(lp[2], lp[3], b); htemp[0] = lp[3]; for(size_t i{1u};i < htemp.size();i++) { lp[0] = std::lerp(0.0, lp[0], b); lp[1] = std::lerp(lp[0], lp[1], b); lp[2] = std::lerp(lp[1], lp[2], b); lp[3] = std::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::ranges::transform(htemp | std::views::take(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; uint a1; double af; CalcAzIndices(field.mEvs[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 auto s1 = std::lerp(field.mEvs[oi].mAzs[a0].mIrs[ti][i], field.mEvs[oi].mAzs[a1].mIrs[ti][i], af); const auto s = std::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] = std::lerp(1.0, lp[0], b); lp[1] = std::lerp(lp[0], lp[1], b); lp[2] = std::lerp(lp[1], lp[2], b); lp[3] = std::lerp(lp[2], lp[3], b); htemp[0] = lp[3]; for(size_t i{1u};i < htemp.size();i++) { lp[0] = std::lerp(0.0, lp[0], b); lp[1] = std::lerp(lp[0], lp[1], b); lp[2] = std::lerp(lp[1], lp[2], b); lp[3] = std::lerp(lp[2], lp[3], b); htemp[i] = lp[3]; } FftForward(static_cast(htemp.size()), htemp.data()); std::ranges::transform(htemp | std::views::take(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::ranges::for_each(hData->mFds, 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); const auto m = (mFftSize/2_uz) + 1_uz; 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()); std::cout.flush(); } 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, std::span ir) { /* Calculate the peak amplitude and RMS of this IR. */ ir = ir.first(irSize); auto current = std::accumulate(ir.begin(), ir.end(), 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.begin(), elev.mAzs.end(), levels, measure_azi); }; auto measure_field = [measure_elev](const LevelPair levels, const HrirFdT &field) { return std::accumulate(field.mEvs.begin(), field.mEvs.end(), 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](std::span ir) { std::ranges::transform(ir.first(irSize), ir.begin(), [factor](const double s) noexcept { return s*factor; }); }; auto proc_azi = [channels,proc_channel](HrirAzT &azi) { std::ranges::for_each(azi.mIrs | std::views::take(channels), proc_channel); }; auto proc_elev = [proc_azi](HrirEvT &elev) { std::ranges::for_each(elev.mAzs, proc_azi); }; auto proc1_field = [proc_elev](HrirFdT &field) { std::ranges::for_each(field.mEvs, proc_elev); }; std::ranges::for_each(hData->mFds, 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) { auto azp = std::asin(std::cos(ev) * std::sin(az)); auto dlp = std::sqrt((dist*dist) + (rad*rad) + (2.0*dist*rad*sin(azp))); auto l = std::sqrt((dist*dist) - (rad*rad)); auto al = (0.5 * std::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) { const auto channels = (hData->mChannelType == CT_STEREO) ? 2u : 1u; const auto 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 std::span distances, const std::span evCounts, const std::span,MAX_FD_COUNT> azCounts, HrirDataT *hData) { auto evTotal = 0u; auto azTotal = 0u; 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 = std::span{hData->mEvsBase}.subspan(evTotal, evCounts[fi]); evTotal += evCounts[fi]; for(uint ei{0};ei < evCounts[fi];++ei) { const auto azCount = azCounts[fi][ei]; hData->mFds[fi].mEvs[ei].mElevation = -std::numbers::pi / 2.0 + std::numbers::pi * ei / (evCounts[fi] - 1); hData->mFds[fi].mEvs[ei].mAzs = std::span{hData->mAzsBase}.subspan(azTotal, azCount); for(uint ai{0};ai < azCount;ai++) { hData->mFds[fi].mEvs[ei].mAzs[ai].mAzimuth = 2.0 * std::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::path(al::char_as_u8(inName))); if(!input->is_open()) { fmt::println(std::cerr, "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(std::cerr, "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) { const auto c = (hData.mChannelType == CT_STEREO) ? 2u : 1u; const auto 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::ranges::sort(hData.mFds, std::less{}, &HrirFdT::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, std::ostream &ofile) { fmt::println(ofile, "Usage: {} [